feat: 收票登记新增所属合同带出承办部门替代手选结算组织

This commit is contained in:
刘泉佳
2026-11-05 12:05:42 +08:00
parent 3effc68fde
commit d538250ede
5 changed files with 69 additions and 23 deletions
@@ -59,10 +59,14 @@ public class InvoiceReceiptSaveRequest implements Serializable {
private String attachmentsJson;
private String remark;
/**
* 结算组织(页面手选部门,先票后款链路收票登记不再必选关联结算单)
* 结算组织(历史兼容入参:现由所属合同承办部门带出,页面不再手选)
*/
private Long deptId;
private String deptName;
/**
* 所属合同编号(承运商合同,合同名称/承办部门按编号带出,不落库)
*/
private String contractNo;
/**
* 关联结算单分摊(兼容历史入参:先票后款链路不再传,关联关系由付款结算单侧写入)
*/
@@ -112,6 +112,10 @@ public class InvoiceReceipt extends TenantEntity {
* 收款方
*/
private String payeeName;
/**
* 所属合同编号(承运商合同,结算组织=合同承办部门)
*/
private String contractNo;
/**
* 往来户金蝶供应商代码(发票销售方,保存时经客商档案/A901-1 回填,推送 A303-2 前强校验)
*/
@@ -57,6 +57,7 @@ import org.springblade.transport.constant.KingdeeAttachmentConstant;
import org.springblade.transport.constant.KingdeeBfiConstant;
import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.CustomerContactMapper;
import org.springblade.transport.mapper.CustomerInvoiceInfoMapper;
@@ -150,6 +151,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
private final InvoiceReceiptSettlementMapper settlementRelationMapper;
private final InvoiceReceiptRecordMapper recordMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ContractManageMapper contractManageMapper;
/** 票池认领关系 Mapper,实时视图的 claimed 装饰标记取数来源 */
private final FormalSettlementInvoiceMapper formalSettlementInvoiceMapper;
private final FormalSettlementDetailMapper formalSettlementDetailMapper;
@@ -423,12 +425,25 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
}
copyInvoiceInformation(entity, invoice);
if (requestedRows.isEmpty()) {
// 先票后款:结算组织=页面手选部门(合同承办部门口径);付款方=受票单位、收款方=开票单位
if (request.getDeptId() == null) {
throw new ServiceException("请选择结算组织");
// 先票后款:结算组织=所属合同承办部门(金蝶公司代码沿承办部门对应平台公司派生);付款方=受票单位、收款方=开票单位
// 所属合同仅落 contract_no,合同名称等其余信息按编号实时带出
if (Func.isBlank(request.getContractNo())) {
throw new ServiceException("请选择所属合同");
}
entity.setDeptId(request.getDeptId());
entity.setDeptName(request.getDeptName());
ContractManage contract = contractManageMapper.selectOne(Wrappers.<ContractManage>lambdaQuery()
.eq(ContractManage::getContractNo, request.getContractNo()));
if (contract == null) {
throw new ServiceException("所属合同" + request.getContractNo() + "不存在");
}
if (!"承运商合同".equals(contract.getContractCategory())) {
throw new ServiceException("所属合同须为承运商类型的合同");
}
if (contract.getOrganizationId() == null) {
throw new ServiceException("合同" + contract.getContractNo() + "未维护承办部门,请先在合同管理中补录");
}
entity.setContractNo(contract.getContractNo());
entity.setDeptId(contract.getOrganizationId());
entity.setDeptName(contract.getOrganizationName());
entity.setPayerName(invoice.getReceiverName());
entity.setPayeeName(invoice.getIssuerName());
// 往来户=发票销售方金蝶供应商代码(A901-1),保存时尽力回填,推送前强校验缺失阻断
@@ -735,12 +750,14 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
fetchMirrorItemsFromPool(plan.mirror());
}
log.info("收票登记[{}]开始 A303-2 推送金蝶收票单", receipt.getInvoiceNo());
ContractManage contract = kingdeeApBillSyncSupport.requireContract(plan.settlement().getContractId(), context);
// 明细分录与应付单 A102-2 同源同口径:复用同一结算明细解析结果,结算明细行+结算合计费用项各一条
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups =
kingdeeApBillSyncSupport.resolveEntryGroups(plan.details(), context);
// 组织/币种/往来户按登记所属合同派生(结算组织=合同承办部门对应平台公司金蝶编码);合同按编号实时带出
ContractManage contract = contractManageMapper.selectOne(Wrappers.<ContractManage>lambdaQuery()
.eq(ContractManage::getContractNo, receipt.getContractNo()));
if (contract == null) {
throw new ServiceException("所属合同" + receipt.getContractNo() + "不存在,请核实合同状态");
}
KingdeeReceivedInvoiceSyncSupport.ReceivedInvoicePushOutcome pushOutcome =
kingdeeReceivedInvoiceSyncSupport.pushReceivedInvoice(receipt, plan.mirror(), contract, entryGroups, context);
kingdeeReceivedInvoiceSyncSupport.pushReceivedInvoice(receipt, plan.mirror(), contract, context);
billNo = pushOutcome.billNo();
billPkId = pushOutcome.billId();
log.info("收票登记[{}]A303-2 推送成功:金蝶收票单[{}],星瀚内码={}", receipt.getInvoiceNo(), billNo, billPkId);
@@ -833,8 +850,8 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
if ("2".equals(mirror.getCheckStatus())) {
return new ReceivedInvoicePushPlan(receipt, mirror, null, null, null, "金蝶发票查验不通过,请先人工核实票池发票");
}
if (receipt.getDeptId() == null) {
return new ReceivedInvoicePushPlan(receipt, mirror, null, null, null, "收票登记未选择结算组织,无法确定金蝶结算组织编码");
if (Func.isBlank(receipt.getContractNo())) {
return new ReceivedInvoicePushPlan(receipt, mirror, null, null, null, "收票登记未选择所属合同,无法确定金蝶结算组织编码");
}
return new ReceivedInvoicePushPlan(receipt, mirror, null, null, null, null);
}
@@ -874,7 +891,15 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
receipt.setKingdeePushStatus(pushStatus);
receipt.setKingdeePushMsg(pushMsg);
receipt.setKingdeePushTime(LocalDateTime.now());
updateById(receipt);
// updateById 默认忽略 null 字段,推送成功时失败原因须显式置空,改用 lambdaUpdate 落库
update(Wrappers.<InvoiceReceipt>lambdaUpdate()
.eq(InvoiceReceipt::getId, receipt.getId())
.set(InvoiceReceipt::getKingdeeReceivedInvoiceNo, receipt.getKingdeeReceivedInvoiceNo())
.set(Func.isNotBlank(receipt.getKingdeeReceivedInvoiceId()), InvoiceReceipt::getKingdeeReceivedInvoiceId, receipt.getKingdeeReceivedInvoiceId())
.set(InvoiceReceipt::getKingdeePushStatus, receipt.getKingdeePushStatus())
.set(InvoiceReceipt::getKingdeePushMsg, receipt.getKingdeePushMsg())
.set(InvoiceReceipt::getKingdeePushTime, receipt.getKingdeePushTime())
.set(InvoiceReceipt::getUpdateTime, LocalDateTime.now()));
if (writeRecord) {
record(receipt.getId(), "sync", "推送金蝶收票单", receipt.getApprovalStatus(), receipt.getApprovalStatus(),
Func.isNotBlank(pushMsg) ? pushMsg : "金蝶收票单[" + Func.toStr(receivedInvoiceNo) + "]已生成",
@@ -152,21 +152,19 @@ public class KingdeeReceivedInvoiceSyncSupport {
/**
* 推送金蝶收票单(A303-2 批量保存,调用方已完成 A801-3 幂等预检)。
* 主表:组织/币种/往来户编码按分摊结算单关联合同派生(与应付单同源),销方=开票单位、购方=受票单位;
* 明细分录与财务应付单 A102-2 同源同口径:结算明细按 货物→费用大类→税率 拆分行+手动添加结算合计费用项每行一条,物料/数量/单价/金额取自
* 与应付单分录同一解析结果({@link KingdeeApBillSyncSupport.BfiEntryGroup}),分录标识同键。
* 主表:组织/币种/往来户编码按收票登记所属合同派生(与应付单同源),销方=开票单位、购方=受票单位;
* 明细分录取发票明细行(镜像 items_json,A801-1),标识同键。
* 联调口径沿用应付侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码),
* 非必填的税号/地址/电话/开户行等 setter 注释保留,联调确认后放开
*
* @param receipt 收票登记(审批通过、已绑定镜像)
* @param mirror 票池镜像(票面主表字段取数来源)
* @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
* @param entryGroups 分摊结算单解析出的分录(与应付单 A102-2 同一解析结果)
* @param context 金蝶同步上下文(缓存载体)
* @param receipt 收票登记(审批通过、已绑定镜像)
* @param mirror 票池镜像(票面主表字段取数来源)
* @param contract 收票登记所属合同(组织/币种/往来户派生,结算组织=合同承办部门对应平台公司)
* @param context 金蝶同步上下文(缓存载体)
* @return 推送结果(单据编号 + 星瀚收票单内码,A902 附件上传定位用)
*/
public ReceivedInvoicePushOutcome pushReceivedInvoice(InvoiceReceipt receipt, KingdeeInvoicePool mirror, ContractManage contract,
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups, KingdeeSyncContext context) {
KingdeeSyncContext context) {
if (Func.isBlank(mirror.getInvoiceTypeCode())) {
throw new ServiceException("推送金蝶收票单失败:镜像缺少票易通发票类型编码,请先重新绑定票池发票");
}
@@ -0,0 +1,15 @@
-- 收票登记新增所属合同编号:页面去掉手选结算组织,改为选择承运商合同带出承办部门
-- 口径:仅落 contract_no,合同名称/承办部门按编号实时带出;
-- 结算组织 = 合同承办部门(dept_id/dept_name 取合同 organization_id/organization_name),
-- 金蝶结算组织编码沿承办部门对应平台公司派生(KingdeeApBillSyncSupport#bfiOrgNumber)
-- 幂等可重复执行:列已存在时重复执行报 Duplicate column 属预期,跳过即可
ALTER TABLE `blade_invoice_receipt`
ADD COLUMN `contract_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '所属合同编号(承运商合同)' AFTER `payee_name`;
-- 存量已审批未推送数据无所属合同,推送前需编辑补录(推送预检按 contract_no 拦截)
SELECT COUNT(*) AS approved_receipts_without_contract
FROM `blade_invoice_receipt`
WHERE `is_deleted` = 0
AND `approval_status` = 'approved'
AND (`contract_no` IS NULL OR `contract_no` = '');