merge release test changes and preserve local frontend updates
This commit is contained in:
@@ -2,6 +2,7 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
const source = await readFile(new URL('../src/components/RelabelPalletWork.vue', import.meta.url), 'utf8')
|
||||
const template = source.split('</template>')[0]
|
||||
const script = source.split('<script>')[1].split('</script>')[0].replace(/^import .*$/gm, '').replace('export default', 'return')
|
||||
const component = new Function('ScanTargetInput', script)({})
|
||||
function context() {
|
||||
@@ -13,7 +14,7 @@ function context() {
|
||||
] } }
|
||||
for (const [key, method] of Object.entries(component.methods)) value[key] = method.bind(value)
|
||||
for (const [key, method] of Object.entries(component.computed)) Object.defineProperty(value, key, { get: method.bind(value) })
|
||||
value.selectSource({ positionId: '1', palletCode: 'SOURCE', movablePackages: 2, cartonMarks: ['A', 'B', 'C'] })
|
||||
value.selectSource({ positionId: '1', palletId: '20', palletCode: 'SOURCE', movablePackages: 2, cartonMarks: ['A', 'B', 'C'] })
|
||||
return value
|
||||
}
|
||||
test('quantity batch only includes completed boxes from same inbound and destination', () => {
|
||||
@@ -24,22 +25,58 @@ test('quantity batch only includes completed boxes from same inbound and destina
|
||||
value.selectedDestination = 'JFK8'; value.quantity = '1'; value.fillBatch(); assert.deepEqual(value.marks, ['B'])
|
||||
})
|
||||
|
||||
test('submission requires exact count and scanned distinct source and target pallets', () => {
|
||||
test('submission uses pallet identity instead of scanned presentation text', () => {
|
||||
const value = context(); value.quantity = '1'; value.fillBatch()
|
||||
value.source = { palletCode: 'SOURCE' }; value.sourceCode = 'source'
|
||||
value.target = { palletCode: 'TARGET' }; value.targetCode = 'TARGET'
|
||||
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = ' source\n'
|
||||
value.target = { palletId: '30', palletCode: 'TARGET' }; value.targetCode = 'target-alias'
|
||||
assert.equal(value.readyToBind, true)
|
||||
value.toggleMark('A'); assert.equal(value.readyToBind, false)
|
||||
value.toggleMark('A'); value.sourceCode = 'OTHER'; assert.equal(value.readyToBind, false)
|
||||
value.sourceCode = 'SOURCE'; value.target = { palletCode: 'SOURCE' }; value.targetCode = 'SOURCE'
|
||||
value.toggleMark('A'); value.source = { palletId: '21', palletCode: 'OTHER' }; assert.equal(value.readyToBind, false)
|
||||
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.target = { palletId: '20', palletCode: 'TARGET-ALIAS' }
|
||||
assert.equal(value.readyToBind, false)
|
||||
})
|
||||
|
||||
test('pallet comparison tolerates mixed API versions and canonical code formatting', () => {
|
||||
const value = context()
|
||||
assert.equal(value.samePallet(
|
||||
{ palletCode: ' plt-us-lax-01-1 ' },
|
||||
{ palletCode: 'PLT-US-LAX-01-1' }
|
||||
), true)
|
||||
assert.equal(value.samePallet(
|
||||
{ palletId: '20', palletCode: 'SOURCE' },
|
||||
{ palletId: '21', palletCode: 'OTHER' }
|
||||
), false)
|
||||
assert.equal(value.samePallet(
|
||||
{ palletId: '20', palletCode: 'SOURCE' },
|
||||
{ palletId: '21', palletCode: ' source ' }
|
||||
), true)
|
||||
assert.equal(value.samePallet(
|
||||
{ palletId: 2100203890682015700, palletCode: 'PLT-US-LAX-01-20260916-2100203890682015746' },
|
||||
{ palletId: '2100203890682015746', palletCode: ' plt-us-lax-01-20260916-2100203890682015746 ' }
|
||||
), true)
|
||||
})
|
||||
test('quantity disassembly has no extra carton scanner', () => {
|
||||
assert.doesNotMatch(source, /relabel-move-carton/)
|
||||
assert.match(source, /actualSource:/)
|
||||
assert.match(source, /仍归属/)
|
||||
})
|
||||
|
||||
test('source pallet list is concise and hides rows with no movable packages', () => {
|
||||
const value = context()
|
||||
value.sourceRows = [
|
||||
{ palletCode: 'PALLET-1', inboundCode: 'IN-1', stockPackages: 12, movablePackages: 2 },
|
||||
{ palletCode: 'PALLET-2', inboundCode: 'IN-2', stockPackages: 10, movablePackages: 0 }
|
||||
]
|
||||
assert.deepEqual(value.operableSourceRows.map(row => row.palletCode), ['PALLET-1'])
|
||||
assert.match(source, />托盘</)
|
||||
assert.match(source, />刷新</)
|
||||
assert.match(source, /托盘号:\{\{ row\.palletCode \}\} · 入仓码:\{\{ row\.inboundCode \}\}/)
|
||||
assert.match(source, /可拆\{\{ isReturnBulk \? '件数' : '箱数' \}\}:\{\{ row\.movablePackages \}\}/)
|
||||
assert.doesNotMatch(template, /刷新来源托盘/)
|
||||
assert.doesNotMatch(template, /按原入仓单拆托/)
|
||||
assert.doesNotMatch(template, /确认后直接提交本批拆托合托/)
|
||||
})
|
||||
|
||||
test('latest current-round binding is used for rebind source', () => {
|
||||
const value = context()
|
||||
value.task.history = ['OLD', 'NEW'].map(pallet => ({ action: 'PALLET_BIND', detail: JSON.stringify({ round: 'INITIAL', cartonMarks: ['A'], targetPallets: [pallet] }) }))
|
||||
@@ -61,9 +98,9 @@ test('rescan same bound pallet does not call stock movement', async () => {
|
||||
|
||||
test('target confirmation submits binding directly, with no second submit button', async () => {
|
||||
const value = context(); value.quantity = '1'; value.fillBatch()
|
||||
value.source = { palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
|
||||
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
|
||||
let binds = 0
|
||||
value.resolve = async kind => { assert.equal(kind, 'target'); value.target = { palletCode: 'TARGET' } }
|
||||
value.resolve = async kind => { assert.equal(kind, 'target'); value.target = { palletId: '30', palletCode: 'TARGET' } }
|
||||
value.bind = async () => { binds++ }
|
||||
await value.confirmTargetAndBind()
|
||||
assert.equal(binds, 1)
|
||||
@@ -83,7 +120,7 @@ test('missing source scan shows modal and never submits a binding', async () =>
|
||||
|
||||
test('target lookup failure cannot submit binding or report success', async () => {
|
||||
const value = context(); value.quantity = '1'; value.fillBatch()
|
||||
value.source = { palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
|
||||
value.source = { palletId: '20', palletCode: 'SOURCE' }; value.sourceCode = 'SOURCE'; value.targetCode = 'TARGET'
|
||||
value.resolve = async () => { value.error = '目标托盘不可用' }
|
||||
let title = ''
|
||||
value.showBindingMessage = heading => { title = heading }
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
const api = await readFile(new URL('../src/api/relabel.js', import.meta.url), 'utf8')
|
||||
const page = await readFile(new URL('../src/pages/relabel/relabel.vue', import.meta.url), 'utf8')
|
||||
const palletWork = await readFile(new URL('../src/components/RelabelPalletWork.vue', import.meta.url), 'utf8')
|
||||
|
||||
test('PDA exposes the approved return bulk SKU completion endpoint', () => {
|
||||
assert.match(api, /completeReturnRelabelSkuTotal/)
|
||||
assert.match(api, /\/return-skus\/complete-total/)
|
||||
})
|
||||
|
||||
test('return relabel pallet binding validates pieces while binding physical carton marks', () => {
|
||||
assert.match(palletWork, /isReturnBulk\(\).*RETURN_BULK_SKU_LABEL/)
|
||||
assert.match(palletWork, /count === packages && this\.completedItems\(\)\.length === this\.eligibleItems\.length && this\.marks\.length === this\.eligibleItems\.length/)
|
||||
assert.match(palletWork, /this\.isReturnBulk \? this\.eligibleItems : this\.eligibleItems\.slice\(0, count\)/)
|
||||
assert.match(page, /requiredMarks[\s\S]*some\(\(mark\) => !marks\.has\(mark\)\)/)
|
||||
})
|
||||
|
||||
test('return relabel mode works by SKU remaining quantity without carton-mark scanning', () => {
|
||||
assert.match(page, /current\?\.operationType === 'RETURN_BULK_SKU_LABEL'/)
|
||||
assert.match(page, /退货贴标 SKU 明细/)
|
||||
assert.match(page, /name="returnSku"[\s\S]*@submit="verifyReturnSku"/)
|
||||
assert.match(page, /activeTarget === 'return-sku'[\s\S]*this\.verifyReturnSku\(value\)/)
|
||||
assert.match(page, /verifiedReturnSkuKey !== group\.key/)
|
||||
assert.match(page, /Number\.isSafeInteger\(quantity\)[\s\S]*quantity > group\.remaining/)
|
||||
assert.match(page, /returnBulkSkuGroups/)
|
||||
assert.match(page, /来源托盘拆箱数量/)
|
||||
assert.match(page, /登记本次贴标.*group\.remaining/)
|
||||
assert.match(page, /completeReturnRelabelSkuTotal/)
|
||||
assert.match(page, /taskTab === 'carton' && !isReturnBulk/)
|
||||
})
|
||||
|
||||
test('new relabel pallet is immediately copied into the target scan field', () => {
|
||||
assert.match(palletWork, /this\.targetCode = this\.newPallet\.palletCode \|\| ''/)
|
||||
assert.match(palletWork, /退货换标无需扫描箱唛/)
|
||||
})
|
||||
|
||||
test('return relabel distinguishes a whole pallet from a partial unpack', () => {
|
||||
assert.match(palletWork, /wholeSource\(\).*plannedPackages === Number\(this\.selectedSource\.stockPackages/)
|
||||
assert.match(palletWork, /eligibleItems\(\).*this\.isReturnBulk \? this\.sourceItems\(\) : this\.completedItems\(\)/)
|
||||
assert.match(palletWork, /this\.selectedDestination = this\.sourceItems\(\)\[0\]/)
|
||||
assert.match(palletWork, /this\.completedItems\(\)\.length !== this\.eligibleItems\.length/)
|
||||
assert.match(palletWork, /贴标数量等于本托盘全部/)
|
||||
assert.match(palletWork, /贴标数量少于本托盘库存/)
|
||||
assert.match(palletWork, /v-if="!isReturnBulk"[\s\S]*name="relabel-source"/)
|
||||
assert.match(palletWork, /await this\.run\(async \(\) => \{ const value = await resolvePallet\(row\.palletCode\)/)
|
||||
})
|
||||
@@ -5,7 +5,7 @@ import { session } from '../src/stores/session.js'
|
||||
|
||||
test('return receipt start normalization keeps only stable business facts', () => {
|
||||
assert.deepEqual(normalizeReturnReceiptStart({ returnNo: 'RT-001', status: 'PENDING_RECEIPT', expectedItems: [{ skuCode: 'SKU-1', productNameZh: '商品', expectedQuantity: '5', receivedQuantity: 2 }] }), {
|
||||
returnNo: 'RT-001', status: 'PENDING_RECEIPT', expectedItems: [{ skuCode: 'SKU-1', productNameZh: '商品', expectedQuantity: 5, receivedQuantity: 2 }]
|
||||
returnNo: 'RT-001', status: 'PENDING_RECEIPT', expectedItems: [{ skuCode: 'SKU-1', productNameZh: '商品', expectedQuantity: 5, receivedQuantity: 2 }], completedPallets: [], photos: []
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,8 +17,8 @@ test('SKU candidates do not expose the backend customer identifier to the page',
|
||||
})
|
||||
|
||||
test('confirmed receipt normalization preserves generated carton and batch references', () => {
|
||||
assert.deepEqual(normalizeReturnReceiptResult({ returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', cartonMark: 'RC-1', orderStatus: 'COMPLETED', fullyMatched: true, expectedQuantity: 5, receivedQuantity: 5, signedAt: '2026-09-10T10:00:00' }), {
|
||||
returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', cartonMark: 'RC-1', orderStatus: 'COMPLETED', fullyMatched: true, expectedQuantity: 5, receivedQuantity: 5, signedAt: '2026-09-10T10:00:00'
|
||||
assert.deepEqual(normalizeReturnReceiptResult({ returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', palletCode: 'PLT-1', orderStatus: 'COMPLETED', fullyMatched: true, expectedQuantity: 5, receivedQuantity: 5, signedAt: '2026-09-10T10:00:00' }), {
|
||||
returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', palletCode: 'PLT-1', orderStatus: 'COMPLETED', fullyMatched: true, expectedQuantity: 5, receivedQuantity: 5, signedAt: '2026-09-10T10:00:00'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,13 +31,13 @@ test('return receipt API calls only the dedicated PDA routes and preserves the r
|
||||
let data
|
||||
if (options.url.endsWith('/start')) data = { returnNo: 'RT-001', status: 'PENDING_RECEIPT', expectedItems: [] }
|
||||
else if (options.url.endsWith('/sku-candidates')) data = { skuCode: 'SKU-1', candidates: [] }
|
||||
else data = { returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', cartonMark: 'RC-1', orderStatus: 'PARTIALLY_RECEIVED', fullyMatched: false, expectedQuantity: 5, receivedQuantity: 2 }
|
||||
else data = { returnNo: 'RT-001', receiptNo: 'RR-1', receiptBatchNo: 'RB-1', palletCode: 'PLT-1', orderStatus: 'PARTIALLY_RECEIVED', fullyMatched: false, expectedQuantity: 5, receivedQuantity: 2 }
|
||||
options.success({ statusCode: 200, data: { code: '200', data }, header: {} })
|
||||
}
|
||||
}
|
||||
await startReturnReceipt(' RT-001 ')
|
||||
await findReturnSkuCandidates(' SKU-1 ')
|
||||
const confirm = { returnNo: 'RT-001', trackingNo: 'TRACK-1', startedBy: 'SKU', cartonMark: 'RC-1', items: [{ skuCode: 'SKU-1', receivedQuantity: 2 }], idempotencyKey: 'return-key' }
|
||||
const confirm = { returnNo: 'RT-001', trackingNo: 'TRACK-1', startedBy: 'SKU', palletCode: 'PLT-1', items: [{ skuCode: 'SKU-1', receivedQuantity: 2 }], idempotencyKey: 'return-key' }
|
||||
await confirmReturnReceipt(confirm)
|
||||
assert.deepEqual(calls.map((item) => item.url), [
|
||||
'/overseas-api/pda/v1/last-leg/return-receipts/start',
|
||||
|
||||
@@ -91,7 +91,7 @@ test('carton relabel uses one scan input and shows the confirmed FBA mapping aft
|
||||
assert.match(scan, /confirmedItem\?\.fbaCartonMark/)
|
||||
})
|
||||
|
||||
test('mixed relabel keeps SKU work open after carton work and only carton tab mounts pallet work', async () => {
|
||||
test('mixed relabel keeps SKU work open and return bulk also 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)
|
||||
@@ -99,7 +99,7 @@ test('mixed relabel keeps SKU work open after carton work and only carton tab mo
|
||||
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, /<RelabelPalletWork v-if="taskTab === 'carton'"/)
|
||||
assert.match(page, /<RelabelPalletWork v-if="taskTab === 'carton' \|\| isReturnBulk"/)
|
||||
})
|
||||
|
||||
test('relabel list opens a row directly in execution and completion returns to the list', async () => {
|
||||
|
||||
Reference in New Issue
Block a user