import test from 'node:test' import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { session } from '../src/stores/session.js' import { countStocktake, createStocktake, getStocktake, listStocktakes, moveInventory, resolveInventory, resolveWarehouseLocation, resolveWarehousePallet, submitStocktake } from '../src/api/warehouse-operations.js' import { claimRelabelTask, completeRelabelTask, getRelabelTask, listRelabelCandidates, relabelUnit, reportRelabelException, reviewRelabelTask, skipRelabelUnit } from '../src/api/relabel.js' import { releaseWarehouseKey, resetWarehouseWork, restoreWarehouseWork, updateWarehouseWork, warehouseIdempotencyKey, warehouseWork } from '../src/stores/warehouse-work.js' const read = (path) => readFile(new URL(path, import.meta.url), 'utf8') function requestMock() { const calls = [] session.apiBase = '/overseas-api'; session.token = 'warehouse-token' globalThis.uni = { request(options) { calls.push({ method: options.method, url: options.url, data: options.data, authorization: options.header.Authorization }) let data = {} if (options.url.includes('/stocktakes?')) data = { records: [], page: 1, size: 100, total: 0, pages: 0 } else if (options.url.endsWith('/stocktakes')) data = { task: { taskNo: 'CC-1', version: 0 }, items: [] } else if (/\/stocktakes\/[^/]+$/.test(options.url)) data = { task: { taskNo: 'CC/1', version: 0 }, items: [] } else if (options.url.includes('/stocktakes/') && options.url.endsWith('/submit')) data = { task: { taskNo: 'CC/1', status: 'SUBMITTED', version: 1 }, items: [] } else if (options.url.includes('/stocktakes/') && options.url.endsWith('/counts')) data = { positionId: '9', actualPackages: 2 } else if (options.url.endsWith('/inventory-moves')) data = { moveNo: 'MV-1', palletCode: 'PLT-1', status: 'COMPLETED' } else if (options.url.endsWith('/inventory/resolve')) data = { queryCode: 'SKU-1', items: [] } else if (options.url.endsWith('/locations/resolve')) data = { locationCode: 'A-01' } else if (options.url.endsWith('/pallets/resolve')) data = { palletCode: 'PLT-1', version: 2, inventory: [] } else if (options.url.endsWith('/relabel-tasks/candidates')) data = [] else data = { relabelNo: 'RL/1', taskStatus: 'PROCESSING', version: 1, items: [] } options.success({ statusCode: 200, data: { code: '200', data }, header: {} }) } } return calls } test('warehouse API calls exact PDA paths and never supplies client warehouse scope', async () => { const calls = requestMock() await listStocktakes(); await createStocktake({ scopeType: 'LOCATION', scopeCode: 'A-01', idempotencyKey: 'create-1' }) await getStocktake('CC/1'); await countStocktake('CC/1', { positionId: '9', actualPackages: 2, expectedVersion: 0, idempotencyKey: 'count-1' }) await submitStocktake('CC/1', { expectedVersion: 1, reason: 'cycle', idempotencyKey: 'submit-1' }) await moveInventory({ sourcePalletCode: 'PLT-1', targetLocationCode: 'B-01', expectedPalletVersion: 2, reason: 'capacity', idempotencyKey: 'move-1' }) await resolveInventory('SKU-1'); await resolveWarehouseLocation('A-01'); await resolveWarehousePallet('PLT-1') assert.equal(calls.length, 9) for (const path of ['/stocktakes?page=1&size=100&status=IN_PROGRESS', '/stocktakes/CC%2F1/counts', '/stocktakes/CC%2F1/submit', '/inventory-moves', '/inventory/resolve', '/locations/resolve', '/pallets/resolve']) { assert.ok(calls.some((call) => call.url.endsWith(path)), path) } assert.ok(calls.every((call) => call.authorization === 'Bearer warehouse-token')) for (const call of calls) { assert.equal(Object.hasOwn(call.data || {}, 'customerId'), false) assert.equal(Object.hasOwn(call.data || {}, 'warehouseId'), false) } }) test('relabel API covers claim, unit outcomes, completion and review with encoded business numbers', async () => { const calls = requestMock() await listRelabelCandidates(); await getRelabelTask('RL/1') await claimRelabelTask('RL/1', { idempotencyKey: 'claim-1' }) await relabelUnit('RL/1', { itemId: '7', oldLabel: 'OLD', newLabel: 'NEW', expectedVersion: 1, idempotencyKey: 'unit-1' }) await skipRelabelUnit('RL/1', { itemId: '7', oldLabel: 'OLD-2', reason: 'skip', expectedVersion: 2, idempotencyKey: 'skip-1' }) await reportRelabelException('RL/1', { itemId: '7', oldLabel: 'OLD-3', exceptionCode: 'DAMAGED', reason: 'damage', expectedVersion: 3, idempotencyKey: 'exception-1' }) await completeRelabelTask('RL/1', { photoFileIds: ['8'], expectedVersion: 4, idempotencyKey: 'complete-1' }) await reviewRelabelTask('RL/1', { decision: 'APPROVE', expectedVersion: 5, idempotencyKey: 'review-1' }) for (const path of ['/relabel-tasks/candidates', '/relabel-tasks/RL%2F1', '/claim', '/units', '/units/skip', '/exceptions', '/complete', '/review']) { assert.ok(calls.some((call) => call.url.endsWith(path)), path) } assert.ok(calls.every((call) => !/[?&](?:customerId|warehouseId)=/.test(call.url))) }) test('carton relabel uses one scan input and shows the confirmed FBA mapping after saving', async () => { const page = await read('../src/pages/relabel/relabel.vue') assert.equal((page.match(/name="quickCarton"/g) || []).length, 1) assert.match(page, /name="quickSku"/) assert.match(page, /v-if="taskTab === 'sku' && selectedItem && action !== 'RELABEL' && remaining\(selectedItem\) > 0"/) assert.match(page, /selectItem\(item\) \{ if \(this\.taskTab === 'carton'\) \{ this\.activeTarget = 'quick-carton'; return \}/) const start = page.indexOf('async submitCarton(raw)') const scan = page.slice(start, page.indexOf('removePhoto(index)', start)) assert.ok(scan.indexOf('await scanRelabelCarton') < scan.indexOf('this.applyCurrent(result)')) assert.ok(scan.indexOf('this.applyCurrent(result)') < scan.indexOf('uni.showModal')) assert.match(scan, /showCancel: false/) assert.match(scan, /confirmedItem\?\.fbaCartonMark/) }) test('mixed relabel keeps SKU work open after carton work and only carton tab mounts pallet work', async () => { const page = await read('../src/pages/relabel/relabel.vue') const body = page.match(/remaining\(item\) \{([^\n]+)\},/)[1] const remaining = new Function('item', body) const mixed = { operationType: 'CARTON_AND_SKU', expectedPackages: 1, relabelledPackages: 1, cartonProcessedPackages: 1, skuProcessedPackages: 0, skippedPackages: 0, exceptionPackages: 0 } assert.equal(remaining.call({ taskTab: 'sku' }, mixed), 1) assert.equal(remaining.call({ taskTab: 'carton' }, mixed), 0) assert.match(page, / { const page = await read('../src/pages/relabel/relabel.vue') assert.doesNotMatch(page, /name="relabelTask"/) assert.match(page, /@tap="openExecution\(item\)"/) assert.match(page, /async openExecution\(item\)/) assert.match(page, /uni\.redirectTo\(\{ url: '\/pages\/relabel\/relabel\?op=relabelExecute' \}\)/) assert.match(page, /当前账号正在执行 \$\{active\.relabelNo\}/) assert.match(page, /v-if="stage === 'execute'" class="work-card">