优化
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { run as createQualifiedMerchant } from './merchant-qualification-fixture.mjs'
|
||||
import { run as createQualifiedMerchant, normalizeFixedCustomerId, requireFixedSecondaryCustomer } from './merchant-qualification-fixture.mjs'
|
||||
import { prepareFleetTrip } from './fleet-trip-fulfillment.mjs'
|
||||
import { fixtureContract } from './fleet-cross-customer-acceptance.mjs'
|
||||
|
||||
@@ -27,7 +27,7 @@ const localTime = delay => {
|
||||
export const preparationContract = Object.freeze({
|
||||
stage: fixtureContract.stage,
|
||||
inputs: 'execute:true, current-run context, warehouseId (public active OVERSEAS with RECEIVING), destinationWarehouseId (public active FBA), pdaToken bound to warehouseId, readInventory(scope) returning all raw balances/positions/ledger with CUSTOMER_WAREHOUSE_COMPLETE scope',
|
||||
writes: 'Two fresh qualified customers; one ordinary WarehouseTransfer and one PDA-measured pallet/box per customer; one native cross-customer dispatch with one automatic Fleet resource record and a TEST vehicle/qualified driver; both reviews approved. No picking or delivery arrangement yet.',
|
||||
writes: 'TEST/GRAY reuse two designated existing ACTIVE customers; one ordinary WarehouseTransfer and one PDA-measured pallet/box per customer; one native cross-customer dispatch with one automatic Fleet resource record and configured reusable vehicle/driver; both reviews approved. No picking or delivery arrangement yet.',
|
||||
lifecycle: 'Caller creates/disables PDA identity and supplies raw readFleetInventory for verification. This helper logs out its own merchant sessions, retains all business fixtures, and refuses automatic retry. No SQL, environment files or external bookings.',
|
||||
})
|
||||
|
||||
@@ -35,6 +35,8 @@ export const preparationContract = Object.freeze({
|
||||
export async function createFleetCrossCustomerFixture(context, { execute = false, warehouseId, destinationWarehouseId, destinationWarehouseIds,
|
||||
shipmentReferences, prepareBatchAllocations, pdaToken, readInventory } = {}) {
|
||||
if (!execute) return preparationContract
|
||||
if (!context?.options?.fixedCustomerId) throw Error('跨客户验收必须配置两个既有固定客户,禁止自动建客')
|
||||
requireFixedSecondaryCustomer(context.options)
|
||||
if (!context?.marker || !id(warehouseId) || !id(destinationWarehouseId) || !pdaToken || typeof readInventory !== 'function'
|
||||
|| ['request', 'requestWithBody', 'requestMultipart', 'findAvailablePhone', 'apiSuccess', 'assert'].some(name => typeof context[name] !== 'function'))
|
||||
throw new Error('Missing current-run context, public warehouse/destination, caller-owned PDA identity or raw inventory reader')
|
||||
@@ -95,11 +97,15 @@ export async function createFleetCrossCustomerFixture(context, { execute = false
|
||||
check('Raw receipt reader proves complete current customer and warehouse scope', raw?.scope?.marker === scope.marker
|
||||
&& raw.scope.customerId === scope.customerId && raw.scope.warehouseId === scope.warehouseId
|
||||
&& raw.scope.coverage === 'CUSTOMER_WAREHOUSE_COMPLETE')
|
||||
for (const collection of ['balances', 'positions', 'ledger']) check(`Fresh customer raw ${collection} has exactly one original receipt row`,
|
||||
Array.isArray(raw[collection]) && raw[collection].length === 1 && id(raw[collection][0].id)
|
||||
&& id(raw[collection][0].customerId) === member.customerId && id(raw[collection][0].overseasWarehouseId) === id(warehouseId)
|
||||
&& (collection === 'positions' || raw[collection][0].inboundCode === member.incomingNo))
|
||||
const rawBalance = raw.balances[0], rawPosition = raw.positions[0], rawMovement = raw.ledger[0]
|
||||
// 固定客户可有历史库存;完整快照逐行核对归属,本轮只选原入仓码及其实际余额/位置。
|
||||
for (const collection of ['balances', 'positions', 'ledger']) check(`Complete customer raw ${collection} preserves scoped unique identities`,
|
||||
Array.isArray(raw[collection]) && new Set(raw[collection].map(row => id(row.id))).size === raw[collection].length
|
||||
&& raw[collection].every(row => id(row.id) && id(row.customerId) === member.customerId && id(row.overseasWarehouseId) === id(warehouseId)))
|
||||
const currentBalances = raw.balances.filter(row => row.inboundCode === member.incomingNo)
|
||||
const currentPositions = raw.positions.filter(row => currentBalances.some(balance => id(balance.id) === id(row.balanceId)))
|
||||
const currentMovements = raw.ledger.filter(row => row.inboundCode === member.incomingNo && row.businessOrderNo === member.incomingNo)
|
||||
check('Current receipt has exactly one original raw balance position and movement', currentBalances.length === 1 && currentPositions.length === 1 && currentMovements.length === 1)
|
||||
const rawBalance = currentBalances[0], rawPosition = currentPositions[0], rawMovement = currentMovements[0]
|
||||
check('Raw receipt identities and quantities match the public evidence', id(rawBalance.id) === id(balance.id)
|
||||
&& id(rawPosition.id) === id(positions[0].id) && id(rawPosition.balanceId) === id(balance.id)
|
||||
&& id(rawPosition.palletId) === member.palletId && rawPosition.palletNo === member.palletNo
|
||||
@@ -131,7 +137,9 @@ export async function createFleetCrossCustomerFixture(context, { execute = false
|
||||
for (const suffix of ['A', 'B']) {
|
||||
const memberDestinationId = id(memberDestinations[suffix === 'A' ? 0 : 1])
|
||||
const { destination, deliveryAddress } = destinations.get(memberDestinationId)
|
||||
const member = { marker: context.marker, customerMarker: `${context.marker}-CROSS-${suffix}`, receiptVerified: false }
|
||||
const configuredCustomer = normalizeFixedCustomerId(suffix === 'A' ? context.options.fixedCustomerId : context.options.fixedSecondaryCustomer.customerId)
|
||||
const member = { marker: context.marker, customerMarker: `${context.marker}-CROSS-${suffix}`, receiptVerified: false,
|
||||
...(configuredCustomer ? { customerId: configuredCustomer } : {}) }
|
||||
proof.members.push(member)
|
||||
// Qualification helper's mutable fixture, options and resources must not touch the parent's merchant slot.
|
||||
const child = Object.create(context)
|
||||
@@ -140,23 +148,21 @@ export async function createFleetCrossCustomerFixture(context, { execute = false
|
||||
child.context = {}; child.createdResources = {}
|
||||
child.request = async (method, path, body, token = context.token) => {
|
||||
const response = await context.request(method, path, body, token)
|
||||
record(method, path, body, response, `Qualify fresh customer ${suffix}`)
|
||||
if (method === 'POST' && path === '/api/platform/v1/iam/tenants')
|
||||
member.customerId = id((response.body.data?.tenant || response.body.data?.customer || response.body.data)?.id || response.body.data?.tenantId)
|
||||
record(method, path, body, response, `Verify customer ${suffix}`)
|
||||
return response
|
||||
}
|
||||
await createQualifiedMerchant(child, { publishEnvironment: false })
|
||||
await createQualifiedMerchant(child, { publishEnvironment: false, requireDistinctCustomer: suffix === 'B' })
|
||||
const credentials = child.context.merchantQualificationFixture
|
||||
check('Qualification belongs to this independent current-run context', credentials?.marker === member.customerMarker
|
||||
&& id(credentials.tenantId) === member.customerId && proof.members.filter(row => row.customerId === member.customerId).length === 1)
|
||||
member.qualification = child.createdResources.merchantQualificationFixture
|
||||
let auth
|
||||
try {
|
||||
auth = await request('POST', '/api/auth/login', { loginId: credentials.username, password: credentials.password }, `Login fresh customer ${suffix}`, '')
|
||||
auth = await request('POST', '/api/auth/login', { loginId: credentials.username, password: credentials.password }, `Login customer ${suffix}`, '')
|
||||
} finally { delete child.context.merchantQualificationFixture }
|
||||
// Retain a returned session for cleanup even if the login contract is incomplete.
|
||||
if (auth?.accessToken) sessions.push({ customerId: member.customerId, accessToken: auth.accessToken, refreshToken: auth.refreshToken })
|
||||
check('Fresh customer session is complete', auth?.accessToken && auth.refreshToken)
|
||||
check('Customer session is complete', auth?.accessToken && auth.refreshToken)
|
||||
const profile = await request('GET', '/api/merchant/v1/iam/tenant/profile', undefined, 'Verify newly authenticated customer identity', auth.accessToken)
|
||||
check('Merchant session belongs to the newly qualified customer', id(profile?.id) === member.customerId)
|
||||
const createBody = { sourceLastlegOrderIds: [], operatingWarehouseId: id(warehouseId),
|
||||
|
||||
Reference in New Issue
Block a user