From 1be1a18ea82817b2f46448af1af9ee8fbda895f4 Mon Sep 17 00:00:00 2001
From: b2894lxlx <517289602@qq.com>
Date: Thu, 27 Aug 2026 23:51:55 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A6=96=E4=BB=98=E6=AC=BE?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../system/pojo/entity/InvoiceItem.java | 56 +++++++++++
.../system/pojo/vo/InvoiceItemVO.java | 50 ++++++++++
.../pojo/dto/InvoiceReceiptSaveRequest.java | 14 +++
.../controller/InvoiceItemController.java | 90 ++++++++++++++++++
.../system/mapper/InvoiceItemMapper.java | 39 ++++++++
.../system/mapper/InvoiceItemMapper.xml | 40 ++++++++
.../system/service/IInvoiceItemService.java | 37 ++++++++
.../service/impl/InvoiceItemServiceImpl.java | 92 +++++++++++++++++++
.../system/wrapper/InvoiceItemWrapper.java | 50 ++++++++++
.../impl/InvoiceApplicationServiceImpl.java | 92 ++++++++++++++-----
.../impl/InvoiceReceiptServiceImpl.java | 70 ++++++++------
.../service/impl/ReceiptFlowServiceImpl.java | 17 +---
.../blade_invoice_application_20260821.sql | 2 +-
...application_department_emails_20260827.sql | 3 +
doc/sql/transport/blade_invoice_item.sql | 28 ++++++
15 files changed, 611 insertions(+), 69 deletions(-)
create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/InvoiceItem.java
create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/InvoiceItemVO.java
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/controller/InvoiceItemController.java
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.java
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.xml
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/IInvoiceItemService.java
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/impl/InvoiceItemServiceImpl.java
create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/wrapper/InvoiceItemWrapper.java
create mode 100644 doc/sql/transport/blade_invoice_application_department_emails_20260827.sql
create mode 100644 doc/sql/transport/blade_invoice_item.sql
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/InvoiceItem.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/InvoiceItem.java
new file mode 100644
index 0000000..86afaba
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/InvoiceItem.java
@@ -0,0 +1,56 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.mp.base.BaseEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+
+/**
+ * 开票项目实体类
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("blade_invoice_item")
+@Schema(description = "开票项目")
+public class InvoiceItem extends BaseEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "货物或服务简称")
+ private String shortName;
+
+ @Schema(description = "税收分类编码")
+ private String taxClassificationCode;
+
+ @Schema(description = "商品和服务分类名称")
+ private String categoryName;
+
+ @Schema(description = "默认税率")
+ private BigDecimal defaultTaxRate;
+
+}
diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/InvoiceItemVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/InvoiceItemVO.java
new file mode 100644
index 0000000..f56ecab
--- /dev/null
+++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/InvoiceItemVO.java
@@ -0,0 +1,50 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.pojo.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.system.pojo.entity.InvoiceItem;
+
+import java.io.Serial;
+
+/**
+ * 开票项目视图实体类
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "开票项目")
+public class InvoiceItemVO extends InvoiceItem {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @TableField(exist = false)
+ @Schema(description = "创建部门名称")
+ private String createDeptName;
+
+ @TableField(exist = false)
+ @Schema(description = "更新人姓名")
+ private String updateUserName;
+
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
index e50ef5b..5f73b6c 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
@@ -30,6 +30,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.util.List;
/**
@@ -42,6 +43,19 @@ public class InvoiceReceiptSaveRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private Long id;
private Long kingdeeInvoicePoolId;
+ private String invoiceNo;
+ private LocalDate invoiceDate;
+ private String invoiceType;
+ private BigDecimal taxRate;
+ private BigDecimal invoiceAmount;
+ private BigDecimal taxAmount;
+ private String receiverName;
+ private String issuerName;
+ private String bankName;
+ private String bankAccount;
+ private String issuingBank;
+ private String kingdeeBillNo;
+ private String kingdeeStatus;
private String phone;
private String customerEmails;
private String departmentEmails;
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/InvoiceItemController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/InvoiceItemController.java
new file mode 100644
index 0000000..9e3acd2
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/InvoiceItemController.java
@@ -0,0 +1,90 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.annotation.PreAuth;
+import org.springblade.core.tenant.annotation.NonDS;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.pojo.entity.InvoiceItem;
+import org.springblade.system.pojo.vo.InvoiceItemVO;
+import org.springblade.system.service.IInvoiceItemService;
+import org.springblade.system.wrapper.InvoiceItemWrapper;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 开票项目控制器
+ *
+ * @author Chill
+ */
+@NonDS
+@RestController
+@AllArgsConstructor
+@PreAuth(menu = "invoice_item")
+@RequestMapping("/invoice-item")
+@Tag(name = "开票项目", description = "开票项目")
+public class InvoiceItemController extends BladeController {
+
+ private final IInvoiceItemService invoiceItemService;
+
+ @GetMapping("/detail")
+ @ApiOperationSupport(order = 1)
+ @Operation(summary = "详情", description = "传入invoiceItem")
+ public R detail(InvoiceItem invoiceItem) {
+ InvoiceItem detail = invoiceItemService.getOne(Condition.getQueryWrapper(invoiceItem));
+ return R.data(InvoiceItemWrapper.build().entityVO(detail));
+ }
+
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @Operation(summary = "分页", description = "传入invoiceItem")
+ public R> list(InvoiceItemVO invoiceItem, Query query) {
+ return R.data(invoiceItemService.selectInvoiceItemPage(Condition.getPage(query), invoiceItem));
+ }
+
+ @PostMapping("/submit")
+ @ApiOperationSupport(order = 3)
+ @Operation(summary = "新增或修改", description = "传入invoiceItem")
+ public R submit(@Valid @RequestBody InvoiceItem invoiceItem) {
+ return R.status(invoiceItemService.submit(invoiceItem));
+ }
+
+ @PostMapping("/remove")
+ @ApiOperationSupport(order = 4)
+ @Operation(summary = "逻辑删除", description = "传入ids")
+ public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
+ return R.status(invoiceItemService.deleteLogic(Func.toLongList(ids)));
+ }
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.java
new file mode 100644
index 0000000..d1c80cd
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.java
@@ -0,0 +1,39 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.system.pojo.entity.InvoiceItem;
+import org.springblade.system.pojo.vo.InvoiceItemVO;
+
+import java.util.List;
+
+/**
+ * 开票项目 Mapper 接口
+ *
+ * @author Chill
+ */
+public interface InvoiceItemMapper extends BaseMapper {
+
+ List selectInvoiceItemPage(IPage page,
+ @Param("invoiceItem") InvoiceItemVO invoiceItem);
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.xml
new file mode 100644
index 0000000..41a6929
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/InvoiceItemMapper.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT bii.*, bd.dept_name AS create_dept_name, bu.real_name AS update_user_name
+ FROM blade_invoice_item bii
+ LEFT JOIN blade_dept bd ON bd.id = bii.create_dept
+ LEFT JOIN blade_user bu ON bu.id = bii.update_user
+ WHERE bii.is_deleted = 0
+
+
+ AND bii.short_name LIKE #{shortNameLike}
+
+
+
+ AND bii.category_name LIKE #{categoryNameLike}
+
+
+
+ AND bii.tax_classification_code LIKE #{taxCodeLike}
+
+ ORDER BY bii.create_time DESC
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IInvoiceItemService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IInvoiceItemService.java
new file mode 100644
index 0000000..e889850
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IInvoiceItemService.java
@@ -0,0 +1,37 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.system.pojo.entity.InvoiceItem;
+import org.springblade.system.pojo.vo.InvoiceItemVO;
+
+/**
+ * 开票项目服务类
+ *
+ * @author Chill
+ */
+public interface IInvoiceItemService extends BaseService {
+
+ IPage selectInvoiceItemPage(IPage page, InvoiceItemVO invoiceItem);
+
+ boolean submit(InvoiceItem invoiceItem);
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/InvoiceItemServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/InvoiceItemServiceImpl.java
new file mode 100644
index 0000000..0974a4f
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/InvoiceItemServiceImpl.java
@@ -0,0 +1,92 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.mapper.InvoiceItemMapper;
+import org.springblade.system.pojo.entity.InvoiceItem;
+import org.springblade.system.pojo.vo.InvoiceItemVO;
+import org.springblade.system.service.IInvoiceItemService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+
+/**
+ * 开票项目服务实现类
+ *
+ * @author Chill
+ */
+@Service
+public class InvoiceItemServiceImpl extends BaseServiceImpl
+ implements IInvoiceItemService {
+
+ private static final int SHORT_NAME_MAX_LENGTH = 100;
+ private static final int TAX_CODE_MAX_LENGTH = 30;
+ private static final int CATEGORY_NAME_MAX_LENGTH = 200;
+
+ @Override
+ public IPage selectInvoiceItemPage(IPage page, InvoiceItemVO invoiceItem) {
+ return page.setRecords(baseMapper.selectInvoiceItemPage(page, invoiceItem));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean submit(InvoiceItem invoiceItem) {
+ prepare(invoiceItem);
+ validate(invoiceItem);
+ return saveOrUpdate(invoiceItem);
+ }
+
+ private void prepare(InvoiceItem invoiceItem) {
+ invoiceItem.setShortName(trim(invoiceItem.getShortName()));
+ invoiceItem.setTaxClassificationCode(trim(invoiceItem.getTaxClassificationCode()));
+ invoiceItem.setCategoryName(trim(invoiceItem.getCategoryName()));
+ if (Func.isEmpty(invoiceItem.getStatus())) {
+ invoiceItem.setStatus(1);
+ }
+ }
+
+ private void validate(InvoiceItem invoiceItem) {
+ if (Func.isEmpty(invoiceItem.getShortName())) throw new ServiceException("货物或服务简称不能为空");
+ if (invoiceItem.getShortName().length() > SHORT_NAME_MAX_LENGTH) throw new ServiceException("货物或服务简称不能超过100字");
+ if (Func.isEmpty(invoiceItem.getTaxClassificationCode())) throw new ServiceException("税收分类编码不能为空");
+ if (invoiceItem.getTaxClassificationCode().length() > TAX_CODE_MAX_LENGTH) throw new ServiceException("税收分类编码不能超过30字");
+ if (Func.isEmpty(invoiceItem.getCategoryName())) throw new ServiceException("商品和服务分类名称不能为空");
+ if (invoiceItem.getCategoryName().length() > CATEGORY_NAME_MAX_LENGTH) throw new ServiceException("商品和服务分类名称不能超过200字");
+ BigDecimal taxRate = invoiceItem.getDefaultTaxRate();
+ if (taxRate == null) throw new ServiceException("默认税率不能为空");
+ if (taxRate.compareTo(BigDecimal.ZERO) < 0 || taxRate.compareTo(new BigDecimal("100")) > 0) throw new ServiceException("默认税率必须在0到100之间");
+ boolean duplicate = count(Wrappers.lambdaQuery()
+ .eq(InvoiceItem::getTaxClassificationCode, invoiceItem.getTaxClassificationCode())
+ .eq(InvoiceItem::getShortName, invoiceItem.getShortName())
+ .eq(InvoiceItem::getIsDeleted, 0)
+ .ne(Func.isNotEmpty(invoiceItem.getId()), InvoiceItem::getId, invoiceItem.getId())) > 0;
+ if (duplicate) throw new ServiceException("该开票项目已存在");
+ }
+
+ private String trim(String value) {
+ return value == null ? "" : value.trim();
+ }
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/InvoiceItemWrapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/InvoiceItemWrapper.java
new file mode 100644
index 0000000..beac880
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/wrapper/InvoiceItemWrapper.java
@@ -0,0 +1,50 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes. Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.system.wrapper;
+
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.cache.SysCache;
+import org.springblade.system.cache.UserCache;
+import org.springblade.system.pojo.entity.InvoiceItem;
+import org.springblade.system.pojo.vo.InvoiceItemVO;
+
+import java.util.Objects;
+
+/**
+ * 开票项目包装类
+ *
+ * @author Chill
+ */
+public class InvoiceItemWrapper extends BaseEntityWrapper {
+
+ public static InvoiceItemWrapper build() {
+ return new InvoiceItemWrapper();
+ }
+
+ @Override
+ public InvoiceItemVO entityVO(InvoiceItem invoiceItem) {
+ InvoiceItemVO vo = Objects.requireNonNull(BeanUtil.copyProperties(invoiceItem, InvoiceItemVO.class));
+ vo.setCreateDeptName(Func.isEmpty(invoiceItem.getCreateDept()) ? "" : SysCache.getDeptName(invoiceItem.getCreateDept()));
+ vo.setUpdateUserName(UserCache.getUserRealName(invoiceItem.getUpdateUser()));
+ return vo;
+ }
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java
index 19f1b8e..ba1724f 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceApplicationServiceImpl.java
@@ -70,9 +70,11 @@ import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -183,14 +185,16 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl settlements = distinctIds(settlementIds).stream().map(this::availableSettlement).toList();
assertCompatible(settlements);
FormalSettlement first = settlements.get(0);
- CustomerArchive customer = findCustomer(first.getPayerName());
+ CustomerArchive customer = findCustomer(first.getPayeeName());
+ List invoiceInfos = customer == null ? List.of() : activeInvoiceInfos(customer.getId());
Map result = new LinkedHashMap<>();
result.put("issuerName", first.getPayeeName());
result.put("receiverName", first.getPayerName());
result.put("customer", customer);
- result.put("invoiceInfos", customer == null ? List.of() : customerInvoiceInfoMapper.selectList(
- Wrappers.lambdaQuery().eq(CustomerInvoiceInfo::getCustomerId, customer.getId())
- .eq(CustomerInvoiceInfo::getStatus, 1).orderByDesc(CustomerInvoiceInfo::getIsDefault)));
+ // 应付结算单的开票方(payeeName)是客商,受票方信息和部门邮箱均来源于该客商发票信息。
+ result.put("invoices", invoiceInfos);
+ result.put("invoiceInfos", invoiceInfos);
+ result.put("departmentEmails", invoiceInfoEmails(invoiceInfos));
result.put("contacts", customer == null ? List.of() : customerContactMapper.selectList(
Wrappers.lambdaQuery().eq(CustomerContact::getCustomerId, customer.getId())
.eq(CustomerContact::getStatus, 1).orderByDesc(CustomerContact::getIsDefault)));
@@ -217,7 +221,6 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl settlementMap = settlements.stream()
.collect(Collectors.toMap(FormalSettlement::getId, Function.identity()));
BigDecimal totalAvailable = BigDecimal.ZERO;
- BigDecimal allocatedTotal = BigDecimal.ZERO;
for (InvoiceApplicationSaveRequest.SettlementRow row : requestedRows) {
FormalSettlement settlement = settlementMap.get(row.getSettlementId());
BigDecimal available = availableAmount(settlement, entity.getId());
@@ -226,21 +229,18 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl invoiceInfos = activeInvoiceInfos(customer.getId());
+ CustomerInvoiceInfo invoiceInfo = invoiceInfos.stream()
+ .filter(item -> Objects.equals(item.getId(), request.getReceiverInvoiceInfoId()))
+ .findFirst().orElseThrow(() -> new ServiceException("请选择受票方有效的开票信息"));
+ String departmentEmails = normalizeDepartmentEmails(request.getDepartmentEmails(), invoiceInfos);
String invoiceTitle = required(invoiceInfo.getInvoiceTitle(), "受票方单位");
String taxpayerNo = limit(invoiceInfo.getTaxNo(), 20, "纳税人识别号");
String bankName = limit(invoiceInfo.getBankName(), 100, "开户行");
@@ -271,7 +271,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpllambdaQuery()
- .and(wrapper -> wrapper.eq(CustomerArchive::getFullName, name).or().eq(CustomerArchive::getShortName, name))
+ String value = name.trim();
+ CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.lambdaQuery()
+ .and(wrapper -> wrapper.eq(CustomerArchive::getFullName, value).or().eq(CustomerArchive::getShortName, value))
.eq(CustomerArchive::getStatus, 1)
.eq(CustomerArchive::getIsDeleted, 0).last("limit 1"));
+ if (customer != null) return customer;
+ String normalized = normalizeCustomerName(value);
+ return customerArchiveMapper.selectList(Wrappers.lambdaQuery()
+ .eq(CustomerArchive::getStatus, 1)
+ .eq(CustomerArchive::getIsDeleted, 0))
+ .stream()
+ .filter(item -> normalized.equals(normalizeCustomerName(item.getFullName()))
+ || normalized.equals(normalizeCustomerName(item.getShortName())))
+ .findFirst().orElse(null);
+ }
+
+ private String normalizeCustomerName(String value) {
+ return value == null ? "" : value.replaceAll("\\s+", "");
+ }
+
+ private List activeInvoiceInfos(Long customerId) {
+ return customerInvoiceInfoMapper.selectList(Wrappers.lambdaQuery()
+ .eq(CustomerInvoiceInfo::getCustomerId, customerId)
+ .eq(CustomerInvoiceInfo::getStatus, 1)
+ .eq(CustomerInvoiceInfo::getIsDeleted, 0)
+ .orderByDesc(CustomerInvoiceInfo::getIsDefault)
+ .orderByAsc(CustomerInvoiceInfo::getCreateTime));
+ }
+
+ private List invoiceInfoEmails(List invoiceInfos) {
+ return invoiceInfos.stream().map(CustomerInvoiceInfo::getEmail).filter(Func::isNotEmpty)
+ .flatMap(value -> splitEmails(value).stream())
+ .collect(Collectors.toMap(email -> email.toLowerCase(Locale.ROOT), Function.identity(),
+ (first, duplicate) -> first, LinkedHashMap::new)).values().stream().toList();
}
private void changeStatus(Long id, String from, String to, String actionType, String node, String reason) {
@@ -587,12 +617,28 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl emails = value == null ? List.of() : List.of(value.split("[;,,;]"));
- List normalized = emails.stream().map(String::trim).filter(item -> !item.isEmpty()).distinct().toList();
+ private String normalizeDepartmentEmails(String value, List invoiceInfos) {
+ Map configuredEmails = invoiceInfoEmails(invoiceInfos).stream()
+ .collect(Collectors.toMap(email -> email.toLowerCase(Locale.ROOT), Function.identity(),
+ (first, duplicate) -> first, LinkedHashMap::new));
+ if (configuredEmails.isEmpty()) throw new ServiceException("当前客商的开票信息未配置邮箱");
+ List normalized = splitEmails(value).stream()
+ .collect(Collectors.toMap(email -> email.toLowerCase(Locale.ROOT), Function.identity(),
+ (first, duplicate) -> first, LinkedHashMap::new)).values().stream().toList();
if (normalized.isEmpty() || normalized.size() > 3) throw new ServiceException("部门邮箱必填且最多选择3个");
- normalized.forEach(email -> validateEmail(email, "部门邮箱"));
- return String.join(";", normalized);
+ List selectedEmails = normalized.stream().map(email -> {
+ validateEmail(email, "部门邮箱");
+ String configuredEmail = configuredEmails.get(email.toLowerCase(Locale.ROOT));
+ if (configuredEmail == null) throw new ServiceException("部门邮箱必须选择当前客商开票信息中配置的邮箱");
+ return configuredEmail;
+ }).distinct().toList();
+ return String.join(";", selectedEmails);
+ }
+
+ private List splitEmails(String value) {
+ if (Func.isEmpty(value)) return List.of();
+ return Arrays.stream(value.split("[;,,;]"))
+ .map(String::trim).filter(item -> !item.isEmpty()).toList();
}
private String validateEmail(String value, String name) {
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java
index 43d1152..f6b71bb 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java
@@ -212,6 +212,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl oldSettlementIds = entity.getId() == null ? List.of() : relationSettlementIds(entity.getId());
KingdeeInvoicePool invoice = lockedInvoice(request.getKingdeeInvoicePoolId());
+ if (invoice == null) invoice = invoiceSnapshot(request);
assertInvoiceUnused(invoice, entity.getId());
Map requestedRows = distinctSettlementRows(
@@ -219,10 +220,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl settlementMap = lockSettlements(requestedRows.keySet().stream().sorted().toList());
List settlements = requestedRows.keySet().stream().map(settlementMap::get).toList();
assertCompatible(settlements);
- assertInvoiceParties(invoice, settlements.get(0));
- BigDecimal invoiceAmount = positive(invoice.getInvoiceAmount(), "开票金额");
- BigDecimal allocatedTotal = BigDecimal.ZERO;
for (Map.Entry entry : requestedRows.entrySet()) {
FormalSettlement settlement = settlementMap.get(entry.getKey());
BigDecimal allocated = nonNegative(entry.getValue().getAllocatedInvoiceAmount(), "分摊发票金额");
@@ -231,10 +229,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpllambdaQuery()
+ return invoicePoolMapper.selectOne(Wrappers.lambdaQuery()
.eq(KingdeeInvoicePool::getId, id)
.last("FOR UPDATE"));
- if (invoice == null || Objects.equals(invoice.getIsDeleted(), 1)
- || !Objects.equals(invoice.getStatus(), 1)) {
- throw new ServiceException("金蝶票据池发票不存在或已失效");
- }
- required(invoice.getInvoiceNo(), "发票号码");
+ }
+
+ private KingdeeInvoicePool invoiceSnapshot(InvoiceReceiptSaveRequest request) {
+ KingdeeInvoicePool invoice = new KingdeeInvoicePool();
+ invoice.setId(request.getKingdeeInvoicePoolId());
+ invoice.setInvoiceNo(request.getInvoiceNo());
+ invoice.setInvoiceDate(request.getInvoiceDate());
+ invoice.setInvoiceType(request.getInvoiceType());
+ invoice.setTaxRate(request.getTaxRate());
+ invoice.setInvoiceAmount(request.getInvoiceAmount());
+ invoice.setTaxAmount(request.getTaxAmount());
+ invoice.setReceiverName(request.getReceiverName());
+ invoice.setIssuerName(request.getIssuerName());
+ invoice.setBankName(request.getBankName());
+ invoice.setBankAccount(request.getBankAccount());
+ invoice.setIssuingBank(request.getIssuingBank());
+ invoice.setKingdeeBillNo(request.getKingdeeBillNo());
+ invoice.setKingdeeStatus(request.getKingdeeStatus());
+ return invoice;
+ }
+
+ private KingdeeInvoicePool invoiceSnapshot(InvoiceReceipt receipt) {
+ KingdeeInvoicePool invoice = new KingdeeInvoicePool();
+ invoice.setId(receipt.getKingdeeInvoicePoolId());
+ invoice.setInvoiceNo(receipt.getInvoiceNo());
+ invoice.setInvoiceDate(receipt.getInvoiceDate());
+ invoice.setInvoiceType(receipt.getInvoiceType());
+ invoice.setTaxRate(receipt.getTaxRate());
+ invoice.setInvoiceAmount(receipt.getInvoiceAmount());
+ invoice.setTaxAmount(receipt.getTaxAmount());
+ invoice.setReceiverName(receipt.getReceiverName());
+ invoice.setIssuerName(receipt.getIssuerName());
+ invoice.setBankName(receipt.getBankName());
+ invoice.setBankAccount(receipt.getBankAccount());
+ invoice.setIssuingBank(receipt.getIssuingBank());
+ invoice.setKingdeeBillNo(receipt.getKingdeeBillNo());
+ invoice.setKingdeeStatus(receipt.getKingdeeStatus());
return invoice;
}
@@ -456,17 +482,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl relations = settlementRelationMapper.selectList(
Wrappers.lambdaQuery()
@@ -545,8 +561,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl settlementMap.get(item.getFormalSettlementId()))
.toList();
assertCompatible(settlements);
- assertInvoiceParties(invoice, settlements.get(0));
- BigDecimal allocatedTotal = BigDecimal.ZERO;
for (InvoiceReceiptSettlement relation : relations) {
FormalSettlement settlement = settlementMap.get(relation.getFormalSettlementId());
BigDecimal allocated = nonNegative(relation.getAllocatedInvoiceAmount(), "分摊发票金额");
@@ -555,10 +569,6 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl> settlementCandidates(String keyword, Long flowId) {
- KingdeeReceiptFlow flow = existing(flowId);
+ existing(flowId);
List settlements = formalSettlementMapper.selectList(
Wrappers.lambdaQuery()
.eq(FormalSettlement::getSettlementType, RECEIVABLE)
@@ -139,9 +139,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl Func.isEmpty(flow.getCounterpartyName())
- || sameName(flow.getCounterpartyName(), settlement.getPayerName()))
- .map(settlement -> candidateRow(settlement))
+ .map(this::candidateRow)
.filter(row -> ((BigDecimal) row.get("remainingReceiptAmount")).compareTo(BigDecimal.ZERO) > 0)
.toList();
}
@@ -163,7 +161,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl settlementMap = lockSettlements(settlementIds);
List settlements = settlementIds.stream().map(settlementMap::get).toList();
assertCompatible(settlements);
- assertCounterparty(flow, settlements.get(0));
Map previousClaimedMap = new LinkedHashMap<>();
BigDecimal allocatedTotal = BigDecimal.ZERO;
@@ -351,16 +348,6 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpllambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, settlementId)
diff --git a/doc/sql/transport/blade_invoice_application_20260821.sql b/doc/sql/transport/blade_invoice_application_20260821.sql
index 67aa6a6..dd1c585 100644
--- a/doc/sql/transport/blade_invoice_application_20260821.sql
+++ b/doc/sql/transport/blade_invoice_application_20260821.sql
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `blade_invoice_application` (
`applicant_name` varchar(100) DEFAULT NULL,
`undertaking_dept_id` bigint(20) DEFAULT NULL,
`undertaking_dept_name` varchar(100) DEFAULT NULL,
- `department_emails` varchar(320) DEFAULT NULL,
+ `department_emails` varchar(320) DEFAULT NULL COMMENT '部门邮箱(多个以分号分隔,最多3个)',
`receiver_invoice_info_id` bigint(20) DEFAULT NULL,
`taxpayer_no` varchar(20) DEFAULT NULL,
`bank_name` varchar(100) DEFAULT NULL,
diff --git a/doc/sql/transport/blade_invoice_application_department_emails_20260827.sql b/doc/sql/transport/blade_invoice_application_department_emails_20260827.sql
new file mode 100644
index 0000000..ecdc0df
--- /dev/null
+++ b/doc/sql/transport/blade_invoice_application_department_emails_20260827.sql
@@ -0,0 +1,3 @@
+-- 开票申请部门邮箱支持最多3个客商开票信息邮箱,以分号分隔保存。
+ALTER TABLE `blade_invoice_application`
+ MODIFY COLUMN `department_emails` varchar(320) DEFAULT NULL COMMENT '部门邮箱(多个以分号分隔,最多3个)';
diff --git a/doc/sql/transport/blade_invoice_item.sql b/doc/sql/transport/blade_invoice_item.sql
new file mode 100644
index 0000000..1c7fe82
--- /dev/null
+++ b/doc/sql/transport/blade_invoice_item.sql
@@ -0,0 +1,28 @@
+-- 开票项目
+DROP TABLE IF EXISTS `blade_invoice_item`;
+CREATE TABLE `blade_invoice_item` (
+ `id` bigint NOT NULL COMMENT '主键',
+ `short_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '货物或服务简称',
+ `tax_classification_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '税收分类编码',
+ `category_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品和服务分类名称',
+ `default_tax_rate` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '默认税率(百分比)',
+ `create_user` bigint DEFAULT NULL COMMENT '创建人',
+ `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
+ `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+ `update_user` bigint DEFAULT NULL COMMENT '修改人',
+ `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+ `status` int DEFAULT 1 COMMENT '状态',
+ `is_deleted` int DEFAULT 0 COMMENT '是否已删除',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_invoice_item_code_name` (`tax_classification_code`,`short_name`),
+ KEY `idx_invoice_item_category` (`category_name`),
+ KEY `idx_invoice_item_short_name` (`short_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='开票项目';
+
+-- 系统管理(parent_id=1164733399668962201)
+INSERT INTO `blade_menu` (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) VALUES
+(2075449200000000101,1123598815738675203,'invoice_item','开票项目','invoice_item','/base/invoice-item','iconfont icon-shoucang',61,1,0,1,NULL,'',0),
+(2075449200000000102,2075449200000000101,'invoice_item_add','新增','invoice_item_add','', '',1,2,1,1,NULL,'',0),
+(2075449200000000103,2075449200000000101,'invoice_item_edit','编辑','invoice_item_edit','', '',2,2,2,1,NULL,'',0),
+(2075449200000000104,2075449200000000101,'invoice_item_delete','删除','invoice_item_delete','', '',3,2,3,1,NULL,'',0),
+(2075449200000000105,2075449200000000101,'invoice_item_view','查看','invoice_item_view','', '',4,2,2,1,NULL,'',0);