本文目录导读:

实现一个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>
关键扩展点(可自行添加)
- 红冲发票:生成一张负数金额的新发票,关联原发票ID。
- 批量导入/导出:使用 EasyExcel 解析 Excel / CSV 文件。
- 金额自动校验:校验
amountTotal = amountWithoutTax + taxAmount。 - 税率配置化:从数据库读取税率,而非硬编码 13%。
- 日志审计:记录每次作废、红冲的操作人、时间。
代码实现了一个最小可用的发票管理后端,涵盖了:
- 基础CRUD
- 发票状态流转控制
- 金额自动计算
- 分页与模糊搜索
- 前端交互示例
你可以将此作为骨架,根据实际业务(如不同税率的发票、多用户权限、开票限额控制等)进行扩展,如果更关注PDF/OFD电子发票解析、税控盘对接等高级功能,则需要引入专用SDK,但核心架构保持不变。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播