fix(logistics-arrival): 换货单不再把已换货的 0 数量行算成条码
内部单号 9627695(赔偿换货单)看板显示 X00005、SJ0061100N430V01 两个条码, 但聚水潭里 SJ0061100N430V01 是 qty=0 / item_status=Replaced 的"已换货"原始行, 实际发出的是手工换货替换品 X00005,看板应只显示 X00005。 - 条码匹配时跳过 qty<=0 的明细行(已换货/取消的原始行不产生条码); - 明细查询补上 oi.qty 字段; - 补单测:换货单只保留实际发出条码、0/负数行不计入,并把原有两条条码用例补上 qty。 实测(10-01~10-09 窗口):jst_OrderItem 2436 行、jst_qmcust_orderlist_item 3007 行 数量为 0 且全部是 Replaced;过滤后没有任何订单会变成无条码。 注:快照里的条码是生成时固化的,快照区间需重跑当天批次后才会更新。 任务名:物流到货签收时长-deepseek
This commit is contained in:
parent
158995b71b
commit
253c803be6
@ -108,19 +108,35 @@ describe('物流到货签收时长明细', () => {
|
||||
expect(logisticsArrivalSigningDurationArchiveFallbackBarcodes('')).toEqual([])
|
||||
})
|
||||
|
||||
it('订单条码只用本单明细,不并集同一物流单号上的其它订单', () => {
|
||||
// 真实案例:9627695 的物流单号是 75627908174893,同单号下还有 9593499 的两项变体条码。
|
||||
it('换货单只保留实际发出的条码(qty=0 的已换货行不计)', () => {
|
||||
// 真实案例:9627695 是赔偿换货单——SJ0061100N430V01 是 qty=0 / item_status=Replaced 的原始行,
|
||||
// 实际发出的是手工换货替换品 X00005;同一物流单号 75627908174893 上还挂着补发单 9593499。
|
||||
const rows = [{
|
||||
orderKeys: ['9627695', '351207604012'],
|
||||
logisticsPayKey: '75627908174893|2026-10-03 17:35:36',
|
||||
logisticsId: '75627908174893',
|
||||
}]
|
||||
const items = [
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'SJ0061100N430V01' },
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'X00005' },
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'SJ0061100N430V01', qty: 0 },
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'X00005', qty: 1 },
|
||||
// 同物流单号的另一张订单(合并发货)——不能串进本单
|
||||
{ orderKeys: ['9593499', '351207604012-9'], logisticsPayKey: '75627908174893|2026-10-03 18:00:00', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-5' },
|
||||
{ orderKeys: ['9593499', '351207604012-9'], logisticsPayKey: '75627908174893|2026-10-03 18:00:00', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-7' },
|
||||
{ orderKeys: ['9593499', '3634438015247023'], logisticsPayKey: '75627908174893|2026-09-30 16:49:13', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-5', qty: 5 },
|
||||
{ orderKeys: ['9593499', '3634438015247023'], logisticsPayKey: '75627908174893|2026-09-30 16:49:13', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-7', qty: 4 },
|
||||
]
|
||||
expect(matchLogisticsArrivalSigningDurationBarcodes(rows, items)).toEqual(['X00005'])
|
||||
})
|
||||
|
||||
it('订单条码只用本单明细,不并集同一物流单号上的其它订单', () => {
|
||||
const rows = [{
|
||||
orderKeys: ['9627695', '351207604012'],
|
||||
logisticsPayKey: '75627908174893|2026-10-03 17:35:36',
|
||||
logisticsId: '75627908174893',
|
||||
}]
|
||||
const items = [
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'SJ0061100N430V01', qty: 1 },
|
||||
{ orderKeys: ['9627695', '351207604012'], logisticsPayKey: '@75627908174893|2026-10-03 17:35:36', logisticsId: '@75627908174893', barcode: 'X00005', qty: 1 },
|
||||
{ orderKeys: ['9593499', '3634438015247023'], logisticsPayKey: '75627908174893|2026-09-30 16:49:13', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-5', qty: 5 },
|
||||
{ orderKeys: ['9593499', '3634438015247023'], logisticsPayKey: '75627908174893|2026-09-30 16:49:13', logisticsId: '75627908174893', barcode: 'SJ0061100N430V01-7', qty: 4 },
|
||||
]
|
||||
expect(matchLogisticsArrivalSigningDurationBarcodes(rows, items)).toEqual(['SJ0061100N430V01, X00005'])
|
||||
})
|
||||
@ -133,12 +149,12 @@ describe('物流到货签收时长明细', () => {
|
||||
{ orderKeys: ['7777777'], logisticsPayKey: '', logisticsId: '79000000000003' },
|
||||
]
|
||||
const items = [
|
||||
{ orderKeys: ['8888888'], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'A1' },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'A1' },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'B2' },
|
||||
{ orderKeys: [], logisticsPayKey: '79000000000002|2026-10-01 10:00:00', logisticsId: '79000000000002', barcode: 'C3' },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000003', barcode: 'D4' },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000003', barcode: 'D4' },
|
||||
{ orderKeys: ['8888888'], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'A1', qty: 1 },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'A1', qty: 1 },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000001', barcode: 'B2', qty: 1 },
|
||||
{ orderKeys: [], logisticsPayKey: '79000000000002|2026-10-01 10:00:00', logisticsId: '79000000000002', barcode: 'C3', qty: 1 },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000003', barcode: 'D4', qty: 1 },
|
||||
{ orderKeys: [], logisticsPayKey: '', logisticsId: '79000000000003', barcode: 'D4', qty: 1 },
|
||||
]
|
||||
expect(matchLogisticsArrivalSigningDurationBarcodes(rows, items)).toEqual([
|
||||
// 第一行精确命中 A1(去重后只剩一条),不再叠加物流单号上的 B2
|
||||
@ -150,6 +166,16 @@ describe('物流到货签收时长明细', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('数量为 0 或负数的明细不计入条码', () => {
|
||||
const rows = [{ orderKeys: ['777'], logisticsPayKey: '', logisticsId: '' }]
|
||||
const items = [
|
||||
{ orderKeys: ['777'], logisticsPayKey: '', logisticsId: '', barcode: '已换货行', qty: 0 },
|
||||
{ orderKeys: ['777'], logisticsPayKey: '', logisticsId: '', barcode: '负数行', qty: -1 },
|
||||
{ orderKeys: ['777'], logisticsPayKey: '', logisticsId: '', barcode: '正常行', qty: 2 },
|
||||
]
|
||||
expect(matchLogisticsArrivalSigningDurationBarcodes(rows, items)).toEqual(['正常行'])
|
||||
})
|
||||
|
||||
it('按中国日期生成默认月份范围并规范页码', () => {
|
||||
expect(normalizeLogisticsArrivalSigningDurationFilters({ page: 0, pageSize: 100 }, new Date('2026-09-24T01:00:00Z'))).toMatchObject({
|
||||
dateFrom: '2026-09-01',
|
||||
|
||||
@ -213,6 +213,11 @@ export type LogisticsArrivalSigningDurationBarcodeItemKeys = {
|
||||
logisticsPayKey: string
|
||||
logisticsId: string
|
||||
barcode: string
|
||||
/**
|
||||
* 明细数量。聚水潭把"已换货"的原始商品行留在明细里但数量为 0
|
||||
* (`item_status = Replaced`),那不是实际发出的商品,不产生条码。
|
||||
*/
|
||||
qty: number
|
||||
}
|
||||
|
||||
function uniqueNonEmpty(values: string[]) {
|
||||
@ -226,6 +231,9 @@ function uniqueNonEmpty(values: string[]) {
|
||||
* 物流单号是**最后兜底**——一个物流单号可能合并发货多个订单(也可能被两套
|
||||
* 订单表分别登记),无脑并集会把别单的条码串到本单,且同一张单在不同分页
|
||||
* 会显示不同条码。只有精确键一个都匹配不到时才退到物流单号。
|
||||
*
|
||||
* 另外数量 <= 0 的明细("已换货 Replaced"的原始行)直接跳过:
|
||||
* 例:换货单 9627695 里 `SJ0061100N430V01` 是 qty=0 的被换行,实际发出的是 `X00005`。
|
||||
*/
|
||||
export function matchLogisticsArrivalSigningDurationBarcodes(
|
||||
rows: LogisticsArrivalSigningDurationBarcodeRowKeys[],
|
||||
@ -239,6 +247,8 @@ export function matchLogisticsArrivalSigningDurationBarcodes(
|
||||
index.set(key, current)
|
||||
}
|
||||
for (const item of items) {
|
||||
// 已换货/取消的原始行数量为 0,不是实际发出的商品。
|
||||
if (!(item.qty > 0)) continue
|
||||
for (const key of item.orderKeys) push(key, item.barcode)
|
||||
push(item.logisticsPayKey, item.barcode)
|
||||
push(item.logisticsId, item.barcode)
|
||||
|
||||
@ -64,6 +64,7 @@ type SqlItemRow = {
|
||||
i_id: string | null
|
||||
l_id: string | null
|
||||
pay_date: string | null
|
||||
qty: number | string | null
|
||||
}
|
||||
|
||||
type ArchiveRecord = { barcode: string; productName: string; supplier: string; purchaser: string }
|
||||
@ -372,7 +373,7 @@ async function listItemRows(rows: SqlExpressRow[]) {
|
||||
const orderParams = batch.map((_, index) => `@orderId${index}`)
|
||||
batch.forEach((value, index) => request.input(`orderId${index}`, sql.BigInt, value))
|
||||
const batchResult = await request.query<SqlItemRow>(`
|
||||
SELECT oi.[o_id], oi.[outer_so_id], oi.[raw_so_id], oi.[sku_id], oi.[i_id],
|
||||
SELECT oi.[o_id], oi.[outer_so_id], oi.[raw_so_id], oi.[sku_id], oi.[i_id], oi.[qty],
|
||||
o.[l_id], CONVERT(varchar(19), o.[pay_date], 120) AS [pay_date]
|
||||
FROM ${sourceObject(ITEM_OBJECT)} AS oi
|
||||
LEFT JOIN ${sourceObject('jst_qmcust_orderlist')} AS o ON o.[o_id] = oi.[o_id]
|
||||
@ -384,7 +385,7 @@ async function listItemRows(rows: SqlExpressRow[]) {
|
||||
otherRequest.timeout = EXPORT_REQUEST_TIMEOUT_MS
|
||||
batch.forEach((value, index) => otherRequest.input(`orderId${index}`, sql.BigInt, value))
|
||||
const otherResult = await otherRequest.query<SqlItemRow>(`
|
||||
SELECT oi.[o_id], CONVERT(nvarchar(120), o.[so_id]) AS [outer_so_id], oi.[raw_so_id], oi.[sku_id], oi.[i_id],
|
||||
SELECT oi.[o_id], CONVERT(nvarchar(120), o.[so_id]) AS [outer_so_id], oi.[raw_so_id], oi.[sku_id], oi.[i_id], oi.[qty],
|
||||
o.[l_id], CONVERT(varchar(19), o.[pay_date], 120) AS [pay_date]
|
||||
FROM ${sourceObject(ITEM_OBJECT_OTHER)} AS oi
|
||||
LEFT JOIN ${sourceObject(ORDER_OBJECT_OTHER)} AS o ON o.[o_id] = oi.[o_id]
|
||||
@ -437,6 +438,7 @@ function buildBarcodeMap(rows: SqlExpressRow[], items: SqlItemRow[]) {
|
||||
logisticsPayKey: itemLogisticId && item.pay_date ? `${itemLogisticId}|${text(item.pay_date)}` : '',
|
||||
logisticsId: itemLogisticId,
|
||||
barcode,
|
||||
qty: Number(item.qty ?? 0),
|
||||
}
|
||||
})
|
||||
.filter((item) => Boolean(item.barcode)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user