Просмотр исходного кода

feat(pl): add arrange-required check for stop product fields

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 2 месяцев назад
Родитель
Сommit
765a27779c

+ 66 - 0
web-admin/src/utils/pl-plan-stop-product-arrange-required.test.ts

@@ -0,0 +1,66 @@
+import assert from 'node:assert/strict'
+import { describe, it } from 'node:test'
+import { findFirstStopProductArrangeGap } from './pl-plan-stop-product-arrange-required'
+
+function row(partial: Record<string, unknown> = {}) {
+  return {
+    productId: 'p1',
+    unit: '吨',
+    planQty: 10,
+    packageSpec: '槽罐',
+    deliveryMethod: '送货上门',
+    ...partial
+  }
+}
+
+describe('findFirstStopProductArrangeGap', () => {
+  it('returns null when load and deliveries are complete', () => {
+    assert.equal(
+      findFirstStopProductArrangeGap({
+        loadProducts: [row()],
+        deliveries: [{ products: [row()] }]
+      }),
+      null
+    )
+  })
+
+  it('flags load product missing fields including planQty 0', () => {
+    assert.equal(
+      findFirstStopProductArrangeGap({
+        loadProducts: [row({ unit: '', planQty: 0, packageSpec: ' ', deliveryMethod: null })],
+        deliveries: []
+      }),
+      '安排运输前请完善装货产品第1行:单位、计划数量、包装规格、送货方式'
+    )
+  })
+
+  it('flags delivery segment product and stops at first gap', () => {
+    assert.equal(
+      findFirstStopProductArrangeGap({
+        loadProducts: [row()],
+        deliveries: [
+          { products: [row()] },
+          { products: [row({ packageSpec: '' }), row({ deliveryMethod: '' })] }
+        ]
+      }),
+      '安排运输前请完善送货段#2产品第1行:包装规格'
+    )
+  })
+
+  it('treats null/undefined/NaN planQty as missing', () => {
+    assert.equal(
+      findFirstStopProductArrangeGap({
+        loadProducts: [row({ planQty: undefined })],
+        deliveries: []
+      }),
+      '安排运输前请完善装货产品第1行:计划数量'
+    )
+    assert.equal(
+      findFirstStopProductArrangeGap({
+        loadProducts: [row({ planQty: Number.NaN })],
+        deliveries: []
+      }),
+      '安排运输前请完善装货产品第1行:计划数量'
+    )
+  })
+})

+ 58 - 0
web-admin/src/utils/pl-plan-stop-product-arrange-required.ts

@@ -0,0 +1,58 @@
+export type ArrangeRequiredProductLike = {
+  unit?: string | null
+  planQty?: number | null
+  packageSpec?: string | null
+  deliveryMethod?: string | null
+}
+
+export type ArrangeRequiredDeliveryLike = {
+  products?: ArrangeRequiredProductLike[] | null
+}
+
+const FIELD_LABELS = {
+  unit: '单位',
+  planQty: '计划数量',
+  packageSpec: '包装规格',
+  deliveryMethod: '送货方式'
+} as const
+
+type FieldKey = keyof typeof FIELD_LABELS
+
+function missingFields(row: ArrangeRequiredProductLike): FieldKey[] {
+  const gaps: FieldKey[] = []
+  if (!String(row.unit ?? '').trim()) gaps.push('unit')
+  const qty = Number(row.planQty)
+  if (!Number.isFinite(qty) || qty <= 0) gaps.push('planQty')
+  if (!String(row.packageSpec ?? '').trim()) gaps.push('packageSpec')
+  if (!String(row.deliveryMethod ?? '').trim()) gaps.push('deliveryMethod')
+  return gaps
+}
+
+function formatGap(prefix: string, fields: FieldKey[]): string {
+  return `${prefix}${fields.map((k) => FIELD_LABELS[k]).join('、')}`
+}
+
+/** 返回第一处产品字段缺失提示;齐全则 null */
+export function findFirstStopProductArrangeGap(input: {
+  loadProducts?: ArrangeRequiredProductLike[] | null
+  deliveries?: ArrangeRequiredDeliveryLike[] | null
+}): string | null {
+  const loadProducts = input.loadProducts || []
+  for (let i = 0; i < loadProducts.length; i += 1) {
+    const fields = missingFields(loadProducts[i])
+    if (fields.length) {
+      return formatGap(`安排运输前请完善装货产品第${i + 1}行:`, fields)
+    }
+  }
+  const deliveries = input.deliveries || []
+  for (let d = 0; d < deliveries.length; d += 1) {
+    const products = deliveries[d].products || []
+    for (let i = 0; i < products.length; i += 1) {
+      const fields = missingFields(products[i])
+      if (fields.length) {
+        return formatGap(`安排运输前请完善送货段#${d + 1}产品第${i + 1}行:`, fields)
+      }
+    }
+  }
+  return null
+}