This commit is contained in:
2026-09-23 17:47:50 +08:00
parent 0d145b84db
commit 962dbe7c02
12 changed files with 45 additions and 15 deletions
+3 -3
View File
@@ -99,7 +99,7 @@
- 跨服务修改先检查调用方 `client/`、被调用方 Controller/DTO、`src/main/resources/openapi/` 和双方契约测试;禁止共享领域 Entity。
- 修改 Java 方法、构造器、Controller、Client、DTO 或 Mapper 参数签名时,先用 `rg` 定向查找符号及旧参数形态,并同步更新 `src/main/`、`src/test/`、Mock、Fixture、契约测试和脚本中的全部调用点。新增可选参数也必须补齐旧测试调用,不能因生产调用已更新就视为完成。
- 所有生产 MyBatis Mapper 接口必须显式标记 `@Mapper`;每个持久化服务只允许在启动类使用一次服务根包 `@MapperScan(..., annotationClass = Mapper.class)`,不得新增子域包白名单或分散的 `*MapperConfiguration`。新增或移动 Mapper 后先运行 `node scripts/mapper-scan-contract.mjs`,全量后端回归也会在 Maven 前执行该门禁。
- 所有生产 MyBatis Mapper 接口必须显式标记 `@Mapper`;每个持久化服务只允许在启动类使用一次服务根包 `@MapperScan(..., annotationClass = Mapper.class)`,不得新增子域包白名单或分散的 `*MapperConfiguration`。新增、移动 Mapper 或修改扫描配置后运行 `node scripts/mapper-scan-contract.mjs`;全量后端回归已在 Maven 前执行该门禁时复用结果,输入未变不重复执行。
- 平台后台存在正式列表页时,`*:list` 同时授权列表、详情及只读辅助接口;`create`、`edit`、`delete`、`status`、`approve`、`review`、`export`、`upload`、`complete` 等操作保持独立。当前未上线,不兼容旧独立 `*:view`,Controller 不得使用 `hasAnyAuthority(list, view)`,OpenAPI 和角色初始化只保留 `*:list`。
- 供应商查询接口固定只使用 `platform:supplier:list`;IAM 中 `platform:supplier:list` 是正式菜单资源,`platform:supplier:view` 必须停用/移除,不得重新提升或兼容。
- 权限语义变更必须同步 Controller `@PreAuthorize`、OpenAPI、模块权限 SQL、IAM 菜单资源、统一增量脚本和契约测试。并行模块任务不得覆盖菜单与权限总控已写入共享 SQL 或权限契约的改动。
@@ -107,7 +107,7 @@
- 后端同样遵守根指引中的“开发数据和上传文件可丢弃”边界:不得为现有开发库孤儿文件、旧文件归属或历史部分失败新增删除补偿接口、兼容复用、回填或清理链路;只修复当前正式上传与持久化路径,既有开发文件由人工重新上传。
- 通用配置在 `config/nacos/{local,cn}/`;服务日志、上传文件、`target/`、`reports/` 不属于源码。
- Java 签名变更的最低编译门禁是 `mvn -pl <module> -am test-compile`,它必须在部署前完成并覆盖测试源码;有行为变化时继续运行 `mvn -pl <module> -am test`。`-DskipTests` 可跳过测试执行但仍须保留测试编译,`-Dmaven.test.skip=true` 会跳过测试编译,不得作为签名变更的交付验证。全量验证仍使用 `mvn -DskipTests validate`、`mvn test`;不要在本地启动 Java/Nacos/数据库,除非用户明确要求。
- `AGENTS.md` 索引维护完成后运行 `./scripts/agents-index-contract.ps1`;脚本只读源码目录和指定指引,验证根快速表唯一性、部署单元、Client active 路由、海外仓业务范围漂移、仓库主档唯一权威、订舱关联候选与柜量语义、整柜出仓与船节点单调性、海外 Gateway 覆盖、PDA 运行配置/独立更新/语言预留、人工分段验收链、Lastleg 海外安全投影、同步 Trace、模块覆盖、关键入口与排除目录,不访问 HTTP、数据库或部署环境。
- 同一批 `AGENTS.md` 索引修改完成后从后端根统一运行一次 `./scripts/agents-index-contract.ps1`;脚本只读源码目录和指定指引,不访问 HTTP、数据库或部署环境。
- Mapper 注册静态合同为 `scripts/mapper-scan-contract.mjs`、`mapper-scan-contract.test.mjs`:自动发现聚合模块、生产 `*Mapper` 接口、服务启动类和分散扫描配置,不启动 Spring、不连接数据库;遗漏注解、扫描根漂移或重新引入子域扫描时直接失败并输出类与文件路径。
- 单服务镜像构建/部署从 `Jenkinsfile.service`、`deploy/docker/Dockerfile`、`deploy/scripts/deploy-built-service.sh` 进入;新镜像启动失败或健康检查失败时只允许回滚到同服务先前镜像,并保持任务失败,离线合同见 `scripts/deploy-built-service-rollback.test.mjs`。实际部署仍须当前对话明确授权。
@@ -120,7 +120,7 @@
- Admin 总入口默认按模块列表覆盖平台全链路;需要定位单个业务时使用 `--modules <name>`,模块名称和静态合同先查 `scripts/admin-test-env-modules/README.md`。
- 订舱与拖车跨服务完整状态链由 Admin 注册模块 `service-center-flow` 验证,必须复用本轮已放舱订舱并由总入口展开依赖,不得直接执行模块文件或借历史数据。
- 尾程公网控制由 `phase4-tail-order-controls` 在海外 PDA 前验证整柜接柜仓变更与恢复、单/批目的仓变更、单/批合托标记、CSV 导出、订单费用、暂扣/解除、八个人工节点和逐票人工计划;目的港码头拖车由 `phase5-terminal-drayage` 在卸柜后验证选车队、预约提柜、实际提柜和实际还柜,`phase4-carrier-nodes` 只读核验最终船节点终态。
- 这些脚本会创建、更新或最终停用测试数据,必须在当前对话明确授权测试环境验收后执行,并保存 JSON 报告;未授权不得请求 Gateway。
- 这些脚本会创建、更新或最终停用测试数据;指定测试环境和服务后,本轮非破坏性写入复用一次授权,删除、重置、SQL、部署或发布仍逐项授权,并保存 JSON 报告;未授权不得请求 Gateway。
- 后端 Maven、前端 `pnpm test/typecheck`、PDA `node --test` 和数据库 Dry-run 都是本地质量检查,不属于全量 HTTP 验收。
- Java 模块、OpenAPI 覆盖、JaCoCo 与 SpotBugs 聚合摘要使用 `node scripts/java-quality-summary.mjs`;默认仅报告存量缺口,是否阻断由显式 `--require-*` 参数决定,输出位于 `target/` 时不得作为业务事实或提交物。
- HTTP 全量通过后,才进入 Admin/Client 真实页面调用、浏览器样式和 PDA 页面/设备验收;自动化 HTTP 通过不代表视觉验收完成。
+2
View File
@@ -77,6 +77,8 @@
完整操作请先读 [数据库迁移操作手册](数据库迁移操作手册.md)。离线入口为 `scripts/db-migrations.mjs`:
编号统一采用每个 schema 独立递增的 `V1、V2、V3……`,有 SQL 变更才由工具新增,成功执行后不改内容和编号。功能分支从最新 `main` 创建,进入测试前同步 main 并解决草稿编号冲突。生产发布另记 `release-1、release-2……`;该发布编号及分支门禁尚待流水线实现,完整流程见操作手册“编号与功能分支规则”。
```powershell
# 自动取得该 schema 的下一个整数序号并创建草稿;不连接数据库
node deploy/sql/scripts/db-migrations.mjs new --region CN --schema baiyue_auth --description add_login_audit
+17
View File
@@ -11,6 +11,22 @@
当前测试分支只完成了版本迁移的离线工具和目录骨架。没有冻结基线、没有具体业务变更时,不要复制初始化 SQL 生成 `V1`,也不要连接测试或生产数据库试跑。
## 编号与功能分支规则(2026-09-22 确认)
SQL 编号统一保持 **`V1、V2、V3、V4、V5……`**,不使用时间戳。每个数据库独立计数,由工具生成下一个整数;数字变长不需要重置或重新编号。
- 有数据库逻辑变更才新增迁移;普通提交、重复测试和重复部署不增加 SQL 编号。
- 同一未冻结、未在保留历史环境执行的变更可以反复编辑;成功执行后不改内容、不改编号,修复新增迁移。失败候选的受控修订仍按方案第 13.2.1 节处理。
- 功能分支从最新 `main` 创建;进入共享测试前先拉取并合并最新 `main`,解决代码和同库编号冲突。只允许调整尚未冻结、未在保留历史环境执行的草稿。
- 两人并行开发,共享测试、合并和发布依次进行。前一功能完成发布或处理失败后,下一功能同步 main 再进入测试;若前一功能留下未合并 SQL 或未决失败,先处理测试起点,不能直接继续验收。
- 测试后 main 又变化,必须重新同步和验证。合并 main 后构建正式候选并再次自动验证,生产使用这份正式候选,不重新构建或修改 SQL。
**生产发布版本另外记为 `release-1、release-2……`,不与 SQL 编号绑定。** 例如 `release-12` 中 Auth 是 `V3`、Order 是 `V7`;只有代码变化也可以发布 `release-13`,SQL 编号保持不变。
目标流程由 Jenkins 为新的生产候选自动分配发布编号;成功后更新当前生产版本并记录 Git 标签、完整提交、镜像 digest 和 SQL 清单。失败编号保留且不复用,同一制品重试沿用原编号;修改制品后分配新编号并重新验证。main 是已合并代码,生产实际版本以成功发布记录为准;发布失败时还需核对各库、各服务的实际状态,不能假定全部未变。
当前工具只依据当前分支的迁移目录生成整数编号;跨分支冲突预检、main 同步门禁、`release-N` 分配和标签归档尚待接入,不因写入本手册就视为已自动执行。
## 日常开发:新建和修改迁移
以下命令均从 `baiyue-firstleg` 根目录执行,PowerShell 和 Bash 都可以使用 Node 命令。
@@ -136,6 +152,7 @@ node deploy/sql/scripts/db-migrations.mjs run `
- 生产或测试数据库基线登记;
- Flyway 实际执行和历史表校验;
- Jenkins 的数据库阶段、环境锁和构建/部署解耦;
- 功能分支同步检查、合并后候选验证、生产 `release-N` 自动编号及发布标签归档;
- 真实测试环境部署与生产晋级。
因此现在可以开始编写和评审迁移文件,但不能把离线检查通过当作测试或生产发布通过。
+1 -1
View File
@@ -3,7 +3,7 @@
## 模块定位
- 本模块是独立财务服务,拥有客户结算条款、财务费用来源、CNY 账单与明细、客户资金账户及充值申请、应收款月度统计、客户对账确认、收款及分配、成本及分摊、账单修订、单据编号、幂等记录和财务审计状态。
- 产品范围先读 `../../docs/财务管理/财务管理产品确认表.md` 与 `财务核心五页产品与接口验收契约.md`,跨服务契约再读 `Firstleg-Finance跨服务契约.md`;专题 README、证据矩阵和测试环境报告只用于状态或验收问题,不作为普通开发首读。
- 产品范围先读 `../../docs/财务管理/财务管理产品确认表.md` 与 `财务核心五页产品与接口验收契约.md`,跨服务契约再读 `Firstleg-Finance跨服务契约.md`;只有海外仓费用阶段 0A—0E 的设计或实现任务再读取 `../../docs/财务管理/海外仓费用统一接入阶段0技术契约.md`,并明确区分其目标 V2 协议与当前 V1 代码事实。专题 README、证据矩阵和测试环境报告只用于状态或验收问题,不作为普通开发首读。
- Firstleg 拥有入库/柜次业务费用和履约事实,File 拥有凭证文件;Finance 通过内部接口读取或消费快照,不跨库查询,也不复制上游业务状态机。
## 快速查询
+1 -1
View File
@@ -58,7 +58,7 @@
| 异常订单与派送退回入仓 | `exceptionorder/controller/ExceptionOrderController.java`、`exceptionorder/service/ExceptionOrderService.java`、`exceptionorder/gateway/ExceptionEvidenceFileGateway.java`、`inventory/command/service/InventoryCommandService.java`;证据固定按客户、异常单 ID 和 `OVERSEAS_EXCEPTION_EVIDENCE` 校验。退回候选按原出库拣货事实冻结,不接受自由 SKU 或数量;异常按 `PENDING→PROCESSING→COMPLETED`(待处理可取消)、版本与幂等键流转。退回海外仓须把实收按原出库来源恢复到服务端校验的真实托盘/库位并写 `RETURN_STOCK_IN` 流水,库存恢复、异常完成与派送 `RETURNED` 在同一事务完成 |
| 海外采购、客户下单、采购审核、客户/采购仓候选与操作流水 | `procurement/controller/`、`procurement/service/OverseasProcurementService.java`;客户入口按 `merchant:cross-border:procurement:list/create` 拆分且只取 Token 客户范围;OpenAPI `src/main/resources/openapi/overseas-business-v1.yaml` |
| 海外业务资金指令与结果 | `wallet/WalletCommandOutboxService.java`、`WalletCommandCompletedConsumer.java`、`WalletResultHandler.java`;采购和换标通过 `WALLET_COMMAND_REQUESTED` 请求国内 Finance 冻结、结算或释放,并以 `WALLET_COMMAND_COMPLETED` 幂等回写业务资金状态;不得跨区同步调用 Finance 钱包或在 Lastleg 自建余额账本 |
| 海外业务费用来源 | `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` |
| 海外业务费用来源 | `financefee/controller/PlatformOverseasFeeSourceController.java`、`financefee/service/OverseasFeeSourceService.java`、`OverseasFeeBusinessGuard.java`、`OverseasFeeOutboxService.java`、`OverseasFeeOutboxRelay.java`;海外入仓、采购、美国卡车、本土散板和海外仓服务费必须匹配当前客户的真实业务,入仓记录和入仓码按事实选填;当前 V1 审核通过、调整或作废后以版本化完整快照经 Outbox/Event Bridge 进入独立 Finance,OpenAPI `src/main/resources/openapi/overseas-finance-fees-v1.yaml`。阶段 0A—0E 开发目标另读 `../../docs/财务管理/海外仓费用统一接入阶段0技术契约.md`,收费身份、候选版本、Finance 结果事件与资金计划 V2 尚须以实际代码落地验证,不能因文档存在宣称已实现 |
| 美国/本土卡车订单 | `trucking/controller/PlatformUsTruckOrderController.java`、`MerchantUsTruckOrderController.java` 及同子域 Service/DTO;客户入口按 `merchant:cross-border:domestic-trucking:list/create/edit` 拆分 |
| 本土散板订单 | `looseboard/controller/PlatformLocalLooseboardController.java`、`MerchantLocalLooseboardController.java` 及同子域 Service/DTO/Entity/Mapper;客户入口按 `merchant:cross-border:pallet-shipping:list/create/edit` 拆分;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`,车辆档案不含派送履约历史 |
+1 -1
View File
@@ -48,4 +48,4 @@
- 修改 Admin 模块时运行直接对应的 `*contract.test.mjs`;修改造数链时同时运行 `admin-test-env-modules/it-test-wait-split-bulk-contract.test.mjs` 等受影响合同。
- 宣称脚本“全量补齐”前,必须运行 `scripts/` 根级离线合同与 `admin-test-env-modules/*contract.test.mjs`,并分别报告测试文件数、断言通过/失败数和精确失败文件;历史真实 HTTP 报告全绿不能覆盖当前离线合同失败。
- 新增、移动或删除脚本后运行 `node --test scripts/script-documentation-contract.test.mjs`,确认目录已登记且所有直接入口都有说明。
- 修改任一 `AGENTS.md` 后从后端根运行 `./scripts/agents-index-contract.ps1`;真实 HTTP、数据库、Nacos、Jenkins 和部署验证仍须当前轮明确授权。
- `AGENTS.md` 修改统一遵循 `../AGENTS.md` 的索引维护门禁,复用同一批次结果;环境操作授权遵循外层根指引的确认边界。
+1 -1
View File
@@ -158,7 +158,7 @@ Client 全量、Client 入库测量与计费、待审核订单和批量待分柜
| 迁移辅助 | `migrate-local-qualification-file.ps1` | 本地资质文件迁移辅助,会修改本地文件/数据;运行前核对脚本参数和备份策略 |
| Jenkins 管理 | `provision-development-service-jobs.groovy` | Jenkins Script Console 批量准备开发服务 Job;默认 `applyChanges=false` 仅预览,改为 true 会创建 Job |
部署目录的入口另有:`deploy/scripts/deploy-built-service.sh`(单服务健康检查与失败回滚)、`deploy/scripts/deploy-development-service.sh`(经 SSH 部署开发服务)、`deploy/scripts/deploy-services.sh`(按服务列表编排部署)、`deploy/scripts/initialize-test-databases.sh`(初始化测试基础设施数据库)和 `deploy/rocketmq/render-broker-config.sh`(容器内渲染并启动 RocketMQ broker)。这些脚本都会改变运行环境,不能作为普通本地检查执行。
部署目录的入口另有:`deploy/scripts/deploy-built-service.sh`(单服务健康检查与失败回滚)、`deploy/scripts/deploy-development-service.sh`(经 SSH 部署开发服务)、`deploy/scripts/deploy-services.sh`(按服务列表编排部署)、`deploy/scripts/initialize-test-databases.sh`(初始化测试基础设施数据库)、`deploy/scripts/check-lingxing-browser.sh`(只读检查 Lingxing 浏览器容器及健康状态)和 `deploy/rocketmq/render-broker-config.sh`(容器内渲染并启动 RocketMQ broker)。这些脚本都会改变运行环境或依赖受控部署上下文,不能作为普通本地检查执行。
数据库迁移离线入口为 `deploy/sql/scripts/db-migrations.mjs`:`new` 自动生成单 schema 的下一个版本草稿,`check` 做范围/文件/跨 schema 静态检查,`plan` 生成选定服务的不可变清单,`run --dry-run` 只输出执行计划。当前不连接数据库;真实 Flyway 执行须等迁移阶段完成并获得对应环境授权。用途、参数和边界见 `deploy/sql/README.md`。
+2 -2
View File
@@ -1112,7 +1112,7 @@ export class AcceptanceRun {
async runCustomers() {
const module = 'customers'; const phone = await this.findAvailablePhone(module); const suffix = this.marker.slice(-12)
const payload = {
tenantName: `后台客户验收-${suffix}`, legalName: `后台客户验收有限公司-${suffix}`,
tenantName: `后台客户验收-${suffix}`, customerNickname: `后台客户-${suffix}`, legalName: `后台客户验收有限公司-${suffix}`,
legalRepresentative: '接口验收法人', contactName: '接口验收联系人', contactPhone: phone,
contactEmail: `admin-api-${suffix.toLowerCase()}@example.com`, servicePackage: 'API验收套餐',
contactWechat: `api_${suffix.toLowerCase()}`, salesOwnerName: '接口验收销售', paymentTermDays: 30,
@@ -1166,7 +1166,7 @@ export class AcceptanceRun {
const detailPath = `/api/platform/v1/iam/tenants/${tenantId}`
const detailResponse = await this.request('GET', detailPath); const detail = this.apiSuccess(module, '查询客户详情', detailResponse)
this.assert(module, '客户详情基本信息正确', detailResponse, detail && detail.tenantName === payload.tenantName && detail.contactPhone === phone && detail.internalRemark === payload.internalRemark, `tenantId=${tenantId}`)
const editedPayload = { ...payload, tenantName: `${payload.tenantName}-已编辑`, contactName: '接口验收联系人-已编辑', contactEmail: `edited-${payload.contactEmail}`, paymentTermDays: 45, internalRemark: `${this.marker}-EDITED` }
const editedPayload = { ...payload, tenantName: `${payload.tenantName}-已编辑`, customerNickname: `${payload.customerNickname}-已编辑`, contactName: '接口验收联系人-已编辑', contactEmail: `edited-${payload.contactEmail}`, paymentTermDays: 45, internalRemark: `${this.marker}-EDITED` }
const editResponse = await this.request('PUT', detailPath, editedPayload); const edited = this.apiSuccess(module, '编辑客户', editResponse)
this.assert(module, '客户编辑正确回显', editResponse, edited?.tenantName === editedPayload.tenantName && edited?.contactEmail === editedPayload.contactEmail && Number(edited?.paymentTermDays) === 45, `tenantId=${tenantId}`)
const persistedResponse = await this.request('GET', detailPath); const persisted = this.apiSuccess(module, '回查客户编辑结果', persistedResponse)
@@ -112,7 +112,8 @@ async function createConfirmedContainer(context, source, label) {
if (!agent) throw new Error(`${label} 未取得当前船公司关联的 ACTIVE 订舱代理`)
const draft = {
customerId: source.tenantId, customerName: summary.customerName || 'IT 测试客户',
channelCode: summary.channelCode, warehouseId: source.warehouseId,
// Ocean booking 当前契约接收稳定的渠道分组码;入库摘要仍保留真实渠道码用于候选匹配。
channelCode: channelGroup(summary.channelCode), warehouseId: source.warehouseId,
billOfLadingNo: `BL-${value}`.slice(0, 128),
shippingCompanyId: id(carrier.id), shippingCompanyName: carrier.name, carrierCode: carrier.carrierCode,
bookingAgentId: id(agent.id), bookingAgentName: agent.name, departurePortCode: 'CNSZX',
@@ -137,16 +137,18 @@ async function readOrder(context, fixture, orderId) {
return { response, detail: context.apiSuccess(MODULE, '回读订单状态样本', response) }
}
async function waitForInbound(context, fixture, orderId) {
async function waitForInbound(context, fixture, orderId, allowCompletedOrder = false) {
const path = `/api/platform/v1/orders/${query(orderId)}?tenantId=${query(fixture.tenantId)}`
for (let attempt = 0; attempt < 20; attempt += 1) {
// 审核/接受/完成会经过跨服务履约事件;测试环境高负载时可能超过10秒才落入库单。
// 保持轮询只读并延长观察窗口,避免把异步延迟误判为“未生成入库单”。
for (let attempt = 0; attempt < 240; attempt += 1) {
const response = await context.request('GET', path)
const summary = data(response)?.summary
if (ok(response) && summary?.inboundId
&& ['WAIT_MEASURE', 'MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT'].includes(summary.mainStatus)) {
&& (allowCompletedOrder || ['WAIT_MEASURE', 'MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT'].includes(summary.mainStatus))) {
return { response, summary }
}
if (attempt < 19) await wait(500)
if (attempt < 239) await wait(500)
}
return { response: null, summary: null }
}
@@ -248,7 +250,7 @@ export async function run(context, matrix = context.createdResources.manualState
}
if (actions.includes('approve')) {
const inbound = await waitForInbound(context, fixture, orderId)
const inbound = await waitForInbound(context, fixture, orderId, target === 'COMPLETED')
const inboundId = id(inbound.summary?.inboundId)
context.assert(MODULE, `${target} 订单生成待测量入库单`, inbound.response, Boolean(inboundId),
`orderId=${orderId}, inboundId=${inboundId || 'missing'}`)
+7
View File
@@ -846,11 +846,17 @@ $eventTypes = Read-RequiredText (Join-Path $backendRoot 'common-event-contract/s
$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')
$overseasFinancePhase0ContractPath = Join-Path $workspaceRoot 'docs/财务管理/海外仓费用统一接入阶段0技术契约.md'
$overseasFinancePhase0Contract = Read-RequiredText $overseasFinancePhase0ContractPath
Assert-Contains $businessIndex '财务管理与柜级公共费用分摊' '根柜费分摊快速路由'
Assert-Contains $businessIndex '海外业务费用来源与客户财务' '根海外费用快速路由'
Assert-Contains $adminAgents '财务、客户资金账户与柜级公共费用分摊' 'Admin 柜费分摊快速路由'
Assert-Contains $backendAgents '柜级公共费用跨客户分摊' '后端柜费分摊快速路由'
Assert-Contains $backendAgents '海外业务费用来源跨区入池' '后端海外费用快速路由'
Assert-Contains $businessIndex '海外仓费用统一接入阶段0技术契约.md' '根海外费用阶段 0 按需入口'
Assert-Contains $financeAgents '海外仓费用统一接入阶段0技术契约.md' 'Finance 海外费用阶段 0 按需入口'
Assert-Contains $lastlegAgents '海外仓费用统一接入阶段0技术契约.md' 'Lastleg 海外费用阶段 0 按需入口'
Assert-Contains $lastlegAgents '不能因文档存在宣称已实现' '阶段 0 设计与代码事实边界'
Assert-Contains $firstlegAgents '单行入口拒绝分摊组' 'Firstleg 分摊组原子边界'
Assert-Contains $adminFinanceAgents '`allocationGroupId` 整组审核、驳回、调整或作废' 'Admin 分摊组操作边界'
Assert-Contains $containerFeeOpenApi '/fee-details/allocation-groups:' '柜费分摊组创建 OpenAPI'
@@ -916,6 +922,7 @@ foreach ($module in $pom.project.modules.module) {
# 对本轮高变化链路做精确文件门禁,防止改名或删除后留下悬空索引。
$criticalPaths = @(
'apps/admin-web/src/modules/overseas-warehouse/api/inboundOrders.ts',
'docs/财务管理/海外仓费用统一接入阶段0技术契约.md',
'apps/admin-web/src/modules/overseas-warehouse/api/lastMileOrders.ts',
'apps/pda-overseas-uniapp/src/api/pallet.js',
'apps/pda-overseas-uniapp/src/pages/pallet/pallet.vue',
@@ -13,6 +13,7 @@ const excludedDirectoryNames = new Set([
])
const allowedDirectoryPrefixes = [
'scripts/',
'deploy/docker/',
'deploy/scripts/',
'deploy/sql/scripts/',
'deploy/rocketmq/',