From c19318d0d0fce13b0aefc48d9f5f3cf21a8ea628 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Tue, 6 Oct 2026 09:36:26 +0800 Subject: [PATCH] feat: add per-row load and delivery qty helpers for plan list Co-authored-by: Cursor --- .../utils/pl-plan-list-display-rows.test.ts | 64 +++++++++++++++++++ .../src/utils/pl-plan-list-display-rows.ts | 32 ++++++++++ 2 files changed, 96 insertions(+) diff --git a/web-admin/src/utils/pl-plan-list-display-rows.test.ts b/web-admin/src/utils/pl-plan-list-display-rows.test.ts index 03883e763..2b22a69d9 100644 --- a/web-admin/src/utils/pl-plan-list-display-rows.test.ts +++ b/web-admin/src/utils/pl-plan-list-display-rows.test.ts @@ -5,6 +5,9 @@ import { buildPlPlanListSpanMethod, flattenPlanArchivePairRows, flattenPlanListRows, + formatDashNumber, + getRowSideIoQty, + getRowSidePlanQty, matchLoadProduct, PL_DELIVERY_SPAN_PROPS, PL_PLAN_SPAN_PROPS @@ -189,3 +192,64 @@ describe('buildPlPlanListSpanMethod two-level', () => { ) }) }) + +describe('row side qty helpers', () => { + it('reads load and delivery qtys from matched products', () => { + const rows = flattenPlanArchivePairRows([samplePlan()]) + assert.equal(getRowSidePlanQty(rows[0], 'load'), 30) + assert.equal(getRowSidePlanQty(rows[0], 'delivery'), 20) + assert.equal(getRowSidePlanQty(rows[1], 'load'), 10) + assert.equal(getRowSidePlanQty(rows[1], 'delivery'), 8) + assert.equal(getRowSidePlanQty(rows[2], 'load'), 30) + assert.equal(getRowSidePlanQty(rows[2], 'delivery'), 12) + }) + + it('returns null and dash when qty is missing', () => { + const rows = flattenPlanArchivePairRows([samplePlan()]) + const empty = { ...rows[0], loadProduct: undefined, deliveryProduct: undefined } + assert.equal(getRowSidePlanQty(empty, 'load'), null) + assert.equal(formatDashNumber(null), '—') + assert.equal(formatDashNumber(12), '12') + }) + + it('maps IO qty by internal customer direction', () => { + const plan = samplePlan() + plan.load = { + ...plan.load, + customerName: '安徽云栋化工有限公司', + products: [ + sp({ + planProductId: 'pp-u', + productId: 'u', + productName: '尿素', + planQty: 30, + inboundQty: 1, + outboundQty: 28 + }) + ] + } + plan.deliveries = [ + { + seq: 1, + customerName: '宜昌磷肥厂', + products: [ + sp({ + planProductId: 'pp-u', + productId: 'u', + productName: '尿素', + planQty: 20, + inboundQty: 2, + outboundQty: 19 + }) + ] + } + ] as Plan['deliveries'] + const rows = flattenPlanArchivePairRows([plan]) + // 装货内部客户 → 出库列展示;数量仍读装货段 inboundQty + assert.equal(getRowSideIoQty(rows[0], 'load', 'outbound'), 1) + assert.equal(getRowSideIoQty(rows[0], 'load', 'inbound'), null) + // 送货外部客户 → 出库列展示;数量读送货段 outboundQty + assert.equal(getRowSideIoQty(rows[0], 'delivery', 'outbound'), 19) + assert.equal(getRowSideIoQty(rows[0], 'delivery', 'inbound'), null) + }) +}) diff --git a/web-admin/src/utils/pl-plan-list-display-rows.ts b/web-admin/src/utils/pl-plan-list-display-rows.ts index 75d2ce04f..a917db730 100644 --- a/web-admin/src/utils/pl-plan-list-display-rows.ts +++ b/web-admin/src/utils/pl-plan-list-display-rows.ts @@ -602,6 +602,38 @@ export function resolveArchiveListIoColumn( return internal ? 'inbound' : 'outbound' } +/** 列表行:读取装货/送货侧计划数量(单件) */ +export function getRowSidePlanQty( + row: PlPlanListDisplayRow, + side: 'load' | 'delivery' +): number | null { + const product = side === 'load' ? row.loadProduct : row.deliveryProduct + if (product?.planQty == null || product.planQty === ('' as unknown)) return null + const num = Number(product.planQty) + return Number.isFinite(num) ? num : null +} + +export function formatDashNumber(value: number | null | undefined): string { + if (value == null) return '—' + return String(value) +} + +/** + * 列表行:按装/送侧与入出库列读取数量。 + * 列是否展示由 resolveArchiveListIoColumn 决定;数值仍走 getStopArchiveActualQty。 + */ +export function getRowSideIoQty( + row: PlPlanListDisplayRow, + side: 'load' | 'delivery', + column: 'inbound' | 'outbound' +): number | null { + const stop = side === 'load' ? getPairLoadStop(row) : getPairDeliveryStop(row) + const product = side === 'load' ? row.loadProduct : row.deliveryProduct + if (!product) return null + if (resolveArchiveListIoColumn(side, stop?.customerName) !== column) return null + return getStopArchiveActualQty(product, side) +} + /** 段次归档实际数量(装货读 inboundQty,送货读 outboundQty) */ export function getStopArchiveActualQty(product: PlanStopProduct, kind: PlPlanListDisplayRowKind): number | null { const raw = kind === 'load' ? product.inboundQty : product.outboundQty