This commit is contained in:
2026-08-17 18:04:04 +08:00
parent fd4afd6ae6
commit 5c22018edd
5 changed files with 25 additions and 14 deletions
@@ -240,6 +240,9 @@ public class FbaPricingService {
|| inbound.getActualVolumeCbm() == null || inbound.getActualVolumeCbm().signum() <= 0) {
throw conflict("审核通过的测量快照必须包含大于零的实重和体积");
}
if (inbound.getCustomerId() == null || inbound.getCustomerId() <= 0) {
throw conflict("入库单缺少订单客户归属,不能生成对客费用");
}
if (inbound.getWarehouseId() == null || !StringUtils.hasText(inbound.getChannelCode())) {
throw conflict("入库单缺少起运仓或渠道匹配维度");
}
@@ -91,24 +91,23 @@ class FbaPricingServiceTest {
assertThat(fee.getValue().getPricingTaxMode()).isEqualTo("TAX_INCLUDED");
assertThat(fee.getValue().getChargeRuleId()).isNull();
assertThat(fee.getValue().getCarrierId()).isNull();
assertThat(fee.getValue().getCustomerId()).isEqualTo(11L);
}
@Test
@DisplayName("平台公共渠道计费允许业务客户快照为空")
void calculatesWithoutOptionalBusinessCustomerSnapshot() {
@DisplayName("缺少订单客户归属时拒绝生成对客费用")
void rejectsMissingOrderCustomerOwnership() {
readyRepository();
InboundPricingEntity inbound = inbound("200.000", "0.2000");
inbound.setCustomerId(null);
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(priceBookEstimator.estimate(any())).thenReturn(
estimate("KG", "200.000", "14.300000", "2860.00", "0.00"));
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-no-customer");
assertThat(response.totalAmount()).isEqualByComparingTo("2860.00");
ArgumentCaptor<InboundFeeEntity> fee = ArgumentCaptor.forClass(InboundFeeEntity.class);
verify(repository).insertFee(fee.capture());
assertThat(fee.getValue().getCustomerId()).isNull();
assertThatThrownBy(() -> service.calculate(
TENANT, OPERATOR, INBOUND, NOW, "calc-no-customer"))
.isInstanceOf(BizException.class)
.hasMessageContaining("缺少订单客户归属");
verify(priceBookEstimator, never()).estimate(any());
verify(repository, never()).insertFee(any());
}
@Test
@@ -144,6 +143,7 @@ class FbaPricingServiceTest {
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
verify(repository).markInboundCalculated(TENANT, INBOUND, "WAIT_SPLIT", "KG",
new BigDecimal("167.000"));
verify(financeOutbox, never()).publishInbound(any(), anyLong(), anyLong(), anyLong(), anyInt(), any());
}
@Test
+9 -2
View File
@@ -67,8 +67,15 @@ export function normalizeModuleOrder(modules) {
for (const prerequisite of financeChain) {
if (!normalized.includes(prerequisite)) normalized.splice(normalized.indexOf('finance-downloads'), 0, prerequisite)
}
normalized.splice(normalized.indexOf('finance-downloads'), 1)
normalized.splice(normalized.indexOf('finance-settlement-full') + 1, 0, 'finance-downloads')
const completeFinanceChain = [...financeChain, 'finance-downloads']
const firstChainIndex = normalized.findIndex((name) => completeFinanceChain.includes(name))
let predecessor = firstChainIndex > 0 ? normalized[firstChainIndex - 1] : null
if (!predecessor) predecessor = ['phase3-container-loading', 'inbound-measurement', 'orders', 'merchant-qualification-fixture']
.find((name) => normalized.includes(name)) || null
const withoutChain = normalized.filter((name) => !completeFinanceChain.includes(name))
const insertionIndex = predecessor ? withoutChain.indexOf(predecessor) + 1 : withoutChain.length
withoutChain.splice(insertionIndex, 0, ...completeFinanceChain)
normalized.splice(0, normalized.length, ...withoutChain)
}
if (normalized.includes('fee-items')) {
const prerequisites = ['merchant-qualification-fixture', 'orders']
@@ -6,6 +6,7 @@
|---|---|---|
| 当前总控对话 | Admin 全量脚本 | 总入口、`system.mjs`、`master-data.mjs`、`downloads.mjs`、最终集成与全量报告 |
| `019ff0da-7699-7532-ac64-703325c995fd` | Finance 一期验收总控 | `finance.mjs`、Finance 正式全量接入与报告 |
| `019ff905-a477-7a03-b806-953ba598d305` | Finance 下载中心共享契约 | `finance-downloads.mjs`、直接契约测试与 Finance 完整链接入;不覆盖通用 `downloads.mjs` |
| `019fdbd3-f24f-7903-9124-de79c3ba37d0` | 客户尺寸规则 | 总入口内 `customer-dimension-rules` 子场景;不得改其他 customers 流程 |
| `019fdb7d-8e29-7c40-bda6-5a29dcb37971` | OCR 扫描 | `inbound-weight-ocr.mjs` |
| `019fdac1-ae04-7032-bac0-968497ea54ad` | 墨家测量 | `inbound-measurement.mjs` |
+2 -2
View File
@@ -51,7 +51,7 @@ export async function run(context) {
| 订舱共享主数据前置与订舱验收 | `ocean-booking-fixture.mjs`、`ocean-bookings.mjs`、`phase3-port-schedules.mjs`、`phase3-carrier-schedule-queries.mjs` | `ocean-booking-prototype-contract.test.mjs`、`ocean-booking-fixture-contract.test.mjs`、`phase3-carrier-schedule-queries.test.mjs` |
| 拖车预约 | `truck-appointments.mjs` | `truck-appointments-contract.test.mjs` |
| 消息中心管理 | `notifications.mjs` | `notifications-contract.test.mjs` |
| Finance 真实前置、对账与结算 | `finance-full-fixtures.mjs`、`finance-reconciliation-full.mjs`、`finance-settlement-full.mjs` | `finance-full-fixtures-contract.test.mjs`、`finance-reconciliation-full-contract.test.mjs`、`finance-settlement-full-contract.test.mjs` |
| Finance 真实前置、对账、结算与下载中心 | `finance-full-fixtures.mjs`、`finance-reconciliation-full.mjs`、`finance-settlement-full.mjs`、`finance-downloads.mjs` | `finance-full-fixtures-contract.test.mjs`、`finance-reconciliation-full-contract.test.mjs`、`finance-settlement-full-contract.test.mjs`、`finance-downloads-contract.test.mjs` |
| 初始化基线(仅离线) | — | `initialization-baseline-contract.test.mjs` |
表中的 `*-fixture.mjs` 是被业务模块复用的测试数据前置,不是新增的独立全量入口。执行 HTTP 时仍从 `../admin-test-env-acceptance.mjs` 进入;`*contract.test.mjs` 只做离线脚本结构和数据归属回归。
@@ -108,7 +108,7 @@ node --test scripts/admin-test-env-modules/suppliers-contract.test.mjs
## Finance 全链前置
`finance-full-fixtures` 会通过正式供应商 API 创建本轮隔离的 ACTIVE 供应商,并按费用项目类别校验后再生成 PAYABLE;不会使用历史供应商 ID。只请求 `finance-full-fixtures,finance-reconciliation-full,finance-settlement-full` 时,总入口会自动补齐 `merchant-qualification-fixture -> orders -> inbound-measurement -> phase3-container-loading`,确保 Finance 能消费本轮真实已完成柜次。若这些前置仍未产出同 marker 的完成柜次,Finance 会 fail-closed,不能把 finance-only 前置不足计为 Finance 业务成功。
`finance-full-fixtures` 会通过正式供应商 API 创建本轮隔离的 ACTIVE 供应商,并按费用项目类别校验后再生成 PAYABLE;不会使用历史供应商 ID。只请求 `finance-full-fixtures,finance-reconciliation-full,finance-settlement-full,finance-downloads` 时,总入口会自动补齐 `merchant-qualification-fixture -> orders -> inbound-measurement -> phase3-container-loading`,确保 Finance 能消费本轮真实已完成柜次,并在结算链完成后验证八类 XLSX 下载任务。若这些前置仍未产出同 marker 的完成柜次,Finance 会 fail-closed,不能把 finance-only 前置不足计为 Finance 业务成功。单独请求 `finance-downloads` 只执行最小部署能力探测,不借历史 Finance 数据。
## 国内整柜与客户运营统计