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 @@ + + + + + + + + + + + + + + + + + + + + + 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);