fix(acceptance): align latest overseas contracts
This commit is contained in:
@@ -1440,7 +1440,7 @@ BEGIN
|
||||
'll_outbound_order','ll_outbound_pallet_item','ll_outbound_pda_task','ll_outbound_pda_pick',
|
||||
'll_delivery_order','ll_delivery_fee','ll_delivery_operation',
|
||||
'll_dropship_order_profile','ll_dropship_carton','ll_dropship_sku_inventory','ll_dropship_carton_ledger',
|
||||
'll_exception_order','ll_customer_visit')) <> 22 THEN
|
||||
'll_exception_order','ll_exception_return_item','ll_exception_order_command','ll_customer_visit')) <> 24 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期排车、出库、派送、一件代发、异常或回访表未完整部署';
|
||||
END IF;
|
||||
IF EXISTS (SELECT dispatch_no FROM ll_dispatch_order GROUP BY dispatch_no HAVING COUNT(*)>1) THEN
|
||||
@@ -1556,7 +1556,7 @@ BEGIN
|
||||
'll_outbound_order','ll_outbound_pallet_item','ll_outbound_pda_task','ll_outbound_pda_pick',
|
||||
'll_delivery_order','ll_delivery_fee','ll_delivery_operation',
|
||||
'll_dropship_order_profile','ll_dropship_carton','ll_dropship_sku_inventory','ll_dropship_carton_ledger',
|
||||
'll_exception_order','ll_customer_visit')) <> 22 THEN
|
||||
'll_exception_order','ll_exception_return_item','ll_exception_order_command','ll_customer_visit')) <> 24 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期完整业务表集合未部署';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='baiyue_lastleg'
|
||||
@@ -1818,6 +1818,20 @@ CREATE TABLE IF NOT EXISTS ll_destuff_pallet_cost_allocation (
|
||||
CONSTRAINT chk_ll_destuff_allocation CHECK(actual_volume_cbm>0 AND allocated_amount>=0)
|
||||
) ENGINE=InnoDB COMMENT='拆柜打托成本按客户实收体积分摊';
|
||||
|
||||
USE baiyue_lastleg;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='destination_fba_warehouse_code'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN destination_fba_warehouse_code VARCHAR(128) NULL COMMENT ''FBA目的仓编码(与海外操作仓分离)''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='operation_type'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN operation_type VARCHAR(32) NOT NULL DEFAULT ''CARTON_LABEL'' COMMENT ''换标模式:箱唛、SKU或组合换标''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='sku_relabel_items_json'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN sku_relabel_items_json JSON NULL COMMENT ''同箱SKU换标子明细快照(数量按件)''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_inventory_balance_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_inventory_balance_id BIGINT NULL COMMENT ''原库存余额ID''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_inventory_position_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_inventory_position_id BIGINT NULL COMMENT ''原库存库位ID''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_inbound_order_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_inbound_order_id BIGINT NULL COMMENT ''原入仓单ID''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_inbound_order_no'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_inbound_order_no VARCHAR(128) NULL COMMENT ''原入仓单号快照''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_inbound_code'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_inbound_code VARCHAR(128) NULL COMMENT ''原进仓编码快照''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_warehouse_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_warehouse_id VARCHAR(128) NULL COMMENT ''原海外中转仓ID''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_warehouse_code'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_warehouse_code VARCHAR(128) NULL COMMENT ''原海外中转仓编码快照''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='source_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN source_warehouse_name VARCHAR(255) NULL COMMENT ''原海外中转仓名称快照''')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND index_name='idx_ll_relabel_item_source_position'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD KEY idx_ll_relabel_item_source_position (customer_id,source_inventory_position_id)')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
|
||||
-- 海外接柜仓由平台在尾程建单时预分配;历史测试数据不回填,缺失事实由业务接口失败关闭。
|
||||
USE baiyue_lastleg;
|
||||
|
||||
@@ -1591,12 +1591,23 @@ CREATE TABLE IF NOT EXISTS ll_relabel_item (
|
||||
relabel_file_url VARCHAR(1000) NULL COMMENT '换标文件地址',
|
||||
carton_mark VARCHAR(255) NULL COMMENT '客户自有箱唛',
|
||||
fba_carton_mark VARCHAR(255) NULL COMMENT '关联FBA箱唛',
|
||||
destination_warehouse_id VARCHAR(128) NULL COMMENT '该箱目的海外仓ID',
|
||||
destination_warehouse_id VARCHAR(128) NOT NULL COMMENT '该箱目的海外仓ID',
|
||||
destination_fba_warehouse_code VARCHAR(128) NOT NULL COMMENT 'FBA目的仓编码(与海外操作仓分离)',
|
||||
operation_type VARCHAR(32) NOT NULL DEFAULT 'CARTON_LABEL' COMMENT '换标模式:箱唛、SKU或组合换标',
|
||||
sku_relabel_items_json JSON NULL COMMENT '同箱SKU换标子明细快照(数量按件)',
|
||||
source_inventory_balance_id BIGINT NOT NULL COMMENT '原库存余额ID',
|
||||
source_inventory_position_id BIGINT NOT NULL COMMENT '原库存库位ID',
|
||||
source_inbound_order_id BIGINT NOT NULL COMMENT '原入仓单ID',
|
||||
source_inbound_order_no VARCHAR(128) NULL COMMENT '原入仓单号快照',
|
||||
source_inbound_code VARCHAR(128) NULL COMMENT '原进仓编码快照',
|
||||
source_warehouse_id VARCHAR(128) NOT NULL COMMENT '原海外中转仓ID',
|
||||
source_warehouse_code VARCHAR(128) NULL COMMENT '原海外中转仓编码快照',
|
||||
source_warehouse_name VARCHAR(255) NULL COMMENT '原海外中转仓名称快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_ll_relabel_item_application (customer_id,application_id),
|
||||
KEY idx_ll_relabel_item_sku (customer_id,sku_code),
|
||||
KEY idx_ll_relabel_item_destination (customer_id,destination_warehouse_id,application_id)
|
||||
KEY idx_ll_relabel_item_source_position (customer_id,source_inventory_position_id)
|
||||
) ENGINE=InnoDB COMMENT='换标货物冻结明细';
|
||||
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='remark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''货物明细备注'' AFTER label_requirement')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@@ -2040,6 +2051,21 @@ CREATE TABLE IF NOT EXISTS ll_security_projection (
|
||||
KEY idx_security_projection_permission (aggregate_id,permission_version)
|
||||
) ENGINE=InnoDB COMMENT='海外IAM安全状态与权限版本投影';
|
||||
|
||||
-- 兼容已有的旧版投影表:CREATE TABLE IF NOT EXISTS 不会补充后续新增字段。
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='ll_security_projection' AND column_name='roles_json'), 'SELECT 1',
|
||||
'ALTER TABLE ll_security_projection ADD COLUMN roles_json JSON NULL COMMENT ''IAM投影的当前角色快照'' AFTER permission_version'));
|
||||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
|
||||
AND table_name='ll_security_projection' AND column_name='authorities_json'), 'SELECT 1',
|
||||
'ALTER TABLE ll_security_projection ADD COLUMN authorities_json JSON NULL COMMENT ''IAM投影的当前海外权限快照'' AFTER roles_json'));
|
||||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
UPDATE ll_security_projection SET roles_json=JSON_ARRAY() WHERE roles_json IS NULL;
|
||||
UPDATE ll_security_projection SET authorities_json=JSON_ARRAY() WHERE authorities_json IS NULL;
|
||||
ALTER TABLE ll_security_projection
|
||||
MODIFY COLUMN roles_json JSON NOT NULL COMMENT 'IAM投影的当前角色快照',
|
||||
MODIFY COLUMN authorities_json JSON NOT NULL COMMENT 'IAM投影的当前海外权限快照';
|
||||
|
||||
-- 开发基线固定管理员;后续真实 IAM 事件从 aggregate_version=1 单调覆盖该 version=0 投影。
|
||||
INSERT IGNORE INTO ll_security_projection (
|
||||
aggregate_type,aggregate_id,tenant_id,security_status,permission_version,roles_json,authorities_json,aggregate_version,
|
||||
|
||||
+1
-1
@@ -399,7 +399,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
org.mockito.Mockito.reset(plans, lines, orders, sessions, operations, cartons, warehouse, pallets, workflow);
|
||||
stubCurrent("OVERSEAS", "CONT-A");
|
||||
when(cartons.selectOne(any())).thenReturn(carton(1L, "BOX-1"));
|
||||
when(warehouse.inspectPallet("PAL-1")).thenReturn(resource(100L, null));
|
||||
when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(100L, null));
|
||||
when(pallets.selectList(any())).thenReturn(List.of(planPallet()));
|
||||
doThrow(new ResponseStatusException(org.springframework.http.HttpStatus.CONFLICT, "duplicate carton"))
|
||||
.when(workflow).validateCartonResolved(any(), any(), any(), any(), any());
|
||||
|
||||
@@ -15,11 +15,12 @@ test('admin full acceptance registers inbound fee lifecycle after measurement',
|
||||
test('inbound fee lifecycle covers frozen source, version, publication and WAIT_SPLIT contracts', async () => {
|
||||
const source = await readFile(moduleFile, 'utf8')
|
||||
for (const token of [
|
||||
'/fees/latest', '/fees/published', '/fees/confirm', '/fees/send', '/fee-details',
|
||||
'/fees/latest', '/fees/calculate', '/fees/published', '/fees/confirm', '/fees/send', '/fee-details',
|
||||
'FBA_PRICING', 'MANUAL', 'CALCULATED', 'CONFIRMED', 'SENT', 'PUBLISHED',
|
||||
'sourceFeeVersion', 'reviewStatus', 'publicationStatus', 'WAIT_SPLIT', 'toleranceConfirmed: true',
|
||||
]) assert.match(source, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
||||
assert.match(source, /人工发布头程费用并触发 Finance Outbox/)
|
||||
assert.match(source, /String\(initialResponse\?\.body\?\.code\) === 'A0404'/)
|
||||
assert.match(source, /对客合计仅含已发布头程与已审核其他应收且其他应付单列/)
|
||||
assert.doesNotMatch(source, /settlementIncluded|是否结算|参与结算/)
|
||||
})
|
||||
|
||||
@@ -36,15 +36,20 @@ export async function run(context) {
|
||||
if (!tenantId || !inboundId) { finish(context, start); return }
|
||||
|
||||
const latestPath = `/api/platform/v1/inbounds/${encodeURIComponent(inboundId)}/fees/latest?tenantId=${encodeURIComponent(tenantId)}`
|
||||
const latestResponse = await context.request('GET', latestPath)
|
||||
const initial = context.apiSuccess(MODULE, '审核成功后读取自动初算费用', latestResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '自动初算保持 WAIT_SPLIT 且尚未发布', latestResponse,
|
||||
let initialResponse = await context.request('GET', latestPath)
|
||||
if (!success(initialResponse) && String(initialResponse?.body?.code) === 'A0404') {
|
||||
initialResponse = await context.request('POST',
|
||||
`/api/platform/v1/inbounds/${encodeURIComponent(inboundId)}/fees/calculate?tenantId=${encodeURIComponent(tenantId)}`,
|
||||
undefined, undefined, requestHeaders(`${context.marker}-inbound-fee-initial-calculate`))
|
||||
}
|
||||
const initial = context.apiSuccess(MODULE, '读取或补齐审核后初始费用', initialResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '初始费用保持 WAIT_SPLIT 且尚未发布', initialResponse,
|
||||
initial?.feeStatus === 'CALCULATED' && initial?.nextProcessStatus === 'WAIT_SPLIT'
|
||||
&& Number(initial?.feeVersion) >= 1 && money(initial?.totalAmount) >= 0,
|
||||
`feeId=${id(initial?.feeId) || 'missing'}, version=${initial?.feeVersion ?? 'missing'}, status=${initial?.feeStatus || 'missing'}, process=${initial?.nextProcessStatus || 'missing'}`, OPTIONS)
|
||||
if (!initial?.feeId || initial?.feeStatus !== 'CALCULATED') { finish(context, start); return }
|
||||
|
||||
const initialDetails = await listDetails(context, tenantId, inboundId, '查询自动初算派生费用明细')
|
||||
const initialDetails = await listDetails(context, tenantId, inboundId, '查询初始费用派生明细')
|
||||
const initialDerived = initialDetails.rows.find((row) => row.sourceType === 'FBA_PRICING'
|
||||
&& id(row.sourceFeeId) === id(initial.feeId))
|
||||
context.assert(MODULE, 'FBA_PRICING 明细是初算版本的只读草稿派生', initialDetails.response,
|
||||
|
||||
@@ -30,6 +30,9 @@ test('PDA module covers the container-number receiving contract and excludes out
|
||||
for (const role of ['LICENSE_PLATE', 'CONTAINER_SEAL', 'DOOR_OPEN']) assert.ok(source.includes(role), role)
|
||||
for (const forbidden of ['/outbound', '/dispatch', '/delivery', '/internal/']) assert.equal(source.includes(forbidden), false, forbidden)
|
||||
assert.match(source, /同键同载荷重放/)
|
||||
assert.match(source, /inbound-plans\/container-preview/)
|
||||
assert.match(source, /snapshotVersion: Number\(preview\.snapshotVersion\)/)
|
||||
assert.match(source, /snapshotHash: String\(preview\.snapshotHash\)/)
|
||||
assert.match(source, /同键异载荷冲突/)
|
||||
assert.match(source, /跨柜切换锁返回409/)
|
||||
assert.match(source, /item\.operable === true && item\.allPlansReady === true/)
|
||||
|
||||
@@ -306,7 +306,23 @@ async function convertReceivingContainers(context, producers) {
|
||||
convertible,
|
||||
`sourceContainerBookingId=${id(summary?.sourceContainerBookingId) || 'missing'}, allPlansReady=${summary?.allPlansReady === true}, orders=${totalOrders}, inboundCodes=${inboundCodes}, plans=${existingPlans}, pending=${pendingPlans}; ${trace(summaryResponse)}`, OPTIONS)
|
||||
if (!convertible) return false
|
||||
const body = { sourceContainerBookingId: id(summary.sourceContainerBookingId) }
|
||||
const previewResponse = await context.requestOverseas('GET',
|
||||
`/api/platform/v1/last-leg/inbound-plans/container-preview?sourceContainerBookingId=${encodeURIComponent(summary.sourceContainerBookingId)}`,
|
||||
undefined, {}, context.overseasToken)
|
||||
const preview = success(previewResponse) ? previewResponse.body?.data : null
|
||||
const previewReady = preview?.ready === true
|
||||
&& Number.isInteger(Number(preview?.snapshotVersion)) && Number(preview.snapshotVersion) >= 0
|
||||
&& /^[0-9a-f]{64}$/.test(String(preview?.snapshotHash || ''))
|
||||
&& Number(preview?.pendingOrderCount) === pendingPlans
|
||||
context.addStep(MODULE, `预览整柜原子转换快照 ${containerNo}`, previewResponse,
|
||||
previewReady,
|
||||
`ready=${preview?.ready === true}, version=${preview?.snapshotVersion ?? 'missing'}, hash=${nonBlank(preview?.snapshotHash) ? 'present' : 'missing'}, pending=${preview?.pendingOrderCount ?? 'missing'}; ${trace(previewResponse)}`, OPTIONS)
|
||||
if (!previewReady) return false
|
||||
const body = {
|
||||
sourceContainerBookingId: id(summary.sourceContainerBookingId),
|
||||
snapshotVersion: Number(preview.snapshotVersion),
|
||||
snapshotHash: String(preview.snapshotHash),
|
||||
}
|
||||
const key = `${context.marker}-pda-convert-${containerNo}`.slice(0, 128)
|
||||
const convertedResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/inbound-plans/convert-container', body,
|
||||
|
||||
Reference in New Issue
Block a user