fix(fleet): preserve unknown commercial facts for pooled freight
This commit is contained in:
@@ -55,6 +55,18 @@ test('customer identity joins use the same explicit collation as existing fulfil
|
||||
assert.doesNotMatch(sql.replace(/^--.*$/gm, ''), /\b(?:DROP|TRUNCATE|DELETE|UPDATE|REPLACE)\b/i)
|
||||
})
|
||||
|
||||
test('commercial schemas do not invent container or palletizing facts for pool orders', () => {
|
||||
for (const name of ['ll_local_looseboard_order', 'll_us_truck_order']) {
|
||||
const definition = table(overseas, 'baiyue_lastleg', name)
|
||||
assert.match(definition, /container_type\s+varchar\(32\)(?:\s+COLLATE\s+\w+)?\s+(?:NULL\s+)?DEFAULT NULL/i)
|
||||
assert.doesNotMatch(definition, /container_type[^\n]*DEFAULT '40HQ'/i)
|
||||
}
|
||||
assert.match(table(overseas, 'baiyue_lastleg', 'll_local_looseboard_order'), /pallet_required\s+varchar\(8\)(?:\s+COLLATE\s+\w+)?\s+(?:NULL\s+)?DEFAULT NULL/i)
|
||||
const sql = read('deploy/sql/migrations/overseas/baiyue_lastleg/V12__preserve_unknown_pool_order_fields.sql')
|
||||
assert.equal((sql.match(/MODIFY COLUMN/g) || []).length, 3)
|
||||
assert.doesNotMatch(sql.replace(/^--.*$/gm, ''), /\b(?:DROP|TRUNCATE|DELETE|UPDATE|REPLACE)\b/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)
|
||||
|
||||
@@ -34,6 +34,7 @@ export const POOL_COVERAGE = Object.freeze([
|
||||
'receipt-overcapacity-rejected-with-source-unchanged', 'open-offer-bind-replan-requires-explicit-resume',
|
||||
'bound-cargo-reservation-removed-without-double-count', 'other-sales-write-and-sensitive-trip-denied',
|
||||
'other-customer-commercial-read-denied-with-source-unchanged',
|
||||
'pool-unspecified-container-and-palletizing-remain-unknown',
|
||||
])
|
||||
export const POOL_REMAINING = Object.freeze([
|
||||
'out-of-band-assigned-vehicle-drift-not-constructible-through-supported-api',
|
||||
@@ -62,6 +63,14 @@ export function assertSingleCommercial(before, after) {
|
||||
&& before.customerId === after.customerId, 'COMMERCIAL_REPLAY_IDENTITY_CHANGED')
|
||||
}
|
||||
|
||||
export function assertPoolUnknownCommercialFacts(businessType, summary) {
|
||||
requireThat(['LOCAL_LOOSEBOARD', 'US_TRUCK'].includes(businessType) && summary && typeof summary === 'object',
|
||||
'POOL_COMMERCIAL_SUMMARY_REQUIRED')
|
||||
requireThat(summary.containerType == null, 'POOL_CONTAINER_TYPE_MUST_REMAIN_UNKNOWN')
|
||||
if (businessType === 'LOCAL_LOOSEBOARD') requireThat(summary.palletRequired == null, 'POOL_PALLETIZING_SERVICE_MUST_REMAIN_UNKNOWN')
|
||||
return true
|
||||
}
|
||||
|
||||
export function poolInquiryWrite(inquiry, idempotencyKey) {
|
||||
return { tripRecordVersion: inquiry.tripRecordVersion, offerRecordVersion: inquiry.offerRecordVersion,
|
||||
recordVersion: inquiry.recordVersion, customerId: inquiry.customerId, businessType: inquiry.businessType,
|
||||
@@ -397,6 +406,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
const poolMembers = []
|
||||
for (const [index, order] of proof.orders.entries()) {
|
||||
const source = await request('GET', businessPath(order))
|
||||
check('New pool commercial order retains unknown container and palletizing facts', assertPoolUnknownCommercialFacts(order.businessType, source.summary))
|
||||
check('Pool commercial order has source arrangement and waits for real inbound', source.summary.status === 'WAITING_INBOUND'
|
||||
&& source.summary.fleetArrangement?.bookingNo === order.bookingNo && !source.summary.fleetArrangement.incomingNo)
|
||||
const oldStatusPath = businessPath(order).replace('?', '/status?')
|
||||
@@ -459,6 +469,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
proof.covered.push('shared-real-departure-and-signature')
|
||||
for (const [index, order] of proof.orders.entries()) {
|
||||
const commercial = await request('GET', businessPath(order))
|
||||
check('Completed commercial order retains unknown container and palletizing facts', assertPoolUnknownCommercialFacts(order.businessType, commercial.summary))
|
||||
check('Each business type completes from actual signature', commercial.summary.status === 'COMPLETED'
|
||||
&& commercial.summary.fleetArrangement?.fulfillmentStage === 'COMPLETED' && Number(commercial.summary.fleetArrangement.signedPackages) === 1)
|
||||
const member = poolMembers[index], actual = await request('GET', `${API}/delivery-orders/${member.deliveryNo}?customerId=${order.customerId}`)
|
||||
@@ -467,9 +478,10 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
const merchant = merchantSessions.find(session => session.customerId === order.customerId)
|
||||
const merchantPath = `/api/merchant/v1/last-leg/${order.businessType === 'US_TRUCK' ? 'us-truck-orders' : 'local-looseboard-orders'}/${encode(order.businessOrderNo)}`
|
||||
const own = await request('GET', merchantPath, undefined, merchant.accessToken)
|
||||
check('Client completed order retains unknown container and palletizing facts', assertPoolUnknownCommercialFacts(order.businessType, own.summary))
|
||||
check('Client sees only own complete commercial arrangement', own.summary.orderNo === order.businessOrderNo && own.summary.fleetArrangement?.businessOrderNo === order.businessOrderNo)
|
||||
}
|
||||
proof.covered.push('business-completion-and-fee-uniqueness')
|
||||
proof.covered.push('business-completion-and-fee-uniqueness', 'pool-unspecified-container-and-palletizing-remain-unknown')
|
||||
proof.poolMembers = poolMembers; proof.status = 'VERIFIED'
|
||||
return proof
|
||||
} catch (error) { proof.status = 'FAILED'; throw error }
|
||||
|
||||
@@ -2,7 +2,8 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { parsePoolArguments, poolPlan, runPoolAcceptance, assertPoolRemaining, assertSingleCommercial,
|
||||
shrinkingVehicleRequest, expiryWaitMs, poolLocalDateTime, poolPickScope, pickPoolDispatch, POOL_COVERAGE, POOL_REMAINING,
|
||||
poolInquiryWrite, poolReadIsDenied, assertPoolBoundCapacity, verifyPoolConfirmedCancellation, verifyPoolBindingRevalidation } from './fleet-capacity-pool-test-env.mjs'
|
||||
poolInquiryWrite, poolReadIsDenied, assertPoolBoundCapacity, verifyPoolConfirmedCancellation, verifyPoolBindingRevalidation,
|
||||
assertPoolUnknownCommercialFacts } from './fleet-capacity-pool-test-env.mjs'
|
||||
|
||||
test('default and explicit plans make no network requests or invoke the mutating runner', async () => {
|
||||
const original = globalThis.fetch
|
||||
@@ -167,6 +168,22 @@ test('foreign read rejection requires actual access/not-found code and no leaked
|
||||
{ http: 200, body: { code: 'A0409' } }, { http: 403, body: { data: { orderNo: 'PRIVATE' } } }]) assert.equal(poolReadIsDenied(response), false)
|
||||
})
|
||||
|
||||
test('pool commercial facts accept nullish Jackson fields but reject invented container or palletizing answers', () => {
|
||||
for (const type of ['LOCAL_LOOSEBOARD', 'US_TRUCK']) {
|
||||
for (const summary of [{}, { containerType: null, palletRequired: null }, { containerType: undefined, palletRequired: undefined }]) {
|
||||
assert.doesNotThrow(() => assertPoolUnknownCommercialFacts(type, summary))
|
||||
}
|
||||
for (const containerType of ['40HQ', '40GP', '45HQ', '', 'UNKNOWN', false, 0]) {
|
||||
assert.throws(() => assertPoolUnknownCommercialFacts(type, { containerType }), /CONTAINER_TYPE_MUST_REMAIN_UNKNOWN/)
|
||||
}
|
||||
assert.throws(() => assertPoolUnknownCommercialFacts(type, null), /SUMMARY_REQUIRED/)
|
||||
}
|
||||
for (const palletRequired of ['YES', 'NO', '', 'UNKNOWN', false, 0]) {
|
||||
assert.throws(() => assertPoolUnknownCommercialFacts('LOCAL_LOOSEBOARD', { palletRequired }), /PALLETIZING_SERVICE_MUST_REMAIN_UNKNOWN/)
|
||||
}
|
||||
assert.throws(() => assertPoolUnknownCommercialFacts('OTHER', {}), /SUMMARY_REQUIRED/)
|
||||
})
|
||||
|
||||
test('owner rejection sends a valid current edit rather than malformed input', () => {
|
||||
const source = { tripRecordVersion: 5, offerRecordVersion: 7, recordVersion: 9, customerId: '42', businessType: 'US_TRUCK',
|
||||
deliveryMode: 'SELF_DELIVERY', estimatedArrivalAt: '2030-01-01T00:00:00Z', expectedPalletCount: 1,
|
||||
|
||||
Reference in New Issue
Block a user