feat(web-admin): plan form package UI keyed by product and package spec

Contract-bound stop products use package options from the contract, arrange-qty
cache/API include packageSpec, and save validates required package selections.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-09-10 18:06:52 +08:00
1 parent ded6aeaa85
commit 39161e43e4
9 files changed
+342 -37

No files matched your search

+3
View File
@@ -901,6 +901,7 @@ export interface ContractProductArrangeQtyDetail {
contractNo?: string | null
customerName?: string | null
productId: string
packageSpec?: string | null
productName?: string | null
hasFixedQty: boolean
contractQty?: number | null
@@ -923,6 +924,7 @@ export const planApi = {
getContractProductArrangeQty: (params: {
contractId: string
productId: string
packageSpec?: string
excludePlanId?: string
}) =>
request.get<ContractProductArrangeQtyDetail>(
@@ -1165,6 +1167,7 @@ export interface ContractQtyReminderItem {
customerName?: string
stopKind: string
productId: string
packageSpec?: string | null
productName?: string
model?: string
unit?: string
@@ -355,6 +355,7 @@
contractId?: string
productId?: string
productName?: string
packageSpec?: string
excludePlanId?: string
}>()
@@ -439,6 +440,7 @@
const res = await planApi.getContractProductArrangeQty({
contractId,
productId,
packageSpec: props.packageSpec || undefined,
excludePlanId: props.excludePlanId || undefined
})
const data = pickBxData<ContractProductArrangeQtyDetail>(res) || null
@@ -470,7 +472,14 @@
}
watch(
() => [props.modelValue, props.contractId, props.productId, props.excludePlanId] as const,
() =>
[
props.modelValue,
props.contractId,
props.productId,
props.packageSpec,
props.excludePlanId
] as const,
([visible]) => {
if (visible) void loadDetail()
}
@@ -221,6 +221,7 @@
:allow-remove="!readonly"
:contract-id="deliveryContractId(delivery)"
:exclude-plan-id="excludePlanId"
:contract-product-rows="contractProductRowsFor(delivery)"
@remove="(productIndex) => removeStopProduct(index, productIndex)"
/>
</article>
@@ -245,7 +246,10 @@
type PlanDeliveryForm,
type PlanStopProductFormRow
} from '@/utils/pl-plan-stop-form'
import { findDeliveryProductsMissingFromLoad } from '@/utils/pl-plan-contract-product'
import {
findDeliveryProductsMissingFromLoad,
type ContractProductRowLite
} from '@/utils/pl-plan-contract-product'
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import { pickBxData } from '@/utils/http/bx'
@@ -263,6 +267,8 @@
loadProducts?: PlanStopProductFormRow[]
/** 当前计划车牌(主牌),用于地磅可选车例外 */
vehiclePlate?: string
/** 合同 id → 产品行摘要(包装下拉) */
contractProductRowsByContract?: Record<string, ContractProductRowLite[]>
}>(),
{
tabMode: true,
@@ -272,7 +278,8 @@
showSplitHint: false,
excludePlanId: '',
loadProducts: () => [],
vehiclePlate: ''
vehiclePlate: '',
contractProductRowsByContract: () => ({})
}
)
@@ -297,6 +304,11 @@
return String(delivery.contracts?.[0]?.contractId || '').trim()
}
function contractProductRowsFor(delivery: PlanDeliveryForm): ContractProductRowLite[] {
const cid = deliveryContractId(delivery)
return cid ? props.contractProductRowsByContract?.[cid] || [] : []
}
function selectProductsDisabled(delivery: PlanDeliveryForm) {
if (isPlInternalCustomerName(delivery.customerName)) return false
return !deliveryContractId(delivery)
@@ -193,6 +193,7 @@
compact-products
:exclude-plan-id="planId || ''"
:vehicle-plate="currentVehiclePlate"
:contract-product-rows="loadContractProductRows"
@open-customer-picker="openCustomerSelector('load')"
@add-products="openProductSelector('load')"
/>
@@ -235,6 +236,7 @@
:exclude-plan-id="planId || ''"
:load-products="loadForm.products"
:vehicle-plate="currentVehiclePlate"
:contract-product-rows-by-contract="contractProductRowsCache"
@active-index-change="deliveryActiveIndex = $event"
@open-customer-picker="openCustomerSelector('delivery', $event)"
@sync-products="syncDeliveryProductsFromLoadManual"
@@ -383,7 +385,8 @@
sumStopProductsPlanQty,
syncDeliveriesProductsFromLoad,
type PlanDeliveryForm,
type PlanLoadForm
type PlanLoadForm,
type PlanStopProductFormRow
} from '@/utils/pl-plan-stop-form'
import { findFirstStopProductArrangeGap } from '@/utils/pl-plan-stop-product-arrange-required'
import {
@@ -418,7 +421,12 @@
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import {
collectContractProductIds,
filterProductsByAllowedProductIds
collectContractProductRows,
filterProductsByAllowedProductIds,
isPackageAllowedForContractProduct,
normalizePackageSpec,
packageSpecsForProduct,
type ContractProductRowLite
} from '@/utils/pl-plan-contract-product'
import { getPersonnelAffiliationLabel } from '@/utils/pl-personnel-cert'
@@ -555,6 +563,8 @@
let loadContractFetchSeq = 0
/** 合同 id → 明细 productId(装/送货共用缓存) */
const contractProductIdsCache = ref<Record<string, string[]>>({})
/** 合同 id → 明细行摘要(含包装,供段产品表下拉与保存校验) */
const contractProductRowsCache = ref<Record<string, ContractProductRowLite[]>>({})
/** 送货段合同明细拉取序号(按段) */
const deliveryContractFetchSeqByIndex: Record<number, number> = {}
/**
@@ -573,6 +583,11 @@
return loadContractProductIds.value
})
const loadContractProductRows = computed((): ContractProductRowLite[] => {
const cid = String(loadForm.value.contracts?.[0]?.contractId || '').trim()
return cid ? contractProductRowsCache.value[cid] || [] : []
})
function openProductSelector(side: 'load' | 'delivery', deliveryIndex = 0) {
productSelectorTarget.value = { side, deliveryIndex }
productSelectorVisible.value = true
@@ -586,12 +601,15 @@
}
try {
const res = await getContract(id)
const detail = pickBxData<{ products?: Array<{ productId?: string | null }> }>(res)
const detail = pickBxData<{ products?: ContractProductRowLite[] }>(res)
const productRows = collectContractProductRows(detail?.products || [])
const allowedIds = collectContractProductIds(detail?.products || [])
contractProductIdsCache.value = { ...contractProductIdsCache.value, [id]: allowedIds }
contractProductRowsCache.value = { ...contractProductRowsCache.value, [id]: productRows }
return allowedIds
} catch {
contractProductIdsCache.value = { ...contractProductIdsCache.value, [id]: [] }
contractProductRowsCache.value = { ...contractProductRowsCache.value, [id]: [] }
return []
}
}
@@ -1317,12 +1335,25 @@
async function handleProductConfirm(products: any[]) {
const target = productSelectorTarget.value
const resolveDefaultPackageSpec = (productId: string) => {
let rows: ContractProductRowLite[] = []
if (target.side === 'delivery') {
const delivery = deliveryForms.value[target.deliveryIndex]
const cid = String(delivery?.contracts?.[0]?.contractId || '').trim()
rows = cid ? contractProductRowsCache.value[cid] || [] : []
} else {
rows = loadContractProductRows.value
}
const specs = packageSpecsForProduct(rows, productId)
return specs.length === 1 ? specs[0] : ''
}
const buildRows = async () => {
const rows: ReturnType<typeof createStopProductsFromPlanProducts> = []
for (const product of products) {
const brandMeta = initLineBrandMetaFromProduct(product as Record<string, unknown>)
const productId = String(product.id || '')
const row: PlanFormProductRow = {
productId: String(product.id || ''),
productId,
productNo: String(product.productNo || product.id || ''),
productName: String(product.productName || ''),
model: product.model as string | undefined,
@@ -1330,7 +1361,7 @@
unit: String(product.unit || '吨'),
quantity: 1,
actualQty: 0,
packageSpec: '',
packageSpec: resolveDefaultPackageSpec(productId),
deliveryMethod: '',
isToxic: Number(product.isToxic || 0),
isExplosive: Number(product.isExplosive || 0),
@@ -2009,6 +2040,59 @@
return true
}
/** 绑合同时:合同产品有包装选项则包装必填且须在选项内 */
function validateContractPackageSpecs(): boolean {
const checkProducts = (
products: PlanStopProductFormRow[] | undefined,
contractId: string,
stopLabel: string
): boolean => {
const cid = String(contractId || '').trim()
if (!cid) return true
const contractRows = contractProductRowsCache.value[cid] || []
for (const product of products || []) {
const pid = String(product.productId || '').trim()
if (!pid) continue
const allowed = packageSpecsForProduct(contractRows, pid)
if (!allowed.length) continue
const pkg = normalizePackageSpec(product.packageSpec)
const name = String(product.productName || product.standardProductName || pid).trim() || pid
if (!pkg) {
ElMessage.error(`请选择包装规格:${stopLabel}${name}`)
return false
}
if (!isPackageAllowedForContractProduct(contractRows, pid, pkg)) {
ElMessage.error(`包装规格不在合同范围内:${stopLabel}${name}`)
return false
}
}
return true
}
if (
!checkProducts(
loadForm.value.products,
String(loadForm.value.contracts?.[0]?.contractId || ''),
'装货 '
)
) {
return false
}
for (let i = 0; i < deliveryForms.value.length; i += 1) {
const row = deliveryForms.value[i]
if (
!checkProducts(
row.products,
String(row.contracts?.[0]?.contractId || ''),
`送货段#${i + 1} `
)
) {
return false
}
}
return true
}
/** 非内部客户(山峡/云栋)的装/送货段必须至少选一份合同(保存与安排均校验) */
function validateStopContractsRequired() {
if (
@@ -2106,6 +2190,7 @@
const valid = await basicFormRef.value?.validate().catch(() => false)
if (!valid || !validateBusinessRules()) return
if (!validateSingleContractPerStop()) return
if (!validateContractPackageSpecs()) return
if (!validateStopContractsRequired()) return
if (mode === 'saveAndArrange' || mode === 'rearrange') {
@@ -150,6 +150,7 @@
:compact="compactProducts"
:contract-id="stopContractId"
:exclude-plan-id="excludePlanId"
:contract-product-rows="contractProductRows"
@remove="removeStopProduct"
/>
</div>
@@ -165,6 +166,7 @@
import PlPlanContractMultiSelect from '@/components/business/pl-plan-contract-multi-select/index.vue'
import PlPlanStopProductTable from '@/components/business/pl-plan-stop-product-table/index.vue'
import { resolveAddressNotesFill } from '@/utils/pl-address-notes-fill'
import type { ContractProductRowLite } from '@/utils/pl-plan-contract-product'
import {
canManualToggleStopDibang,
resolveStopDibangDefaults,
@@ -182,12 +184,15 @@
excludePlanId?: string
/** 当前计划车牌(主牌),用于地磅可选车例外 */
vehiclePlate?: string
/** 装货合同产品行摘要(包装下拉) */
contractProductRows?: ContractProductRowLite[]
}>(),
{
readonly: false,
compactProducts: true,
excludePlanId: '',
vehiclePlate: ''
vehiclePlate: '',
contractProductRows: () => []
}
)
@@ -45,15 +45,32 @@
<span class="pl-stop-product-dual-item__label"
>包装规格<span class="pl-stop-field-required" aria-hidden="true">*</span></span
>
<el-select
v-if="sharedEditable && useContractPackageSelect(row)"
v-model="row.packageSpec"
class="pl-plan-product-cell-control pl-stop-product-dual-dict"
clearable
filterable
placeholder="包装"
@change="() => onPackageChange(row)"
>
<el-option
v-for="opt in packageOptionsFor(row)"
:key="opt"
:label="opt"
:value="opt"
/>
</el-select>
<DictSelect
v-if="sharedEditable"
v-else-if="sharedEditable"
v-model="row.packageSpec"
class="pl-plan-product-cell-control pl-stop-product-dual-dict"
dict-type="crm_product_gg"
placeholder="包装"
@change="() => onPackageChange(row)"
/>
<span v-else class="pl-stop-product-dual-item__readonly">{{
resolveDictLabel('crm_product_gg', row.packageSpec)
resolvePackageDisplay(row)
}}</span>
</div>
<div class="pl-stop-product-dual-item__field">
@@ -295,17 +312,39 @@
<span>包装规格<span class="pl-stop-field-required" aria-hidden="true">*</span></span>
</template>
<template #default="{ row }">
<el-select
v-if="sharedEditable && useContractPackageSelect(row)"
v-model="row.packageSpec"
class="pl-plan-product-cell-control"
clearable
filterable
placeholder="请选择包装规格"
style="width: 100%"
@change="() => onPackageChange(row)"
>
<el-option
v-for="opt in packageOptionsFor(row)"
:key="opt"
:label="opt"
:value="opt"
/>
</el-select>
<DictSelect
v-if="sharedEditable"
v-else-if="sharedEditable"
v-model="row.packageSpec"
class="pl-plan-product-cell-control"
dict-type="crm_product_gg"
placeholder="请选择包装规格"
style="width: 100%"
@change="() => onPackageChange(row)"
/>
<template v-else>
<DictTag v-if="row.packageSpec" dict-type="crm_product_gg" :value="row.packageSpec" />
<span v-else>—</span>
<DictTag
v-if="row.packageSpec && !useContractPackageSelect(row)"
dict-type="crm_product_gg"
:value="row.packageSpec"
/>
<span v-else>{{ resolvePackageDisplay(row) }}</span>
</template>
</template>
</el-table-column>
@@ -453,6 +492,7 @@
:contract-id="arrangeQtyContractId"
:product-id="arrangeQtyProductId"
:product-name="arrangeQtyProductName"
:package-spec="arrangeQtyPackageSpec"
:exclude-plan-id="excludePlanId"
/>
</div>
@@ -467,6 +507,11 @@
resolveStopActualQty,
resolveStopUnitPrice
} from '@/utils/pl-plan-stop-form'
import {
normalizePackageSpec,
packageSpecsForProduct,
type ContractProductRowLite
} from '@/utils/pl-plan-contract-product'
import { getDeliveryMethodLabel } from '@/composables/usePlContractToPlan'
import {
applyBrandSelectChange,
@@ -502,6 +547,8 @@
contractId?: string
/** 当前编辑计划 id,计算可安排时排除 */
excludePlanId?: string
/** 本合同产品行摘要(有包装选项时包装列用合同下拉) */
contractProductRows?: ContractProductRowLite[]
}>(),
{
compact: false,
@@ -511,7 +558,8 @@
showStopArchivePrice: false,
stopArchiveMode: 'load',
contractId: '',
excludePlanId: ''
excludePlanId: '',
contractProductRows: () => []
}
)
@@ -540,21 +588,44 @@
const arrangeQtyContractId = ref('')
const arrangeQtyProductId = ref('')
const arrangeQtyProductName = ref('')
const arrangeQtyPackageSpec = ref('')
function cacheKey(productId: string) {
return `${props.contractId || ''}::${productId}::${props.excludePlanId || ''}`
function packageOptionsFor(row: PlanStopProductFormRow) {
return packageSpecsForProduct(props.contractProductRows || [], String(row.productId || ''))
}
async function ensureArrangeQty(productId: string) {
function useContractPackageSelect(row: PlanStopProductFormRow) {
return Boolean(String(props.contractId || '').trim() && packageOptionsFor(row).length > 0)
}
function resolvePackageDisplay(row: PlanStopProductFormRow) {
const raw = normalizePackageSpec(row.packageSpec)
if (!raw) return '—'
if (useContractPackageSelect(row)) return raw
return resolveDictLabel('crm_product_gg', raw)
}
function requiresPackageBeforeArrange(row: PlanStopProductFormRow) {
return useContractPackageSelect(row) && !normalizePackageSpec(row.packageSpec)
}
function cacheKey(productId: string, packageSpec?: string | null) {
return `${props.contractId || ''}::${productId}::${normalizePackageSpec(packageSpec)}::${props.excludePlanId || ''}`
}
async function ensureArrangeQty(row: PlanStopProductFormRow) {
const cid = String(props.contractId || '').trim()
const pid = String(productId || '').trim()
const pid = String(row.productId || '').trim()
if (!cid || !pid) return null
const key = cacheKey(pid)
if (requiresPackageBeforeArrange(row)) return null
const pkg = normalizePackageSpec(row.packageSpec)
const key = cacheKey(pid, pkg)
if (key in arrangeQtyCache.value) return arrangeQtyCache.value[key]
try {
const res = await planApi.getContractProductArrangeQty({
contractId: cid,
productId: pid,
packageSpec: pkg || undefined,
excludePlanId: props.excludePlanId || undefined
})
const data = pickBxData<ContractProductArrangeQtyDetail>(res) || null
@@ -566,12 +637,25 @@
}
}
function onPackageChange(row: PlanStopProductFormRow) {
const pid = String(row.productId || '').trim()
if (!pid) return
const prefix = `${props.contractId || ''}::${pid}::`
const next: Record<string, ContractProductArrangeQtyDetail | null> = {}
for (const [key, value] of Object.entries(arrangeQtyCache.value)) {
if (!key.startsWith(prefix)) next[key] = value
}
arrangeQtyCache.value = next
void ensureArrangeQty(row)
}
function formatAvailableDisplay(row: PlanStopProductFormRow) {
const cid = String(props.contractId || '').trim()
if (!cid) return '—'
const pid = String(row.productId || '').trim()
if (!pid) return '—'
const data = arrangeQtyCache.value[cacheKey(pid)]
if (requiresPackageBeforeArrange(row)) return '请先选包装'
const data = arrangeQtyCache.value[cacheKey(pid, row.packageSpec)]
if (!data) return '…'
if (!data.hasFixedQty) return '—'
if (data.availableQty == null) return '—'
@@ -581,8 +665,8 @@
function isPlanQtyOverAvailable(row: PlanStopProductFormRow) {
const cid = String(props.contractId || '').trim()
const pid = String(row.productId || '').trim()
if (!cid || !pid) return false
const data = arrangeQtyCache.value[cacheKey(pid)]
if (!cid || !pid || requiresPackageBeforeArrange(row)) return false
const data = arrangeQtyCache.value[cacheKey(pid, row.packageSpec)]
if (!data?.hasFixedQty || data.availableQty == null) return false
const planQty = Number(row.planQty)
if (!Number.isFinite(planQty)) return false
@@ -590,7 +674,11 @@
}
function canOpenArrangeQty(row: PlanStopProductFormRow) {
return Boolean(String(props.contractId || '').trim() && String(row.productId || '').trim())
return Boolean(
String(props.contractId || '').trim() &&
String(row.productId || '').trim() &&
!requiresPackageBeforeArrange(row)
)
}
function openArrangeQtyDialog(row: PlanStopProductFormRow) {
@@ -599,9 +687,14 @@
return
}
if (!row.productId) return
if (requiresPackageBeforeArrange(row)) {
ElMessage.warning('请先选择包装规格')
return
}
arrangeQtyContractId.value = String(props.contractId)
arrangeQtyProductId.value = String(row.productId)
arrangeQtyProductName.value = String(row.productName || row.standardProductName || '')
arrangeQtyPackageSpec.value = normalizePackageSpec(row.packageSpec)
arrangeQtyDialogVisible.value = true
}
@@ -611,7 +704,9 @@
props.editable,
props.contractId,
props.excludePlanId,
(props.products || []).map((p) => p.productId).join(',')
(props.products || [])
.map((p) => `${p.productId}:${normalizePackageSpec(p.packageSpec)}`)
.join(',')
] as const,
() => {
if (!props.editable) return
@@ -619,7 +714,7 @@
if (!cid) return
for (const row of props.products || []) {
const pid = String(row.productId || '').trim()
if (pid) void ensureArrangeQty(pid)
if (pid) void ensureArrangeQty(row)
}
},
{ immediate: true }
@@ -12,6 +12,7 @@ export type ContractQtyDialogRow = {
customerName?: string
stopKind?: string
productId?: string
packageSpec?: string
productName?: string
model?: string
unit?: string
@@ -86,6 +87,7 @@ function normalizeDialogRows(
customerName: it.customerName != null ? String(it.customerName) : '',
stopKind: it.stopKind != null ? String(it.stopKind) : '',
productId: it.productId != null ? String(it.productId) : '',
packageSpec: it.packageSpec != null ? String(it.packageSpec).trim() : '',
productName: it.productName != null ? String(it.productName) : '',
model: it.model != null ? String(it.model) : '',
unit: it.unit != null ? String(it.unit) : '',
@@ -141,7 +143,10 @@ export function buildContractQtyDialogHtml(
const title = groupTitle(list[0])
const body = list
.map((row) => {
const name = escapeHtml(row.productName || row.productId || '—')
const baseName = row.productName || row.productId || '—'
const name = escapeHtml(
row.packageSpec ? `${baseName}(${row.packageSpec})` : baseName
)
const model = escapeHtml(row.model || '—')
const unit = escapeHtml(resolveUnitLabel(row.unit))
const contractQty = row.framework
+89 -10
View File
@@ -13,6 +13,72 @@ export function collectContractProductIds(
return ids
}
export type ContractProductRowLite = {
productId?: string | null
packageSpec?: string | null
productName?: string | null
productNo?: string | null
quantity?: number | null
quantityMode?: string | null
}
export function normalizePackageSpec(value?: string | null): string {
return String(value ?? '').trim()
}
/** 稳定复合键:productId + '\\0' + 归一化包装 */
export function productPackageKey(
productId?: string | null,
packageSpec?: string | null
): string {
const pid = String(productId || '').trim()
if (!pid) return ''
return `${pid}\0${normalizePackageSpec(packageSpec)}`
}
export function collectContractProductRows(
products?: ContractProductRowLite[] | null
): ContractProductRowLite[] {
return (products || [])
.map((row) => ({
productId: String(row?.productId || '').trim(),
packageSpec: normalizePackageSpec(row?.packageSpec),
productName: row?.productName,
productNo: row?.productNo,
quantity: row?.quantity,
quantityMode: row?.quantityMode
}))
.filter((row) => row.productId)
}
/** 本合同该产品的包装列表(去重保序,仅非空) */
export function packageSpecsForProduct(
rows: ContractProductRowLite[] | null | undefined,
productId: string
): string[] {
const pid = String(productId || '').trim()
const out: string[] = []
const seen = new Set<string>()
for (const row of rows || []) {
if (String(row.productId || '').trim() !== pid) continue
const pkg = normalizePackageSpec(row.packageSpec)
if (!pkg || seen.has(pkg)) continue
seen.add(pkg)
out.push(pkg)
}
return out
}
export function isPackageAllowedForContractProduct(
rows: ContractProductRowLite[] | null | undefined,
productId: string,
packageSpec?: string | null
): boolean {
const allowed = packageSpecsForProduct(rows, productId)
if (!allowed.length) return true
return allowed.includes(normalizePackageSpec(packageSpec))
}
/** 保留 productId 落在 allowed 内的装货行;allowed 为空则全部移除(换合同且新合同无产品) */
export function filterProductsByAllowedProductIds<T extends { productId?: string | null }>(
products: T[],
@@ -27,23 +93,36 @@ export function filterProductsByAllowedProductIds<T extends { productId?: string
/**
* 找出不在装货产品列表中的送货产品(仅提醒,不拦截)。
* 返回展示名列表(去重)。
* 按 productId + packageSpec 比对;返回展示名列表(去重)。
*/
export function findDeliveryProductsMissingFromLoad(
deliveryProducts: Array<{ productId?: string | null; productName?: string | null }> | null | undefined,
loadProducts: Array<{ productId?: string | null }> | null | undefined
deliveryProducts:
| Array<{
productId?: string | null
productName?: string | null
packageSpec?: string | null
}>
| null
| undefined,
loadProducts:
| Array<{ productId?: string | null; packageSpec?: string | null }>
| null
| undefined
): string[] {
const loadIds = new Set(
(loadProducts || []).map((row) => String(row?.productId || '').trim()).filter(Boolean)
const loadKeys = new Set(
(loadProducts || [])
.map((row) => productPackageKey(row?.productId, row?.packageSpec))
.filter(Boolean)
)
const names: string[] = []
const seen = new Set<string>()
for (const row of deliveryProducts || []) {
const pid = String(row?.productId || '').trim()
if (!pid || loadIds.has(pid) || seen.has(pid)) continue
seen.add(pid)
const name = String(row?.productName || '').trim() || pid
names.push(name)
const key = productPackageKey(row?.productId, row?.packageSpec)
if (!key || loadKeys.has(key) || seen.has(key)) continue
seen.add(key)
const name = String(row?.productName || '').trim() || String(row?.productId || '').trim()
const pkg = normalizePackageSpec(row?.packageSpec)
names.push(pkg ? `${name}(${pkg})` : name)
}
return names
}
+12
View File
@@ -6,6 +6,7 @@ import {
isPlDibangOptionalVehiclePlate,
isPlInternalCustomerName
} from '@/utils/pl-internal-customer'
import { normalizePackageSpec } from '@/utils/pl-plan-contract-product'
export interface PlanStopProductFormRow {
id?: string
@@ -214,6 +215,17 @@ function matchPrevStopProduct(
return stopProducts[byPlanProductId]
}
}
const targetPkg = normalizePackageSpec(planRow.packageSpec)
const byProductAndPkg = stopProducts.findIndex(
(row, index) =>
!used.has(index) &&
String(row.productId) === String(planRow.productId) &&
normalizePackageSpec(row.packageSpec) === targetPkg
)
if (byProductAndPkg >= 0) {
used.add(byProductAndPkg)
return stopProducts[byProductAndPkg]
}
const byProductId = stopProducts.findIndex(
(row, index) => !used.has(index) && String(row.productId) === String(planRow.productId)
)