✨ 应收正式结算单同步金蝶财务应收单(A302-2保存+A302-4幂等预检与定时勾稽纠偏)
This commit is contained in:
+104
@@ -21,6 +21,8 @@ import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 正式结算单实体类
|
||||
* <p>
|
||||
* 结算单主表,明细快照见 {@link FormalSettlementDetail},合计费用见 {@link FormalSettlementSummaryFee}
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@@ -30,38 +32,140 @@ import java.time.LocalDateTime;
|
||||
@Schema(description = "正式结算单")
|
||||
public class FormalSettlement extends TenantEntity {
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 正式结算单号(同租户唯一)
|
||||
*/
|
||||
private String formalSettlementNo;
|
||||
/**
|
||||
* 来源类型:预结算合并/应收应付/混合来源
|
||||
*/
|
||||
private String sourceType;
|
||||
/**
|
||||
* 结算类型:receivable-应收 payable-应付
|
||||
*/
|
||||
private String settlementType;
|
||||
/**
|
||||
* 所属项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 所属项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
/**
|
||||
* 所属组织ID
|
||||
*/
|
||||
private Long deptId;
|
||||
/**
|
||||
* 所属组织名称
|
||||
*/
|
||||
private String deptName;
|
||||
/**
|
||||
* 结算合同ID
|
||||
*/
|
||||
private Long contractId;
|
||||
/**
|
||||
* 结算合同编号
|
||||
*/
|
||||
private String contractNo;
|
||||
/**
|
||||
* 结算合同名称
|
||||
*/
|
||||
private String contractName;
|
||||
/**
|
||||
* 付款方名称
|
||||
*/
|
||||
private String payerName;
|
||||
/**
|
||||
* 收款方名称
|
||||
*/
|
||||
private String payeeName;
|
||||
/**
|
||||
* 结算币种,默认 RMB
|
||||
*/
|
||||
private String currency;
|
||||
/**
|
||||
* 结算金额(结算币种,含全部合计费用)
|
||||
*/
|
||||
private BigDecimal settlementAmount;
|
||||
/**
|
||||
* 本位币,默认 RMB
|
||||
*/
|
||||
private String localCurrency;
|
||||
/**
|
||||
* 本位币结算金额 = 结算金额 × 结算汇率
|
||||
*/
|
||||
private BigDecimal localSettlementAmount;
|
||||
/**
|
||||
* 已申请付款金额,含来源预结算的预付
|
||||
*/
|
||||
private BigDecimal appliedPaymentAmount;
|
||||
/**
|
||||
* 已收/已付金额合计,含来源预结算的预付
|
||||
*/
|
||||
private BigDecimal paidAmount;
|
||||
/**
|
||||
* 剩余可付金额 = 结算金额 - 已收已付合计,最小为 0
|
||||
*/
|
||||
private BigDecimal remainingPayableAmount;
|
||||
/**
|
||||
* 已开票/已收票金额,取开票申请与收票登记的分摊合计
|
||||
*/
|
||||
private BigDecimal invoiceAmount;
|
||||
/**
|
||||
* 汇率日期
|
||||
*/
|
||||
private LocalDate exchangeRateDate;
|
||||
/**
|
||||
* 结算汇率
|
||||
*/
|
||||
private BigDecimal exchangeRate;
|
||||
/**
|
||||
* 开票状态(应收为开票、应付为收票):unreceived-未开票 partial-部分开票 completed-已开票
|
||||
*/
|
||||
private String invoiceStatus;
|
||||
/**
|
||||
* 收付款状态:unpaid-未收付款 partial-部分收付款 paid-已收付款
|
||||
*/
|
||||
private String paymentStatus;
|
||||
/**
|
||||
* 审核状态:draft-草稿 reviewing-审批中 approved-审批通过 returned-已驳回 voided-已作废
|
||||
*/
|
||||
private String approvalStatus;
|
||||
/**
|
||||
* 当前审批节点,如「草稿」「财务审核」「审批通过」
|
||||
*/
|
||||
private String currentNode;
|
||||
/**
|
||||
* 当前处理人姓名
|
||||
*/
|
||||
private String currentProcessor;
|
||||
/**
|
||||
* 金蝶单据编号
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶同步状态:unsynced-未同步 synced-已同步 failed-同步失败
|
||||
*/
|
||||
private String kingdeeSyncStatus;
|
||||
/**
|
||||
* 附件JSON
|
||||
*/
|
||||
private String attachmentsJson;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 审核通过时间
|
||||
*/
|
||||
private LocalDateTime approvedTime;
|
||||
/**
|
||||
* 同步金蝶时间
|
||||
*/
|
||||
private LocalDateTime syncedTime;
|
||||
/**
|
||||
* 作废原因,驳回时记录驳回原因
|
||||
*/
|
||||
private String voidReason;
|
||||
}
|
||||
|
||||
+18
@@ -55,6 +55,11 @@ public class BfiProperties {
|
||||
*/
|
||||
private PayApply payApply = new PayApply();
|
||||
|
||||
/**
|
||||
* 开票申请推送配置(A303-1 开票申请单新增 / A303-5 开票申请发票查询)
|
||||
*/
|
||||
private InvoiceApply invoiceApply = new InvoiceApply();
|
||||
|
||||
/**
|
||||
* 付款申请推送配置项
|
||||
*/
|
||||
@@ -77,4 +82,17 @@ public class BfiProperties {
|
||||
private String srcType = "0KzXs";
|
||||
}
|
||||
|
||||
/**
|
||||
* 开票申请推送配置项(A303-1 / A303-5):
|
||||
* 销方名称取申请单开票单位快照、销方税号取客商档案统一社会信用代码、税收分类编码取商品行开票内容快照,均不走配置
|
||||
*/
|
||||
@Data
|
||||
public static class InvoiceApply {
|
||||
|
||||
/**
|
||||
* 分录外部单据类型,默认 0KzXs(与付款申请对齐)
|
||||
*/
|
||||
private String srcType = "0KzXs";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+24
@@ -104,6 +104,30 @@ public final class KingdeeBfiConstant {
|
||||
Map.entry("标准销售应收", "arfin_standard_BT_S")
|
||||
);
|
||||
|
||||
/** A302-2 默认单据类型名称:服务销售应收(经 AR_BILL_BILLTYPE_MAP 转换为编码)。待确认:具体类型需金蝶侧最终确认,确认后修改此名称即可。 */
|
||||
public static final String BFI_AR_BILL_BILLTYPE_NAME = "服务销售应收";
|
||||
|
||||
/** A302-2 款项性质编码(暂按 1001 一般销售,与应付 2001 材料采购区分)。待确认:编码需金蝶侧最终确认,确认后如有调整直接改此常量。 */
|
||||
public static final String BFI_AR_BILL_PAY_PROPERTY = "1001";
|
||||
|
||||
/** A302-2 付款方式:现销 CASH(与应付现购对称,由金蝶自动推收款)。待确认:取值需金蝶侧确认。 */
|
||||
public static final String BFI_AR_BILL_PAY_MODE_CASH = "CASH";
|
||||
|
||||
/** A302-2 必填:外部单据类型(应收单来源标识)。待确认:取值需金蝶侧确认,确认后如有调整直接改此常量。 */
|
||||
public static final String BFI_AR_BILL_SRC_TYPE = "TMS财务应收单";
|
||||
|
||||
/** A303-1 上游来源单据类型:财务应收单(awrp_src_bt)。 */
|
||||
public static final String BFI_AR_BILL_SRC_BT = "ar_finarbill";
|
||||
|
||||
/** A303-1 发票种类:数电发票(普通发票)。 */
|
||||
public static final String BFI_INVOICE_TYPE_DIGITAL_GENERAL = "10xdp";
|
||||
|
||||
/** A303-1 发票种类:数电发票(增值税专用发票)。 */
|
||||
public static final String BFI_INVOICE_TYPE_DIGITAL_SPECIAL = "08xdp";
|
||||
|
||||
/** 金蝶同步状态:开票完成(A303-5 查到发票,终态,开票申请专用)。 */
|
||||
public static final String KINGDEE_STATUS_INVOICED = "invoiced";
|
||||
|
||||
/** 付款申请单(ap_payapply)单据类型映射:中文名称 -> 星瀚编码(使用规则待业务确认)。 */
|
||||
public static final Map<String, String> PAY_APPLY_BILLTYPE_MAP = Map.of(
|
||||
"采购付款申请", "ap_payapply_BT_S",
|
||||
|
||||
+7
-2
@@ -206,11 +206,16 @@ public class FormalSettlementController extends BladeController {
|
||||
|
||||
@PostMapping("/sync-kingdee")
|
||||
@ApiOperationSupport(order = 14)
|
||||
@Operation(summary = "推送金蝶应付单")
|
||||
@Operation(summary = "推送金蝶(应付单/应收单)")
|
||||
public R<String> syncKingdee(@RequestParam Long id) { return R.data(formalSettlementService.syncKingdee(id)); }
|
||||
|
||||
@GetMapping("/detail-fees")
|
||||
@PostMapping("/sync-kingdee-ar-result")
|
||||
@ApiOperationSupport(order = 15)
|
||||
@Operation(summary = "手动勾稽金蝶应收单(A302-4 回写纠偏)")
|
||||
public R<String> syncKingdeeArResult() { return R.data(formalSettlementService.syncKingdeeArResult()); }
|
||||
|
||||
@GetMapping("/detail-fees")
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "正式结算货物费用快照")
|
||||
public R<List<FormalSettlementDetailFee>> detailFees(@RequestParam Long detailId) {
|
||||
return R.data(formalSettlementService.detailFees(detailId));
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
|
||||
void returnBill(FormalSettlementStatusRequest request);
|
||||
void voidBill(FormalSettlementStatusRequest request);
|
||||
String syncKingdee(Long id);
|
||||
String syncKingdeeArResult();
|
||||
List<FormalSettlementDetailFee> detailFees(Long detailId);
|
||||
void adjustDetail(PreSettlementDetailAdjustRequest request);
|
||||
void refreshPaymentSummary(Long settlementId);
|
||||
|
||||
+83
-16
@@ -90,6 +90,7 @@ import org.springblade.transport.service.IPreSettlementService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.service.KingdeeSyncContext;
|
||||
import org.springblade.transport.support.KingdeeApBillSyncSupport;
|
||||
import org.springblade.transport.support.KingdeeArBillSyncSupport;
|
||||
import org.springblade.transport.wrapper.PreSettlementWrapper;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.transport.wrapper.FormalSettlementWrapper;
|
||||
@@ -154,6 +155,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
private final IPreSettlementService preSettlementService;
|
||||
private final IWaybillService waybillService;
|
||||
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
|
||||
private final KingdeeArBillSyncSupport kingdeeArBillSyncSupport;
|
||||
private final IBfiService bfiService;
|
||||
|
||||
@Override
|
||||
@@ -495,11 +497,6 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
public String syncKingdee(Long id) {
|
||||
FormalSettlement settlement = existing(id);
|
||||
if (!APPROVED.equals(settlement.getApprovalStatus())) throw new ServiceException("仅审批通过的正式结算单允许同步金蝶");
|
||||
// 单据类型路由:应付对应财务应付单(A102-2),应收对应财务应收单(A302-2),推送规则待业务确认,暂拦截报错
|
||||
if (!"payable".equals(settlement.getSettlementType())) {
|
||||
throw new ServiceException("推送金蝶失败:结算单[" + Func.toStr(settlement.getFormalSettlementNo())
|
||||
+ "]结算类型为应收,对应财务应收单(A302-2),推送规则待业务确认后启用");
|
||||
}
|
||||
// 已同步幂等返回(含历史由付款申请同步联动回写的存量已同步数据)
|
||||
if ("synced".equals(settlement.getKingdeeSyncStatus())) return settlement.getKingdeeBillNo();
|
||||
// 流程拆分:正式结算单「同步金蝶」负责推送财务应付单(A102-2)并回写结算单单据编号;
|
||||
@@ -509,22 +506,92 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.eq(FormalSettlementDetail::getIsDeleted, 0)
|
||||
.orderByAsc(FormalSettlementDetail::getLineNo));
|
||||
if (Func.isEmpty(details)) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败:正式结算单无结算明细,无法解析物料与计量单位");
|
||||
throw new ServiceException("推送金蝶失败:正式结算单无结算明细,无法解析物料与计量单位");
|
||||
}
|
||||
KingdeeSyncContext context = KingdeeSyncContext.empty();
|
||||
// 财务应付单防重键(awrp_src_code):正式结算单号_结算明细单据号下划线拼接,
|
||||
// 付款申请 A103-1 下推前的前置校验与付款计划反查均按该键勾稽
|
||||
String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups = kingdeeApBillSyncSupport.resolveEntryGroups(details, context);
|
||||
KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeApBillSyncSupport.pushApBill(
|
||||
apBillSrcCode, settlement.getContractId(), entryGroups, settlement.getFormalSettlementNo(), context);
|
||||
// 回写财务应付单单据编号(A102-2 billNo)与同步状态;付款申请侧回填的付款申请单编号(A103-2)不是同一单据
|
||||
settlement.setKingdeeBillNo(outcome.billNo());
|
||||
// 单据类型路由:应付对应财务应付单(A102-2),应收对应财务应收单(A302-2),两链路同一套幂等预检与推送逻辑
|
||||
String billNo;
|
||||
String changeDesc;
|
||||
if ("receivable".equals(settlement.getSettlementType())) {
|
||||
// 应收结算:财务应收单防重键(awrp_src_code):正式结算单号_结算明细单据号下划线拼接
|
||||
String arBillSrcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement, details);
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups = kingdeeArBillSyncSupport.resolveArEntryGroups(details, context);
|
||||
KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeArBillSyncSupport.pushArBill(
|
||||
arBillSrcCode, settlement.getContractId(), settlement.getPayerName(), entryGroups,
|
||||
settlement.getFormalSettlementNo(), context);
|
||||
billNo = outcome.billNo();
|
||||
changeDesc = "同步金蝶财务应收单" + billNo;
|
||||
} else {
|
||||
// 应付结算:财务应付单防重键,付款申请「同步金蝶」(A103-1 应付下推)推送前按该键经 A102-4 预检本单应付单已生成
|
||||
// 付款申请 A103-1 下推前的前置
|
||||
String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups = kingdeeApBillSyncSupport.resolveEntryGroups(details, context);
|
||||
KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeApBillSyncSupport.pushApBill(
|
||||
apBillSrcCode, settlement.getContractId(), entryGroups, settlement.getFormalSettlementNo(), context);
|
||||
billNo = outcome.billNo();
|
||||
changeDesc = "同步金蝶财务应付单" + billNo;
|
||||
}
|
||||
// 回写金蝶单据编号(A102-2/A302-2 单据编号)与同步状态;付款申请侧回填的付款申请单编号(A103-2)不是同一单据
|
||||
settlement.setKingdeeBillNo(billNo);
|
||||
settlement.setKingdeeSyncStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
|
||||
settlement.setSyncedTime(LocalDateTime.now());
|
||||
updateById(settlement);
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "同步金蝶财务应付单" + outcome.billNo(), "");
|
||||
return outcome.billNo();
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", changeDesc, "");
|
||||
return billNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String syncKingdeeArResult() {
|
||||
// 待勾稽应收结算单:审批通过 + 结算类型应收 + 金蝶状态为 已同步/生成失败(已同步一并轮询为补齐单号,生成失败纳入勾稽纠偏:
|
||||
// 金蝶侧已生成说明上次推送实际成功,查到即回填纠正为已同步,避免人工重新推送产生重复单据)
|
||||
List<FormalSettlement> settlements = list(Wrappers.<FormalSettlement>lambdaQuery()
|
||||
.eq(FormalSettlement::getApprovalStatus, APPROVED)
|
||||
.eq(FormalSettlement::getSettlementType, "receivable")
|
||||
.in(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED,
|
||||
KingdeeBfiConstant.KINGDEE_STATUS_FAILED)
|
||||
.orderByAsc(FormalSettlement::getId));
|
||||
if (Func.isEmpty(settlements)) {
|
||||
return "金蝶应收单勾稽:无待勾稽应收结算单";
|
||||
}
|
||||
// 组装防重键(与 A302-2 保存传参一致),同一防重键对应一张财务应收单
|
||||
Map<String, FormalSettlement> settlementBySrcCode = new LinkedHashMap<>();
|
||||
for (FormalSettlement settlement : settlements) {
|
||||
List<FormalSettlementDetail> details = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
|
||||
.eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())
|
||||
.eq(FormalSettlementDetail::getIsDeleted, 0)
|
||||
.orderByAsc(FormalSettlementDetail::getLineNo));
|
||||
String srcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement, details);
|
||||
if (Func.isNotBlank(srcCode)) {
|
||||
settlementBySrcCode.putIfAbsent(srcCode, settlement);
|
||||
}
|
||||
}
|
||||
// A302-4 批量查询:查到 → 回填金蝶单据编号(缺失时)、生成失败纠偏为已同步;查不到 → 状态保持不变
|
||||
Map<String, String> existBillNos = kingdeeArBillSyncSupport.queryArBillExistNos(new ArrayList<>(settlementBySrcCode.keySet()));
|
||||
int updated = 0;
|
||||
int corrected = 0;
|
||||
for (Map.Entry<String, FormalSettlement> entry : settlementBySrcCode.entrySet()) {
|
||||
String billNo = existBillNos.get(entry.getKey());
|
||||
if (Func.isBlank(billNo)) {
|
||||
continue;
|
||||
}
|
||||
FormalSettlement settlement = entry.getValue();
|
||||
boolean failed = KingdeeBfiConstant.KINGDEE_STATUS_FAILED.equals(settlement.getKingdeeSyncStatus());
|
||||
if (!failed && Func.isNotBlank(settlement.getKingdeeBillNo())) {
|
||||
continue;
|
||||
}
|
||||
if (failed) {
|
||||
corrected++;
|
||||
}
|
||||
if (Func.isBlank(settlement.getKingdeeBillNo())) {
|
||||
settlement.setKingdeeBillNo(billNo);
|
||||
}
|
||||
settlement.setKingdeeSyncStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
|
||||
settlement.setSyncedTime(LocalDateTime.now());
|
||||
updateById(settlement);
|
||||
updated++;
|
||||
}
|
||||
return "金蝶应收单勾稽完成:待查应收结算单" + settlementBySrcCode.size() + "笔,回写单据信息" + updated
|
||||
+ "笔(生成失败纠偏" + corrected + "笔)";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+20
-19
@@ -80,7 +80,8 @@ import java.util.stream.Collectors;
|
||||
* <p>流程拆分后:财务应付单(A102-2)由正式结算单「同步金蝶」推送并回写结算单;
|
||||
* 付款申请「同步金蝶」仅负责付款申请单(A103-1 应付下推 / A103-3 直接保存),
|
||||
* 推送前经 A102-4 预检应付单已生成、下推时经 A102-4 反查付款计划分录 awrp_entry_code_p。
|
||||
* 两侧共用的防重键组装、A102-4 查询/反查、A102-2 保存、结算明细分录解析与档案取数统一收口在本组件。</p>
|
||||
* 两侧共用的防重键组装、A102-4 查询/反查、A102-2 保存、结算明细分录解析与档案取数统一收口在本组件;
|
||||
* 财务应收单(A302-2)同步组件 {@link KingdeeArBillSyncSupport} 亦复用本组件的结算明细分录解析与档案取数方法(public 成员)。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@@ -414,8 +415,8 @@ public class KingdeeApBillSyncSupport {
|
||||
sourceDesc, entryCode, materialNumber, measureunitNumber, quantity, price, amount, amountNoTax);
|
||||
}
|
||||
|
||||
/** 付款申请关联的正式结算货物费用行(blade_formal_settlement_detail_fee,用于不含税金额回链应付费用行) */
|
||||
private List<FormalSettlementDetailFee> formalSettlementDetailFees(List<FormalSettlementDetail> settlementDetails) {
|
||||
/** 付款申请关联的正式结算货物费用行(blade_formal_settlement_detail_fee,用于不含税金额回链应付费用行;应收单同步组件复用) */
|
||||
public List<FormalSettlementDetailFee> formalSettlementDetailFees(List<FormalSettlementDetail> settlementDetails) {
|
||||
List<Long> settlementDetailIds = settlementDetails.stream().map(FormalSettlementDetail::getId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (Func.isEmpty(settlementDetailIds)) {
|
||||
@@ -427,8 +428,8 @@ public class KingdeeApBillSyncSupport {
|
||||
.orderByAsc(FormalSettlementDetailFee::getLineNo));
|
||||
}
|
||||
|
||||
/** 应付明细id->应付货物费用行(blade_receivable_payable_cargo_fee,经结算明细 sourceDetailId 关联,每货物一条,分录主取数来源) */
|
||||
private Map<Long, List<ReceivablePayableCargoFee>> payableCargoFeesByDetailId(List<FormalSettlementDetail> settlementDetails) {
|
||||
/** 应付明细id->应付货物费用行(blade_receivable_payable_cargo_fee,经结算明细 sourceDetailId 关联,每货物一条,分录主取数来源;应收单同步组件复用) */
|
||||
public Map<Long, List<ReceivablePayableCargoFee>> payableCargoFeesByDetailId(List<FormalSettlementDetail> settlementDetails) {
|
||||
List<Long> sourceDetailIds = settlementDetails.stream()
|
||||
.map(FormalSettlementDetail::getSourceDetailId).filter(Objects::nonNull).distinct().toList();
|
||||
if (Func.isEmpty(sourceDetailIds)) {
|
||||
@@ -444,9 +445,9 @@ public class KingdeeApBillSyncSupport {
|
||||
/**
|
||||
* 应付货物费用行id->结算金额(不含税):应付费用行无不含税字段,经结算货物费用行 sourceFeeId 回链获取。
|
||||
* 直接选应收应付明细路径 sourceFeeId=应付费用行id;预结算合并路径 sourceFeeId=预结算费用行id,
|
||||
* 经预结算费用行 sourceFeeId(指向应付费用行)回溯;无对应结算费用行时无映射,调用方兜底 0
|
||||
* 经预结算费用行 sourceFeeId(指向应付费用行)回溯;无对应结算费用行时无映射,调用方兜底 0(应收单同步组件复用)
|
||||
*/
|
||||
private Map<Long, BigDecimal> settlementNoTaxByPayableFeeId(List<FormalSettlementDetail> settlementDetails) {
|
||||
public Map<Long, BigDecimal> settlementNoTaxByPayableFeeId(List<FormalSettlementDetail> settlementDetails) {
|
||||
List<FormalSettlementDetailFee> settlementFees = formalSettlementDetailFees(settlementDetails);
|
||||
if (Func.isEmpty(settlementFees)) {
|
||||
return Map.of();
|
||||
@@ -473,8 +474,8 @@ public class KingdeeApBillSyncSupport {
|
||||
return noTaxByPayableFeeId;
|
||||
}
|
||||
|
||||
/** 货物类型名称->货物编码映射(取自货物类型档案;名称重复且编码不一致时拦截;批量同步时全批共享一次加载结果) */
|
||||
private Map<String, String> cargoCodeByName(KingdeeSyncContext context) {
|
||||
/** 货物类型名称->货物编码映射(取自货物类型档案;名称重复且编码不一致时拦截;批量同步时全批共享一次加载结果;应收单同步组件复用) */
|
||||
public Map<String, String> cargoCodeByName(KingdeeSyncContext context) {
|
||||
Map<String, String> cache = context.cargoCodeByName();
|
||||
if (!cache.isEmpty()) {
|
||||
return cache;
|
||||
@@ -506,18 +507,18 @@ public class KingdeeApBillSyncSupport {
|
||||
*
|
||||
* @param priceUnit 快照运费计算单位(可能为空)
|
||||
* @param fallbackUnit 兜底数量单位
|
||||
* @return 计量单位编码,无法解析时返回 null
|
||||
* @return 计量单位编码,无法解析时返回 null(应收单同步组件复用)
|
||||
*/
|
||||
private String resolveMeasureunitNumber(String priceUnit, String fallbackUnit) {
|
||||
public String resolveMeasureunitNumber(String priceUnit, String fallbackUnit) {
|
||||
String unitName = Func.isNotBlank(priceUnit) ? priceUnit : fallbackUnit;
|
||||
return Func.isBlank(unitName) ? null : unitName.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 货物类型支持“、/,”分隔的多类型写法,解析为类型名称列表;
|
||||
* 分录不按类型拆行(业务约定整行一条),物料编码仅取列表第一种类型
|
||||
* 分录不按类型拆行(业务约定整行一条),物料编码仅取列表第一种类型(应收单同步组件复用)
|
||||
*/
|
||||
private List<String> splitCargoTypes(String cargoType) {
|
||||
public List<String> splitCargoTypes(String cargoType) {
|
||||
if (Func.isBlank(cargoType)) {
|
||||
throw new ServiceException("推送金蝶财务应付单失败:结算明细货物类型为空,无法解析物料编码");
|
||||
}
|
||||
@@ -629,8 +630,8 @@ public class KingdeeApBillSyncSupport {
|
||||
return customerCode;
|
||||
}
|
||||
|
||||
/** 按乙方名称(全称或简称)查询客商编码,未维护返回 null */
|
||||
private String queryCustomerCode(String partyB) {
|
||||
/** 按乙方名称(全称或简称)查询客商编码,未维护返回 null(应收单同步组件复用:按结算单付款方查询) */
|
||||
public String queryCustomerCode(String partyB) {
|
||||
CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, partyB).or().eq(CustomerArchive::getShortName, partyB))
|
||||
.eq(CustomerArchive::getIsDeleted, 0).last("limit 1"));
|
||||
@@ -643,16 +644,16 @@ public class KingdeeApBillSyncSupport {
|
||||
* @param items 待处理列表
|
||||
* @param batchSize 批大小
|
||||
* @param batchConsumer 批处理逻辑
|
||||
* @param <T> 元素类型
|
||||
* @param <T> 元素类型(应收单同步组件复用)
|
||||
*/
|
||||
private <T> void batchForeach(List<T> items, int batchSize, Consumer<List<T>> batchConsumer) {
|
||||
public <T> void batchForeach(List<T> items, int batchSize, Consumer<List<T>> batchConsumer) {
|
||||
for (int from = 0; from < items.size(); from += batchSize) {
|
||||
batchConsumer.accept(items.subList(from, Math.min(from + batchSize, items.size())));
|
||||
}
|
||||
}
|
||||
|
||||
/** 金额空值兜底并保留 2 位小数 */
|
||||
private BigDecimal money(BigDecimal value) {
|
||||
/** 金额空值兜底并保留 2 位小数(应收单同步组件复用) */
|
||||
public BigDecimal money(BigDecimal value) {
|
||||
return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
|
||||
* Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
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.utils.Func;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiArBillSaveEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiArBillVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
|
||||
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
|
||||
import org.springblade.transport.service.KingdeeSyncContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 金蝶财务应收单(A302-2)同步支撑组件。
|
||||
* <p>应收(receivable)正式结算单「同步金蝶」推送财务应收单(A302-2),推送前经 A302-4 按防重键幂等预检,
|
||||
* 与应付链路(A102-2/A102-4,{@link KingdeeApBillSyncSupport})同一套逻辑;防重键组装、A302-4 查询、
|
||||
* A302-2 组装保存与应收分录解析收口在本组件,档案取数(合同/组织/币种/客商/货物类型/不含税回链等)
|
||||
* 复用应付支撑组件的公共方法。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class KingdeeArBillSyncSupport {
|
||||
|
||||
/** A302-4 防重键批量分批查询单次条数上限(与 A102-4 一致) */
|
||||
private static final int AR_RESULT_QUERY_BATCH = 50;
|
||||
/** A302-4 分页查询页大小 */
|
||||
private static final int AR_RESULT_PAGE_SIZE = 200;
|
||||
|
||||
private final IBfiService bfiService;
|
||||
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
|
||||
private final BfiProperties bfiProperties;
|
||||
|
||||
// ==================== 财务应收单防重键(awrp_src_code) ====================
|
||||
|
||||
/**
|
||||
* 财务应收单防重键(awrp_src_code):正式结算单号 + 结算明细单据号下划线拼接(与 A302-2 保存传参一致),
|
||||
* 规则与财务应付单(A102-2)同源,一张应收结算单只生成一张金蝶财务应收单
|
||||
*
|
||||
* @param settlement 正式结算单
|
||||
* @param settlementDetails 该结算单下的结算明细
|
||||
* @return 防重键
|
||||
*/
|
||||
public String arBillSrcCode(FormalSettlement settlement, List<FormalSettlementDetail> settlementDetails) {
|
||||
return ExternalSrcCodeSupport.buildSrcCode(settlement.getFormalSettlementNo(),
|
||||
kingdeeApBillSyncSupport.settlementDocumentNos(settlementDetails));
|
||||
}
|
||||
|
||||
// ==================== A302-4 财务应收单查询 ====================
|
||||
|
||||
/**
|
||||
* A302-4 批量查询已生成财务应收单:来源单据号批量 + 外部系统编号(防重键),自动翻页,
|
||||
* 用于推送前幂等预检与定时任务应收勾稽纠偏(awrp_src_code 支持多值,整批一次查询消除逐单远程查询 N+1)
|
||||
*
|
||||
* @param awrpSrcCodes 来源单据号(正式结算单号_结算明细单据号拼接)批次
|
||||
* @return 已生成财务应收单的 来源单据号->单据编号 映射
|
||||
*/
|
||||
public Map<String, String> queryArBillExistNos(List<String> awrpSrcCodes) {
|
||||
Map<String, String> existBillNos = new LinkedHashMap<>();
|
||||
kingdeeApBillSyncSupport.batchForeach(awrpSrcCodes, AR_RESULT_QUERY_BATCH, batch -> {
|
||||
BfiArBillQueryDTO param = new BfiArBillQueryDTO();
|
||||
param.setAwrpSrcCode(batch);
|
||||
param.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
for (BfiArBillVO arBill : BfiUtil.queryAllPages(pageNo -> {
|
||||
param.setPageNo(pageNo);
|
||||
param.setPageSize(AR_RESULT_PAGE_SIZE);
|
||||
return bfiService.queryArBill(param);
|
||||
})) {
|
||||
if (Func.isNotEmpty(arBill.getAwrpSrcCode())) {
|
||||
existBillNos.put(arBill.getAwrpSrcCode(), arBill.getBillno());
|
||||
}
|
||||
}
|
||||
});
|
||||
return existBillNos;
|
||||
}
|
||||
|
||||
// ==================== A302-2 财务应收单保存 ====================
|
||||
|
||||
/**
|
||||
* 推送金蝶财务应收单(A302-2 财务应收单保存,由应收正式结算单「同步金蝶」触发)。
|
||||
* 推送前先经 A302-4 按防重键(awrp_src_sys + awrp_src_code)查询:金蝶已存在财务应收单时
|
||||
* 幂等跳过保存并返回已有单据编号(说明上次同步已保存成功、在后续环节中断,应续推而非拦截报错)。
|
||||
* 明细分录逐应收货物费用行生成(不聚合),含税金额取应收货物费用行调整后总金额、不含税取结算货物费用行不含税;
|
||||
* 收款计划 planentity 暂不传,由金蝶按明细自动汇总生成
|
||||
*
|
||||
* @param arBillSrcCode 财务应收单防重键(正式结算单号_结算明细单据号拼接)
|
||||
* @param contractId 正式结算单关联合同id
|
||||
* @param payerName 结算单付款方(客户名称),往来户取数来源
|
||||
* @param entryGroups 结算明细解析出的应收单分录
|
||||
* @param subject 业务单据号(正式结算单号,日志与异常提示用)
|
||||
* @param context 金蝶同步上下文(缓存载体,预检命中与应付共用防重键缓存)
|
||||
* @return 财务应收单推送结果(单据编号 + 是否金蝶已存在幂等跳过)
|
||||
*/
|
||||
public KingdeeApBillSyncSupport.BfiApBillPushOutcome pushArBill(String arBillSrcCode, Long contractId, String payerName,
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups,
|
||||
String subject, KingdeeSyncContext context) {
|
||||
// A302-4 幂等预检:按防重键查询金蝶是否已生成财务应收单,已存在则幂等跳过保存,直接返回已有单据编号
|
||||
String existBillNo = context.apBillNoCache().computeIfAbsent(arBillSrcCode, code ->
|
||||
queryArBillExistNos(List.of(code)).getOrDefault(code, ""));
|
||||
if (Func.isNotEmpty(existBillNo)) {
|
||||
log.info("正式结算单[{}]金蝶财务应收单已生成(单据编号[{}]),幂等跳过 A302-2 保存", subject, existBillNo);
|
||||
return new KingdeeApBillSyncSupport.BfiApBillPushOutcome(existBillNo, true);
|
||||
}
|
||||
ContractManage contract = kingdeeApBillSyncSupport.requireContract(contractId, context);
|
||||
// 单据类型按映射字典转换:默认服务销售应收,金蝶确认其他类型后修改 BFI_AR_BILL_BILLTYPE_NAME 即可
|
||||
String billtypeNumber = KingdeeBfiConstant.billtypeBy(KingdeeBfiConstant.AR_BILL_BILLTYPE_MAP, KingdeeBfiConstant.BFI_AR_BILL_BILLTYPE_NAME);
|
||||
if (Func.isBlank(billtypeNumber)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败:单据类型[" + KingdeeBfiConstant.BFI_AR_BILL_BILLTYPE_NAME + "]未在映射字典中维护");
|
||||
}
|
||||
String orgNumber = kingdeeApBillSyncSupport.bfiOrgNumber(contract);
|
||||
// 币种取合同结算币种
|
||||
String currency = kingdeeApBillSyncSupport.bfiCurrency(contract);
|
||||
// 单据日期:yyyy-MM-dd HH:mm:ss(各接口统一格式),以同步当日作为应收单据日期
|
||||
String billDateText = kingdeeApBillSyncSupport.bfiDateTimeText(LocalDate.now());
|
||||
// 往来户.编码:应收结算单付款方(客户)关联客商档案客商编码
|
||||
if (Func.isBlank(payerName)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败,结算单付款方不能为空,请检查正式结算单[" + Func.toStr(subject) + "]付款方信息");
|
||||
}
|
||||
String customerCode = context.customerCodeCache().computeIfAbsent(payerName.trim(), kingdeeApBillSyncSupport::queryCustomerCode);
|
||||
if (Func.isBlank(customerCode)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败,结算单付款方[" + payerName + "]未在客商档案中维护客商编码,请补充后重试");
|
||||
}
|
||||
BfiArBillSaveDTO param = new BfiArBillSaveDTO();
|
||||
// 【必填】单据类型.编码
|
||||
param.setBilltypeNumber(billtypeNumber);
|
||||
// 【必填】结算组织.编码
|
||||
param.setOrgNumber(orgNumber);
|
||||
// 收款组织.编码:同结算组织
|
||||
param.setRecorgNumber(orgNumber);
|
||||
// 【必填】单据日期
|
||||
param.setBizdate(billDateText);
|
||||
// 【必填】往来类型:应收固定客户
|
||||
param.setAsstacttype(KingdeeBfiConstant.BFI_CUSTOMER_ASSTACT_TYPE);
|
||||
// 【必填】往来户.编码:结算单付款方(客户)
|
||||
param.setAsstactNumber(customerCode);
|
||||
// 【必填】付款方式(CASH:现销, CREDIT:赊销),现销由金蝶自动推收款
|
||||
param.setPaymode(KingdeeBfiConstant.BFI_AR_BILL_PAY_MODE_CASH);
|
||||
// 【必填】结算币别.货币代码
|
||||
param.setCurrencyNumber(currency);
|
||||
// 【必填】款项性质.编码
|
||||
param.setPaypropertyNumber(KingdeeBfiConstant.BFI_AR_BILL_PAY_PROPERTY);
|
||||
// TODO 待确认:结算方式编码(settlementtype_number)与税率编码(taxrateid_number)、到期日(duedate)需金蝶侧确认后补充,当前不传
|
||||
// 录入含税价(非必传)
|
||||
param.setIsincludetax(true);
|
||||
// 录入总价,为 true 后可不录入单价(非必传)
|
||||
param.setIspricetotal(true);
|
||||
// 【必填】外部系统编号(候选键)
|
||||
param.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
// 【必填】外部系统来源单据号(候选键):正式结算单号_结算明细单据号1_单据号2...(复用预检已组装的防重键)
|
||||
param.setAwrpSrcCode(arBillSrcCode);
|
||||
// 来源单据(非必传,有上游合同时传 awrp_contract_info),当前与应付单保持一致不传
|
||||
// 【必填】外部单据类型
|
||||
param.setAwrpSrcType(KingdeeBfiConstant.BFI_AR_BILL_SRC_TYPE);
|
||||
|
||||
// 明细分录:每条应收货物费用行一条(不聚合),无吨位时数量兜底 1
|
||||
List<BfiArBillSaveEntryDTO> entry = new ArrayList<>();
|
||||
for (KingdeeApBillSyncSupport.BfiEntryGroup entryGroup : entryGroups) {
|
||||
BfiArBillSaveEntryDTO row = new BfiArBillSaveEntryDTO();
|
||||
// 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
|
||||
row.setEMaterialNumber(entryGroup.materialNumber());
|
||||
// 【必填】计量单位.编码:货物费用行运费计算单位(快照 price_unit,档案暂无编码字段暂传单位名称)
|
||||
row.setEMeasureunitNumber(entryGroup.measureunitNumber());
|
||||
// 【必填】数量:该行运输数量,无吨位数据时兜底 1
|
||||
row.setEQuantity(entryGroup.quantity().compareTo(BigDecimal.ZERO) > 0 ? entryGroup.quantity() : BigDecimal.ONE);
|
||||
// 明细.单价:该行运输单价(费用明细 unit_price;单据头 ispricetotal=true 录入总价,单价仅作参考)
|
||||
row.setEUnitprice(entryGroup.price());
|
||||
// 应收金额(含税):该行应收货物费用行调整后总金额(含税)
|
||||
row.setERecamount(entryGroup.amount());
|
||||
// 金额(不含税):该行结算金额(不含税)(应收费用行无不含税字段,经结算货物费用行回链)
|
||||
row.setEAmount(entryGroup.amountNoTax());
|
||||
// 【必填】项目.项目编码(联调期临时传金蝶测试档案值)
|
||||
row.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
|
||||
|
||||
// 调试
|
||||
|
||||
if (true) {
|
||||
// 【必填】物料编码.编码:结算明细货物类型匹配货物类型档案货物编码
|
||||
row.setEMaterialNumber(KingdeeBfiConstant.BFI_AP_BILL_MATERIAL_NUMBER_TEST);
|
||||
// 【必填】计量单位.编码:货物费用行运费计算单位(快照 price_unit,档案暂无编码字段暂传单位名称)
|
||||
row.setEMeasureunitNumber(KingdeeBfiConstant.BFI_AP_BILL_MEASUREUNIT_NUMBER_TEST);
|
||||
}
|
||||
|
||||
// 【必填】外部系统来源分录标识(候选键):应收货物费用行id(唯一)
|
||||
row.setAwrpEntryCode(entryGroup.entryCode());
|
||||
// 分录备注(非必传)
|
||||
// row.setERemark(subject);
|
||||
entry.add(row);
|
||||
}
|
||||
param.setEntry(entry);
|
||||
|
||||
// 收款计划 planentity 不传:由金蝶按明细自动汇总生成(启用方案待业务确认后迭代)
|
||||
|
||||
BfiBillSaveResultVO saveResult = bfiService.saveArBill(param);
|
||||
// 应收单id不落库,后续如需可经 A302-4 按防重键(awrp_src_sys + awrp_src_code)查询
|
||||
kingdeeApBillSyncSupport.requireBfiSaveSuccess(saveResult, "推送金蝶财务应收单");
|
||||
return new KingdeeApBillSyncSupport.BfiApBillPushOutcome(saveResult.getResult().get(0).getNumber(), false);
|
||||
}
|
||||
|
||||
// ==================== 结算明细分录解析 ====================
|
||||
|
||||
/**
|
||||
* 解析正式结算明细,逐行生成应收单分录(不聚合,每条来源费用行一条分录),分录携带所属正式结算单id。
|
||||
* <p>取数来源与应付单分录同源:结算明细 sourceDetailId 关联应收应付货物费用行(blade_receivable_payable_cargo_fee,
|
||||
* 每货物一条)——货物类型/计量单位/运输数量/含税金额(调整后总金额 after_amount)直接取费用行;不含税金额经结算
|
||||
* 货物费用行 sourceFeeId 回链;无货物费用行时直接报错拦截。物料编码取第一种货物类型匹配货物类型档案货物编码;
|
||||
* 分录唯一标识(awrp_entry_code)取费用明细行id。</p>
|
||||
*
|
||||
* @param settlementDetails 正式结算明细列表
|
||||
* @param context 金蝶同步上下文(缓存载体)
|
||||
* @return 应收单分录列表(保持行序)
|
||||
*/
|
||||
public List<KingdeeApBillSyncSupport.BfiEntryGroup> resolveArEntryGroups(List<FormalSettlementDetail> settlementDetails,
|
||||
KingdeeSyncContext context) {
|
||||
if (Func.isEmpty(settlementDetails)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败:正式结算单无结算明细,无法解析物料与计量单位");
|
||||
}
|
||||
Map<String, String> cargoCodeByName = kingdeeApBillSyncSupport.cargoCodeByName(context);
|
||||
// 应收应付明细id->货物费用行(分录主取数来源,应收应付共用同一张费用行表)
|
||||
Map<Long, List<ReceivablePayableCargoFee>> feesByDetailId = kingdeeApBillSyncSupport.payableCargoFeesByDetailId(settlementDetails);
|
||||
// 费用行id->结算金额(不含税):经结算货物费用行 sourceFeeId 回链
|
||||
Map<Long, BigDecimal> noTaxByFeeId = kingdeeApBillSyncSupport.settlementNoTaxByPayableFeeId(settlementDetails);
|
||||
List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups = new ArrayList<>();
|
||||
for (FormalSettlementDetail settlementDetail : settlementDetails) {
|
||||
List<ReceivablePayableCargoFee> fees = feesByDetailId.getOrDefault(settlementDetail.getSourceDetailId(), List.of());
|
||||
if (Func.isEmpty(fees)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败:结算明细[" + Func.toStr(settlementDetail.getDocumentNo())
|
||||
+ "]对应应收明细无货物费用行,分录取数必须来自应收费用明细,请先维护费用数据");
|
||||
}
|
||||
for (ReceivablePayableCargoFee fee : fees) {
|
||||
appendArEntryGroups(entryGroups, cargoCodeByName, settlementDetail.getFormalSettlementId(),
|
||||
fee.getCargoType(),
|
||||
kingdeeApBillSyncSupport.resolveMeasureunitNumber(fee.getPriceUnit(), fee.getQuantityUnit()),
|
||||
fee.getId(), kingdeeApBillSyncSupport.money(fee.getUnitPrice()),
|
||||
kingdeeApBillSyncSupport.money(fee.getAfterAmount()),
|
||||
noTaxByFeeId.getOrDefault(fee.getId(), BigDecimal.ZERO),
|
||||
kingdeeApBillSyncSupport.money(fee.getTransportQuantity()),
|
||||
"应收货物费用[" + Func.toStr(fee.getCargoName()) + " 第" + Func.toStr(fee.getLineNo()) + "行]");
|
||||
}
|
||||
}
|
||||
return entryGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条应收货物费用行生成一条分录;货物类型含多种(“、/,”分隔)时不拆行,物料编码取第一种货物类型,金额数量整行直送
|
||||
*
|
||||
* @param entryGroups 分录集合(保持行序)
|
||||
* @param cargoCodeByName 货物类型名称->货物编码映射
|
||||
* @param formalSettlementId 分录所属正式结算单id
|
||||
* @param cargoType 货物类型(支持“、”分隔多类型)
|
||||
* @param measureunitNumber 计量单位编码(运费计算单位,档案暂无编码字段暂传单位名称)
|
||||
* @param sourceRowId 应收货物费用行id,雪花id天然唯一
|
||||
* @param price 该行运输单价(费用明细 unit_price)
|
||||
* @param amount 该行金额(含税)
|
||||
* @param amountNoTax 该行金额(不含税)
|
||||
* @param quantity 该行运输数量
|
||||
* @param sourceDesc 来源行描述,用于异常提示
|
||||
*/
|
||||
private void appendArEntryGroups(List<KingdeeApBillSyncSupport.BfiEntryGroup> entryGroups, Map<String, String> cargoCodeByName,
|
||||
Long formalSettlementId, String cargoType, String measureunitNumber, Long sourceRowId,
|
||||
BigDecimal price, BigDecimal amount, BigDecimal amountNoTax, BigDecimal quantity,
|
||||
String sourceDesc) {
|
||||
if (Func.isBlank(measureunitNumber)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败:" + sourceDesc + "计量单位为空,请先维护运费计算单位");
|
||||
}
|
||||
// 货物类型含多种时不拆行(业务约定整行一条分录),物料编码取第一种货物类型
|
||||
String cargoName = kingdeeApBillSyncSupport.splitCargoTypes(cargoType).get(0);
|
||||
String materialNumber = cargoCodeByName.get(cargoName);
|
||||
if (Func.isBlank(materialNumber)) {
|
||||
throw new ServiceException("推送金蝶财务应收单失败:" + sourceDesc + "货物类型[" + cargoName + "]未在货物类型档案中维护编码,请补充后重试");
|
||||
}
|
||||
// 分录唯一标识(awrp_entry_code):应收货物费用行id,雪花id天然唯一
|
||||
String entryCode = String.valueOf(sourceRowId);
|
||||
entryGroups.add(new KingdeeApBillSyncSupport.BfiEntryGroup(materialNumber, measureunitNumber, entryCode,
|
||||
quantity, price, amount, amountNoTax, formalSettlementId));
|
||||
log.info("金蝶应收单分录生成:来源[{}],分录标识[{}],物料编码[{}],计量单位[{}],数量[{}],单价[{}],含税金额[{}],不含税金额[{}]",
|
||||
sourceDesc, entryCode, materialNumber, measureunitNumber, quantity, price, amount, amountNoTax);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user