From fa9a98f0789f5db7671b437d65b783004f69f32b Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 19:53:07 +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 --- AGENTS.md | 2 + common-event-contract/AGENTS.md | 1 + event-bridge-service/AGENTS.md | 3 +- finance/AGENTS.md | 4 +- firstleg/AGENTS.md | 2 +- lastleg-service/AGENTS.md | 2 +- .../coload-groups-contract.test.mjs | 2 +- .../admin-test-env-modules/coload-groups.mjs | 20 +++---- scripts/agents-index-contract.ps1 | 53 ++++++++++++++++++- 9 files changed, 73 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 526e62f64..072dd198c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,6 +33,8 @@ | 智能装柜入仓码候选与箱数分配 | `firstleg/` | `controller/PlatformSmartLoadingController.java` 的 `/inbound-candidates`、`/plans/{planId}/inbound-assignments`,业务编排 `service/SmartLoadingService.java` | | 订舱资料/打印、船期、订舱级拖车、报关 | `firstleg/` | `oceanbooking/`、`portschedule/`、`TruckAppointmentService.java`、`outbound/dispatch/`、`customs/`;拖车只绑定 `RELEASED` 订舱,不携带客户、柜次、装柜计划或真实柜号,每订舱最多一张非终态预约,订舱文件联查 `file/AGENTS.md` | | 客户账期、费用池、账单、收款、成本、核销 | `finance/` | `finance/AGENTS.md`;费用来源和柜次成本快照再联查 `firstleg/AGENTS.md`,凭证再联查 `file/AGENTS.md` | +| 柜级公共费用跨客户分摊 | `firstleg/`、`finance/` | Firstleg `PlatformContainerFeeDetailController.java`、`ContainerFeeDetailService.java`、`container-fees-v1.yaml` 按柜内真实客户与订单原子创建 `allocation-groups`;金额合计必须等于总额,分组审核、驳回、调整、作废均整组处理,Finance 只消费各客户不可变来源快照 | +| 海外业务费用来源跨区入池 | `lastleg-service/`、`common-event-contract/`、`event-bridge-service/`、`finance/` | Lastleg `financefee/` 审核五类海外费用并发布 `BUSINESS_FEE_SOURCE_CHANGED` 完整快照;Event Bridge 对 `targetRegion=CN` 定向投递 Finance `/internal/finance/v1/business-fee-source-events`;Finance 按事件 ID、Payload 哈希、来源系统/费用 ID/版本幂等入池,`PAYABLE + DIRECT_PAID` 单独形成已确认成本且不进入 AP 待结算 | | 入库/柜次应付、直接已付、付款流水与凭证 | `firstleg/`、`finance/`、`file/` | 业务费用与付款快照见 `firstleg/AGENTS.md` 的 `FeePaymentSnapshotValidator`;`/api/platform/finance/v1/vouchers` 上传凭证,File 按 `FINANCE_VOUCHER` 和客户范围校验 | | 仓库、往来单位、快递、托盘、库位 | `warehouse/` | `warehouse/AGENTS.md` | | 供应商、国内整柜库存与出入库 | `warehouse/` | `warehouse/AGENTS.md`;OpenAPI `warehouse/src/main/resources/openapi/supplier-v1.yaml`、`domestic-container-v1.yaml` | diff --git a/common-event-contract/AGENTS.md b/common-event-contract/AGENTS.md index bf9e87321..743df9a71 100644 --- a/common-event-contract/AGENTS.md +++ b/common-event-contract/AGENTS.md @@ -18,6 +18,7 @@ | 头程装柜完成 V1 解析模型 | `FirstLegLoadingPlanCompletedV1.java`;用于消费端冻结字段映射,新增生产不得回退为 V1 | | 海外卸柜完成 V1 | `OverseasContainerUnloadedV1.java`;按来源柜预订 ID 做柜级聚合/幂等,同一柜只发布一次;`unloadingCompletedAt` 必须是全柜 complete 时刻,不得用签收拍照时刻代替;海外生产者见 `../lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundUnloadCompletedOutboxService.java`,国内消费者见 `../firstleg/src/main/java/com/baiyue/firstleg/service/OverseasContainerEventConsumer.java` | | 国内船节点记录 V1 | `FirstLegContainerShippingNodeRecordedV1.java`;只接受十节点中带实际时间和完整供应商审计字段的事实,国内生产者 `../firstleg/src/main/java/com/baiyue/firstleg/portschedule/service/CarrierScheduleNodeOutboxService.java`,海外消费者 `../lastleg-service/src/main/java/com/baiyue/lastleg/shipping/service/ContainerShippingNodeConsumer.java` | +| 海外业务费用来源变更 V1 | `BusinessFeeSourceChangedV1.java`、`EventTypes.BUSINESS_FEE_SOURCE_CHANGED`;携带可独立校验入池的完整不可变客户、业务、方向、供应商、付款、来源版本与哈希快照,长整型标识使用字符串;海外生产者见 `../lastleg-service/src/main/java/com/baiyue/lastleg/financefee/`,Event Bridge 定向投递国内 `finance/`,Finance 不跨区同步回查 | | IAM 安全事件 V1 | `IamSecurityEventsV1.java`;账号、授权快照等安全事件的冻结模型 | | 契约说明 | `package-info.java` | | 契约测试 | `src/test/java/com/baiyue/common/event/EventEnvelopeTest.java`、`FirstLegLoadingPlanCompletedV1Test.java`、`FirstLegLoadingPlanCompletedV2Test.java`、`OverseasContainerUnloadedV1Test.java`、`FirstLegContainerShippingNodeRecordedV1Test.java` | diff --git a/event-bridge-service/AGENTS.md b/event-bridge-service/AGENTS.md index 71f749459..591d0dfb4 100644 --- a/event-bridge-service/AGENTS.md +++ b/event-bridge-service/AGENTS.md @@ -13,9 +13,10 @@ |---|---| | 接收、状态、人工补投 API | `src/main/java/com/baiyue/eventbridge/controller/EventBridgeController.java` | | Inbox 接收与幂等 | `service/EventInboxService.java` | -| 按目标区域调度与重试 | `service/OverseasEventDispatcher.java`;`targetRegion=CN` 回投 Firstleg;海外业务事件投 Lastleg 通用入口,三类 IAM 安全事件按 eventType 投 Lastleg `/internal/lastleg/security/v1/events`,传输层不解析 payload | +| 按目标区域调度与重试 | `service/OverseasEventDispatcher.java`;`targetRegion=CN` 的 `BUSINESS_FEE_SOURCE_CHANGED` 定向投 Finance,其余国内回投 Firstleg;海外业务事件投 Lastleg 通用入口,三类 IAM 安全事件按 eventType 投 Lastleg `/internal/lastleg/security/v1/events`,传输层不解析 payload | | 尾程 HTTP 客户端 | `service/LastlegEventClient.java`、`HttpLastlegEventClient.java` | | 国内头程 HTTP 客户端 | `service/FirstlegEventClient.java`、`HttpFirstlegEventClient.java`;默认内部入口 `/internal/firstleg/v1/container-events` | +| 国内 Finance 费用事件客户端 | `service/FinanceEventClient.java`、`HttpFinanceEventClient.java`;统一内部入口 `/internal/finance/v1/business-fee-source-events`,配置 `baiyue.event-dispatcher.finance-consumer-url`,不得误投 Firstleg | | 人工补投 | `service/EventReplayService.java`;只允许 `DEAD` 转回 `RETRY`,记录操作人与原因,并保留原 `eventId`、payload 与 `payloadHash` | | Inbox 与投递记录 | `domain/BridgeInbox.java`、`BridgeDeliveryAttempt.java` 及对应 Mapper | | 传输指标与 Inbox 采样 | `metrics/EventBridgeMetrics.java`、`EventBridgeInboxMetricsSampler.java`;SLO 与告警合同 `docs/development-observability-slo.md` | diff --git a/finance/AGENTS.md b/finance/AGENTS.md index 9127eb182..68acf3d68 100644 --- a/finance/AGENTS.md +++ b/finance/AGENTS.md @@ -22,7 +22,7 @@ | 财务单据编号 | `service/FinanceDocumentNumberGenerator.java`;对账单、应收结算单、应付结算单分别使用 `REC`、`AR`、`AP` 前缀 | | Finance 导出数据 Provider | `controller/FinanceExportProviderController.java`、`service/FinanceExportProviderService.java`;由 File `ExportProviderClient` 按已登记 `exportType` 分批拉取,不从 Admin 直接生成文件 | | 五页产品 OpenAPI | `../../docs/财务管理/openapi/finance-phase1-five-pages-v1.yaml`;人工财务补充契约 `finance-f1-manual-v1.yaml` | -| 业务费用来源事件入口 | Firstleg:`controller/InternalFinanceFeeSourceController.java`、`service/FinanceFeeSourceEventService.java`;海外完整快照:`controller/InternalBusinessFeeSourceController.java`、`service/BusinessFeeSourceEventService.java` | +| 业务费用来源事件入口 | Firstleg:`controller/InternalFinanceFeeSourceController.java`、`service/FinanceFeeSourceEventService.java`;海外完整快照:`controller/InternalBusinessFeeSourceController.java` 的 `/internal/finance/v1/business-fee-source-events`、`service/BusinessFeeSourceEventService.java`;事件模型 `../common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java` | | 查询模型与客户可见性 | `service/FinanceQueryService.java`、`dto/FinanceReadModels.java` | | 账期、账单、收款、成本及幂等写模型 | `service/FinanceService.java` | | Firstleg 费用来源与柜次成本快照 | `client/FirstlegFeeSourceClient.java`、`client/FirstlegCostSnapshotClient.java`;生产端见 `../firstleg/AGENTS.md` | @@ -43,6 +43,8 @@ - 对账单、应收结算单和应付结算单编号统一按北京时间生成 `REC/AR/AP-yyMMdd-####`,各前缀每日独立递增且单日最多 9999;不得回退为数据库 ID 拼接、随机数或跨时区日期。 - 应收款统计只按正式应收结算单的生成月份汇总 CNY 应收、已收和未收,支持可选客户筛选和分页;导出必须通过 Finance Provider + File `FINANCE_RECEIVABLE_STATISTICS` 异步任务,不在 Controller 或 Admin 直接生成文件。 - 费用入池只接受满足上游发布与结算条件的不可变来源版本;来源 ID、版本、客户归属、方向或金额变化时同时核对事件消费、主动快照查询、唯一约束和账单明细消费者。 +- 海外业务费用事件只接受非 `FIRSTLEG` 来源、`APPROVED` 审核快照和 `PUBLISHED` 活跃版本或明确 `VOIDED` 动作;按 `eventId + payloadHash` 防重放冲突,并以 `sourceSystem + sourceType + sourceFeeId + sourceVersion` 单调落库。事件携带完整客户、业务、方向、供应商、付款与来源哈希事实,Finance 不跨区同步回查 Lastleg。 +- 海外 `PAYABLE + DIRECT_PAID` 审核版本必须生成 `BUSINESS_DIRECT_PAID` 已确认成本并计入已付成本/客户流水;不得进入 ON_ACCOUNT 应付池或再次生成 AP 结算。调整或作废须作废旧版本成本;客户隔离、同键异载荷冲突和已生成单据冻结不得放宽。 - 整柜成本分摊使用 Firstleg 返回的真实客户归属与订单体积快照;不得信任前端提交的订单归属或体积。凭证必须由 File 元数据校验客户和业务类型,依赖不可用时不得静默跳过。 - 应付费用来源、成本登记和应付结算单引用的供应商必须通过 Warehouse 校验 ID 与 `ACTIVE` 状态;费用项目 `supplierCategory` 为空时允许任意启用供应商,非空时严格校验类别,不得从费用编码或名称猜测。名称快照只能取 Warehouse 返回值,不能接受自由文本替代。Warehouse 不可用、供应商停用或非空类别不匹配时必须失败关闭;修改该链路时同步检查事件消费、人工成本、结算创建/付款、Nacos 客户端地址和双方契约测试。 - 修改平台或客户 DTO 时同步检查 Admin `../../apps/admin-web/src/modules/finance/`、Client `../../apps/client-web/src/modules/finance/`、Gateway 路由、权限码和契约测试;客户响应不得暴露供应商成本、内部备注、平台凭证或其他客户数据。 diff --git a/firstleg/AGENTS.md b/firstleg/AGENTS.md index 9e94fa7a8..0183fcafd 100644 --- a/firstleg/AGENTS.md +++ b/firstleg/AGENTS.md @@ -27,7 +27,7 @@ | 费用项目与价格簿 | `controller/PlatformFeeItemController.java`、`PlatformPriceBookController.java`、`MerchantPriceBookController.java` | | 价格簿查询内部切片 | `pricebookquery/application/port/in/PriceBookQueryUseCase.java`、`application/PriceBookQueryService.java`、`application/port/out/PriceBookQueryReader.java`、`adapter/out/persistence/PriceBookQueryMapper.java`;查询行模型位于 `application/model/PriceBookQueryRows.java` | | 财务费用来源快照 | `controller/InternalFinanceFeeSourceController.java`;入库费用与柜次费用分别按来源 ID、版本向 Finance 提供不可变快照 | -| 柜次费用明细 | `controller/PlatformContainerFeeDetailController.java`、`service/ContainerFeeDetailService.java`、`dto/ContainerFeeDetailDtos.java`;跨客户公共柜费使用 `allocation-groups` 按真实客户和订单原子分摊,入仓ID/入仓码同时填写或同时留空;契约 `src/main/resources/openapi/container-fees-v1.yaml`;Admin 消费端见 `../../apps/admin-web/src/modules/loading-lists/api/containerFees.ts` | +| 柜次费用明细与跨客户分摊组 | `controller/PlatformContainerFeeDetailController.java`、`service/ContainerFeeDetailService.java`、`dto/ContainerFeeDetailDtos.java`;跨客户公共柜费使用 `allocation-groups` 按柜内真实客户和订单原子分摊,入仓 ID/入仓码同时填写或同时留空;各行金额合计必须等于原始柜费,审核、驳回、调整、作废均整组处理,单行入口拒绝分摊组;契约 `src/main/resources/openapi/container-fees-v1.yaml`;Admin 消费端见 `../../apps/admin-web/src/modules/loading-lists/api/containerFees.ts` | | 财务整柜成本分摊快照 | `loading/controller/InternalFinanceCostSnapshotController.java`、`loading/service/FinanceCostSnapshotService.java` | | 分柜、预装、真实柜号后补与正式装柜清单生命周期 | `split/controller/PlatformSplitPreloadController.java`、`controller/PlatformLoadingListController.java`、`service/LoadingListService.java`;契约 `src/main/resources/openapi/loading-lists-v1.yaml`,详情阶段先查 `/loading-plans/{planId}/lifecycle`。创建柜次时原子生成唯一预装计划,加入入仓码明细时同步创建入库预装聚合与关联,不设推送步骤;同一 `planId` 在 `EDITING/LOADING` 返回 `PRELOAD`,`COMPLETED` 返回 `LOADING_LIST`。订舱关联、预装确认和 PDA task 创建不依赖真实集装箱号;可在 `EDITING/LOADING` 首次补录,最终 PDA 完成前必须存在;已有值纠正仅限 `EDITING` 且任务未领取、无物理作业事实 | | 同柜不可拆组 | `loading/coload/controller/PlatformColoadGroupController.java`、`service/ColoadGroupService.java`、`persistence/ColoadGroupMapper.java`;契约 `src/main/resources/openapi/coload-groups-v1.yaml`。请求按订单 ID 建组,服务端冻结每票全部 `WAIT_SPLIT` 可用入仓片段与满箱数;仅要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均保持整组原子 | diff --git a/lastleg-service/AGENTS.md b/lastleg-service/AGENTS.md index 57e81372f..079d6a229 100644 --- a/lastleg-service/AGENTS.md +++ b/lastleg-service/AGENTS.md @@ -37,7 +37,7 @@ | 一件代发接收、SKU/箱唛库存 | `dropship/service/DropshipOrderService.java`、`dropship/entity/`、`dropship/mapper/` | | 货物废弃、待审锁箱、审批扣减 | `discard/controller/CargoDiscardController.java`、`discard/service/CargoDiscardService.java` | | 海外采购、客户下单、采购审核、客户/采购仓候选与操作流水 | `procurement/controller/`、`procurement/service/OverseasProcurementService.java`;OpenAPI `src/main/resources/openapi/overseas-business-v1.yaml` | -| 海外业务费用来源 | `financefee/`;海外入仓、采购、本土卡车、本土散板和海外仓服务费审核后以 Outbox/Event Bridge 进入独立 Finance,入仓记录和入仓码按事实选填;OpenAPI `src/main/resources/openapi/overseas-finance-fees-v1.yaml` | +| 海外业务费用来源 | `financefee/controller/PlatformOverseasFeeSourceController.java`、`financefee/service/OverseasFeeSourceService.java`、`OverseasFeeBusinessGuard.java`、`OverseasFeeOutboxService.java`、`OverseasFeeOutboxRelay.java`;海外入仓、采购、美国卡车、本土散板和海外仓服务费必须匹配当前客户的真实业务,入仓记录和入仓码按事实选填;审核通过、调整或作废后以版本化完整快照经 Outbox/Event Bridge 进入独立 Finance,OpenAPI `src/main/resources/openapi/overseas-finance-fees-v1.yaml` | | 美国/本土卡车订单 | `trucking/controller/PlatformUsTruckOrderController.java`、`MerchantUsTruckOrderController.java` 及同子域 Service/DTO | | 本土散板订单 | `looseboard/controller/PlatformLocalLooseboardController.java` 及同子域 Service/DTO/Entity/Mapper;OpenAPI `src/main/resources/openapi/overseas-business-v1.yaml` | | 海外仓报价、月度汇率、车辆主数据 | 报价 `masterdata/quote/`、汇率 `masterdata/exchange/`;车辆入口 `masterdata/vehicle/adapter/in/web/PlatformVehicleController.java`,应用服务 `application/VehicleApplicationService.java`,持久化端口与适配器 `application/port/out/VehicleRepository.java`、`adapter/out/persistence/`;OpenAPI `src/main/resources/openapi/overseas-master-data-v1.yaml`,车辆档案不含派送履约历史 | diff --git a/scripts/admin-test-env-modules/coload-groups-contract.test.mjs b/scripts/admin-test-env-modules/coload-groups-contract.test.mjs index 7b7f33686..5a03ba02c 100644 --- a/scripts/admin-test-env-modules/coload-groups-contract.test.mjs +++ b/scripts/admin-test-env-modules/coload-groups-contract.test.mjs @@ -24,7 +24,7 @@ test('同柜不可拆组HTTP验收锁定公开接口与防绕过矩阵', async ( '整组原子移除', '容量竞争时智能推荐不因整组容量失败', '容量竞争时同柜组只能整组全取或全不取', - '推荐候选仅含当前目标同柜组且成员身份精确', + '推荐候选包含当前目标同柜组且成员身份精确', 'ACTIVE同柜组整组取消成功', '同柜组PDA原子批次记录短拣异常', '同柜组任一成员短拣时完成被拒绝', diff --git a/scripts/admin-test-env-modules/coload-groups.mjs b/scripts/admin-test-env-modules/coload-groups.mjs index fec970b37..816a857b7 100644 --- a/scripts/admin-test-env-modules/coload-groups.mjs +++ b/scripts/admin-test-env-modules/coload-groups.mjs @@ -461,8 +461,8 @@ export async function run(context) { `members=${JSON.stringify(crossDestinationFacts)}`, OPTIONS) if (crossDestinationGroup?.groupId) { const cleanupResponse = await context.request('POST', - `/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(crossDestinationGroup.groupId)}/cancel`, { - expectedVersion: Number(crossDestinationGroup.version), reason: '释放跨目的仓正向验收组成员', + `/api/platform/v1/first-leg/coload-groups/by-inbound-code/${encodeURIComponent(fifth.inboundCode)}/cancel`, { + reason: '释放跨目的仓正向验收组成员', }, undefined, { 'Idempotency-Key': `${context.marker}-coload-cross-destination-cancel`.slice(0, 128) }) context.apiSuccess(MODULE, '跨目的仓正向组整组取消并释放成员', cleanupResponse, evidence(cleanupResponse), OPTIONS) @@ -532,8 +532,8 @@ export async function run(context) { .map((item) => id(item?.coLoadGroupId)).filter(Boolean))] const targetCandidateInbounds = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId) .map((item) => id(item?.inboundId)).sort() - context.assert(MODULE, '推荐候选仅含当前目标同柜组且成员身份精确', isolatedCandidateResponse, - candidateGroupIds.length === 1 && candidateGroupIds[0] === groupId + context.assert(MODULE, '推荐候选包含当前目标同柜组且成员身份精确', isolatedCandidateResponse, + candidateGroupIds.includes(groupId) && JSON.stringify(targetCandidateInbounds) === JSON.stringify([first.inboundId, second.inboundId].sort()), `groupId=${groupId}, candidateGroupIds=${candidateGroupIds.join('|') || 'none'}, members=${targetCandidateInbounds.join('|')}, capacity=33cbm/28000kg`, OPTIONS) const allCandidateVolume = isolatedCandidates @@ -675,8 +675,8 @@ export async function run(context) { `before=${beforeMutation}, after=${planSignature(afterMutation.data)}`, OPTIONS) const cancelLockedResponse = await context.request('POST', - `/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(groupId)}/cancel`, { - expectedVersion: Number(group?.version) + 1, reason: '锁定组取消反向验收', + `/api/platform/v1/first-leg/coload-groups/by-inbound-code/${encodeURIComponent(first.inboundCode)}/cancel`, { + reason: '锁定组取消反向验收', }, undefined, { 'Idempotency-Key': `${context.marker}-coload-cancel-locked`.slice(0, 128) }) conflict(context, '已锁定组不能取消', cancelLockedResponse) @@ -729,8 +729,8 @@ export async function run(context) { const cancelGroupId = id(cancelGroup?.groupId) if (!cancelGroupId) return const cancelResponse = await context.request('POST', - `/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(cancelGroupId)}/cancel`, { - expectedVersion: Number(cancelGroup?.version), reason: '验收ACTIVE整组取消', + `/api/platform/v1/first-leg/coload-groups/by-inbound-code/${encodeURIComponent(fifth.inboundCode)}/cancel`, { + reason: '验收ACTIVE整组取消', }, undefined, { 'Idempotency-Key': `${context.marker}-coload-active-cancel`.slice(0, 128) }) const cancelled = context.apiSuccess(MODULE, 'ACTIVE同柜组整组取消成功', cancelResponse, evidence(cancelResponse), OPTIONS) @@ -917,8 +917,8 @@ export async function run(context) { `treeStable=${splitTreeBeforeGroup === treeSignature(splitTreeAfter)}`, OPTIONS) if (splitGroup?.groupId) { const splitCancelResponse = await context.request('POST', - `/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(splitGroup.groupId)}/cancel`, { - expectedVersion: Number(splitGroup.version), reason: '完成拆分余量正向验收', + `/api/platform/v1/first-leg/coload-groups/by-inbound-code/${encodeURIComponent(eighth.inboundCode)}/cancel`, { + reason: '完成拆分余量正向验收', }, undefined, { 'Idempotency-Key': `${context.marker}-coload-split-cancel`.slice(0, 128) }) context.apiSuccess(MODULE, '拆分余量正向组整组取消', splitCancelResponse, evidence(splitCancelResponse), OPTIONS) diff --git a/scripts/agents-index-contract.ps1 b/scripts/agents-index-contract.ps1 index 272e6ccb2..295b016ab 100644 --- a/scripts/agents-index-contract.ps1 +++ b/scripts/agents-index-contract.ps1 @@ -503,6 +503,57 @@ Assert-Contains $lastlegBusinessOpenApi 'allPlansReady' 'Lastleg 全柜计划就 Assert-Contains $lastlegBusinessOpenApi 'plannedArrivalAt' 'Lastleg 计划到仓冻结事实' Assert-Contains $lastlegAgents 'service/FbaWarehouseValidator.java' 'Lastleg FBA 权威仓校验入口' +# 国内柜费分摊与海外费用跨区入池必须能从根索引直达,并锁住归属、金额、版本与直接已付隔离。 +$financeAgents = Read-RequiredText (Join-Path $backendRoot 'finance/AGENTS.md') +$eventBridgeAgents = Read-RequiredText (Join-Path $backendRoot 'event-bridge-service/AGENTS.md') +$commonEventAgents = Read-RequiredText (Join-Path $backendRoot 'common-event-contract/AGENTS.md') +$adminFinanceAgents = Read-RequiredText (Join-Path $workspaceRoot 'apps/admin-web/src/modules/finance/AGENTS.md') +$containerFeeOpenApi = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/resources/openapi/container-fees-v1.yaml') +$containerFeeController = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformContainerFeeDetailController.java') +$containerFeeService = Read-RequiredText (Join-Path $backendRoot 'firstleg/src/main/java/com/baiyue/firstleg/service/ContainerFeeDetailService.java') +$overseasFinanceOpenApi = Read-RequiredText (Join-Path $backendRoot 'lastleg-service/src/main/resources/openapi/overseas-finance-fees-v1.yaml') +$businessFeeEvent = Read-RequiredText (Join-Path $backendRoot 'common-event-contract/src/main/java/com/baiyue/common/event/BusinessFeeSourceChangedV1.java') +$eventTypes = Read-RequiredText (Join-Path $backendRoot 'common-event-contract/src/main/java/com/baiyue/common/event/EventTypes.java') +$overseasDispatcher = Read-RequiredText (Join-Path $backendRoot 'event-bridge-service/src/main/java/com/baiyue/eventbridge/service/OverseasEventDispatcher.java') +$financeEventConsumer = Read-RequiredText (Join-Path $backendRoot 'finance/src/main/java/com/baiyue/finance/service/BusinessFeeSourceEventService.java') +$financePhase1Service = Read-RequiredText (Join-Path $backendRoot 'finance/src/main/java/com/baiyue/finance/service/FinancePhase1OperationService.java') +Assert-Contains $rootAgents '财务管理与柜级公共费用分摊' '根柜费分摊快速路由' +Assert-Contains $rootAgents '海外业务费用来源与客户财务' '根海外费用快速路由' +Assert-Contains $adminAgents '财务与柜级公共费用分摊' 'Admin 柜费分摊快速路由' +Assert-Contains $backendAgents '柜级公共费用跨客户分摊' '后端柜费分摊快速路由' +Assert-Contains $backendAgents '海外业务费用来源跨区入池' '后端海外费用快速路由' +Assert-Contains $firstlegAgents '单行入口拒绝分摊组' 'Firstleg 分摊组原子边界' +Assert-Contains $adminFinanceAgents '`allocationGroupId` 整组审核、驳回、调整或作废' 'Admin 分摊组操作边界' +Assert-Contains $containerFeeOpenApi '/fee-details/allocation-groups:' '柜费分摊组创建 OpenAPI' +Assert-Contains $containerFeeOpenApi '各分摊金额必须大于0且合计等于费用总额' '柜费分摊金额守恒' +Assert-Contains $containerFeeOpenApi '单行接口拒绝处理分摊组' '柜费分摊整组操作契约' +Assert-Contains $containerFeeController '@PostMapping("/allocation-groups/{groupId}/approve")' '柜费分摊整组审核入口' +Assert-Contains $containerFeeController '@PostMapping("/allocation-groups/{groupId}/adjust")' '柜费分摊整组调整入口' +Assert-Contains $containerFeeService '分摊客户、订单及入仓信息与柜内真实归属不一致' '柜费分摊真实归属校验' +Assert-Contains $containerFeeService '调整必须包含分摊组全部费用行' '柜费分摊完整调整校验' +Assert-Contains $lastlegAgents '海外入仓、采购、美国卡车、本土散板和海外仓服务费必须匹配当前客户的真实业务' 'Lastleg 五类海外费用归属' +Assert-Contains $overseasFinanceOpenApi 'OVERSEAS_INBOUND, OVERSEAS_PROCUREMENT, US_TRUCK, LOCAL_LOOSEBOARD, WAREHOUSE_SERVICE' '海外五类费用 OpenAPI' +Assert-Contains $overseasFinanceOpenApi '审核通过与作废均事务性写入Outbox' '海外费用可靠发布契约' +Assert-Contains $commonEventAgents '海外业务费用来源变更 V1' '公共海外费用事件入口' +Assert-Contains $eventTypes 'BUSINESS_FEE_SOURCE_CHANGED = "BusinessFeeSourceChanged"' '海外费用事件类型' +Assert-Contains $businessFeeEvent 'int sourceVersion' '海外费用事件来源版本' +Assert-Contains $businessFeeEvent 'List paymentVoucherFileIds' '海外费用事件付款证据快照' +Assert-Contains $businessFeeEvent 'String sourceHash' '海外费用事件来源哈希' +Assert-Contains $eventBridgeAgents '`BUSINESS_FEE_SOURCE_CHANGED` 定向投 Finance' 'Event Bridge Finance 定向路由' +Assert-Contains $eventBridgeAgents '/internal/finance/v1/business-fee-source-events' 'Event Bridge Finance 内部入口' +Assert-Matches $overseasDispatcher '(?s)EventTypes\.BUSINESS_FEE_SOURCE_CHANGED\.equals\(eventType\).{0,150}?financeEventClient\.deliver\(event\)' 'Event Bridge 海外费用投 Finance' +Assert-Contains $financeAgents '`eventId + payloadHash` 防重放冲突' 'Finance 海外事件幂等边界' +Assert-Contains $financeAgents '`PAYABLE + DIRECT_PAID` 审核版本必须生成 `BUSINESS_DIRECT_PAID`' 'Finance 直接已付隔离边界' +Assert-Contains $financeEventConsumer 'envelope.payloadHash().equals(row.get("payload_hash"))' 'Finance 海外事件 Payload 哈希幂等' +Assert-Contains $financeEventConsumer "cost_origin='BUSINESS_DIRECT_PAID'" 'Finance 海外直接已付成本投影' +Assert-Contains $financePhase1Service "f.settlement_mode='ON_ACCOUNT'" 'Finance AP 只纳入账期应付' +Assert-Contains $financePhase1Service "'FIRSTLEG_DIRECT_PAID','BUSINESS_DIRECT_PAID'" 'Finance 已付成本汇总' +Assert-Contains $scriptsAgents 'Finance 扩展 HTTP 模块' '脚本指引 Finance 扩展模块' +foreach ($financeModule in @('firstleg-container-fee-allocation', 'finance-direct-paid-isolation', 'phase4-overseas-finance-cross-region')) { + Assert-Contains $adminAcceptanceEntry "'$financeModule'" "Admin 总入口 $financeModule 注册" + Assert-Contains $adminAcceptanceModuleIndex "$financeModule.mjs" "Admin 模块索引 $financeModule" +} + # 国内 PDA 部分拣货必须展示真实可用库位事实,并在无库位时保持页面整洁。 Assert-Contains $pdaAgents 'tests/partial-layout.test.mjs' '国内 PDA 部分拣货布局合同' Assert-Contains $pdaAgents 'tests/picking-location-display.test.mjs' '国内 PDA 库位展示合同' @@ -810,5 +861,5 @@ if ($failures.Count -gt 0) { exit 1 } -Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、前端模块、Client active 路由、脚本目录治理、企业联系信息与登录标识解耦、选填法人身份证独立上传、待审核订单状态优先、订单申报版本同步与渠道门禁、装柜证据内容哈希重放、业务编号与内部 ID 隔离、装柜清单生命周期与真实柜号后补、订舱级拖车与柜级证据、四期柜级待处理计划筛选与范围漂移、仓库主档唯一权威、订舱候选与柜量语义、整柜出仓与船节点单调性、Gateway 覆盖、PDA 页面/运行配置/独立更新/语言预留/能力开关、人工分段验收链、Maven 模块、Lastleg 海外安全投影、同步 Trace、关键入口和排除目录。" -ForegroundColor Green +Write-Host "AGENTS 索引合同通过:$checked 项检查,覆盖快速表唯一性、部署单元、前端模块、Client active 路由、脚本目录治理、Finance 扩展 HTTP 模块、柜费跨客户原子分摊、海外五类费用跨区入池与直接已付隔离、企业联系信息与登录标识解耦、选填法人身份证独立上传、待审核订单状态优先、订单申报版本同步与渠道门禁、装柜证据内容哈希重放、业务编号与内部 ID 隔离、装柜清单生命周期与真实柜号后补、订舱级拖车与柜级证据、四期柜级待处理计划筛选与范围漂移、仓库主档唯一权威、订舱候选与柜量语义、整柜出仓与船节点单调性、Gateway 覆盖、PDA 页面/运行配置/独立更新/语言预留/能力开关、人工分段验收链、Maven 模块、Lastleg 海外安全投影、同步 Trace、关键入口和排除目录。" -ForegroundColor Green exit 0