Files
shanxia2.0/web-admin/src/utils/pl-plan-stop-form.ts
T
wuzilongandCursor 39161e43e4 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>
2026-09-10 18:06:52 +08:00

774 lines
25 KiB
TypeScript

import type { Plan, PlanContractLink, PlanDelivery, PlanLoad, PlanStopProduct } from '@/api/pl'
import type { PlanFormProductRow } from '@/composables/usePlContractToPlan'
import type { BrandOption } from '@/utils/crm-product-brand'
import { formatBrandConstraintLabel } from '@/utils/crm-product-brand'
import {
isPlDibangOptionalVehiclePlate,
isPlInternalCustomerName
} from '@/utils/pl-internal-customer'
import { normalizePackageSpec } from '@/utils/pl-plan-contract-product'
export interface PlanStopProductFormRow {
id?: string
planProductId?: string
productId: string
productNo: string
productName: string
/** 展示用:主数据标准名 */
standardProductName?: string
/** 展示用:别名 */
aliasName?: string
aliasOptions?: string[]
brandMode?: string
brandId?: string
brandName?: string
brandOptions?: BrandOption[]
model?: string
unit: string
packageSpec?: string
deliveryMethod?: string
planQty?: number
/** 装货段归档:入库数量即实际数量 */
inboundQty?: number
inboundPrice?: number
inboundAmount?: number
/** 送货段归档:出库数量即实际数量 */
outboundQty?: number
outboundPrice?: number
outboundAmount?: number
/** 本段归档品牌数量 */
brandQtys?: Array<{ brandId: string; brandName?: string; qty: number; unitPrice?: number }>
remark?: string
isExplosive?: number
isToxic?: number
}
/** 段次查看/归档:装货段取 inbound,送货段取 outbound */
export function resolveStopActualQty(
row: PlanStopProductFormRow,
stopType: 'load' | 'delivery'
): number | undefined {
const raw = stopType === 'load' ? row.inboundQty : row.outboundQty
return raw != null ? Number(raw) : undefined
}
export function resolveStopUnitPrice(
row: PlanStopProductFormRow,
stopType: 'load' | 'delivery'
): number | undefined {
const raw = stopType === 'load' ? row.inboundPrice : row.outboundPrice
return raw != null ? Number(raw) : undefined
}
export function resolveStopArchiveAmount(
row: PlanStopProductFormRow,
stopType: 'load' | 'delivery'
): number | undefined {
const stored = stopType === 'load' ? row.inboundAmount : row.outboundAmount
if (stored != null) {
const n = Number(stored)
return Number.isFinite(n) ? n : undefined
}
const qty = resolveStopActualQty(row, stopType)
const price = resolveStopUnitPrice(row, stopType)
if (qty == null || price == null) return undefined
const amount = qty * price
return Number.isFinite(amount) ? amount : undefined
}
export function formatStopArchiveAmount(
row: PlanStopProductFormRow,
stopType: 'load' | 'delivery'
): string {
const amount = resolveStopArchiveAmount(row, stopType)
return amount != null ? amount.toFixed(2) : '—'
}
export interface PlanLoadForm {
id?: string
customerId: string
customerName: string
address: string
lng: number | null
lat: number | null
loadTime: string
contact: string
notes: string
generateDibang: number
dibangDirection: string
contracts: PlanContractLink[]
products: PlanStopProductFormRow[]
}
export interface PlanDeliveryForm {
id?: string
seq: number
customerId: string
customerName: string
address: string
lng: number | null
lat: number | null
deliveryTime: string
contact: string
notes: string
generateDibang: number
dibangDirection: string
contracts: PlanContractLink[]
products: PlanStopProductFormRow[]
}
/**
* 地磅勾选规则:
* - 内部客户(山峡/云栋):默认勾选且不可改
* - 地磅可选车(如皖H21522)+ 内部客户:默认不勾,可手动勾选
* - 其他客户:默认不勾且不可勾
*/
export function resolveStopDibangDefaults(
customerName?: string | null,
stopType: 'load' | 'delivery' = 'load',
vehiclePlate?: string | null
): { generateDibang: number; dibangDirection: string } {
if (!isPlInternalCustomerName(customerName)) {
return { generateDibang: 0, dibangDirection: 'none' }
}
if (isPlDibangOptionalVehiclePlate(vehiclePlate)) {
return { generateDibang: 0, dibangDirection: 'none' }
}
if (stopType === 'load') {
return { generateDibang: 1, dibangDirection: 'outbound' }
}
return { generateDibang: 1, dibangDirection: 'inbound' }
}
/** 仅「内部客户 + 地磅可选车」允许用户手动勾选地磅单 */
export function canManualToggleStopDibang(
customerName?: string | null,
vehiclePlate?: string | null
): boolean {
return isPlInternalCustomerName(customerName) && isPlDibangOptionalVehiclePlate(vehiclePlate)
}
function normalizeStopDibangFieldsFromApi(
customerName: string | null | undefined,
stopType: 'load' | 'delivery',
generateDibang?: number | null,
dibangDirection?: string | null,
vehiclePlate?: string | null
): { generateDibang: number; dibangDirection: string } {
const defaults = resolveStopDibangDefaults(customerName, stopType, vehiclePlate)
// 非山峡/云栋:禁止生成地磅单(编辑回填也强制清掉历史勾选)
if (!isPlInternalCustomerName(customerName)) {
return { generateDibang: 0, dibangDirection: 'none' }
}
const gen = Number(generateDibang ?? defaults.generateDibang) === 1 ? 1 : 0
let direction = (dibangDirection || '').trim()
if (gen) {
if (direction !== 'outbound' && direction !== 'inbound') {
// 可选车默认 direction 为 none,勾选回填时按段类型补方向
direction = stopType === 'load' ? 'outbound' : 'inbound'
}
} else {
direction = 'none'
}
return { generateDibang: gen, dibangDirection: direction }
}
export function createStopProductsFromPlanProducts(
products: PlanFormProductRow[],
qtyField: 'quantity' | 'actualQty' = 'quantity'
): PlanStopProductFormRow[] {
return products.map((item) => ({
planProductId: item.id,
productId: item.productId,
productNo: item.productNo,
productName: item.productName,
standardProductName: item.standardProductName || item.productName,
aliasName: item.aliasName,
aliasOptions: item.aliasOptions ? [...item.aliasOptions] : undefined,
brandMode: item.brandMode,
brandId: item.brandId,
brandName: item.brandName,
brandOptions: item.brandOptions ? [...item.brandOptions] : undefined,
model: item.model,
unit: item.unit || '吨',
packageSpec: item.packageSpec,
deliveryMethod: item.deliveryMethod,
planQty: Number(item[qtyField] ?? item.quantity ?? 0),
remark: item.remark,
isExplosive: item.isExplosive,
isToxic: item.isToxic
}))
}
function matchPrevStopProduct(
stopProducts: PlanStopProductFormRow[],
planRow: PlanFormProductRow,
used: Set<number>
): PlanStopProductFormRow | undefined {
if (planRow.id) {
const byPlanProductId = stopProducts.findIndex(
(row, index) =>
!used.has(index) && row.planProductId && String(row.planProductId) === String(planRow.id)
)
if (byPlanProductId >= 0) {
used.add(byPlanProductId)
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)
)
if (byProductId >= 0) {
used.add(byProductId)
return stopProducts[byProductId]
}
return undefined
}
/**
* 按计划产品重建段产品列表:增删对齐计划层;共享字段取自计划;
* 可选保留段内已改的数量 / 备注。
*/
export function reconcileStopProductsWithPlan(
stopProducts: PlanStopProductFormRow[],
planProducts: PlanFormProductRow[],
options?: {
preserveQty?: boolean
preserveRemark?: boolean
}
): PlanStopProductFormRow[] {
const preserveQty = options?.preserveQty !== false
const preserveRemark = options?.preserveRemark !== false
const used = new Set<number>()
const nextRows = createStopProductsFromPlanProducts(planProducts)
return nextRows.map((row, index) => {
const planRow = planProducts[index]
const prev = planRow ? matchPrevStopProduct(stopProducts, planRow, used) : undefined
if (!prev) return row
return {
...row,
planQty: preserveQty ? (prev.planQty ?? row.planQty) : row.planQty,
remark: preserveRemark ? (prev.remark ?? row.remark) : row.remark
}
})
}
/** 用计划产品行补齐装货/送货产品表展示字段(别名、品牌等) */
export function sumStopProductsPlanQty(products: PlanStopProductFormRow[]): number {
return products.reduce((sum, row) => sum + Number(row.planQty ?? 0), 0)
}
/** 段内紧凑表「产品」列副行:别名 · 品牌 · 规格 · 单位 */
export function formatStopProductMetaSubline(
row: PlanStopProductFormRow,
unitLabel?: string
): string {
const parts: string[] = []
if (row.aliasName) parts.push(`别名:${row.aliasName}`)
const brand = formatBrandConstraintLabel(row.brandMode, row.brandName)
if (brand && brand !== '—') parts.push(`品牌:${brand}`)
if (row.model) parts.push(row.model)
const unit = unitLabel || row.unit
if (unit) parts.push(unit)
return parts.join(' · ')
}
export function enrichStopProductsDisplayFromPlan(
stopProducts: PlanStopProductFormRow[],
planProducts: Array<{
id?: string
productId: string
productNo?: string
productName?: string
standardProductName?: string
aliasName?: string
aliasOptions?: string[]
brandMode?: string
brandId?: string
brandName?: string
brandOptions?: BrandOption[]
model?: string
unit?: string
packageSpec?: string
deliveryMethod?: string
quantity?: number
isExplosive?: number
isToxic?: number
}>
): PlanStopProductFormRow[] {
return stopProducts.map((row) => {
const match =
planProducts.find(
(p) => row.planProductId && p.id && String(p.id) === String(row.planProductId)
) || planProducts.find((p) => String(p.productId) === String(row.productId))
if (!match) return row
const planQty =
row.planQty != null && Number.isFinite(Number(row.planQty))
? Number(row.planQty)
: match.quantity != null
? Number(match.quantity)
: row.planQty
return {
...row,
productNo: row.productNo || match.productNo || row.productNo,
standardProductName: match.standardProductName || match.productName || row.productName,
aliasName: match.aliasName ?? row.aliasName,
aliasOptions: match.aliasOptions?.length ? [...match.aliasOptions] : row.aliasOptions,
brandMode: match.brandMode ?? row.brandMode,
brandId: match.brandId ?? row.brandId,
brandName: match.brandName ?? row.brandName,
brandOptions: match.brandOptions?.length ? [...match.brandOptions] : row.brandOptions,
model: row.model || match.model,
unit: row.unit || match.unit || '吨',
packageSpec: row.packageSpec || match.packageSpec,
deliveryMethod: row.deliveryMethod || match.deliveryMethod,
planQty,
isExplosive: match.isExplosive ?? row.isExplosive,
isToxic: match.isToxic ?? row.isToxic
}
})
}
/** 查看态:仅展示段上真实产品(空段不回退计划产品);有段产品时补齐展示字段 */
export function resolveStopProductsForView(
stopProducts: PlanStopProductFormRow[] | undefined | null,
planProducts: PlanFormProductRow[]
): PlanStopProductFormRow[] {
const rows = stopProducts?.length ? stopProducts : []
return enrichStopProductsDisplayFromPlan(rows, planProducts)
}
export function createEmptyLoadForm(): PlanLoadForm {
return {
customerId: '',
customerName: '',
address: '',
lng: null,
lat: null,
loadTime: '',
contact: '',
notes: '',
generateDibang: 0,
dibangDirection: 'none',
contracts: [],
products: []
}
}
export function createEmptyDeliveryForm(seq = 1): PlanDeliveryForm {
return {
seq,
customerId: '',
customerName: '',
address: '',
lng: null,
lat: null,
deliveryTime: '',
contact: '',
notes: '',
generateDibang: 0,
dibangDirection: 'none',
contracts: [],
products: []
}
}
function mapStopProductFromApi(row: PlanStopProduct): PlanStopProductFormRow {
return {
id: row.id,
planProductId: row.planProductId,
productId: row.productId,
productNo: row.productNo,
productName: row.productName,
model: row.model,
unit: row.unit || '吨',
packageSpec: row.packageSpec,
deliveryMethod: row.deliveryMethod,
planQty: row.planQty != null ? Number(row.planQty) : undefined,
inboundQty: row.inboundQty != null ? Number(row.inboundQty) : undefined,
inboundPrice: row.inboundPrice != null ? Number(row.inboundPrice) : undefined,
inboundAmount: row.inboundAmount != null ? Number(row.inboundAmount) : undefined,
outboundQty: row.outboundQty != null ? Number(row.outboundQty) : undefined,
outboundPrice: row.outboundPrice != null ? Number(row.outboundPrice) : undefined,
outboundAmount: row.outboundAmount != null ? Number(row.outboundAmount) : undefined,
brandQtys: (row.brandQtys || [])
.filter((item) => Number(item.qty) > 0)
.map((item) => ({
brandId: String(item.brandId || ''),
brandName: item.brandName,
qty: Number(item.qty),
unitPrice:
item.unitPrice != null && Number.isFinite(Number(item.unitPrice))
? Number(item.unitPrice)
: undefined
})),
remark: row.remark
}
}
/** 查看态装货地址:优先段表 address,回退计划 pickupAddress */
export function resolveLoadViewAddress(
plan?: { pickupAddress?: string | null } | null,
load?: { address?: string | null } | null
): string {
const fromStop = (load?.address || '').trim()
if (fromStop) return fromStop
return (plan?.pickupAddress || '').trim()
}
/** 查看态送货地址:优先段表 address,首段可回退计划 destinationAddress */
export function resolveDeliveryViewAddress(
plan?: { destinationAddress?: string | null } | null,
delivery?: { address?: string | null } | null,
index = 0
): string {
const fromStop = (delivery?.address || '').trim()
if (fromStop) return fromStop
if (index === 0) {
return (plan?.destinationAddress || '').trim()
}
return ''
}
export function mapLoadFromApi(
load?: PlanLoad | null,
vehiclePlate?: string | null
): PlanLoadForm {
if (!load) return createEmptyLoadForm()
const dibang = normalizeStopDibangFieldsFromApi(
load.customerName,
'load',
load.generateDibang,
load.dibangDirection,
vehiclePlate
)
return {
id: load.id || undefined,
customerId: load.customerId || '',
customerName: load.customerName || '',
address: load.address || '',
lng: load.lng != null ? Number(load.lng) : null,
lat: load.lat != null ? Number(load.lat) : null,
loadTime: (load.loadTime || '').slice(0, 10),
contact: load.contact || '',
notes: load.notes || '',
generateDibang: dibang.generateDibang,
dibangDirection: dibang.dibangDirection,
contracts: (load.contracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
})),
products: (load.products || []).map(mapStopProductFromApi)
}
}
export function mapDeliveriesFromApi(
deliveries?: PlanDelivery[],
vehiclePlate?: string | null
): PlanDeliveryForm[] {
if (!deliveries?.length) return [createEmptyDeliveryForm(1)]
return deliveries.map((row, index) => {
const dibang = normalizeStopDibangFieldsFromApi(
row.customerName,
'delivery',
row.generateDibang,
row.dibangDirection,
vehiclePlate
)
return {
id: row.id || undefined,
seq: row.seq ?? index + 1,
customerId: row.customerId || '',
customerName: row.customerName || '',
address: row.address || '',
lng: row.lng != null ? Number(row.lng) : null,
lat: row.lat != null ? Number(row.lat) : null,
deliveryTime: (row.deliveryTime || '').slice(0, 10),
contact: row.contact || '',
notes: row.notes || '',
generateDibang: dibang.generateDibang,
dibangDirection: dibang.dibangDirection,
contracts: (row.contracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
})),
products: (row.products || []).map(mapStopProductFromApi)
}
})
}
/** 旧数据无 load/deliveries 时从供方/需方字段回填 */
export function mapLegacyPlanToStops(plan: Plan): {
load: PlanLoadForm
deliveries: PlanDeliveryForm[]
} {
const plate = plan.licensePlate
const loadDefaults = resolveStopDibangDefaults(plan.supplierName, 'load', plate)
const deliveryDefaults = resolveStopDibangDefaults(plan.customerName, 'delivery', plate)
const stopProducts = createStopProductsFromPlanProducts(
(plan.products || []).map((item, index) => ({
id: item.id,
productId: String(item.productId || index),
productNo: item.productNo || '',
productName: item.productName || '',
model: item.model,
unit: item.unit || '吨',
quantity: Number(item.quantity ?? item.actualQty ?? 0),
packageSpec: item.packageSpec,
deliveryMethod: item.deliveryMethod,
remark: item.remark
}))
)
return {
load: {
customerId: plan.supplierId || '',
customerName: plan.supplierName || '',
address: plan.pickupAddress || '',
lng: plan.pickupLng != null ? Number(plan.pickupLng) : null,
lat: plan.pickupLat != null ? Number(plan.pickupLat) : null,
loadTime: (plan.pickupTime || '').slice(0, 10),
contact: plan.pickupContact || '',
notes: plan.pickupNotes || '',
generateDibang: loadDefaults.generateDibang,
dibangDirection: loadDefaults.dibangDirection,
contracts: (plan.supplierContracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
})),
products: stopProducts.length ? stopProducts.map((row) => ({ ...row })) : []
},
deliveries: [
{
seq: 1,
customerId: plan.customerId || '',
customerName: plan.customerName || '',
address: plan.destinationAddress || '',
lng: plan.destinationLng != null ? Number(plan.destinationLng) : null,
lat: plan.destinationLat != null ? Number(plan.destinationLat) : null,
deliveryTime: (plan.deliveryTime || '').slice(0, 10),
contact: plan.deliveryContact || '',
notes: plan.destinationNotes || '',
generateDibang: deliveryDefaults.generateDibang,
dibangDirection: deliveryDefaults.dibangDirection,
contracts: (plan.demanderContracts || []).map((item) => ({
contractId: String(item.contractId || ''),
contractNo: item.contractNo
})),
products: stopProducts.length ? stopProducts.map((row) => ({ ...row })) : []
}
]
}
}
export function syncLoadProductsFromPlan(
load: PlanLoadForm,
planProducts: PlanFormProductRow[],
preserveQty = true
) {
load.products = reconcileStopProductsWithPlan(load.products, planProducts, {
preserveQty,
preserveRemark: preserveQty
})
}
export function syncDeliveryProductsFromPlan(
delivery: PlanDeliveryForm,
planProducts: PlanFormProductRow[],
preserveQty = false
) {
delivery.products = reconcileStopProductsWithPlan(delivery.products, planProducts, {
preserveQty,
preserveRemark: preserveQty
})
}
function matchPrevDeliveryProduct(
stopProducts: PlanStopProductFormRow[],
loadRow: PlanStopProductFormRow,
used: Set<number>
): PlanStopProductFormRow | undefined {
if (loadRow.planProductId) {
const byPlanProductId = stopProducts.findIndex(
(row, index) =>
!used.has(index) &&
row.planProductId &&
String(row.planProductId) === String(loadRow.planProductId)
)
if (byPlanProductId >= 0) {
used.add(byPlanProductId)
return stopProducts[byPlanProductId]
}
}
const byProductId = stopProducts.findIndex(
(row, index) => !used.has(index) && String(row.productId) === String(loadRow.productId)
)
if (byProductId >= 0) {
used.add(byProductId)
return stopProducts[byProductId]
}
return undefined
}
function cloneStopProductSharedFields(
loadRow: PlanStopProductFormRow,
planQty: number,
remark?: string
): PlanStopProductFormRow {
return {
...loadRow,
aliasOptions: loadRow.aliasOptions ? [...loadRow.aliasOptions] : undefined,
brandOptions: loadRow.brandOptions ? [...loadRow.brandOptions] : undefined,
planQty,
remark: remark ?? loadRow.remark
}
}
/**
* 从装货产品同步到各送货段(产品清单 ⊆ 装货)。
* C1:仅 1 个送货段时数量跟装货;≥2 段时保留已有段数量,新增行/新增段默认 0。
*/
export function syncDeliveriesProductsFromLoad(
deliveries: PlanDeliveryForm[],
loadProducts: PlanStopProductFormRow[],
options?: { forceQtyFromLoad?: boolean }
): { deliveries: PlanDeliveryForm[]; needSplitHint: boolean } {
const multi = deliveries.length >= 2
const forceQty = options?.forceQtyFromLoad === true
const next = deliveries.map((delivery) => {
const used = new Set<number>()
const products = loadProducts.map((loadRow) => {
const prev = matchPrevDeliveryProduct(delivery.products || [], loadRow, used)
let planQty: number
if (forceQty || !multi) {
planQty = Number(loadRow.planQty ?? 0)
} else if (prev) {
planQty = Number(prev.planQty ?? 0)
} else {
planQty = 0
}
return cloneStopProductSharedFields(loadRow, planQty, prev?.remark)
})
return { ...delivery, products }
})
return { deliveries: next, needSplitHint: multi }
}
/** 装货产品 → 计划层产品行(保存 pl_plan_product / 运费计算) */
export function mapLoadProductsToPlanFormRows(
loadProducts: PlanStopProductFormRow[]
): PlanFormProductRow[] {
return loadProducts.map((row) => ({
id: row.planProductId,
productId: row.productId,
productNo: row.productNo,
productName: row.productName,
standardProductName: row.standardProductName || row.productName,
aliasName: row.aliasName,
aliasOptions: row.aliasOptions ? [...row.aliasOptions] : undefined,
brandMode: row.brandMode,
brandId: row.brandId,
brandName: row.brandName,
brandOptions: row.brandOptions ? [...row.brandOptions] : undefined,
model: row.model,
unit: row.unit || '吨',
quantity: Number(row.planQty ?? 0),
actualQty: 0,
packageSpec: row.packageSpec,
deliveryMethod: row.deliveryMethod,
remark: row.remark,
isExplosive: row.isExplosive,
isToxic: row.isToxic
}))
}
/** 合并装货映射行与既有计划产品元数据(危化证、税额等) */
export function mergePlanProductsFromLoadMirror(
fromLoad: PlanFormProductRow[],
previous: PlanFormProductRow[]
): PlanFormProductRow[] {
return fromLoad.map((row) => {
const prev =
(row.id && previous.find((item) => item.id && String(item.id) === String(row.id))) ||
previous.find((item) => String(item.productId) === String(row.productId))
if (!prev) return row
return {
...row,
id: prev.id || row.id,
categoryName: row.categoryName || prev.categoryName,
standardProductName: row.standardProductName || prev.standardProductName || row.productName,
aliasOptions: row.aliasOptions?.length ? row.aliasOptions : prev.aliasOptions,
brandOptions: row.brandOptions?.length ? row.brandOptions : prev.brandOptions,
taxRate: prev.taxRate,
taxPrice: prev.taxPrice ?? row.taxPrice,
taxAmount: prev.taxAmount,
taxSubtotal: prev.taxSubtotal,
purchaseCertUrl: prev.purchaseCertUrl,
transportCertUrl: prev.transportCertUrl,
legalUseCertUrl: prev.legalUseCertUrl
}
})
}
export function buildStopProductPayload(row: PlanStopProductFormRow) {
return {
planProductId: row.planProductId || undefined,
productId: row.productId,
productNo: row.productNo,
productName: row.productName,
model: row.model,
unit: row.unit,
packageSpec: row.packageSpec,
deliveryMethod: row.deliveryMethod,
planQty: row.planQty ?? 0,
remark: row.remark
}
}
export function buildLoadPayload(load: PlanLoadForm) {
return {
...(load.id ? { id: load.id } : {}),
customerId: load.customerId,
customerName: load.customerName,
address: load.address || undefined,
lng: load.lng,
lat: load.lat,
loadTime: load.loadTime || undefined,
contact: load.contact || undefined,
notes: load.notes || undefined,
generateDibang: load.generateDibang,
dibangDirection: load.dibangDirection,
contracts: load.contracts,
products: load.products.map(buildStopProductPayload)
}
}
export function buildDeliveriesPayload(deliveries: PlanDeliveryForm[]) {
return deliveries.map((row, index) => ({
...(row.id ? { id: row.id } : {}),
seq: row.seq || index + 1,
customerId: row.customerId,
customerName: row.customerName,
address: row.address || undefined,
lng: row.lng,
lat: row.lat,
deliveryTime: row.deliveryTime || undefined,
contact: row.contact || undefined,
notes: row.notes || undefined,
generateDibang: row.generateDibang,
dibangDirection: row.dibangDirection,
contracts: row.contracts,
products: row.products.map(buildStopProductPayload)
}))
}