feat(fleet): add internal capacity pool and warehouse fulfillment bridge
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
# Admin 测试环境 HTTP 模块
|
||||
|
||||
`fleet-capacity-pool-schema-contract.test.mjs` 是离线数据库合同:验证发布与持久命令唯一约束、唯一商业履约来源、可空实际数量、服务与统一基线一致、非破坏性结构补齐及独立销售/调度权限;不访问环境、不证明真实履约已通过。
|
||||
|
||||
## 当前新增脚本的执行边界
|
||||
|
||||
- 人工状态矩阵的 `FEE_CONFIRMING` 使用独立新订单,在测量提交成功后暂停审核,经正式 `fees/calculate` 生成费用;回查同一费用版本、入库客户身份、测量仍待审核及入仓码精确筛选结果。该状态是必验项,不再列入不可造数缺口;不发送费用、不推进该样本、不借历史数据。入口仍为 `--modules manual-state-matrix`,对应 `order-inbound-status-fixtures-contract.test.mjs` 和 `manual-state-matrix-contract.test.mjs`。
|
||||
- `WAIT_PRICING` 另建保留原始 `APPROVED` 的订单,先形成未发布的 `FEE_CONFIRMING`,再通过客户实时渠道目录与报价选择同仓、同目的仓、同税制的不同渠道,以正式申报更新触发费用失效;回查 `WAIT_PRICING/UNPRICED`、新渠道及精确筛选。缺少可计价替换渠道、客户漂移或状态未收敛均失败;不写业务SQL,退出专用目录查询会话。该状态同样为必验项,原先借历史 `WAIT_SPLIT` 样本的兜底已移除。
|
||||
|
||||
- `grouped-dispatch-acceptance.mjs` 由默认 `phase4-overseas-dispatch` 在主链预排前调用。使用本轮独立未拣托盘,通过 `/drafts/pre-arrange` 验证错组拒绝、原草稿不变、每组独立排车、同命令重放及数量/来源;成功响应明确的本轮未拣明细通过正式 API 移除并释放锁,保留空对照单据。写请求超时或 5xx 保留资源,不自动重试或清理未知结果。对应 `grouped-dispatch-acceptance-contract.test.mjs`。
|
||||
- `fleet-trip-finance-acceptance.mjs` 由默认 `phase5-overseas-fulfillment` 结案后调用。只读本轮实际发车车次及已冻结派送来源,核对 AVAILABLE/BILLED/SETTLED 来源、原币应收、明确归属实收与单据,未分配实收保持未知(null 或 NON_NULL 响应省略);前后车次、资源、审核不得变化。失败保留 CHECKING 证据,不创建账单、收款或财务分配。对应 `fleet-trip-finance-acceptance-contract.test.mjs` 的部分/全额付款和受限权限反例不代替真实样本;默认链没有独立实付样本,不能据此宣称付款生命周期或真实权限边界已经验收。
|
||||
|
||||
两者都是内部模块,没有独立 CLI;沿 Admin 原入口的 TEST 配置、统一会话、JSON 报告与权限执行。依赖缺失、版本未部署或前置链失败时明确失败,不借历史样本或生成模拟成功。源码准备与实际执行分开记录;离线合同不能代替真实 HTTP。开始验收后,先跑新增合同及脚本文档门禁,再跑相应依赖链和全量;当前执行结果仅维护在测试环境部署主契约及其原始报告中。
|
||||
|
||||
`fleet-handover-acceptance.mjs` 是车辆专项实际DEPART后的内部交接助手,由`../fleet-test-env-acceptance.mjs`调用;仅接受本轮已验证车次/派送及自有PDA会话,核对原DEPART/费用不变、真实剩余货物和接替资源、同键重放及旧资源分别释放。调用方必须注入发送前持久化检查点,落盘失败不发送HTTP;仅保存脱敏原命令及附件摘要。签收前导出本轮`handover-fixture.json`供页面/运营统计检查,成本验证与账号清理由同一入口编排;失败保留证据,不借旧车次或自动续跑。对应`fleet-handover-acceptance-contract.test.mjs`为离线合同。
|
||||
|
||||
`fleet-quotes-operations.mjs` 的`runQuoteSession`使用独立TEST登录及本轮客户准备,`run`只验证报价增改/状态/历史及原子CSV;`verifyOperations`另需当前轮DEPART的独立托位/货量/原币成本期望,报价通过不代表运营统计通过。后者默认只读,实测更正必须显式传入写入开关和独立幂等命名空间;不推算财务实付。由专项编排器提供持久化检查点和独占报告目录,原请求未知时不自动重造样本,结束退出自身会话。对应`fleet-quotes-operations-contract.test.mjs`不访问真实环境。
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { readInitialization, table, seedRows } from '../sql-baseline-contract-helpers.mjs'
|
||||
|
||||
const overseas = readInitialization('OVERSEAS')
|
||||
const cn = readInitialization()
|
||||
const read = relative => readFileSync(new URL(`../../${relative}`, import.meta.url), 'utf8')
|
||||
|
||||
test('pool has a unique publication per trip and full durable command identity, no second capacity balance', () => {
|
||||
const offer = table(overseas, 'baiyue_lastleg', 'll_fleet_capacity_offer')
|
||||
assert.match(offer, /UNIQUE KEY\s+\w+\(trip_id\)/i)
|
||||
assert.match(offer, /UNIQUE KEY\s+\w+\(offer_no\)/i)
|
||||
assert.match(offer, /published_fingerprint CHAR\(64\) NULL/i)
|
||||
assert.doesNotMatch(offer, /remaining_pallet|available_pallet|reserved_pallet/i)
|
||||
const command = table(overseas, 'baiyue_lastleg', 'll_fleet_capacity_offer_command')
|
||||
assert.match(command, /UNIQUE KEY\s+\w+\(trip_id,operator_id,action,scope_no,idempotency_key\)/i)
|
||||
assert.match(command, /request_hash CHAR\(64\) NOT NULL/i)
|
||||
assert.match(command, /result_json JSON NOT NULL/i)
|
||||
})
|
||||
|
||||
test('one booking maps to exactly one commercial order and one actual incoming source', () => {
|
||||
const link = table(overseas, 'baiyue_lastleg', 'll_fleet_pool_order_link')
|
||||
for (const columns of ['booking_id', 'booking_no', 'business_type,business_order_id', 'incoming_no', 'lastleg_order_id']) {
|
||||
assert.match(link, new RegExp(`UNIQUE KEY\\s+\\w+\\(${columns}\\)`, 'i'))
|
||||
}
|
||||
assert.match(link, /incoming_no VARCHAR\(64\) NULL/i)
|
||||
assert.match(link, /lastleg_order_id BIGINT NULL/i)
|
||||
assert.match(link, /actual_pallet_count INT NULL/i)
|
||||
assert.match(link, /actual_pallet_spaces INT NULL/i)
|
||||
assert.doesNotMatch(link, /(?:agreed_amount|receivable_amount|paid_amount)\s+DECIMAL/i)
|
||||
const booking = table(overseas, 'baiyue_lastleg', 'll_fleet_external_booking')
|
||||
assert.match(booking, /sales_owner_user_id VARCHAR\(64\) NULL/i)
|
||||
assert.match(booking, /expected_pallet_count INT NULL/i)
|
||||
})
|
||||
|
||||
test('module and unified fresh baselines contain the exact same new table definitions', () => {
|
||||
const module = read('lastleg-service/src/main/resources/db/lastleg.sql')
|
||||
for (const name of ['ll_fleet_capacity_offer', 'll_fleet_capacity_offer_command', 'll_fleet_pool_order_link']) {
|
||||
const definition = table(overseas, 'baiyue_lastleg', name)
|
||||
assert.ok(module.includes(definition), `${name}: module and unified contract drift`)
|
||||
}
|
||||
const sql = read('deploy/sql/migrations/overseas/baiyue_lastleg/V10__fleet_capacity_pool.sql')
|
||||
assert.doesNotMatch(sql.replace(/^--.*$/gm, ''), /\b(?:DROP|TRUNCATE|DELETE|UPDATE|REPLACE)\b/i)
|
||||
assert.match(sql, /ADD COLUMN expected_pallet_count INT NULL/i)
|
||||
assert.match(sql, /ADD CONSTRAINT fk_fleet_booking_offer/i)
|
||||
})
|
||||
|
||||
test('pool permissions are independent of trip details and default only to the platform administrator', () => {
|
||||
const permissions = seedRows(cn, 'iam_permission').filter(row => String(row.permission_code).startsWith('platform:fleet:capacity-offer:'))
|
||||
assert.equal(permissions.length, 5)
|
||||
assert.deepEqual(permissions.map(row => row.permission_code.split(':').at(-1)).sort(), ['inquiry-cancel','inquiry-create','inquiry-edit','list','manage'])
|
||||
const ids = new Set(permissions.map(row => String(row.id)))
|
||||
const grants = seedRows(cn, 'iam_role_permission').filter(row => ids.has(String(row.permission_id)))
|
||||
assert.equal(grants.length, 5)
|
||||
assert.ok(grants.every(row => String(row.role_id) === '4000001' && String(row.tenant_id) === '0'))
|
||||
})
|
||||
@@ -9,7 +9,7 @@ const domesticSchemas = ['baiyue_auth', 'baiyue_iam', 'baiyue_order', 'baiyue_wa
|
||||
const overseasSchemas = ['baiyue_lastleg', 'baiyue_lastlag_warehouse']
|
||||
|
||||
test('两个入口分别只操作八个主业务库与两个仓储业务库,包括限定名引用', () => {
|
||||
for (const [sql, allowed, other, count] of [[cn, domesticSchemas, overseasSchemas, 226], [overseas, overseasSchemas, domesticSchemas, 154]]) {
|
||||
for (const [sql, allowed, other, count] of [[cn, domesticSchemas, overseasSchemas, 226], [overseas, overseasSchemas, domesticSchemas, 157]]) {
|
||||
assert.deepEqual([...sql.matchAll(/^CREATE DATABASE IF NOT EXISTS (\w+)/gm)].map(m => m[1]), allowed)
|
||||
assert.deepEqual([...new Set([...sql.matchAll(/^USE (\w+);/gm)].map(m => m[1]))], allowed)
|
||||
const body = sql.replace(/^--.*$/gm, '')
|
||||
@@ -39,7 +39,7 @@ test('纯初始化禁止迁移、数据回填、清理、重复建表和提前
|
||||
assert.doesNotMatch(body.replace(/^CREATE TRIGGER [\s\S]*?\$\$/gm, ''), /^\s*(?:UPDATE|DELETE FROM|REPLACE INTO)\b/gmi)
|
||||
assert.doesNotMatch(body, /CREATE TABLE IF NOT EXISTS/i)
|
||||
assert.equal((body.match(/AS initialization_status/g) ?? []).length, 1)
|
||||
assert.match(body.trim(), /SELECT 'PASS' AS initialization_status, '(CN|OVERSEAS)' AS region, (226|154) AS initialized_tables;$/)
|
||||
assert.match(body.trim(), /SELECT 'PASS' AS initialization_status, '(CN|OVERSEAS)' AS region, (226|157) AS initialized_tables;$/)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -121,7 +121,7 @@ test('平台管理员由外部bcrypt引导,强制首登改密且不创建客
|
||||
})
|
||||
|
||||
test('必要系统种子完整保留,但不写入示例资源与业务事实', () => {
|
||||
for (const [name, count] of Object.entries({ iam_role: 8, iam_permission: 412, iam_role_permission: 593,
|
||||
for (const [name, count] of Object.entries({ iam_role: 8, iam_permission: 417, iam_role_permission: 598,
|
||||
iam_data_scope: 7, wh_warehouse: 318, wh_warehouse_address: 318, wh_service_capability: 4,
|
||||
fl_fee_item: 112, fl_price_channel_catalog: 15, fl_price_origin_alias: 7, fl_price_warehouse_mapping: 4,
|
||||
file_export_task_guard: 1, fin_subject: 1 })) assert.equal(seedRows(cn, name).length, count, name)
|
||||
|
||||
Reference in New Issue
Block a user