fix phase3 acceptance roots and payable validation
This commit is contained in:
@@ -7492,6 +7492,9 @@ CREATE TABLE IF NOT EXISTS fl_preload_plan (
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
|
||||
items_json JSON NOT NULL COMMENT '可编辑分柜明细JSON快照;人工确认后作为预装依据',
|
||||
remark VARCHAR(1000) NULL COMMENT '预装备注',
|
||||
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID',
|
||||
confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照',
|
||||
confirmed_at DATETIME(3) NULL COMMENT '预装确认时间',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建预装清单的操作人ID',
|
||||
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
|
||||
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
|
||||
@@ -7504,6 +7507,30 @@ CREATE TABLE IF NOT EXISTS fl_preload_plan (
|
||||
CONSTRAINT fk_preload_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
||||
) ENGINE=InnoDB COMMENT='订单分柜预装清单';
|
||||
|
||||
DROP PROCEDURE IF EXISTS migrate_preload_confirmation_columns;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE migrate_preload_confirmation_columns()
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_by') THEN
|
||||
ALTER TABLE fl_preload_plan
|
||||
ADD COLUMN confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID' AFTER remark;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_by_name') THEN
|
||||
ALTER TABLE fl_preload_plan
|
||||
ADD COLUMN confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照' AFTER confirmed_by;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_at') THEN
|
||||
ALTER TABLE fl_preload_plan
|
||||
ADD COLUMN confirmed_at DATETIME(3) NULL COMMENT '预装确认时间' AFTER confirmed_by_name;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL migrate_preload_confirmation_columns();
|
||||
DROP PROCEDURE IF EXISTS migrate_preload_confirmation_columns;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_split_operation (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
|
||||
@@ -41,7 +41,7 @@ public class FeePaymentSnapshotValidator {
|
||||
}
|
||||
return Snapshot.empty();
|
||||
}
|
||||
if (!"PAYABLE".equals(direction) || !MODES.contains(mode)) {
|
||||
if (!"PAYABLE".equals(direction) || mode == null || !MODES.contains(mode)) {
|
||||
throw bad("应付费用必须选择ON_ACCOUNT或DIRECT_PAID");
|
||||
}
|
||||
if ("ON_ACCOUNT".equals(mode)) {
|
||||
|
||||
@@ -10,6 +10,9 @@ CREATE TABLE IF NOT EXISTS fl_preload_plan (
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
|
||||
items_json JSON NOT NULL COMMENT '可编辑分柜明细JSON快照;人工确认后作为预装依据',
|
||||
remark VARCHAR(1000) NULL COMMENT '预装备注',
|
||||
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID',
|
||||
confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照',
|
||||
confirmed_at DATETIME(3) NULL COMMENT '预装确认时间',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建预装清单的操作人ID',
|
||||
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
|
||||
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
|
||||
@@ -62,6 +65,13 @@ BEGIN
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_by',
|
||||
'BIGINT UNSIGNED NULL COMMENT ''确认预装的操作人ID'' AFTER remark');
|
||||
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_by_name',
|
||||
'VARCHAR(64) NULL COMMENT ''确认预装的操作人姓名快照'' AFTER confirmed_by');
|
||||
CALL phase3_split_add_column('fl_preload_plan', 'confirmed_at',
|
||||
'DATETIME(3) NULL COMMENT ''预装确认时间'' AFTER confirmed_by_name');
|
||||
|
||||
CALL phase3_split_add_column('fl_inbound_order', 'parent_inbound_id',
|
||||
'BIGINT UNSIGNED NULL COMMENT ''生成本单的主入库单ID;仅二级子单有值'' AFTER id');
|
||||
CALL phase3_split_add_column('fl_inbound_order', 'split_level',
|
||||
|
||||
+23
@@ -10,10 +10,33 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaSplitSettlementSqlContractTest {
|
||||
@Test
|
||||
void addsPreloadConfirmationAuditColumnsForIncrementalPdaAllocation() throws Exception {
|
||||
String moduleSql = new ClassPathResource("db/phase3-split-preload-schema.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
String unifiedSql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(moduleSql)
|
||||
.contains("confirmed_by BIGINT UNSIGNED NULL")
|
||||
.contains("confirmed_by_name VARCHAR(64) NULL")
|
||||
.contains("confirmed_at DATETIME(3) NULL")
|
||||
.contains("phase3_split_add_column('fl_preload_plan', 'confirmed_by'")
|
||||
.contains("phase3_split_add_column('fl_preload_plan', 'confirmed_by_name'")
|
||||
.contains("phase3_split_add_column('fl_preload_plan', 'confirmed_at'");
|
||||
assertThat(unifiedSql)
|
||||
.contains("CREATE PROCEDURE migrate_preload_confirmation_columns()")
|
||||
.contains("ADD COLUMN confirmed_by BIGINT UNSIGNED NULL")
|
||||
.contains("ADD COLUMN confirmed_by_name VARCHAR(64) NULL")
|
||||
.contains("ADD COLUMN confirmed_at DATETIME(3) NULL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void protectsLoadedAndRemainderSettlement() throws Exception {
|
||||
String sql = new ClassPathResource("db/phase3-inbound-parent-child-schema.sql")
|
||||
|
||||
+8
@@ -37,6 +37,14 @@ class FeePaymentSnapshotValidatorTest {
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("账期应付不得");
|
||||
}
|
||||
|
||||
@Test
|
||||
void payableWithoutSettlementModeReturnsBusinessValidationInsteadOfNullPointer() {
|
||||
assertThatThrownBy(() -> validator.validate(9L, "PAYABLE", null,
|
||||
null, null, null, null))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("应付费用必须选择ON_ACCOUNT或DIRECT_PAID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void directPaidRequiresTimeMethodAndReferenceOrVoucher() {
|
||||
LocalDateTime paidAt = LocalDateTime.of(2026, 8, 20, 10, 0);
|
||||
|
||||
@@ -740,12 +740,16 @@ export class AcceptanceRun {
|
||||
await this.restoreAvailableLocationsAfterA0(warehouseId, disabledLocations)
|
||||
}
|
||||
const fixture = this.createdResources.inboundMeasurement || this.createdResources.orders
|
||||
const fixtureOrder = this.createdResources.orders || {}
|
||||
const loadingFixture = fixture ? {
|
||||
marker: originalMarker,
|
||||
sourceMarker: fixtureMarker,
|
||||
tenantId: fixture.tenantId,
|
||||
orderId: fixture.orderId || this.createdResources.orders?.orderId,
|
||||
inboundId: fixture.inboundId,
|
||||
customerRemark: fixtureOrder.customerRemark,
|
||||
internalRemark: fixtureOrder.internalRemark,
|
||||
cargoRemark: fixtureOrder.cargoRemark,
|
||||
} : { marker: originalMarker, sourceMarker: fixtureMarker, failed: true }
|
||||
const incrementalMarker = `${originalMarker}-LOAD-INC`
|
||||
this.marker = incrementalMarker
|
||||
|
||||
@@ -13,7 +13,7 @@ export const REQUIRED_PERMISSIONS = Object.freeze([
|
||||
])
|
||||
|
||||
export const EXPORT_SPECS = Object.freeze({
|
||||
FINANCE_CUSTOMER_ACCOUNT_LIST: ['客户编码', '客户', '联系人', '联系电话', '待收(CNY)', '待开账(CNY)', '本期已收(CNY)', '待付(CNY)', '客户状态', '最近变动'],
|
||||
FINANCE_CUSTOMER_ACCOUNT_LIST: ['客户编码', '客户', '联系人', '联系电话', '待收(CNY)', '待开账(CNY)', '本期已收(CNY)', '总成本(CNY)', '已付成本(CNY)', '待付(CNY)', '客户状态', '最近变动'],
|
||||
FINANCE_CUSTOMER_ACCOUNT_LEDGER: ['流水号', '发生时间', '业务类型', '关联单据', '应收变动(CNY)', '应付变动(CNY)', '变动后未收(CNY)', '操作人', '备注'],
|
||||
FINANCE_FEE_SOURCE_LIST: ['客户', '收付方向', '来源类型', '来源单据ID', '来源版本', '入仓号', '柜次号', '业务发生日', '费用项目', '计费量', '单价(CNY)', '金额(CNY)', '审核状态', '来源状态', '已生成对账单'],
|
||||
FINANCE_RECONCILIATION_STATEMENT_LIST: ['对账单号', '客户', '结算起始日', '结算结束日', '账期(天)', '货币', '总应收金额(CNY)', '已收金额(CNY)', '未收金额(CNY)', '是否结清', '审核状态', '备注', '创建人', '创建时间', '更新人', '更新时间'],
|
||||
|
||||
@@ -17,6 +17,8 @@ test('finance fixture module is independently exported and marker isolated', ()
|
||||
|
||||
test('fixture covers weight, volume, manual order, and container allocation contracts', () => {
|
||||
for (const basis of ['WEIGHT_KG', 'VOLUME_CBM', 'MANUAL_ORDER_FEE', 'CONTAINER_SHARED']) assert.match(source, new RegExp(basis))
|
||||
assert.match(source, /direction === 'PAYABLE' \? \{ settlementMode: 'ON_ACCOUNT' \}/)
|
||||
assert.match(source, /supplierId: id\(supplier\.id\), settlementMode: 'ON_ACCOUNT'/)
|
||||
assert.match(source, /request\(context, 'POST', '\/api\/platform\/finance\/v1\/cost-entries'/)
|
||||
assert.match(source, /no-marker-owned-container-snapshot/)
|
||||
})
|
||||
|
||||
@@ -150,6 +150,7 @@ async function createManualFee(context, resource, feeItem, direction, amount, ba
|
||||
const listPath = `${basePath}?accountId=${encodeURIComponent(resource.tenantId)}`
|
||||
const body = { feeItemId: String(feeItem.id), direction, businessDate,
|
||||
unitPrice: amount, currency: 'CNY', amount,
|
||||
...(direction === 'PAYABLE' ? { settlementMode: 'ON_ACCOUNT' } : {}),
|
||||
supplierId: supplier?.id ? String(supplier.id) : null,
|
||||
remark: `${context.marker}-${basis}` }
|
||||
const response = await request(context, 'POST', listPath, body)
|
||||
@@ -443,7 +444,8 @@ export async function run(context) {
|
||||
const basePath = `/api/platform/v1/loading-plans/${encodeURIComponent(container.loadingPlanId)}/fee-details`
|
||||
const path = `${basePath}?accountId=${encodeURIComponent(container.tenantId)}`
|
||||
const body = { feeItemId: String(feeItems.PAYABLE.id), direction: 'PAYABLE', businessDate: dateOffset(-2),
|
||||
amount: 300, currency: 'CNY', supplierId: id(supplier.id), remark: `${context.marker}-CONTAINER_SHARED` }
|
||||
amount: 300, currency: 'CNY', supplierId: id(supplier.id), settlementMode: 'ON_ACCOUNT',
|
||||
remark: `${context.marker}-CONTAINER_SHARED` }
|
||||
const response = await request(context, 'POST', path, body)
|
||||
if (response?.http === 404) {
|
||||
blocker(context, '按柜费用来源接口已部署并可路由',
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function run(context) {
|
||||
&& published?.publicationStatus === 'PUBLISHED' && published?.reviewStatus === 'APPROVED',
|
||||
`status=${sent?.feeStatus || 'missing'}, process=${sent?.nextProcessStatus || 'missing'}, publication=${published?.publicationStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
const feeItemsResponse = await context.request('GET', '/api/platform/v1/fee-items?page=1&size=500&status=ACTIVE')
|
||||
const feeItemsResponse = await context.request('GET', '/api/platform/v1/fee-items?page=1&size=200&status=ACTIVE')
|
||||
const feeItemsPage = context.apiSuccess(MODULE, '查询可用于其他应收的费用项目', feeItemsResponse, '', OPTIONS)
|
||||
const feeItems = Array.isArray(feeItemsPage?.records) ? feeItemsPage.records
|
||||
: Array.isArray(feeItemsPage) ? feeItemsPage : []
|
||||
|
||||
@@ -812,7 +812,7 @@ export async function run(context) {
|
||||
|
||||
context.createdResources.phase3ContainerLoading = {
|
||||
marker: context.marker, tenantId, inboundId, warehouseId, containerBookingId: containerId,
|
||||
loadingPlanId: planId, pdaTaskId: taskId, cabinetNo: stableIdentity.cabinetNo,
|
||||
loadingPlanId: planId, oceanBookingId: bookingId, pdaTaskId: taskId, cabinetNo: stableIdentity.cabinetNo,
|
||||
containerNo: replacementContainerNo, pdaIdentityStable: true,
|
||||
finalStatus: 'COMPLETED',
|
||||
}
|
||||
|
||||
@@ -90,22 +90,26 @@ export function selectCustomsFlow(createdResources) {
|
||||
async function ensureReleasedOceanBooking(context, container) {
|
||||
const containerBookingId = id(container?.containerBookingId)
|
||||
const loadingPlanId = id(container?.loadingPlanId)
|
||||
let ocean = context.createdResources?.oceanBooking
|
||||
if (ocean?.finalStatus !== 'RELEASED'
|
||||
|| id(ocean?.containerBookingId) !== containerBookingId
|
||||
|| id(ocean?.loadingPlanId) !== loadingPlanId) {
|
||||
const oceanBookings = await import(new URL('./ocean-bookings.mjs', import.meta.url))
|
||||
await oceanBookings.run(context)
|
||||
ocean = context.createdResources?.oceanBooking
|
||||
}
|
||||
if (ocean?.finalStatus !== 'RELEASED'
|
||||
|| id(ocean?.containerBookingId) !== containerBookingId
|
||||
|| id(ocean?.loadingPlanId) !== loadingPlanId) {
|
||||
const oceanBookingId = id(container?.oceanBookingId)
|
||||
if (!oceanBookingId) {
|
||||
context.addStep(MODULE, '报关前订舱已放舱并绑定实际装柜柜次', null, false,
|
||||
`oceanStatus=${ocean?.finalStatus || 'missing'}, oceanContainer=${id(ocean?.containerBookingId) || 'missing'}, containerBookingId=${containerBookingId || 'missing'}`, OPTIONS)
|
||||
`oceanBookingId=missing, containerBookingId=${containerBookingId || 'missing'}`, OPTIONS)
|
||||
return false
|
||||
}
|
||||
|
||||
const response = await context.request('GET', `/api/platform/v1/ocean-bookings/${encodeURIComponent(oceanBookingId)}`)
|
||||
const detail = context.apiSuccess(MODULE, '报关前回读装柜链正式订舱', response, '', OPTIONS)
|
||||
const allocated = detail?.containers?.some?.((item) => id(item?.containerBookingId) === containerBookingId)
|
||||
if (detail?.oceanBooking?.status !== 'RELEASED' || !allocated) {
|
||||
context.addStep(MODULE, '报关前订舱已放舱并绑定实际装柜柜次', response, false,
|
||||
`oceanStatus=${detail?.oceanBooking?.status || 'missing'}, oceanBookingId=${oceanBookingId}, containerBookingId=${containerBookingId || 'missing'}, allocated=${allocated === true}`, OPTIONS)
|
||||
return false
|
||||
}
|
||||
context.createdResources.oceanBooking = {
|
||||
id: oceanBookingId, marker: context.marker, finalStatus: 'RELEASED',
|
||||
containerBookingId, loadingPlanId,
|
||||
}
|
||||
context.addStep(MODULE, '报关前订舱已放舱并绑定实际装柜柜次', response, true,
|
||||
`oceanBookingId=${oceanBookingId}, containerBookingId=${containerBookingId}`, OPTIONS)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,12 @@ test('HTTP chain refreshes after stale inventory and keeps one stable PDA identi
|
||||
'已拣项减量低于 minPackageCount 返回 A0409',
|
||||
'已拣项删除返回 A0409',
|
||||
'已拣项删除和减量失败后 plan/task/items 零写入',
|
||||
'订舱拖车报关未齐备时 LOADED 硬门禁拒绝',
|
||||
'订舱或五类证据等保留门禁未齐备时 LOADED 拒绝',
|
||||
])
|
||||
assert.match(loading, /String\(staleIncrementBatch\.body\?\.message \|\| ''\)\.includes\('库存已变化,请刷新后重试'\)/)
|
||||
assert.match(loading, /id\(incrementedTask\?\.loadingPlanId\) === loadingPlanId/)
|
||||
assert.match(loading, /id\(incrementedTask\?\.containerBookingId\) === containerBookingId/)
|
||||
assert.match(loading, /const taskDetailPath = `\$\{pdaBase\}\/tasks\/\$\{canonicalTaskId\}\?warehouseId=/)
|
||||
assert.match(loading, /loadingPlanStatus: 'LOADING', finalStatus: 'AWAITING_REVIEW'/)
|
||||
assert.doesNotMatch(loading, /\/loading\/complete\?tenantId=/)
|
||||
})
|
||||
|
||||
@@ -187,7 +187,7 @@ export async function run(context) {
|
||||
`loadingPlanId=${loadingPlanId}, version=${assignedLoadingPlan?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const assignedItem = assignedLoadingPlan?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
const orderRemarks = context.createdResources?.orders || {}
|
||||
const orderRemarks = source || {}
|
||||
context.assert(MODULE, '装柜明细客户备注与最新内部备注来源精确且不冒用货物备注', assignmentResponse,
|
||||
assignedItem?.customerRemark === orderRemarks.customerRemark
|
||||
&& assignedItem?.internalRemark === orderRemarks.internalRemark
|
||||
@@ -462,6 +462,7 @@ export async function run(context) {
|
||||
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const canonicalTask = context.apiSuccess(MODULE, '读取柜次级 PDA 唯一事实任务', canonicalTaskResponse, '', OPTIONS)
|
||||
const canonicalTaskId = id(canonicalTask?.id)
|
||||
const taskDetailPath = `${pdaBase}/tasks/${canonicalTaskId}?warehouseId=${encodeURIComponent(warehouseId)}`
|
||||
const canonicalItem = canonicalTask?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
context.assert(MODULE, '柜次 PDA 任务与桥接入库明细一致', canonicalTaskResponse,
|
||||
Boolean(canonicalTaskId) && canonicalTaskId === confirmedTaskId && id(canonicalTask?.loadingPlanId) === loadingPlanId
|
||||
@@ -985,7 +986,7 @@ export async function run(context) {
|
||||
if (dynamicCandidate?.inboundDetailId) {
|
||||
const planBeforeIncrementResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const planBeforeIncrement = context.apiSuccess(MODULE, '读取 LOADING 动态增量前计划', planBeforeIncrementResponse, '', OPTIONS)
|
||||
const taskBeforeIncrementResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const taskBeforeIncrementResponse = await context.request('GET', taskDetailPath)
|
||||
const taskBeforeIncrement = context.apiSuccess(MODULE, '读取 LOADING 动态增量前任务', taskBeforeIncrementResponse, '', OPTIONS)
|
||||
const evidenceBeforeIncrement = JSON.stringify(evidenceListData?.evidences || [])
|
||||
const incrementResponse = await request(context, 'POST',
|
||||
@@ -997,7 +998,7 @@ export async function run(context) {
|
||||
const incrementedPlan = context.apiSuccess(MODULE, 'LOADING 阶段向同一 PDA task 增加入库单', incrementResponse, '', OPTIONS)
|
||||
const incrementalPlanItem = incrementedPlan?.items?.find?.((item) => id(item?.inboundId) === incrementalInboundId)
|
||||
const preservedRemarkItem = incrementedPlan?.items?.find?.((item) => id(item?.id) === id(assignedItem.id))
|
||||
const incrementedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const incrementedTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const incrementedTask = context.apiSuccess(MODULE, '增量后回查稳定 PDA 四元身份', incrementedTaskResponse, '', OPTIONS)
|
||||
const incrementalTaskItem = incrementedTask?.items?.find?.((item) =>
|
||||
id(item?.loadingPlanItemId) === id(incrementalPlanItem?.id))
|
||||
@@ -1032,7 +1033,7 @@ export async function run(context) {
|
||||
staleIncrementBatch.http === 200 && codeOf(staleIncrementBatch) === 'A0409'
|
||||
&& String(staleIncrementBatch.body?.message || '').includes('库存已变化,请刷新后重试'),
|
||||
responseEvidence(staleIncrementBatch), OPTIONS)
|
||||
const taskAfterStaleResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const taskAfterStaleResponse = await context.request('GET', taskDetailPath)
|
||||
const taskAfterStale = context.apiSuccess(MODULE, '旧 token 冲突后回查增量任务', taskAfterStaleResponse, '', OPTIONS)
|
||||
const planAfterStaleResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const planAfterStale = context.apiSuccess(MODULE, '旧 token 冲突后回查增量计划', planAfterStaleResponse, '', OPTIONS)
|
||||
@@ -1085,7 +1086,7 @@ export async function run(context) {
|
||||
effectiveDynamicPlan = adjustedPlan
|
||||
reassignedPlanItem = adjustedItem
|
||||
}
|
||||
const refreshedDynamicTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const refreshedDynamicTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const refreshedDynamicTask = context.apiSuccess(MODULE, '重新增量后刷新 PDA task detail', refreshedDynamicTaskResponse, '', OPTIONS)
|
||||
const refreshedDynamicTaskItem = refreshedDynamicTask?.items?.find?.((item) =>
|
||||
id(item?.loadingPlanItemId) === id(reassignedPlanItem?.id))
|
||||
@@ -1108,7 +1109,7 @@ export async function run(context) {
|
||||
const pickedDynamicPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const pickedDynamicPlan = context.apiSuccess(MODULE, '动态新增项拣完后回查 minPackageCount', pickedDynamicPlanResponse, '', OPTIONS)
|
||||
const pickedDynamicPlanItem = pickedDynamicPlan?.items?.find?.((item) => id(item?.id) === id(reassignedPlanItem?.id))
|
||||
const beforePickedMutationTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const beforePickedMutationTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const beforePickedMutationTask = context.apiSuccess(MODULE, '已拣项负向前读取任务快照', beforePickedMutationTaskResponse, '', OPTIONS)
|
||||
const reducePickedResponse = await request(context, 'PATCH',
|
||||
`${smartLoadingBase}/plans/${loadingPlanId}/items/${encodeURIComponent(id(pickedDynamicPlanItem?.id))}`, {
|
||||
@@ -1127,7 +1128,7 @@ export async function run(context) {
|
||||
deletePickedResponse.http === 200 && codeOf(deletePickedResponse) === 'A0409', responseEvidence(deletePickedResponse), OPTIONS)
|
||||
const afterPickedMutationPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const afterPickedMutationPlan = context.apiSuccess(MODULE, '已拣项负向后回查计划', afterPickedMutationPlanResponse, '', OPTIONS)
|
||||
const afterPickedMutationTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const afterPickedMutationTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const afterPickedMutationTask = context.apiSuccess(MODULE, '已拣项负向后回查任务', afterPickedMutationTaskResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '已拣项删除和减量失败后 plan/task/items 零写入', afterPickedMutationPlanResponse,
|
||||
Number(afterPickedMutationPlan?.version) === Number(pickedDynamicPlan?.version)
|
||||
@@ -1148,7 +1149,7 @@ export async function run(context) {
|
||||
beforeHardGate?.summary?.processStatus === 'LOADING' && Number.isInteger(beforeHardGateVersion),
|
||||
`status=${beforeHardGate?.summary?.processStatus || 'missing'}, version=${beforeHardGate?.summary?.version ?? 'missing'}`, OPTIONS)
|
||||
if (!Number.isInteger(beforeHardGateVersion)) return
|
||||
const beforeBlockedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const beforeBlockedTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const beforeBlockedTask = context.apiSuccess(MODULE, '读取下游门禁拒绝前 PDA 任务快照', beforeBlockedTaskResponse, '', OPTIONS)
|
||||
const beforeBlockedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const beforeBlockedPlan = context.apiSuccess(MODULE, '读取下游门禁拒绝前计划快照', beforeBlockedPlanResponse, '', OPTIONS)
|
||||
@@ -1161,7 +1162,7 @@ export async function run(context) {
|
||||
responseEvidence(blockedBeforeDownstream), OPTIONS)
|
||||
const blockedDetailResponse = await context.request('GET', detailPath)
|
||||
const blockedDetail = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的入库状态', blockedDetailResponse, '', OPTIONS)
|
||||
const blockedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
|
||||
const blockedTaskResponse = await context.request('GET', taskDetailPath)
|
||||
const blockedTask = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的 PDA 任务', blockedTaskResponse, '', OPTIONS)
|
||||
const blockedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const blockedPlan = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的计划', blockedPlanResponse, '', OPTIONS)
|
||||
|
||||
@@ -467,14 +467,17 @@ test('phase3 loading fixture forces A0 through reversible public location status
|
||||
assert.match(orders, /receivingWarehouseId: id\(master\.receiving\.id\)/)
|
||||
})
|
||||
|
||||
test('customs provisions released booking without pre-completing truck', () => {
|
||||
test('customs reuses the loading chain released booking without pre-completing truck', () => {
|
||||
const value = source('./phase3-customs.mjs')
|
||||
assertOrder(value, [
|
||||
'selectCustomsFlow(context.createdResources)',
|
||||
'if (!await ensureReleasedOceanBooking(context, container)) return',
|
||||
'const slicesPath =',
|
||||
])
|
||||
assert.match(value, /oceanBookings\.run\(context\)/)
|
||||
assert.match(value, /const oceanBookingId = id\(container\?\.oceanBookingId\)/)
|
||||
assert.match(value, /ocean-bookings\/\$\{encodeURIComponent\(oceanBookingId\)\}/)
|
||||
assert.match(value, /detail\?\.containers\?\.some/)
|
||||
assert.doesNotMatch(value, /oceanBookings\.run\(context\)/)
|
||||
assert.doesNotMatch(value, /serviceCenter\.run\(context\)/)
|
||||
assert.doesNotMatch(value, /truck\?\.finalStatus !== 'COMPLETED'/)
|
||||
assert.match(value, /request\('POST', '\/api\/platform\/v1\/suppliers\?tenantId=0'/)
|
||||
|
||||
Reference in New Issue
Block a user