Files
shanxia2.0/web-admin/src/utils/pl-plan-list-display-rows.ts
T
2026-08-27 19:38:01 +08:00

328 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Plan, PlanDelivery, PlanLoad, PlanProduct, PlanStopProduct } from '@/api/pl'
import { formatDate } from '@/utils/format'
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import {
planContractSideLines,
type PlanContractDisplayLine
} from '@/utils/pl-plan-contract-display'
import { mapLegacyPlanToStops } from '@/utils/pl-plan-stop-form'
export type PlPlanListDisplayRowKind = 'load' | 'delivery'
export type PlPlanListColumnScope = 'plan' | 'stop'
export interface PlPlanListDisplayRow {
rowKey: string
plan: Plan
kind: PlPlanListDisplayRowKind
stop: PlanLoad | PlanDelivery
deliverySeq?: number
/** 当前页内计划序号(0-based),用于整单斑马纹 */
planListIndex: number
isPlanFirstRow: boolean
planRowSpan: number
isPlanLastRow: boolean
}
const PLAN_FIXED_MERGE_COL_COUNT = 3
export function resolvePlanStopsForList(plan: Plan): {
load: PlanLoad
deliveries: PlanDelivery[]
} {
// 有段次结构时优先用 load/deliveries(含段上 contracts);勿因 deliveries 为空回退 legacy,
// 否则会丢掉 stop.contracts,误用空的 supplierContracts/demanderContracts。
if (plan.load) {
return {
load: plan.load,
deliveries: plan.deliveries || []
}
}
const legacy = mapLegacyPlanToStops(plan)
return {
load: legacy.load as PlanLoad,
deliveries: legacy.deliveries as PlanDelivery[]
}
}
export function flattenPlanListRows(plans: Plan[]): PlPlanListDisplayRow[] {
const rows: PlPlanListDisplayRow[] = []
plans.forEach((plan, planListIndex) => {
const { load, deliveries } = resolvePlanStopsForList(plan)
const planRowSpan = 1 + deliveries.length
rows.push({
rowKey: `${plan.id}-load`,
plan,
kind: 'load',
stop: load,
planListIndex,
isPlanFirstRow: true,
planRowSpan,
isPlanLastRow: deliveries.length === 0
})
deliveries.forEach((delivery, index) => {
rows.push({
rowKey: `${plan.id}-delivery-${delivery.seq ?? index + 1}`,
plan,
kind: 'delivery',
stop: delivery,
deliverySeq: delivery.seq ?? index + 1,
planListIndex,
isPlanFirstRow: false,
planRowSpan,
isPlanLastRow: index === deliveries.length - 1
})
})
})
return rows
}
export function getPlanListDisplayIndex(
displayRows: PlPlanListDisplayRow[],
displayRowIndex: number,
page: number,
pageSize: number
): number {
const row = displayRows[displayRowIndex]
if (!row?.isPlanFirstRow) return 0
let planIndex = 0
for (let i = 0; i < displayRowIndex; i++) {
if (displayRows[i]?.isPlanFirstRow) planIndex++
}
return (page - 1) * pageSize + planIndex + 1
}
export function isPlPlanListPlanMergeColumn(
columnIndex: number,
visiblePlanProps: string[]
): boolean {
if (columnIndex < PLAN_FIXED_MERGE_COL_COUNT) return true
const planColIndex = columnIndex - PLAN_FIXED_MERGE_COL_COUNT
return planColIndex >= 0 && planColIndex < visiblePlanProps.length
}
export function buildPlPlanListSpanMethod(
visiblePlanProps: string[]
): (args: {
row: PlPlanListDisplayRow
column: { type?: string; property?: string }
rowIndex: number
columnIndex: number
}) => { rowspan: number; colspan: number } | undefined {
const planMergeCount = PLAN_FIXED_MERGE_COL_COUNT + visiblePlanProps.length
return ({ row, columnIndex }) => {
if (columnIndex >= planMergeCount) return undefined
if (row.isPlanFirstRow) {
return { rowspan: row.planRowSpan, colspan: 1 }
}
return { rowspan: 0, colspan: 0 }
}
}
export function getPlPlanListRowClassName({ row }: { row: PlPlanListDisplayRow }): string {
const classes = [row.kind === 'load' ? 'pl-plan-list-row--load' : 'pl-plan-list-row--delivery']
if (row.planListIndex % 2 === 1) classes.push('pl-plan-list-row--plan-striped')
if (row.isPlanLastRow) classes.push('pl-plan-list-row--plan-end')
return classes.join(' ')
}
export function getStopKindLabel(row: PlPlanListDisplayRow): string {
if (row.kind === 'load') return '装货'
return `送货 #${row.deliverySeq ?? 1}`
}
export function formatStopDibangLabel(stop: PlanLoad | PlanDelivery): string {
if (!stop.generateDibang) return ''
const direction = (stop.dibangDirection || '').trim()
if (direction === 'outbound') return '出库'
if (direction === 'inbound') return '入库'
if (direction === 'none') return '无'
return '是'
}
export function getStopDibangTagType(
stop: PlanLoad | PlanDelivery
): 'success' | 'info' | 'warning' | undefined {
if (!stop.generateDibang) return undefined
const direction = (stop.dibangDirection || '').trim()
if (direction === 'outbound') return 'success'
if (direction === 'inbound') return 'success'
return 'info'
}
export function getStopAddress(row: PlPlanListDisplayRow): string {
return (row.stop.address || '').trim() || '—'
}
export function getStopTime(row: PlPlanListDisplayRow): string {
const raw =
row.kind === 'load' ? (row.stop as PlanLoad).loadTime : (row.stop as PlanDelivery).deliveryTime
const text = (raw || '').trim()
if (!text) return '—'
return formatDate(text) || text.slice(0, 10) || '—'
}
export function getStopCustomerName(row: PlPlanListDisplayRow): string {
return (row.stop.customerName || '').trim() || '—'
}
/**
* 归档列表:入库/出库列展示方向(按本段客户)。
* - 装货:山峡/云栋 → 出库;其他 → 入库
* - 送货:山峡/云栋 → 入库;其他 → 出库
*/
export function resolveArchiveListIoColumn(
kind: PlPlanListDisplayRowKind,
customerName?: string | null
): 'inbound' | 'outbound' {
const internal = isPlInternalCustomerName(customerName)
if (kind === 'load') return internal ? 'outbound' : 'inbound'
return internal ? 'inbound' : 'outbound'
}
/** 段次归档实际数量(装货读 inboundQty,送货读 outboundQty) */
export function getStopArchiveActualQty(product: PlanStopProduct, kind: PlPlanListDisplayRowKind): number | null {
const raw = kind === 'load' ? product.inboundQty : product.outboundQty
if (raw == null || raw === '') return null
const num = Number(raw)
return Number.isFinite(num) ? num : null
}
export function getStopArchiveUnitPrice(
product: PlanStopProduct,
kind: PlPlanListDisplayRowKind
): number | null {
const raw = kind === 'load' ? product.inboundPrice : product.outboundPrice
if (raw == null || raw === '') return null
const num = Number(raw)
return Number.isFinite(num) ? num : null
}
export function getStopArchiveAmount(
product: PlanStopProduct,
kind: PlPlanListDisplayRowKind
): number | null {
const stored = kind === 'load' ? product.inboundAmount : product.outboundAmount
if (stored != null && stored !== '') {
const num = Number(stored)
if (Number.isFinite(num)) return num
}
const qty = getStopArchiveActualQty(product, kind)
const price = getStopArchiveUnitPrice(product, kind)
if (qty == null || price == null) return null
const amount = qty * price
return Number.isFinite(amount) ? amount : null
}
export function sumStopArchiveActualQty(
stopProducts: PlanStopProduct[],
kind: PlPlanListDisplayRowKind
): number {
return stopProducts.reduce((sum, item) => sum + (getStopArchiveActualQty(item, kind) || 0), 0)
}
export function sumStopArchiveIoQty(
stopProducts: PlanStopProduct[],
kind: PlPlanListDisplayRowKind,
customerName: string | null | undefined,
column: 'inbound' | 'outbound'
): number {
if (resolveArchiveListIoColumn(kind, customerName) !== column) return 0
return sumStopArchiveActualQty(stopProducts, kind)
}
export function sumStopArchiveUnitPrice(
stopProducts: PlanStopProduct[],
kind: PlPlanListDisplayRowKind
): number {
return stopProducts.reduce((sum, item) => sum + (getStopArchiveUnitPrice(item, kind) || 0), 0)
}
export function sumStopArchiveAmount(
stopProducts: PlanStopProduct[],
kind: PlPlanListDisplayRowKind
): number {
return stopProducts.reduce((sum, item) => sum + (getStopArchiveAmount(item, kind) || 0), 0)
}
/** 装货行展示供方;送货行留空 */
export function getStopSupplierNameDisplay(row: PlPlanListDisplayRow): string {
if (row.kind !== 'load') return '—'
return (row.plan.supplierName || row.stop.customerName || '').trim() || '—'
}
/** 送货行展示需方;装货行留空 */
export function getStopDemanderNameDisplay(row: PlPlanListDisplayRow): string {
if (row.kind !== 'delivery') return '—'
return (row.stop.customerName || row.plan.customerName || '').trim() || '—'
}
export function getStopContractLines(row: PlPlanListDisplayRow): PlanContractDisplayLine[] {
if (row.kind === 'load') {
return planContractSideLines('供', row.stop.contracts, row.stop.customerName)
}
return planContractSideLines('需', row.stop.contracts, row.stop.customerName)
}
export function getStopProducts(row: PlPlanListDisplayRow): PlanStopProduct[] {
return row.stop.products || []
}
/** 段内危化证展示:按 planProductId 关联计划产品行 */
export function resolveStopCertProducts(
plan: Plan,
stopProducts: PlanStopProduct[]
): PlanProduct[] {
const planProducts = plan.products || []
const resolved: PlanProduct[] = []
for (const stopProduct of stopProducts) {
const match =
planProducts.find(
(item) =>
stopProduct.planProductId &&
item.id &&
String(item.id) === String(stopProduct.planProductId)
) || planProducts.find((item) => String(item.productId) === String(stopProduct.productId))
if (match) {
resolved.push(match)
continue
}
resolved.push({
id: stopProduct.planProductId || stopProduct.id || stopProduct.productId,
planId: plan.id,
productId: stopProduct.productId,
productNo: stopProduct.productNo,
productName: stopProduct.productName,
model: stopProduct.model,
unit: stopProduct.unit || '吨',
packageSpec: stopProduct.packageSpec,
deliveryMethod: stopProduct.deliveryMethod,
quantity: stopProduct.planQty,
isExplosive: 0,
isToxic: 0
})
}
return resolved
}
export function sumStopProductsField(
stopProducts: PlanStopProduct[],
field: 'planQty' | 'inboundQty' | 'outboundQty'
): number {
return stopProducts.reduce((sum, item) => sum + (Number(item[field]) || 0), 0)
}
export function sumStopActualQty(plan: Plan, stopProducts: PlanStopProduct[]): number {
const planProducts = plan.products || []
return stopProducts.reduce((sum, stopProduct) => {
const match =
planProducts.find(
(item) =>
stopProduct.planProductId &&
item.id &&
String(item.id) === String(stopProduct.planProductId)
) || planProducts.find((item) => String(item.productId) === String(stopProduct.productId))
return sum + (Number(match?.actualQty) || 0)
}, 0)
}