From c4e6c28da0affcbf562e9c044baca176db0d2445 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 12 Aug 2026 14:40:05 +0800 Subject: [PATCH] add finance cost allocation detail --- .../controller/PlatformFinanceController.java | 6 ++++++ .../com/baiyue/finance/dto/FinanceReadModels.java | 13 +++++++++++++ .../finance/service/FinanceQueryService.java | 15 +++++++++++++++ .../finance-full-fixtures.mjs | 9 +++++++-- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java index 01376191c..def3dbc3a 100644 --- a/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java +++ b/finance/src/main/java/com/baiyue/finance/controller/PlatformFinanceController.java @@ -162,6 +162,12 @@ public class PlatformFinanceController { return FinanceResponses.success(queryService.payableSettlements(customerId)); } + @GetMapping("/cost-entries/{costId}") + @PreAuthorize("hasAuthority('platform:finance:cost:view')") + public ApiResponse cost(@PathVariable long costId, @RequestParam long customerId) { + return FinanceResponses.success(queryService.cost(customerId, costId)); + } + @PostMapping("/cost-entries") @PreAuthorize("hasAuthority('platform:finance:cost:create')") @BusinessAudit(event = "FINANCE_COST_CREATE", resourceType = "FINANCE_COST") diff --git a/finance/src/main/java/com/baiyue/finance/dto/FinanceReadModels.java b/finance/src/main/java/com/baiyue/finance/dto/FinanceReadModels.java index 8c69fcec2..cacc9ff09 100644 --- a/finance/src/main/java/com/baiyue/finance/dto/FinanceReadModels.java +++ b/finance/src/main/java/com/baiyue/finance/dto/FinanceReadModels.java @@ -102,6 +102,19 @@ public final class FinanceReadModels { LocalDateTime createdAt, int version) {} + public record CostDetail(PayableSettlement cost, List allocations) {} + + public record CostAllocation( + long id, + long costId, + long orderId, + Long inboundId, + BigDecimal volumeCbm, + BigDecimal totalVolumeCbm, + BigDecimal allocationRate, + BigDecimal allocatedAmount, + boolean roundingAdjustment) {} + /** Client-safe receipt view. Voucher and operator/internal fields are deliberately absent. */ public record ClientReceipt( long id, diff --git a/finance/src/main/java/com/baiyue/finance/service/FinanceQueryService.java b/finance/src/main/java/com/baiyue/finance/service/FinanceQueryService.java index 5eef68a30..251fbdf2b 100644 --- a/finance/src/main/java/com/baiyue/finance/service/FinanceQueryService.java +++ b/finance/src/main/java/com/baiyue/finance/service/FinanceQueryService.java @@ -7,6 +7,8 @@ import com.baiyue.finance.dto.FinanceReadModels.ClientReceipt; import com.baiyue.finance.dto.FinanceReadModels.ClientReceiptAllocation; import com.baiyue.finance.dto.FinanceReadModels.BillRevision; import com.baiyue.finance.dto.FinanceReadModels.CustomerAccount; +import com.baiyue.finance.dto.FinanceReadModels.CostAllocation; +import com.baiyue.finance.dto.FinanceReadModels.CostDetail; import com.baiyue.finance.dto.FinanceReadModels.FeePoolItem; import com.baiyue.finance.dto.FinanceReadModels.PayableSettlement; import com.baiyue.finance.dto.FinanceReadModels.PlatformReceipt; @@ -143,6 +145,19 @@ public class FinanceQueryService { }, customerId); } + public CostDetail cost(long customerId, long costId) { + PayableSettlement cost = payableSettlements(customerId).stream() + .filter(item -> item.id() == costId).findFirst() + .orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "成本不存在或不属于客户")); + List allocations = jdbc.query( + "SELECT id,cost_id,order_id,inbound_id,volume_cbm,total_volume_cbm,allocation_rate,allocated_amount,rounding_adjustment FROM fin_cost_allocation WHERE tenant_id=? AND cost_id=? ORDER BY id", + (rs, rowNum) -> new CostAllocation(rs.getLong("id"), rs.getLong("cost_id"), rs.getLong("order_id"), + nullableLong(rs, "inbound_id"), rs.getBigDecimal("volume_cbm"), rs.getBigDecimal("total_volume_cbm"), + rs.getBigDecimal("allocation_rate"), rs.getBigDecimal("allocated_amount"), rs.getBoolean("rounding_adjustment")), + customerId, costId); + return new CostDetail(cost, allocations); + } + public List clientReceipts(long customerId) { requireCustomer(customerId); return jdbc.query("SELECT id,receipt_no,received_amount,received_at,payment_method,currency_code,status FROM fin_receipt WHERE tenant_id=? AND status='CONFIRMED' ORDER BY received_at DESC,id DESC", diff --git a/scripts/admin-test-env-modules/finance-full-fixtures.mjs b/scripts/admin-test-env-modules/finance-full-fixtures.mjs index 0b7f86e78..ab7667f57 100644 --- a/scripts/admin-test-env-modules/finance-full-fixtures.mjs +++ b/scripts/admin-test-env-modules/finance-full-fixtures.mjs @@ -267,7 +267,12 @@ async function createContainerCost(context, customerId, container, voucherFileId `message=${response.body.message || 'missing'}, loadingPlanId=${container.loadingPlanId || 'missing'}, containerBookingId=${container.containerBookingId || 'missing'}, voucherFileId=${voucherFileId || 'missing'}, publicSnapshotValid=${snapshotInput?.valid === true}, validItems=${snapshotInput?.items?.length || 0}, accountId=${first?.accountId ?? 'missing'}, orderId=${first?.orderId ?? 'missing'}, volumeCbm=${first?.volumeCbm ?? 'missing'}, requestId=${response.requestId || 'missing'}, traceId=${response.traceId || 'missing'};若 publicSnapshotValid=true,说明 fixture ID 与公开装柜明细有效,阻断位于 Finance -> Firstleg 内部快照服务部署/契约`) } const cost = context.apiSuccess(MODULE, '创建整柜成本并触发真实体积分摊', response, '', OPTIONS) - return cost?.id ? { costId: id(cost.id), allocations: Array.isArray(cost.allocations) ? cost.allocations : null } : null + if (!cost?.id) return null + const detailResponse = await context.request('GET', + `/api/platform/finance/v1/cost-entries/${encodeURIComponent(cost.id)}?customerId=${encodeURIComponent(customerId)}`) + const detail = context.apiSuccess(MODULE, '回读整柜成本分摊结果', detailResponse, + `costId=${cost.id}`, OPTIONS) + return { costId: id(cost.id), allocations: Array.isArray(detail?.allocations) ? detail.allocations : null } } async function verifyContainerCostSnapshotInput(context, customerId, container) { @@ -382,7 +387,7 @@ export async function run(context) { const cost = snapshotInput.valid ? await createContainerCost(context, customerId, container, voucherFileId, snapshotInput) : null if (Array.isArray(cost?.allocations)) allocations = cost.allocations else blocker(context, '回读整柜成本分摊结果', - `costId=${cost?.costId || 'missing'};Finance POST /api/platform/finance/v1/cost-entries 仅返回 ResourceResult,GET cost-entries 仅返回成本摘要,当前没有公开接口回读 fin_cost_allocation;禁止从金额和体积自行伪造持久化结果`) + `costId=${cost?.costId || 'missing'};Finance 成本详情未返回真实 fin_cost_allocation,禁止从金额和体积自行伪造持久化结果`) } if (allocations.length === 0) blocker(context, '输出真实订单/运单分摊', '缺少已完成柜次、凭证、供应商或 Finance 分摊响应;不伪造 allocation ID/金额')