This commit is contained in:
2026-10-20 14:22:53 +08:00
parent 19745e46c1
commit bf2508b6d6
24 changed files with 1222 additions and 88 deletions
+519
View File
@@ -108,6 +108,11 @@ try {
await orderMutationEvidenceModule()
await orderExcelImportModule()
await orderSubmissionChannelGateModule()
await merchantFinanceBillsModule()
await merchantOverseasWarehouseModule()
await merchantCrossBorderModule()
await merchantWarehouseOrdersModule()
await merchantPricingCustomsModule()
await notificationInboxModule()
await accountSecurityModule()
report.success = report.steps.every((step) => step.passed)
@@ -1490,6 +1495,520 @@ async function isolationModule() {
}, ({ json }) => ({ qualificationId: report.resources.businessLicenseQualificationId, rejectionCode: json?.code }))
}
async function merchantFinanceBillsModule() {
const module = 'merchant-bills'
const base = '/api/client/finance/v1'
const customerId = String(report.resources.tenantId)
const requireArray = (json, label) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data), `${label} must be an array`)
return json.data
}
const bills = await apiStep(module, 'customer bills ignore caller-supplied customer scope', {
path: `${base}/bills?customerId=0&tenantId=0`, token: merchantToken,
}, ({ json }) => {
const rows = requireArray(json, 'bills')
for (const row of rows) {
assertEqual(String(row.customerId), customerId, 'bill owner must be the authenticated customer')
assertPresent(row.billNo, 'billNo')
assertPresent(row.customerName, 'customerName')
assertTrue(['ISSUED', 'PARTIALLY_PAID', 'PAID'].includes(row.status), `unexpected visible bill status ${row.status}`)
assertTrue(Number.isSafeInteger(Number(row.version)), 'bill version required')
assertTrue(['NOT_REQUIRED', 'PENDING_CONFIRMATION', 'CONFIRMED'].includes(row.customerConfirmationStatus),
'customer confirmation status required')
}
}, ({ json }) => ({ customerId, count: json.data?.length ?? null, scope: 'authenticated-enterprise-only' }))
const statements = await apiStep(module, 'customer statements expose the same visible bills', {
path: `${base}/statements`, token: merchantToken,
}, ({ json }) => {
const rows = requireArray(json, 'statements')
assertEqual(rows.length, bills.json.data.length, 'bills and statements must expose the same visible set')
for (const row of rows) assertEqual(String(row.customerId), customerId, 'statement owner must be the authenticated customer')
}, ({ json }) => ({ count: json.data?.length ?? null }))
await apiStep(module, 'customer receipts are confirmed-only and customer-scoped', {
path: `${base}/receipts`, token: merchantToken,
}, ({ json }) => {
const rows = requireArray(json, 'receipts')
for (const row of rows) {
assertPresent(row.receiptNo, 'receiptNo')
assertTrue(Number.isFinite(Number(row.receivedAmount)), 'receipt amount required')
assertEqual(row.status, 'CONFIRMED', 'only confirmed receipts are customer-visible')
}
}, ({ json }) => ({ count: json.data?.length ?? null }))
await apiStep(module, 'customer receivable settlements are readable', {
path: `${base}/receivable-settlements`, token: merchantToken,
}, ({ json }) => {
const rows = requireArray(json, 'receivable settlements')
for (const row of rows) {
assertPresent(row.settlementNo, 'settlementNo')
assertEqual(String(row.customerId), customerId, 'settlement owner must be the authenticated customer')
assertTrue(Number.isFinite(Number(row.receivableAmount)), 'receivable amount required')
}
}, ({ json }) => ({ count: json.data?.length ?? null }))
await apiStep(module, 'unknown bill id is rejected without cross-customer disclosure', {
path: `${base}/bills/999999999999999`, token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', `unknown bill must not succeed, code=${json?.code}`)
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
await apiStep(module, 'unknown statement confirmation is rejected with idempotency key', {
method: 'POST', path: `${base}/statements/999999999999999/confirm`, token: merchantToken,
headers: { 'Idempotency-Key': newIdempotencyKey('bill-confirm-negative') },
body: { expectedVersion: 1 },
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', `unknown statement must not confirm, code=${json?.code}`)
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
if (existingClientToken) {
await apiStep(module, 'cross-customer bill detail stays outside the other enterprise', {
path: `${base}/bills/999999999999999`, token: existingClientToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', 'another enterprise bill must not be readable')
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
} else {
recordSkipped(module, 'cross-customer bill detail stays outside the other enterprise',
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
}
report.merchantFinanceBills = {
customerId,
bills: bills.json.data.length,
statements: statements.json.data.length,
scope: 'read-only; statement confirmation with real issued bills is covered when Admin finance chain runs first',
}
}
async function merchantOverseasWarehouseModule() {
const module = 'merchant-overseas-warehouse'
const base = '/api/merchant/v1/last-leg'
const page = 'page=1&size=20'
const listStep = async (label, path, validateRow) => {
const response = await apiStep(module, label, { path, token: merchantToken }, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records), `${label} records must be an array`)
assertTrue(Number.isFinite(Number(json.data?.total)), `${label} total required`)
for (const row of json.data.records) validateRow?.(row)
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
return response.json.data
}
const warehouseResponse = await apiStep(module, 'customer reads active overseas warehouses', {
path: '/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=OVERSEAS', token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records) && json.data.records.length > 0, 'at least one active overseas warehouse required')
assertTrue(json.data.records.every((row) => row.status === 'ACTIVE' && row.warehouseType === 'OVERSEAS'),
'customer warehouse list must contain only active overseas warehouses')
}, ({ json }) => ({ count: json.data?.records?.length ?? null }))
const warehouse = warehouseResponse.json.data.records[0]
await listStep('return forecasts are customer-scoped', `${base}/return-orders?${page}`, (row) => {
assertPresent(row.returnNo, 'returnNo')
assertPresent(row.status, 'return status')
})
await listStep('relabel applications are customer-scoped', `${base}/relabel-applications?${page}`, (row) => {
assertPresent(row.relabelNo, 'relabelNo')
assertTrue(Number.isFinite(Number(row.totalPackageCount)), 'relabel package count required')
})
await listStep('warehouse transfers are customer-scoped', `${base}/warehouse-transfers?${page}`, (row) => {
assertPresent(row.transferNo, 'transferNo')
assertPresent(row.status, 'transfer status')
})
await listStep('cargo discards are customer-scoped', `${base}/cargo-discards?${page}`, (row) => {
assertPresent(row.discardNo, 'discardNo')
assertTrue(Number.isFinite(Number(row.totalPackages)), 'discard package count required')
})
await listStep('return inventory is customer-scoped', `${base}/return-inventory?${page}`, (row) => {
assertPresent(row.cartonMark, 'cartonMark')
assertTrue(Number.isFinite(Number(row.quantity)), 'return inventory quantity required')
})
await listStep('return inventory summary is customer-scoped', `${base}/return-inventory/summary?${page}`, (row) => {
assertPresent(row.skuCode, 'summary skuCode')
assertTrue(Number.isFinite(Number(row.availableQuantity)), 'available quantity required')
})
await listStep('return cartons are customer-scoped', `${base}/return-inventory/cartons?${page}`, (row) => {
assertPresent(row.cartonMark, 'cartonMark')
assertPresent(row.status, 'carton status')
})
const suffix = marker.replace(/[^0-9A-Za-z]/g, '').slice(-20)
const sku = `ACCEPT-${suffix}`
const createPayload = {
overseasWarehouseId: String(warehouse.id),
warehouseContactName: '验收联系人',
warehouseContactPhone: '13800000000',
customerReferenceNo: `RET-${suffix}`,
needRelabel: false,
remark: `${marker} client overseas self-service acceptance`,
idempotencyKey: newIdempotencyKey('return-order'),
items: [{ skuCode: sku, productNameZh: '验收测试货物', expectedQuantity: 1 }],
}
const created = await apiStep(module, 'create return forecast with marker-owned payload', {
method: 'POST', path: `${base}/return-orders`, token: merchantToken, body: createPayload,
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.order?.returnNo, 'returnNo')
assertEqual(String(json.data.order.overseasWarehouseId), String(warehouse.id), 'return order warehouse')
assertTrue(Array.isArray(json.data.items) && json.data.items.length === 1
&& json.data.items[0].skuCode === sku && Number(json.data.items[0].expectedQuantity) === 1,
'return order items echo the marker payload')
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null, status: json.data?.order?.status ?? null }))
const returnNo = created.json.data.order.returnNo
report.resources.returnOrderNos = [returnNo]
await apiStep(module, 'same idempotency key replays the same return order', {
method: 'POST', path: `${base}/return-orders`, token: merchantToken, body: createPayload,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.order?.returnNo, returnNo, 'replayed returnNo must match the original')
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null }))
await apiStep(module, 'return order detail keeps the marker payload', {
path: `${base}/return-orders/${encodeURIComponent(returnNo)}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.order?.returnNo, returnNo, 'detail returnNo')
assertTrue(Array.isArray(json.data.items) && json.data.items.some((item) => item.skuCode === sku),
'detail keeps the submitted sku')
}, ({ json }) => ({ returnNo: json.data?.order?.returnNo ?? null, items: json.data?.items?.length ?? null }))
await apiStep(module, 'return order appears in the customer list', {
path: `${base}/return-orders?${page}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(json.data.records.some((row) => String(row.returnNo) === String(returnNo)),
'new return order must appear in the first customer page')
}, ({ json }) => ({ total: json.data?.total ?? null, returnNo }))
await apiStep(module, 'unknown return order is rejected', {
path: `${base}/return-orders/${encodeURIComponent(`RET-MISSING-${suffix}`)}`, token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', `unknown return order must not succeed, code=${json?.code}`)
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
if (existingClientToken) {
await apiStep(module, 'cross-customer return order is rejected', {
path: `${base}/return-orders/${encodeURIComponent(returnNo)}`, token: existingClientToken,
}, ({ json, status }) => {
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', 'another enterprise return order must not be readable')
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
} else {
recordSkipped(module, 'cross-customer return order is rejected',
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
}
report.overseasWarehouseReadOnlyGaps = [
'Relabel, transfer and cargo-discard creation require inventory produced by the platform overseas PDA chain; this run only covers their customer-scoped lists and the return forecast write chain.',
'Return relabel creation, completion confirmation and document downloads require received return cartons produced by the PDA return receipt chain.',
]
}
async function merchantCrossBorderModule() {
const module = 'merchant-cross-border'
const truckPath = '/api/merchant/v1/last-leg/us-truck-orders'
const looseboardPath = '/api/merchant/v1/last-leg/local-looseboard-orders'
const procurementPath = '/api/merchant/v1/last-leg/overseas-procurements'
const page = 'page=1&size=20'
const suffix = marker.replace(/[^0-9A-Za-z]/g, '').slice(-18)
const localTime = (days) => {
const date = new Date(Date.now() + days * 86_400_000)
return date.toISOString().slice(0, 19)
}
const listStep = async (label, path, validateRow) => apiStep(module, label, { path, token: merchantToken }, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records), `${label} records must be an array`)
assertTrue(Number.isFinite(Number(json.data?.total)), `${label} total required`)
for (const row of json.data.records) validateRow?.(row)
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
await listStep('customer procurement orders are customer-scoped', `${procurementPath}?${page}`, (row) => {
assertPresent(row.orderNo, 'procurement orderNo')
assertPresent(row.status, 'procurement status')
})
await listStep('customer US truck orders are customer-scoped', `${truckPath}?${page}`, (row) => {
assertPresent(row.orderNo, 'truck orderNo')
assertPresent(row.status, 'truck status')
})
await listStep('customer local looseboard orders are customer-scoped', `${looseboardPath}?${page}`, (row) => {
assertPresent(row.orderNo, 'looseboard orderNo')
assertPresent(row.status, 'looseboard status')
})
const truckPayload = {
vehicleType: 'PALLET_15',
origin: `验收起点-${suffix}`,
destination: `验收终点-${suffix}`,
packageCount: 2,
totalWeightKg: 12.5,
totalVolumeCbm: 0.8,
budgetAmount: 100,
currency: 'USD',
originContactName: '验收发货联系人',
originContactPhone: '13800000001',
destinationContactName: '验收收货联系人',
destinationContactPhone: '13800000002',
pickupAt: localTime(2),
deliveryAt: localTime(3),
remark: `${marker} client US truck acceptance`,
idempotencyKey: newIdempotencyKey('us-truck'),
}
const truck = await apiStep(module, 'customer creates a US truck order', {
method: 'POST', path: truckPath, token: merchantToken, body: truckPayload,
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.summary?.orderNo, 'truck orderNo')
assertTrue(['PENDING_REVIEW', 'SUBMITTED', 'DRAFT'].includes(json.data.summary.status),
`unexpected truck status ${json.data.summary.status}`)
assertTrue(Number(json.data.summary.version) >= 0, 'truck version required')
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null, status: json.data?.summary?.status ?? null }))
const truckNo = truck.json.data.summary.orderNo
report.resources.crossBorderOrderNos = [truckNo]
await apiStep(module, 'same idempotency key replays the same US truck order', {
method: 'POST', path: truckPath, token: merchantToken, body: truckPayload,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.summary?.orderNo, truckNo, 'replayed truck orderNo')
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
await apiStep(module, 'US truck order detail keeps the marker payload', {
path: `${truckPath}/${encodeURIComponent(truckNo)}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.summary?.orderNo, truckNo, 'truck detail orderNo')
assertEqual(json.data?.summary?.vehicleType, 'PALLET_15', 'truck vehicle type')
assertEqual(Number(json.data?.summary?.packageCount), 2, 'truck package count')
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
const looseboardPayload = {
origin: `散板起点-${suffix}`,
destination: `散板终点-${suffix}`,
productName: '验收散板货物',
estimatedPalletCount: 1,
packageCount: 3,
totalWeightKg: 20,
totalVolumeCbm: 1.2,
budgetAmount: 80,
currency: 'USD',
originContactName: '验收发货联系人',
originContactPhone: '13800000001',
destinationContactName: '验收收货联系人',
destinationContactPhone: '13800000002',
pickupAt: localTime(2),
deliveryAt: localTime(4),
remark: `${marker} client looseboard acceptance`,
idempotencyKey: newIdempotencyKey('looseboard'),
}
const looseboard = await apiStep(module, 'customer creates a local looseboard order', {
method: 'POST', path: looseboardPath, token: merchantToken, body: looseboardPayload,
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.summary?.orderNo, 'looseboard orderNo')
assertTrue(Number(json.data.summary.estimatedPalletCount) === 1, 'looseboard pallet count')
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null, status: json.data?.summary?.status ?? null }))
const looseboardNo = looseboard.json.data.summary.orderNo
report.resources.crossBorderOrderNos.push(looseboardNo)
await apiStep(module, 'same idempotency key replays the same looseboard order', {
method: 'POST', path: looseboardPath, token: merchantToken, body: looseboardPayload,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.summary?.orderNo, looseboardNo, 'replayed looseboard orderNo')
}, ({ json }) => ({ orderNo: json.data?.summary?.orderNo ?? null }))
const procurementPayload = {
customerName: companyName,
purchaseCategory: '验收测试',
productName: `验收采购样品-${suffix}`,
productSpecification: '1 件/箱',
purchaseQuantity: 1,
budgetUnitPrice: 10,
currency: 'CNY',
receivingAddress: '广东省深圳市南山区验收路 1 号',
contactName: '验收采购联系人',
contactPhone: '13800000003',
remark: `${marker} client procurement acceptance`,
idempotencyKey: newIdempotencyKey('procurement'),
}
const procurement = await apiStep(module, 'customer creates an overseas procurement order', {
method: 'POST', path: procurementPath, token: merchantToken, body: procurementPayload,
}, ({ json }) => {
requireSuccess(json)
assertPresent(json.data?.order?.orderNo, 'procurement orderNo')
assertTrue(Number(json.data.order.purchaseQuantity) === 1, 'procurement quantity')
}, ({ json }) => ({ orderNo: json.data?.order?.orderNo ?? null, status: json.data?.order?.status ?? null }))
const procurementNo = procurement.json.data.order.orderNo
report.resources.crossBorderOrderNos.push(procurementNo)
await apiStep(module, 'same idempotency key replays the same procurement order', {
method: 'POST', path: procurementPath, token: merchantToken, body: procurementPayload,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.order?.orderNo, procurementNo, 'replayed procurement orderNo')
}, ({ json }) => ({ orderNo: json.data?.order?.orderNo ?? null }))
await apiStep(module, 'created cross-border orders appear in customer lists', {
path: `${truckPath}?${page}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(json.data.records.some((row) => String(row.orderNo) === String(truckNo)),
'new truck order must appear in the customer list')
}, ({ json }) => ({ total: json.data?.total ?? null, truckNo }))
for (const [label, path] of [['truck', truckPath], ['looseboard', looseboardPath], ['procurement', procurementPath]]) {
await apiStep(module, `unknown ${label} order is rejected`, {
path: `${path}/${encodeURIComponent(`CB-MISSING-${suffix}`)}`, token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', `unknown ${label} order must not succeed, code=${json?.code}`)
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
}
if (existingClientToken) {
await apiStep(module, 'cross-customer truck order is rejected', {
path: `${truckPath}/${encodeURIComponent(truckNo)}`, token: existingClientToken,
}, ({ json, status }) => {
assertTrue([200, 403, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', 'another enterprise truck order must not be readable')
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
} else {
recordSkipped(module, 'cross-customer truck order is rejected',
'Set MERCHANT_TEST_USERNAME and MERCHANT_TEST_PASSWORD to enable this check')
}
report.crossBorderLimitations = [
'Customer creation stops at the submitted/pending platform review state; platform review, pricing, purchase execution, receipts and delivery are covered by Admin/fleet specialty entries.',
]
}
async function merchantWarehouseOrdersModule() {
const module = 'merchant-warehouse-orders'
const base = '/api/merchant/v1/last-leg'
const page = 'page=1&size=20'
const inbound = await apiStep(module, 'customer inbound plans are customer-scoped', {
path: `${base}/inbound-plans?${page}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records) && Number.isFinite(Number(json.data?.total)),
'inbound plan page required')
for (const row of json.data.records) {
assertPresent(row.planNo, 'planNo')
assertPresent(row.inboundCode, 'inboundCode')
assertTrue(Number.isFinite(Number(row.packageCount)), 'inbound packageCount')
assertPresent(row.status, 'inbound status')
}
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
const delivery = await apiStep(module, 'customer delivery orders are customer-scoped', {
path: `${base}/delivery-orders?${page}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records) && Number.isFinite(Number(json.data?.total)),
'delivery page required')
for (const row of json.data.records) {
assertPresent(row.deliveryNo, 'deliveryNo')
assertPresent(row.dispatchNo, 'dispatchNo')
assertPresent(row.status, 'delivery status')
assertTrue(Number.isFinite(Number(row.packageCount)), 'delivery packageCount')
}
}, ({ json }) => ({ total: json.data?.total ?? null, count: json.data?.records?.length ?? null }))
const planNo = inbound.json.data.records[0]?.planNo
if (planNo) {
await apiStep(module, 'inbound plan detail keeps plan identity and lines', {
path: `${base}/inbound-plans/${encodeURIComponent(planNo)}`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.planNo, planNo, 'planNo')
assertPresent(json.data?.status, 'plan status')
assertTrue(Array.isArray(json.data?.lines) && Array.isArray(json.data?.pallets), 'plan lines and pallets required')
}, ({ json }) => ({ planNo: json.data?.planNo ?? null, lines: json.data?.lines?.length ?? null }))
} else {
recordSkipped(module, 'inbound plan detail keeps plan identity and lines',
'this run customer has no inbound plan yet')
}
const deliveryNo = delivery.json.data.records[0]?.deliveryNo
if (deliveryNo) {
await apiStep(module, 'delivery order detail keeps customer-visible fees and skus', {
path: `${base}/delivery-orders/${encodeURIComponent(deliveryNo)}?page=1&size=200`, token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertEqual(json.data?.order?.deliveryNo, deliveryNo, 'deliveryNo')
assertTrue(Array.isArray(json.data?.skus) && Array.isArray(json.data?.fees), 'delivery skus and fees required')
assertPresent(json.data?.order?.status, 'delivery status')
}, ({ json }) => ({ deliveryNo: json.data?.order?.deliveryNo ?? null, skus: json.data?.skus?.length ?? null }))
} else {
recordSkipped(module, 'delivery order detail keeps customer-visible fees and skus',
'this run customer has no delivery order yet')
}
for (const [label, path] of [['inbound plan', `${base}/inbound-plans`], ['delivery order', `${base}/delivery-orders`]]) {
await apiStep(module, `unknown ${label} is rejected`, {
path: `${path}/${encodeURIComponent(`WO-MISSING-${marker}`)}`, token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
assertTrue(String(json?.code) !== '200', `unknown ${label} must not succeed, code=${json?.code}`)
}, ({ json, status }) => ({ http: status, code: json?.code ?? null }))
}
}
async function merchantPricingCustomsModule() {
const module = 'merchant-pricing-customs'
const warehouseResponse = await apiStep(module, 'customer reads active warehouses for pricing', {
path: '/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE', token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data?.records) && json.data.records.length > 0, 'active warehouse required')
assertTrue(json.data.records.every((row) => row.status === 'ACTIVE'), 'only active warehouses allowed')
}, ({ json }) => ({ count: json.data?.records?.length ?? null }))
const domestic = warehouseResponse.json.data.records.find((row) => row.warehouseType !== 'OVERSEAS')
if (domestic) {
await apiStep(module, 'customer reads active pricing channels', {
path: `/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${encodeURIComponent(String(domestic.id))}`,
token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Array.isArray(json.data), 'pricing channels must be an array')
for (const row of json.data) {
assertPresent(row.channelCode, 'channelCode')
assertPresent(row.priceModel, 'priceModel')
assertTrue(Array.isArray(row.pricingTaxModes), 'pricingTaxModes required')
assertTrue(Number.isFinite(Number(row.priceBookVersionId)), 'priceBookVersionId required')
}
}, ({ json }) => ({ count: json.data?.length ?? null, warehouseId: String(domestic.id) }))
} else {
recordSkipped(module, 'customer reads active pricing channels',
'this customer has no domestic origin warehouse')
}
await apiStep(module, 'customer customs pending summary is readable', {
path: '/api/merchant/v1/customs/pending-summary', token: merchantToken,
}, ({ json }) => {
requireSuccess(json)
assertTrue(Number.isFinite(Number(json.data?.pendingCount)), 'pendingCount required')
assertTrue(Number(json.data.pendingCount) >= 0, 'pendingCount must be non-negative')
}, ({ json }) => ({ pendingCount: json.data?.pendingCount ?? null }))
const orderId = report.resources.orderIds[0]
if (orderId) {
await apiStep(module, 'order customs documents stay customer-scoped', {
path: `/api/merchant/v1/customs/orders/${encodeURIComponent(String(orderId))}`, token: merchantToken,
}, ({ json, status }) => {
assertTrue([200, 404].includes(status), `unexpected HTTP status ${status}`)
if (String(json?.code) === '200') {
assertTrue(json.data && typeof json.data === 'object', 'customs payload required')
} else {
assertTrue(['A0403', 'A0404'].includes(String(json?.code)),
`unexpected customs code ${json?.code}`)
}
}, ({ json, status }) => ({ http: status, code: json?.code ?? null, orderId: String(orderId) }))
} else {
recordSkipped(module, 'order customs documents stay customer-scoped', 'no current-run order id')
}
}
async function notificationInboxModule() {
const module = 'notification-inbox'
const templateBase = '/api/platform/v1/notification-templates'