add finance cost allocation detail
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -102,6 +102,19 @@ public final class FinanceReadModels {
|
||||
LocalDateTime createdAt,
|
||||
int version) {}
|
||||
|
||||
public record CostDetail(PayableSettlement cost, List<CostAllocation> 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,
|
||||
|
||||
@@ -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<CostAllocation> 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<ClientReceipt> 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",
|
||||
|
||||
@@ -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/金额')
|
||||
|
||||
Reference in New Issue
Block a user