760 lines
25 KiB
TypeScript
760 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'
|
|
|
|
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 {
|
|
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 {
|
|
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 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
|
|
}
|
|
})
|
|
}
|
|
|
|
/** 查看态:段产品为空时回退计划产品,再补齐展示字段(与编辑弹窗 bootstrap 一致) */
|
|
export function resolveStopProductsForView(
|
|
stopProducts: PlanStopProductFormRow[] | undefined | null,
|
|
planProducts: PlanFormProductRow[]
|
|
): PlanStopProductFormRow[] {
|
|
const rows = stopProducts?.length
|
|
? stopProducts
|
|
: planProducts.length
|
|
? createStopProductsFromPlanProducts(planProducts)
|
|
: []
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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) => ({
|
|
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)
|
|
}))
|
|
}
|