Java案例如何实现发票管理?

wen python案例 3

本文目录导读:

Java案例如何实现发票管理?

  1. 技术栈选型(案例标准)
  2. 数据库表设计(核心)
  3. 后端核心实现
  4. 前端(Vue 3 + Element Plus)核心片段
  5. 关键扩展点(可自行添加)

实现一个Java发票管理案例,通常需要涵盖前端界面、后端逻辑和数据库存储三个层面,由于你问的是“案例”,我会提供一个基于Spring Boot + Vue(或Thymeleaf) 的完整思路和核心代码片段,并针对后端逻辑给出详细说明。

一个典型的发票管理系统功能包括:发票录入、发票查询、发票状态更新(作废、红冲)、税额计算、报表统计等。

下面我以后端核心功能为主,提供完整的实现方案。


技术栈选型(案例标准)

层级 技术选项 说明
后端 Spring Boot 2.x / 3.x 提供RESTful API
MyBatis-Plus 数据库ORM,简化CRUD
MySQL / PostgreSQL 存储发票数据
前端 Vue 3 + Element Plus 现代化管理界面
工具 Lombok 简化实体类代码
Hutool 提供日期、金额处理工具

数据库表设计(核心)

一个基本的发票表 invoice 设计如下:

CREATE TABLE `invoice` (
    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `invoice_code` VARCHAR(50) NOT NULL COMMENT '发票代码',
    `invoice_number` VARCHAR(20) NOT NULL COMMENT '发票号码',
    `invoice_type` TINYINT NOT NULL COMMENT '发票类型(1:增值税专用发票,2:普通发票)',
    `issue_date` DATE NOT NULL COMMENT '开票日期',
    `seller_name` VARCHAR(200) NOT NULL COMMENT '销售方名称',
    `buyer_name` VARCHAR(200) NOT NULL COMMENT '购买方名称',
    `amount_total` DECIMAL(12,2) NOT NULL COMMENT '含税总金额',
    `tax_amount` DECIMAL(12,2) NOT NULL COMMENT '税额',
    `amount_without_tax` DECIMAL(12,2) NOT NULL COMMENT '不含税金额',
    `status` TINYINT DEFAULT 0 COMMENT '状态(0:正常,1:作废,2:红冲)',
    `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_invoice` (`invoice_code`, `invoice_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发票信息表';

后端核心实现

实体类(Entity)

使用 Lombok 简化 getter/setter。

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("invoice")
public class Invoice {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String invoiceCode;
    private String invoiceNumber;
    private Integer invoiceType;       // 1:专票 2:普票
    private LocalDate issueDate;
    private String sellerName;
    private String buyerName;
    private BigDecimal amountTotal;    // 含税总金额
    private BigDecimal taxAmount;      // 税额
    private BigDecimal amountWithoutTax; // 不含税金额
    private Integer status;            // 0:正常 1:作废 2:红冲
    private String remark;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

Mapper 层(MyBatis-Plus)

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface InvoiceMapper extends BaseMapper<Invoice> {
    // 可自定义复杂查询SQL
}

Service 层(核心业务逻辑)

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
@Service
public class InvoiceService extends ServiceImpl<InvoiceMapper, Invoice> {
    /**
     * 分页查询发票列表(支持模糊查询)
     */
    public Page<Invoice> queryPage(int current, int size, String keyword) {
        Page<Invoice> page = new Page<>(current, size);
        // 使用LambdaQueryWrapper构建动态条件
        lambdaQuery()
                .like(keyword != null && !keyword.isEmpty(), Invoice::getInvoiceCode, keyword)
                .or()
                .like(keyword != null && !keyword.isEmpty(), Invoice::getBuyerName, keyword)
                .orderByDesc(Invoice::getCreateTime)
                .page(page);
        return page;
    }
    /**
     * 添加发票(含自动计算税额逻辑)
     * 规则:假设一般纳税人增值税率为13%
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean addInvoice(Invoice invoice) {
        // 1. 校验发票唯一性(代码+号码)
        long count = lambdaQuery()
                .eq(Invoice::getInvoiceCode, invoice.getInvoiceCode())
                .eq(Invoice::getInvoiceNumber, invoice.getInvoiceNumber())
                .count();
        if (count > 0) {
            throw new RuntimeException("该发票已存在,不可重复录入");
        }
        // 2. 根据类型计算税额(仅示例,实际规则复杂)
        if (invoice.getTaxAmount() == null && invoice.getAmountWithoutTax() != null) {
            // 不含税金额 * 税率 = 税额(这里假设13%)
            BigDecimal tax = invoice.getAmountWithoutTax()
                    .multiply(new BigDecimal("0.13"))
                    .setScale(2, RoundingMode.HALF_UP);
            invoice.setTaxAmount(tax);
            // 含税金额 = 不含税金额 + 税额
            invoice.setAmountTotal(invoice.getAmountWithoutTax().add(tax));
        }
        // 3. 默认状态正常
        invoice.setStatus(0);
        return save(invoice);
    }
    /**
     * 作废发票(状态变为1)
     * 必须校验发票状态是否允许作废(只有正常状态才能作废)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean voidInvoice(Long id) {
        Invoice invoice = getById(id);
        if (invoice == null) {
            throw new RuntimeException("发票不存在");
        }
        if (invoice.getStatus() != 0) {
            throw new RuntimeException("仅正常状态的发票可以作废");
        }
        return lambdaUpdate()
                .eq(Invoice::getId, id)
                .set(Invoice::getStatus, 1)
                .update();
    }
}

Controller 层(RESTful API)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
    @Autowired
    private InvoiceService invoiceService;
    @GetMapping("/page")
    public Result<Page<Invoice>> page(
            @RequestParam(defaultValue = "1") int current,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        Page<Invoice> page = invoiceService.queryPage(current, size, keyword);
        return Result.success(page);
    }
    @PostMapping
    public Result<String> add(@RequestBody Invoice invoice) {
        invoiceService.addInvoice(invoice);
        return Result.success("添加成功");
    }
    @PutMapping("/void/{id}")
    public Result<String> voidInvoice(@PathVariable Long id) {
        invoiceService.voidInvoice(id);
        return Result.success("作废成功");
    }
}

前端(Vue 3 + Element Plus)核心片段

以下是一个 发票列表 + 新增对话框 的示例:

<template>
  <div>
    <el-table :data="tableData" stripe border>
      <el-table-column prop="invoiceCode" label="发票代码" width="180" />
      <el-table-column prop="invoiceNumber" label="发票号码" width="180" />
      <el-table-column prop="amountTotal" label="含税金额" width="150" />
      <el-table-column prop="status" label="状态" width="100">
        <template #default="{ row }">
          <el-tag :type="row.status === 0 ? 'success' : 'danger'">
            {{ row.status === 0 ? '正常' : '作废' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="180">
        <template #default="{ row }">
          <el-button 
            size="small" 
            type="danger" 
            @click="handleVoid(row.id)"
            :disabled="row.status !== 0">
            作废
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 新增发票对话框 -->
    <el-dialog v-model="dialogVisible" title="新增发票">
      <el-form :model="form">
        <el-form-item label="发票代码">
          <el-input v-model="form.invoiceCode" />
        </el-form-item>
        <el-form-item label="含税金额">
          <el-input-number v-model="form.amountTotal" />
        </el-form-item>
        <!-- 其他字段类似 -->
      </el-form>
      <template #footer>
        <el-button @click="submitForm">提交</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
const tableData = ref([])
const dialogVisible = ref(false)
const form = ref({})
const fetchData = async () => {
  const res = await axios.get('/api/invoices/page')
  tableData.value = res.data.data.records
}
const handleVoid = async (id) => {
  await axios.put(`/api/invoices/void/${id}`)
  ElMessage.success('作废成功')
  fetchData()
}
onMounted(fetchData)
</script>

关键扩展点(可自行添加)

  1. 红冲发票:生成一张负数金额的新发票,关联原发票ID。
  2. 批量导入/导出:使用 EasyExcel 解析 Excel / CSV 文件。
  3. 金额自动校验:校验 amountTotal = amountWithoutTax + taxAmount
  4. 税率配置化:从数据库读取税率,而非硬编码 13%。
  5. 日志审计:记录每次作废、红冲的操作人、时间。

代码实现了一个最小可用的发票管理后端,涵盖了:

  • 基础CRUD
  • 发票状态流转控制
  • 金额自动计算
  • 分页与模糊搜索
  • 前端交互示例

你可以将此作为骨架,根据实际业务(如不同税率的发票、多用户权限、开票限额控制等)进行扩展,如果更关注PDF/OFD电子发票解析税控盘对接等高级功能,则需要引入专用SDK,但核心架构保持不变。

抱歉,评论功能暂时关闭!