From 5c22018eddaf69cbd96117a5f717d31d1120c97c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 17 Aug 2026 18:04:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../firstleg/service/FbaPricingService.java | 3 +++ .../service/FbaPricingServiceTest.java | 20 +++++++++---------- scripts/admin-test-env-acceptance.mjs | 11 ++++++++-- scripts/admin-test-env-modules/OWNERSHIP.md | 1 + scripts/admin-test-env-modules/README.md | 4 ++-- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java index 1be21ac71..da36408e2 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/FbaPricingService.java @@ -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("入库单缺少起运仓或渠道匹配维度"); } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java index adcdf4429..31da1f7ba 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/FbaPricingServiceTest.java @@ -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 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 diff --git a/scripts/admin-test-env-acceptance.mjs b/scripts/admin-test-env-acceptance.mjs index be0587874..fd91d03ce 100644 --- a/scripts/admin-test-env-acceptance.mjs +++ b/scripts/admin-test-env-acceptance.mjs @@ -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'] diff --git a/scripts/admin-test-env-modules/OWNERSHIP.md b/scripts/admin-test-env-modules/OWNERSHIP.md index 74025e00f..3131b201a 100644 --- a/scripts/admin-test-env-modules/OWNERSHIP.md +++ b/scripts/admin-test-env-modules/OWNERSHIP.md @@ -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` | diff --git a/scripts/admin-test-env-modules/README.md b/scripts/admin-test-env-modules/README.md index a1381880e..e5f66fc45 100644 --- a/scripts/admin-test-env-modules/README.md +++ b/scripts/admin-test-env-modules/README.md @@ -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 数据。 ## 国内整柜与客户运营统计