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
+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/',