优化
This commit is contained in:
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
import { inspectOceanBookingIdentity, truckAppointmentPayload } from './truck-appointments.mjs'
|
||||
import { inspectOceanBookingIdentity, selectIndependentTruckCustomer, truckAppointmentPayload } from './truck-appointments.mjs'
|
||||
|
||||
test('truck appointment accepts only the current marker ocean booking identity', () => {
|
||||
const current = inspectOceanBookingIdentity('RUN-2', {
|
||||
@@ -45,9 +45,11 @@ test('truck appointment rejects mismatched booking detail instead of mixing iden
|
||||
assert.match(conflictingCustomer.reason, /customerId/)
|
||||
})
|
||||
|
||||
test('create edit and stale replay payloads keep the resolved booking identity stable', () => {
|
||||
const context = { marker: 'RUN-2' }
|
||||
const identity = { oceanBookingId: '91', customerId: '21', warehouseId: '41', customerName: '本轮客户' }
|
||||
test('create edit and stale replay payloads keep the independent customer and empty links stable', () => {
|
||||
const context = { marker: 'RUN-2', createdResources: {
|
||||
truckSupplier: { supplierId: '501', supplierName: '正式拖车商' },
|
||||
} }
|
||||
const identity = { customerId: '21', customerName: '本轮客户' }
|
||||
const payloads = [
|
||||
truckAppointmentPayload(context, '2', identity),
|
||||
truckAppointmentPayload(context, '2', identity, 3),
|
||||
@@ -55,18 +57,20 @@ test('create edit and stale replay payloads keep the resolved booking identity s
|
||||
]
|
||||
for (const payload of payloads) {
|
||||
assert.deepEqual({
|
||||
oceanBookingId: payload.oceanBookingId, customerId: payload.customerId,
|
||||
warehouseId: payload.warehouseId, customerName: payload.customerName,
|
||||
}, identity)
|
||||
oceanBookingId: payload.oceanBookingId, containerBookingId: payload.containerBookingId,
|
||||
loadingPlanId: payload.loadingPlanId, customerId: payload.customerId,
|
||||
customerName: payload.customerName, carrierId: payload.carrierId,
|
||||
}, { oceanBookingId: null, containerBookingId: null, loadingPlanId: null,
|
||||
customerId: '21', customerName: '本轮客户', carrierId: '501' })
|
||||
}
|
||||
assert.deepEqual(payloads.map((payload) => payload.expectedVersion), [0, 3, 0])
|
||||
})
|
||||
|
||||
test('truck payload sends carrier fields only as a verified ID/name pair', () => {
|
||||
const identity = { oceanBookingId: '91', customerId: '21', warehouseId: '41', customerName: '本轮客户' }
|
||||
test('truck payload requires a verified carrier ID/name pair', () => {
|
||||
const identity = { customerId: '21', customerName: '本轮客户' }
|
||||
const missing = truckAppointmentPayload({ marker: 'RUN-2', createdResources: {} }, '2', identity)
|
||||
assert.equal(Object.hasOwn(missing, 'carrierId'), false)
|
||||
assert.equal(Object.hasOwn(missing, 'carrierName'), false)
|
||||
assert.equal(missing.carrierId, null)
|
||||
assert.equal(missing.carrierName, null)
|
||||
const valid = truckAppointmentPayload({ marker: 'RUN-2', createdResources: {
|
||||
truckSupplier: { supplierId: '501', supplierName: '正式拖车商' },
|
||||
} }, '2', identity)
|
||||
@@ -74,13 +78,80 @@ test('truck payload sends carrier fields only as a verified ID/name pair', () =>
|
||||
const incomplete = truckAppointmentPayload({ marker: 'RUN-2', createdResources: {
|
||||
truckSupplier: { supplierId: '501', supplierName: null },
|
||||
} }, '2', identity)
|
||||
assert.equal(Object.hasOwn(incomplete, 'carrierId'), false)
|
||||
assert.equal(Object.hasOwn(incomplete, 'carrierName'), false)
|
||||
assert.equal(incomplete.carrierId, '501')
|
||||
assert.equal(incomplete.carrierName, null)
|
||||
})
|
||||
|
||||
test('truck appointment source has no historical list or hardcoded identity fallback', () => {
|
||||
test('independent customer comes only from the current marker fixture', () => {
|
||||
const current = selectIndependentTruckCustomer({ marker: 'RUN-2', createdResources: {
|
||||
orders: { marker: 'RUN-2', tenantId: '21', customerName: '本轮客户' },
|
||||
} })
|
||||
assert.deepEqual(current, { customerId: '21', customerName: '本轮客户' })
|
||||
const stale = selectIndependentTruckCustomer({ marker: 'RUN-2', createdResources: {
|
||||
orders: { marker: 'RUN-1', tenantId: '20', customerName: '历史客户' },
|
||||
} })
|
||||
assert.equal(stale, null)
|
||||
})
|
||||
|
||||
test('truck appointment source has no booking prerequisite historical list or hardcoded identity fallback', () => {
|
||||
const source = readFileSync(new URL('truck-appointments.mjs', import.meta.url), 'utf8')
|
||||
assert.doesNotMatch(source, /customerId:\s*['"]10001['"]|warehouseId:\s*['"]193000000000000001['"]/)
|
||||
assert.match(source, /\/api\/platform\/v1\/ocean-bookings/)
|
||||
assert.match(source, /未使用历史订舱或硬编码客户\/仓库/)
|
||||
assert.doesNotMatch(source, /import\(new URL\('\.\/ocean-bookings\.mjs'/)
|
||||
assert.match(source, /创建无订舱柜次计划关联的独立拖车预约/)
|
||||
assert.match(source, /缺少同 marker 的 customerId;未借用历史客户或订舱/)
|
||||
})
|
||||
|
||||
test('backend freezes independent creation association validation and PDA failure isolation', () => {
|
||||
const dto = readFileSync(new URL('../../firstleg/src/main/java/com/baiyue/firstleg/dto/TruckAppointmentDtos.java', import.meta.url), 'utf8')
|
||||
assert.match(dto, /@Positive Long oceanBookingId,@Positive Long containerBookingId,@Positive Long loadingPlanId/)
|
||||
assert.match(dto, /@NotNull @Positive Long customerId/)
|
||||
assert.match(dto, /@NotNull @Positive Long carrierId/)
|
||||
assert.match(dto, /@NotNull LocalDateTime plannedLoadingAt/)
|
||||
assert.doesNotMatch(dto, /@NotNull @Positive Long oceanBookingId|@NotNull @Positive Long containerBookingId/)
|
||||
|
||||
const openapi = readFileSync(new URL('../../firstleg/src/main/resources/openapi/truck-appointments-v1.yaml', import.meta.url), 'utf8')
|
||||
assert.match(openapi, /required: \[customerId, carrierId, plannedLoadingAt, expectedVersion\]/)
|
||||
assert.match(openapi, /Ocean booking, container booking and loading plan are optional links/)
|
||||
assert.match(openapi, /absence, duplicates or concurrent truck edits never roll back loading completion/)
|
||||
|
||||
const service = readFileSync(new URL('../../firstleg/src/main/java/com/baiyue/firstleg/service/TruckAppointmentService.java', import.meta.url), 'utf8')
|
||||
for (const token of ['customers.active(r.customerId(),r.customerName())', 'suppliers.trucking(r.carrierId(),r.carrierName())',
|
||||
'customerId与订舱客户不一致', 'customerId与柜次客户不一致', 'containerBookingId未分配至该订舱',
|
||||
'loadingPlanId与柜次当前装柜计划不一致', 'Propagation.REQUIRES_NEW', 'active.isEmpty()',
|
||||
'active.size()!=1', 'repo.completeLoadingFromPda']) assert.match(service, new RegExp(token.replace(/[().]/g, '\\$&')))
|
||||
|
||||
const loading = readFileSync(new URL('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java', import.meta.url), 'utf8')
|
||||
assert.match(loading, /completeTruckAfterCommit/)
|
||||
assert.match(loading, /afterCommit\(\) \{ completion\.run\(\); \}/)
|
||||
assert.match(loading, /catch \(RuntimeException exception\)/)
|
||||
assert.ok(loading.indexOf('loadingListService.publishCompletedEvent') < loading.indexOf('completeTruckAfterCommit'),
|
||||
'PDA task/plan/inbound completion must precede best-effort truck synchronization')
|
||||
|
||||
const truckTests = readFileSync(new URL('../../firstleg/src/test/java/com/baiyue/firstleg/service/TruckAppointmentServiceTest.java', import.meta.url), 'utf8')
|
||||
for (const scenario of ['pdaCompletionDoesNotRequireTruckAppointment',
|
||||
'pdaCompletionLocksAndCompletesTheOnlyActiveTruckWithHistory',
|
||||
'pdaCompletionRejectsMultipleActiveTruckAppointmentsWithoutMutation',
|
||||
'pdaCompletionRejectsConcurrentTruckModificationWithoutHistory']) assert.match(truckTests, new RegExp(scenario))
|
||||
const loadingTests = readFileSync(new URL('../../firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java', import.meta.url), 'utf8')
|
||||
assert.match(loadingTests, /conflictingTruckAppointmentsDoNotBlockPdaCompletion/)
|
||||
})
|
||||
|
||||
test('Admin requires customer carrier and planned time but keeps booking and container optional', () => {
|
||||
const source = readFileSync(new URL('../../../apps/admin-web/src/modules/trucking/components/TruckAppointmentForm.vue', import.meta.url), 'utf8')
|
||||
assert.match(source, /customerId: \[\{ required: true/)
|
||||
assert.match(source, /carrierId: \[\{ required: true/)
|
||||
assert.match(source, /plannedLoadingAt: \[\{ required: true/)
|
||||
assert.match(source, /label="关联柜次(可选)"/)
|
||||
assert.match(source, /label="关联订舱(可选)"/)
|
||||
assert.doesNotMatch(source, /containerBookingId: \[\{ required: true|oceanBookingId: \[\{ required: true/)
|
||||
})
|
||||
|
||||
test('default HTTP entry carries independent truck positives and association negatives after current-marker producers', () => {
|
||||
const source = readFileSync(new URL('truck-appointments.mjs', import.meta.url), 'utf8')
|
||||
for (const label of ['缺少 customerId 拒绝创建独立拖车', '缺少 ACTIVE TRUCKING carrier 拒绝创建独立拖车',
|
||||
'缺少 plannedLoadingAt 拒绝创建独立拖车', 'loadingPlanId 与关联柜次当前计划不一致时零写入拒绝',
|
||||
'warehouseId 与可选关联柜次不一致时零写入拒绝', '订舱与柜次无 allocation 关系时零写入拒绝',
|
||||
'创建无订舱柜次计划关联的独立拖车预约']) assert.match(source, new RegExp(label))
|
||||
const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
|
||||
assert.match(entry, /'orders'[\s\S]*'phase3-loading'[\s\S]*'ocean-bookings'[\s\S]*'truck-appointments'/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user