feat: 收票单分录改为与应付单同源结算费用行,防重键改用收票登记id
This commit is contained in:
+1
-1
@@ -136,7 +136,7 @@ public class KingdeeInvoicePool extends TenantEntity {
|
||||
*/
|
||||
private String attachmentsJson;
|
||||
/**
|
||||
* A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,A303-2 收票单分录取数来源)
|
||||
* A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,票池存档;收票单分录已改与应付单同源结算费用行)
|
||||
*/
|
||||
private String itemsJson;
|
||||
/**
|
||||
|
||||
+50
-89
@@ -28,7 +28,6 @@ package org.springblade.transport.support;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.thirdparty.bfi.constant.BfiConstant;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO;
|
||||
@@ -37,7 +36,6 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolItemVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
@@ -54,7 +52,6 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -64,9 +61,11 @@ import java.util.Map;
|
||||
* 金蝶收票单同步支撑组件(图05 环节②③:A303-2 收票单批量保存 + A302-6 收票指定应付 + A801-3 收票单查询)。
|
||||
* <p>收票登记「推送金蝶」由 {@link org.springblade.transport.service.IInvoiceReceiptService} 编排前置校验
|
||||
* (审批通过、已绑定镜像、一票一应付、A102-4 应付单已生成预检、A801-3 幂等预检)后调用本组件;
|
||||
* 报文组装取数:主表组织/币种/往来户复用应付支撑组件的合同派生方法,票面字段与分录取自票池镜像
|
||||
* (invoice_type_code / items_json,A801-1 落库)。防重键:收票单 awrp_src_code=发票号码(一票一登记天然唯一),
|
||||
* awrp_entry_code=发票号码-明细行号;A302-6 按「收票单防重键 ↔ 财务应付单防重键」一票一应付指定。
|
||||
* 报文组装取数:主表组织/币种/往来户复用应付支撑组件的合同派生方法,票面字段取自票池镜像
|
||||
* (invoice_type_code 等,A801-1 落库),明细分录与财务应付单 A102-2 同源同口径
|
||||
* (逐结算货物费用行一条,{@link KingdeeApBillSyncSupport.BfiEntryGroup},分录标识同取费用行id)。
|
||||
* 防重键:收票单 awrp_src_code=收票登记id,awrp_entry_code=应付货物费用行id(与应付单分录同键,
|
||||
* 便于金蝶侧逐行勾稽);A302-6 按「收票单防重键 ↔ 财务应付单防重键」一票一应付指定。
|
||||
* 财务应付单侧(A102-2/A102-4)取数与校验复用 {@link KingdeeApBillSyncSupport}。</p>
|
||||
*
|
||||
* @author Chill
|
||||
@@ -149,27 +148,28 @@ public class KingdeeReceivedInvoiceSyncSupport {
|
||||
/**
|
||||
* 推送金蝶收票单(A303-2 批量保存,调用方已完成 A801-3 幂等预检)。
|
||||
* 主表:组织/币种/往来户编码按分摊结算单关联合同派生(与应付单同源),销方=开票单位、购方=受票单位;
|
||||
* 分录逐票池明细行展开(items_json),物料编码为联调期临时配置值。
|
||||
* 联调口径沿用应付/应收侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码、税额金额),
|
||||
* 非必填的地址/电话/开户行等 setter 注释保留,联调确认后放开
|
||||
* 明细分录与财务应付单 A102-2 同源同口径:逐结算货物费用行一条(不聚合),物料/数量/单价/金额取自
|
||||
* 与应付单分录同一解析结果({@link KingdeeApBillSyncSupport.BfiEntryGroup}),分录标识同取费用行id。
|
||||
* 联调口径沿用应付侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码),
|
||||
* 非必填的税号/地址/电话/开户行等 setter 注释保留,联调确认后放开
|
||||
*
|
||||
* @param receipt 收票登记(审批通过、已绑定镜像)
|
||||
* @param mirror 票池镜像(invoice_type_code/items_json 取数来源)
|
||||
* @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
|
||||
* @param context 金蝶同步上下文(缓存载体)
|
||||
* @param receipt 收票登记(审批通过、已绑定镜像)
|
||||
* @param mirror 票池镜像(票面主表字段取数来源)
|
||||
* @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
|
||||
* @param entryGroups 分摊结算单解析出的货物费用行分录(与应付单 A102-2 同一解析结果)
|
||||
* @param context 金蝶同步上下文(缓存载体)
|
||||
* @return 金蝶收票单单据编号
|
||||
*/
|
||||
public String pushReceivedInvoice(InvoiceReceipt receipt, KingdeeInvoicePool mirror, ContractManage contract,
|
||||
KingdeeSyncContext context) {
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups, KingdeeSyncContext context) {
|
||||
if (Func.isBlank(mirror.getInvoiceTypeCode())) {
|
||||
throw new ServiceException("推送金蝶收票单失败:镜像缺少票易通发票类型编码,请先重新绑定票池发票");
|
||||
}
|
||||
List<BfiInvoicePoolItemVO> items = parseItems(mirror);
|
||||
BfiReceivedInvoiceSaveDTO param = new BfiReceivedInvoiceSaveDTO();
|
||||
// 【必填】外部系统编号(候选键)
|
||||
param.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
// 【必填】外部系统来源单据号(候选键):发票号码(一票一登记天然唯一,重推同键幂等)
|
||||
param.setAwrpSrcCode(receipt.getInvoiceNo());
|
||||
param.setAwrpSrcCode(String.valueOf(receipt.getId()));
|
||||
// 发票号码(非必填但为收票单核心业务字段,必送)
|
||||
param.setInvoiceno(receipt.getInvoiceNo());
|
||||
// 【必填】收票日期:以推送当日为收票日期
|
||||
@@ -199,20 +199,20 @@ public class KingdeeReceivedInvoiceSyncSupport {
|
||||
// 录入价税合计(非必传,与应付/应收侧口径一致传 true)
|
||||
param.setIsincludetax(true);
|
||||
// 销方税号(非必传,联调确认后放开)
|
||||
// param.setSellertin(mirror.getSalerTaxNo());
|
||||
param.setSellertin(mirror.getSalerTaxNo());
|
||||
// 购方税号(非必传,联调确认后放开)
|
||||
// param.setBuyertin(mirror.getBuyerTaxNo());
|
||||
param.setBuyertin(mirror.getBuyerTaxNo());
|
||||
// 销方地址/电话/开户行/账号(非必传,被金蝶强校验时从镜像 bank_name/bank_account/issuing_bank/phone 补齐)
|
||||
// param.setSelleraddr(null);
|
||||
// param.setSellertel(mirror.getPhone());
|
||||
// param.setSellerbank(mirror.getBankName());
|
||||
// param.setSellracct(mirror.getBankAccount());
|
||||
param.setSellertel(mirror.getPhone());
|
||||
param.setSellerbank(mirror.getBankName());
|
||||
param.setSelleracct(mirror.getBankAccount());
|
||||
// 备注(非必传)
|
||||
// param.setRemark(receipt.getRemark());
|
||||
// 【必填】外部单据类型
|
||||
param.setAwrpSrcType(KingdeeBfiConstant.BFI_RECEIVED_INVOICE_SRC_TYPE);
|
||||
// 【必填】明细分录:逐票池明细行展开
|
||||
param.setEntry(buildEntries(receipt.getInvoiceNo(), items));
|
||||
// 【必填】明细分录:与应付单 A102-2 同源同口径,逐结算货物费用行一条(不聚合)
|
||||
param.setEntry(buildEntries(entryGroups));
|
||||
|
||||
BfiBillSaveResultVO saveResult = bfiService.saveReceivedInvoice(param);
|
||||
kingdeeApBillSyncSupport.requireBfiSaveSuccess(saveResult, "推送金蝶收票单");
|
||||
@@ -220,85 +220,46 @@ public class KingdeeReceivedInvoiceSyncSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* 票池明细行 → A303-2 分录:金额/税额/数量/单价直取票面明细,税收分类编码取 goodsCode,
|
||||
* 税率换算为金蝶税率编码(0.13→V13),物料编码为联调期临时配置值(发票明细无金蝶物料概念)
|
||||
* 分录组装与应付单 A102-2 明细同源同口径:逐结算货物费用行一条(不聚合),物料/数量/单价/含税/不含税金额
|
||||
* 取自 {@link KingdeeApBillSyncSupport.BfiEntryGroup}(与应付单分录同一解析结果),分录标识同取费用行id,
|
||||
* 便于金蝶侧收票单明细与应付单明细逐行勾稽;数量无吨位时兜底 1;
|
||||
* 票面明细行(items_json)不再作为分录取数来源,税收分类/税率/税额随应付口径暂不送(联调强校验时再补)
|
||||
*
|
||||
* @param invoiceNo 发票号码(分录防重键前缀)
|
||||
* @param items 票池明细行
|
||||
* @param entryGroups 分摊结算单解析出的货物费用行分录(与应付单 A102-2 同一解析结果)
|
||||
* @return A303-2 明细分录
|
||||
*/
|
||||
private List<BfiReceivedInvoiceSaveEntryDTO> buildEntries(String invoiceNo, List<BfiInvoicePoolItemVO> items) {
|
||||
private List<BfiReceivedInvoiceSaveEntryDTO> buildEntries(List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups) {
|
||||
List<BfiReceivedInvoiceSaveEntryDTO> entries = new ArrayList<>();
|
||||
int index = 1;
|
||||
for (BfiInvoicePoolItemVO item : items) {
|
||||
BigDecimal amount = parseItemAmount(invoiceNo, item.getDetailAmount(), "金额");
|
||||
BigDecimal tax = parseItemAmount(invoiceNo, item.getTaxAmount(), "税额");
|
||||
for (KingdeeApBillSyncSupport.BfiEntryGroup entryGroup : entryGroups) {
|
||||
BfiReceivedInvoiceSaveEntryDTO entry = new BfiReceivedInvoiceSaveEntryDTO();
|
||||
// 【必填】价税合计:明细行金额+税额
|
||||
entry.setEPricetaxtotal(kingdeeApBillSyncSupport.money(amount.add(tax)));
|
||||
// 【必填】数量:票面数量,无数量兜底 1
|
||||
BigDecimal quantity = parseItemAmount(invoiceNo, item.getNum(), "数量");
|
||||
entry.setQuantity(quantity.compareTo(BigDecimal.ZERO) > 0 ? quantity : BigDecimal.ONE);
|
||||
// 【必填】单价:票面单价(金额口径为价税合计录入,单价仅作参考)
|
||||
entry.setPrice(parseItemAmount(invoiceNo, item.getUnitPrice(), "单价"));
|
||||
// 【必填】物料编码.编码:联调期临时值,真实口径与金蝶对齐后替换
|
||||
entry.setMaterialNumber(bfiProperties.getReceivedInvoice().getMaterialNumber());
|
||||
// 【必填】税收分类.编码:票面明细税收分类编码
|
||||
if (Func.isBlank(item.getGoodsCode())) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + index + "行明细无税收分类编码");
|
||||
// 【必填】价税合计:该行含税金额(与应付单 e_pricetaxtotal 同源取数)
|
||||
entry.setEPricetaxtotal(kingdeeApBillSyncSupport.money(entryGroup.amount()));
|
||||
// 【必填】数量:该行运输数量,无吨位数据时兜底 1(与应付单口径一致)
|
||||
entry.setQuantity(entryGroup.quantity().compareTo(BigDecimal.ZERO) > 0 ? entryGroup.quantity() : BigDecimal.ONE);
|
||||
// 【必填】单价:该行运输单价(与应付单同源;单据头 isincludetax=true 录入价税合计,单价仅作参考)
|
||||
entry.setPrice(entryGroup.price());
|
||||
// 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
|
||||
entry.setMaterialNumber(entryGroup.materialNumber());
|
||||
// 金额(不含税):该行结算金额(不含税)(与应付单 e_amount 同源取数)
|
||||
entry.setEAmount(kingdeeApBillSyncSupport.money(entryGroup.amountNoTax()));
|
||||
// 【必填】外部系统来源分录标识(候选键):应付货物费用行id(与应付单分录同键,便于逐行勾稽)
|
||||
entry.setAwrpEntryCode(entryGroup.entryCode());
|
||||
|
||||
// 调试(与应付单 A102-2 保持一致:联调期物料编码临时传金蝶测试档案值,联调通过后与应付侧同步移除)
|
||||
|
||||
if (true) {
|
||||
// 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
|
||||
entry.setMaterialNumber(KingdeeBfiConstant.BFI_AP_BILL_MATERIAL_NUMBER_TEST);
|
||||
}
|
||||
entry.setETaxclassNumber(item.getGoodsCode().trim());
|
||||
// 【必填】税率.编码:票面税率(小数)换算金蝶税率编码(0.13→V13)
|
||||
entry.setTaxrateidNumber(taxRateCode(invoiceNo, index, item.getTaxRate()));
|
||||
// 金额(不含税)/税额(非必传,票面直取)
|
||||
entry.setEAmount(kingdeeApBillSyncSupport.money(amount));
|
||||
entry.setETax(kingdeeApBillSyncSupport.money(tax));
|
||||
// 【必填】外部系统来源分录标识(候选键):发票号码-行号
|
||||
entry.setAwrpEntryCode(invoiceNo + "-" + index);
|
||||
|
||||
entries.add(entry);
|
||||
index++;
|
||||
}
|
||||
if (entries.isEmpty()) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "票池明细为空,请先补查票池发票明细");
|
||||
throw new ServiceException("推送金蝶收票单失败:分摊结算单无货物费用行,无法组装收票单明细");
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 票池明细行 JSON 解析(items 为空时返回空列表,由调用方拦截提示补查)
|
||||
*/
|
||||
public List<BfiInvoicePoolItemVO> parseItems(KingdeeInvoicePool mirror) {
|
||||
if (Func.isBlank(mirror.getItemsJson())) {
|
||||
return List.of();
|
||||
}
|
||||
BfiInvoicePoolItemVO[] items = JsonUtil.parse(mirror.getItemsJson(), BfiInvoicePoolItemVO[].class);
|
||||
return items == null ? List.of() : Arrays.asList(items);
|
||||
}
|
||||
|
||||
/** 票面税率(小数文本)换算金蝶税率编码:0.13→V13、0.01→V1、0→V0 */
|
||||
private String taxRateCode(String invoiceNo, int lineNo, String taxRateText) {
|
||||
if (Func.isBlank(taxRateText)) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率为空");
|
||||
}
|
||||
try {
|
||||
BigDecimal percent = new BigDecimal(taxRateText.trim()).multiply(BigDecimal.valueOf(100)).stripTrailingZeros();
|
||||
return "V" + percent.toPlainString();
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率[" + taxRateText + "]无法解析");
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal parseItemAmount(String invoiceNo, String value, String name) {
|
||||
if (Func.isBlank(value)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(value.trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "明细" + name + "[" + value + "]无法解析");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== A302-6 收票指定应付 ====================
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user