优化
This commit is contained in:
@@ -30,6 +30,12 @@ test('fee writes use verified Firstleg paths and do not fabricate historical IDs
|
||||
assert.match(source, /chargeUnit === 'CBM'/)
|
||||
})
|
||||
|
||||
test('PAYABLE fee selection uses the formal warehousePayable flag without name heuristics', () => {
|
||||
const selector = source.slice(source.indexOf('async function loadFeeItem'), source.indexOf('async function createManualFee'))
|
||||
assert.match(selector, /item\.warehousePayable === true/)
|
||||
assert.doesNotMatch(selector, /BOOKING\|FREIGHT|订舱|海运|运费/)
|
||||
})
|
||||
|
||||
test('automatic fee fixture completes the reviewed pricing workflow without recalculating', () => {
|
||||
assert.match(source, /latest\?\.feeId/)
|
||||
assert.doesNotMatch(source, /latest\?\.id/)
|
||||
|
||||
@@ -106,7 +106,7 @@ async function loadFeeItem(context, direction) {
|
||||
const list = context.apiSuccess(MODULE, `查询${direction}人工费用项目`, response, '', OPTIONS)
|
||||
return rows(list).find((item) => item?.status === 'ACTIVE' && (direction === 'RECEIVABLE'
|
||||
? item.warehouseReceivable === true
|
||||
: item.warehousePayable === true && /BOOKING|FREIGHT|OCEAN|SHIPPING|订舱|海运|运费/i.test(`${item.feeCode || item.code || ''} ${item.feeName || item.name || ''}`))) || null
|
||||
: item.warehousePayable === true)) || null
|
||||
}
|
||||
|
||||
async function createManualFee(context, resource, feeItem, direction, amount, basis, businessDate, partner = null) {
|
||||
|
||||
@@ -24,6 +24,15 @@ test('所有写入均走订单生命周期 HTTP API 且不清理保留数据', (
|
||||
assert.doesNotMatch(source, /cleanup|remove|unlink/i)
|
||||
})
|
||||
|
||||
test('状态矩阵复用订单共享的逐仓真实报价选择,不假设首仓或首税制', () => {
|
||||
assert.match(source, /findPricedOrderRouteAcrossOrigins/)
|
||||
assert.match(source, /loadChannels: \(receiving\)/)
|
||||
assert.match(source, /pricing\/estimate/)
|
||||
assert.doesNotMatch(source, /rows\(.*domestic.*\)\[0\]/)
|
||||
assert.doesNotMatch(source, /pricingTaxModes\[0\]/)
|
||||
assert.match(source, /pricingTaxMode: priced\.priced\.pricingTaxMode/)
|
||||
})
|
||||
|
||||
test('入库通过真实订单测量链保留测量态并最终进入 WAIT_SPLIT', () => {
|
||||
assert.match(source, /result\.inbounds\.WAIT_MEASURE/)
|
||||
assert.match(source, /processStatus: 'WAIT_MEASURE'/)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { findPricedOrderRouteAcrossOrigins } from './orders.mjs'
|
||||
|
||||
const MODULE = 'order-inbound-status-fixtures'
|
||||
const OPTIONS = { scenario: 'manual-order-inbound-status-matrix', deploymentBlocker: true }
|
||||
const MERCHANT_USERNAME = process.env.MERCHANT_TEST_USERNAME || 'merchant_admin'
|
||||
@@ -64,19 +66,22 @@ async function loadFixtureInputs(context, token) {
|
||||
const profile = context.apiSuccess(MODULE, '读取 IT 测试客户资料', profileResponse)
|
||||
const domesticResponse = await merchantGet(context,
|
||||
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC', token)
|
||||
const domestic = rows(context.apiSuccess(MODULE, '读取国内收货仓', domesticResponse))[0]
|
||||
const domesticPage = context.apiSuccess(MODULE, '读取国内收货仓', domesticResponse)
|
||||
const receivings = rows(domesticPage).filter((item) => item?.status === 'ACTIVE')
|
||||
const destinationResponse = await merchantGet(context,
|
||||
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA', token)
|
||||
const destination = rows(context.apiSuccess(MODULE, '读取 FBA 目的仓', destinationResponse))[0]
|
||||
if (!profile?.id || !domestic?.id || !destination?.id) throw new Error('状态样本缺少客户或仓库基础数据')
|
||||
const channelResponse = await merchantGet(context,
|
||||
`/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${query(domestic.id)}`, token)
|
||||
const channels = context.apiSuccess(MODULE, '读取可下单渠道', channelResponse)
|
||||
const channel = Array.isArray(channels)
|
||||
? channels.find((item) => Array.isArray(item.pricingTaxModes) && item.pricingTaxModes.length > 0)
|
||||
: null
|
||||
if (!channel) throw new Error('状态样本缺少带计价税制的可用渠道')
|
||||
return { tenantId: id(profile.id), customerName: profile.tenantName || 'IT 测试客户', domestic, destination, channel }
|
||||
const destinations = rows(context.apiSuccess(MODULE, '读取 FBA 目的仓', destinationResponse))
|
||||
if (!profile?.id || receivings.length === 0 || destinations.length === 0) throw new Error('状态样本缺少客户或仓库基础数据')
|
||||
const priced = await findPricedOrderRouteAcrossOrigins({
|
||||
receivings, destinations,
|
||||
loadChannels: (receiving) => merchantGet(context,
|
||||
`/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${query(receiving.id)}`, token),
|
||||
estimate: (body) => merchantRequest(context, 'POST', '/api/merchant/v1/first-leg/pricing/estimate', body, token),
|
||||
})
|
||||
if (!priced.receiving || !priced.priced) throw new Error('状态样本缺少真实可计价的仓库/渠道/目的仓组合')
|
||||
return { tenantId: id(profile.id), customerName: profile.tenantName || 'IT 测试客户',
|
||||
domestic: priced.receiving, destination: priced.priced.destination, channel: priced.priced.channel,
|
||||
pricingTaxMode: priced.priced.pricingTaxMode }
|
||||
}
|
||||
|
||||
function orderPayload(context, fixture, target) {
|
||||
@@ -94,7 +99,7 @@ function orderPayload(context, fixture, target) {
|
||||
receivingWarehouseId: fixture.domestic.id, receivingWarehouseName: fixture.domestic.warehouseName,
|
||||
channelCode: fixture.channel.channelCode, channelName: fixture.channel.channelName,
|
||||
carrierId: null, carrierName: null, taxMode: 'BUY_DECLARATION',
|
||||
pricingTaxMode: fixture.channel.pricingTaxModes[0], containerMode: 'LCL', customsInspection: false,
|
||||
pricingTaxMode: fixture.pricingTaxMode, containerMode: 'LCL', customsInspection: false,
|
||||
destinationType: 'FBA', amazonWarehouseCode: fixture.destination.warehouseCode,
|
||||
shipmentId, referenceId,
|
||||
sender: { companyName: fixture.customerName, contactName: 'IT 测试联系人', phone: '13800138000',
|
||||
|
||||
@@ -10,8 +10,9 @@ test('each remainder uses a simulation linked to a confirmed then released booki
|
||||
assert.match(source, /一次登记直接返回 CONFIRMED/)
|
||||
assert.doesNotMatch(source, /ocean-bookings\/\$\{oceanBookingId\}\/(?:submit|confirm)/)
|
||||
const createPayload = source.slice(source.indexOf('function bookingPayload'), source.indexOf('async function loadOneFragment'))
|
||||
for (const field of ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt',
|
||||
'estimatedArrivalAt']) assert.match(createPayload, new RegExp(`${field}:`))
|
||||
assert.match(createPayload, /oceanBookingDraft\(\{ marker \}/)
|
||||
assert.match(createPayload, /customerId: source\.tenantId/)
|
||||
assert.match(createPayload, /channelCode: source\.channelCode/)
|
||||
assert.match(source, /ocean-booking-link/)
|
||||
assert.match(source, /bookingDetail\.oceanBooking\.status === 'CONFIRMED'/)
|
||||
assert.match(source, /`\$\{bookingPath\}\/release`/)
|
||||
@@ -69,5 +70,11 @@ test('booking freezes the source customer channel and warehouse scope', () => {
|
||||
assert.match(source, /customerId: source\.tenantId/)
|
||||
assert.match(source, /channelCode: source\.channelCode/)
|
||||
assert.match(source, /warehouseId: source\.warehouseId/)
|
||||
assert.match(source, /containerRequirements: \[\{ containerType: '40HQ'/)
|
||||
assert.match(source, /containerType === '40HQ'/)
|
||||
})
|
||||
|
||||
test('booking fixture never invents supplier names without formal IDs', () => {
|
||||
assert.match(source, /oceanBookingDraft/)
|
||||
assert.doesNotMatch(source, /SPLIT-SHIPPING|SPLIT-AGENT/)
|
||||
assert.doesNotMatch(source, /shippingCompanyName: `|bookingAgentName: `/)
|
||||
})
|
||||
|
||||
@@ -7,6 +7,8 @@ const CONTAINERS = '/api/platform/v1/containers'
|
||||
const SMART = '/api/platform/v1/first-leg/smart-loading'
|
||||
const PDA = '/api/platform/v1/first-leg/pda-loading'
|
||||
|
||||
import { oceanBookingDraft } from './ocean-booking-fixture.mjs'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
function isPositiveId(value) { return /^[1-9][0-9]*$/.test(id(value)) }
|
||||
@@ -118,23 +120,11 @@ function snapshotMatches(tree, original, loaded, remaining, activeSuffix) {
|
||||
|
||||
function bookingPayload(marker, sequence, source) {
|
||||
const suffix = `${marker}-${sequence}`.replace(/[^A-Za-z0-9]/g, '').slice(-18)
|
||||
return {
|
||||
customerId: source.tenantId, customerName: source.customerName,
|
||||
channelCode: source.channelCode,
|
||||
return oceanBookingDraft({ marker }, suffix, {
|
||||
customerId: source.tenantId, customerName: source.customerName, channelCode: source.channelCode,
|
||||
warehouseId: source.warehouseId, warehouseName: source.warehouseName,
|
||||
billOfLadingNo: `BL-${suffix}`.slice(0, 128),
|
||||
shippingCompanyId: null, shippingCompanyName: `SPLIT-SHIPPING-${suffix}`.slice(0, 128),
|
||||
bookingAgentId: null, bookingAgentName: `SPLIT-AGENT-${suffix}`.slice(0, 128),
|
||||
vesselName: `SPLIT-VESSEL-${suffix}`.slice(0, 128), voyageNo: `SV-${sequence}`.slice(0, 64),
|
||||
departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX', destinationPortName: '洛杉矶',
|
||||
placeOfDelivery: 'LAX', serviceTerm: 'CY-CY', freightTerm: 'NINGBO',
|
||||
estimatedSailingAt: '2026-09-16T08:00:00', estimatedArrivalAt: '2026-10-16T08:00:00', remark: marker,
|
||||
parties: [{ partyType: 'SHIPPER', partyName: `SPLIT-SHIPPER-${suffix}` },
|
||||
{ partyType: 'CONSIGNEE', partyName: `SPLIT-CONSIGNEE-${suffix}` },
|
||||
{ partyType: 'NOTIFY_PARTY', partyName: `SPLIT-NOTIFY-${suffix}` }],
|
||||
containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', capacityCbm: 100,
|
||||
maxWeightKg: 100000, quantity: 1 }],
|
||||
}
|
||||
}, { vesselName: `SPLIT-VESSEL-${suffix}`.slice(0, 128), voyageNo: `SV-${sequence}`.slice(0, 64),
|
||||
estimatedSailingAt: '2026-09-16T08:00:00', estimatedArrivalAt: '2026-10-16T08:00:00' })
|
||||
}
|
||||
|
||||
async function loadOneFragment(context, state) {
|
||||
|
||||
@@ -62,6 +62,22 @@ test('create edit and stale replay payloads keep the resolved booking identity s
|
||||
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: '本轮客户' }
|
||||
const missing = truckAppointmentPayload({ marker: 'RUN-2', createdResources: {} }, '2', identity)
|
||||
assert.equal(Object.hasOwn(missing, 'carrierId'), false)
|
||||
assert.equal(Object.hasOwn(missing, 'carrierName'), false)
|
||||
const valid = truckAppointmentPayload({ marker: 'RUN-2', createdResources: {
|
||||
truckSupplier: { supplierId: '501', supplierName: '正式拖车商' },
|
||||
} }, '2', identity)
|
||||
assert.deepEqual({ carrierId: valid.carrierId, carrierName: valid.carrierName }, { carrierId: '501', carrierName: '正式拖车商' })
|
||||
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)
|
||||
})
|
||||
|
||||
test('truck appointment source has no 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['"]/)
|
||||
|
||||
@@ -69,10 +69,15 @@ export function inspectOceanBookingIdentity(marker, resource, detail = null) {
|
||||
|
||||
export function truckAppointmentPayload(context, suffix, identity, version = 0) {
|
||||
const supplier = context.createdResources?.truckSupplier || {}
|
||||
const supplierId = id(supplier.supplierId)
|
||||
const supplierName = id(supplier.supplierName)
|
||||
const carrierSnapshot = supplierId && supplierName
|
||||
? { carrierId: supplierId, carrierName: supplierName }
|
||||
: {}
|
||||
return {
|
||||
outboundOrderId: null, oceanBookingId: identity.oceanBookingId, containerBookingId: null, loadingPlanId: null,
|
||||
warehouseId: identity.warehouseId, customerId: identity.customerId, customerName: identity.customerName,
|
||||
carrierId: supplier.supplierId || null, carrierName: supplier.supplierName || `承运商-${suffix}`, fleetContact: '车队联系人', fleetPhone: '13800000004',
|
||||
...carrierSnapshot, fleetContact: '车队联系人', fleetPhone: '13800000004',
|
||||
driverName: '司机验收', driverPhone: '13800000005', driverIdCard: null, vehicleNo: `粤B-${suffix.slice(-6)}`,
|
||||
storeName: `门店-${suffix}`, storeContact: '门店联系人', storePhone: '13800000006', storeAddress: '深圳市南山区验收地址',
|
||||
storeCity: '深圳市', storeZipcode: '518000', packageCount: 10, grossWeightKg: 100, volumeCbm: 2.5,
|
||||
@@ -96,11 +101,12 @@ async function ensureTruckSupplier(context, suffix) {
|
||||
const detailResponse = await request(context, 'GET', `/api/platform/v1/suppliers/${encodeURIComponent(supplierId)}?tenantId=0`)
|
||||
supplier = data(detailResponse)
|
||||
}
|
||||
context.addStep(MODULE, '创建本轮有效拖车供应商', response,
|
||||
Boolean(supplierId) && supplier?.category === 'TRUCKING' && supplier?.status === 'ACTIVE',
|
||||
context.addStep(MODULE, '创建本轮有效拖车供应商(可选)', response,
|
||||
!supplierId || (supplier?.category === 'TRUCKING' && supplier?.status === 'ACTIVE' && Boolean(supplier?.name)),
|
||||
`supplierId=${supplierId || 'missing'}, category=${supplier?.category || 'missing'}, status=${supplier?.status || 'missing'}`, OPTIONS)
|
||||
if (supplierId) context.createdResources.truckSupplier = {
|
||||
marker: context.marker, supplierId, supplierName: supplier.name, finalStatus: 'ACTIVE', retained: true,
|
||||
marker: context.marker, supplierId, supplierName: supplier.name || supplier.supplierName || null,
|
||||
category: supplier.category, status: supplier.status, finalStatus: 'ACTIVE', retained: true,
|
||||
}
|
||||
return context.createdResources.truckSupplier || null
|
||||
}
|
||||
@@ -148,8 +154,7 @@ export async function run(context) {
|
||||
}
|
||||
const bookingIdentity = Object.freeze({ ...identityResult.identity })
|
||||
|
||||
const truckSupplier = await ensureTruckSupplier(context, suffix)
|
||||
if (!truckSupplier) return
|
||||
await ensureTruckSupplier(context, suffix)
|
||||
|
||||
const createResponse = await request(context, 'POST', BASE, truckAppointmentPayload(context, suffix, bookingIdentity)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', createResponse, '', OPTIONS)
|
||||
const appointmentId = id(appointment?.id)
|
||||
|
||||
Reference in New Issue
Block a user