export function getPlVehicleStatusTagType(status: string) { const map: Record = { available: 'success', maintaining: 'warning', scrapped: 'danger' } return map[status] || 'info' } export function getPlVehicleStatusLabel(status: string) { const map: Record = { available: '可用', maintaining: '维修中', scrapped: '报废' } return map[status] || status } export function getPlVehicleTypeLabel(type: string) { return type === 'self' ? '自有' : type === 'carrier' ? '承运商' : type } export function getPlVehicleTypeTagType(type: string) { return type === 'self' ? 'success' : 'warning' } type VehicleTrailerLookup = ReadonlyMap | Record function resolveTrailerFromLookup(plate: string, lookup?: VehicleTrailerLookup) { if (!lookup || !plate) return '' if (lookup instanceof Map) return lookup.get(plate)?.trim() || '' return lookup[plate]?.trim() || '' } /** 按车牌构建挂车号查找表(仅 licensePlate 的业务列表用) */ export function buildVehicleTrailerLookup( vehicles: Array<{ licensePlate?: string | null; trailerNo?: string | null }> = [] ) { const map = new Map() for (const vehicle of vehicles) { const plate = vehicle.licensePlate?.trim() const trailer = vehicle.trailerNo?.trim() if (plate && trailer) map.set(plate, trailer) } return map } /** 列表/摘要:车牌号挂车号(无分隔符) */ export function formatPlVehiclePlateDisplay( licensePlate?: string | null, trailerNo?: string | null, trailerLookup?: VehicleTrailerLookup ) { const plate = licensePlate?.trim() || '' const trailer = trailerNo?.trim() || resolveTrailerFromLookup(plate, trailerLookup) if (!plate && !trailer) return '—' if (plate && trailer) return `${plate}${trailer}` return plate || trailer } export function getPlCarrierStatusLabel(status: string) { return status === '1' ? '合作中' : '已终止' } export function getPlCarrierStatusTagType(status: string) { return status === '1' ? 'success' : 'danger' } export function getPlCarrierAuditStatusLabel(status?: string) { const map: Record = { pending: '待审核', approved: '已通过', rejected: '已驳回' } return status ? map[status] || status : '待审核' } export function getPlCarrierAuditStatusTagType(status?: string) { const map: Record = { pending: 'warning', approved: 'success', rejected: 'danger' } return map[status || ''] || 'info' } export function getPlanProductSpec(product: { model?: string; packageSpec?: string }) { return product.model || product.packageSpec || '—' } export function getPlanProductQuantity(product: { quantity?: number | string | null actualQty?: number | string | null inboundEstimateQty?: number | string | null outboundEstimateQty?: number | string | null }) { const actual = product.actualQty // 实际数量为空或为 0 时,展示合同/计划数量 if (actual !== undefined && actual !== null && actual !== '' && Number(actual) !== 0) { return String(actual) } const qty = product.quantity ?? product.inboundEstimateQty ?? product.outboundEstimateQty if (qty === undefined || qty === null || qty === '') return '—' return String(qty) } export function getPlanProductPlannedQuantity(product: { quantity?: number | string | null }) { const qty = product.quantity if (qty === undefined || qty === null || qty === '') return '—' return String(qty) } export function formatPlanProductListText( product: { productName?: string model?: string packageSpec?: string unit?: string quantity?: number | string | null actualQty?: number | string | null inboundEstimateQty?: number | string | null outboundEstimateQty?: number | string | null }, getUnitLabel?: (unit: string) => string, options?: { usePlannedQuantity?: boolean } ) { const name = product.productName || '—' const spec = getPlanProductSpec(product) const qty = options?.usePlannedQuantity ? getPlanProductPlannedQuantity(product) : getPlanProductQuantity(product) const unitRaw = product.unit || '' const unit = unitRaw ? (getUnitLabel ? getUnitLabel(unitRaw) || unitRaw : unitRaw) : '' const specPart = spec && spec !== '—' ? `(${spec})` : '' let qtyUnit = '—' if (qty !== '—' && unit) { qtyUnit = `${qty}${unit}` } else if (qty !== '—') { qtyUnit = qty } else if (unit) { qtyUnit = unit } return `${name}${specPart},${qtyUnit}` } /** 入库单/出库单列表产品一行:名称 + 品牌/型号规格/单位/包装规格/送货方式 */ export function formatIoOrderProductListText(item: { productName?: string | null brandSummary?: string | null brandName?: string | null specification?: string | null unit?: string | null unitLabel?: string | null packageSpec?: string | null deliveryMethod?: string | null }) { const name = (item.productName || '').trim() || '—' const bits: string[] = [] const brand = (item.brandSummary || item.brandName || '').trim() if (brand) bits.push(`品牌 ${brand}`) const spec = (item.specification || '').trim() if (spec) bits.push(`型号/规格 ${spec}`) const unit = (item.unitLabel || item.unit || '').trim() if (unit) bits.push(`单位 ${unit}`) const packageSpec = (item.packageSpec || '').trim() if (packageSpec) bits.push(`包装规格 ${packageSpec}`) const delivery = (item.deliveryMethod || '').trim() if (delivery) { const deliveryLabels: Record = { self_pick: '山峡自提', one_ticket: '一票制', customer_pick: '客户自提' } bits.push(`送货方式 ${deliveryLabels[delivery] || delivery}`) } return bits.length ? `${name},${bits.join(',')}` : name } /** 品牌数量摘要,如 `君正 16 / 红四方 12` */ export function formatBrandQtySummary( rows?: Array<{ brandName?: string | null; qty?: number | string | null }> | null ): string { if (!rows?.length) return '' const parts = rows .map(row => { const qty = Number(row.qty ?? 0) if (!Number.isFinite(qty) || qty <= 0) return '' const name = (row.brandName || '').trim() || '未命名品牌' return `${name} ${qty}` }) .filter(Boolean) return parts.join(' / ') } /** 现场入出库明细品牌摘要(优先 brandQtys,其次 brandSummary) */ export function resolveSiteBrandSummary(item: { brandQtys?: Array<{ brandName?: string | null; qty?: number | string | null }> | null brandSummary?: string | null brandName?: string | null }) { const fromQtys = formatBrandQtySummary(item.brandQtys) if (fromQtys) return fromQtys const summary = (item.brandSummary || item.brandName || '').trim() return summary || '—' } /** * 出入库「品牌数量」列:优先现场 brandQtys; * 其次归档拼出的「品牌 数量」摘要(排除纯品牌名)。 */ export function resolveIoBrandQtyDisplay(item: { brandQtys?: Array<{ brandName?: string | null; qty?: number | string | null }> | null brandSummary?: string | null brandName?: string | null }) { const fromQtys = formatBrandQtySummary(item.brandQtys) if (fromQtys) return fromQtys const summary = (item.brandSummary || '').trim() if (!summary) return '—' const brandName = (item.brandName || '').trim() if (brandName && summary === brandName) return '—' // 归档摘要形如「君正 12 / 红四方 13」 if (/\s\d/.test(summary)) return summary return '—' } /** 计划品牌约束展示:指定品牌名 / 不限品牌 */ export function resolvePlanBrandDisplayLabel(options: { brandMode?: string | null brandName?: string | null }) { const mode = (options.brandMode || '').trim() const name = (options.brandName || '').trim() if (mode === 'required') return name || '指定品牌' if (mode === 'any') return name || '不限品牌' return name || '' } /** 司机/现场公开页产品一行展示(含别名、品牌、运输计划产品备注) */ export function formatPublicTransportProductLine(product: { productName?: string aliasName?: string | null model?: string packageSpec?: string unit?: string quantity?: number | string | null remark?: string brandMode?: string | null brandName?: string | null }) { const name = product.productName?.trim() || '—' const parts: string[] = [] const alias = product.aliasName?.trim() if (alias) parts.push(`别名 ${alias}`) const brand = resolvePlanBrandDisplayLabel(product) if (brand) parts.push(`品牌 ${brand}`) const model = product.model?.trim() const packageSpec = product.packageSpec?.trim() if (model) parts.push(model) if (packageSpec) parts.push(packageSpec) const qty = getPlanProductPlannedQuantity(product) const unitRaw = product.unit || '吨' parts.push(qty !== '—' ? `${qty} ${unitRaw}` : unitRaw) const remark = product.remark?.trim() parts.push(`备注:${remark || '—'}`) return `${name}(${parts.join(',')})` } export function getPlPlanCompanyLabel(company?: string) { const map: Record = { shanxia: '安庆山峡', yundong: '安徽云栋' } return company ? map[company] || company : '—' } export function getPlPlanTypeTagType(type?: string) { const map: Record = { direct: 'primary', inbound: 'success', outbound: 'warning' } return map[type || ''] || 'info' } export function getPlPlanTypeLabel(type?: string) { const map: Record = { direct: '直销单', inbound: '入库单', outbound: '出库单' } return type ? map[type] || type : '—' } /** 打印单据下拉项:入库单 / 送(提)货单 / 分装单 */ export type PlPrintSlipCommand = 'inbound' | 'delivery' | 'subpack' /** * 按运输计划单据类型返回可打印的单据命令。 * - inbound:仅入库单 * - outbound:送(提)货单、分装单 * - direct / 其他:不可打印 */ export function getPrintableSlipCommands(planType?: string): PlPrintSlipCommand[] { if (planType === 'inbound') return ['inbound'] if (planType === 'outbound') return ['delivery', 'subpack'] return [] } export function canPrintSlipCommand(planType: string | undefined, command: PlPrintSlipCommand) { return getPrintableSlipCommands(planType).includes(command) } export type PlPlanTypeTab = 'all' | 'inbound' | 'outbound' | 'direct' export const PL_PLAN_TYPE_TABS: { label: string; name: PlPlanTypeTab }[] = [ { label: '全部', name: 'all' }, { label: '入库单', name: 'inbound' }, { label: '出库单', name: 'outbound' }, { label: '直销单', name: 'direct' } ] export function plPlanTypeTabToFilter(tab: PlPlanTypeTab): string { return tab === 'all' ? '' : tab } export function plPlanTypeFilterToTab(planType?: string): PlPlanTypeTab { if (planType === 'inbound' || planType === 'outbound' || planType === 'direct') { return planType } return 'all' } /** 运输计划跟进状态选项(筛选) */ export const PL_PLAN_FOLLOW_STATUS_OPTIONS = [ { label: '等通知', value: 'wait_notice' }, { label: '待安排', value: 'pending_arrange' }, { label: '已安排', value: 'arranged' }, { label: '已完成', value: 'completed' }, { label: '已取消', value: 'cancelled' } ] as const export function getPlPlanFollowStatusTagType(status?: string) { const map: Record = { wait_notice: 'warning', pending_arrange: 'danger', arranged: 'success', completed: 'success', cancelled: 'info', // 旧值兼容(迁移前) draft: 'danger', processing: 'warning' } return map[status || ''] || 'info' } export function getPlPlanFollowStatusLabel(status?: string) { const map: Record = { wait_notice: '等通知', pending_arrange: '待安排', arranged: '已安排', completed: '已完成', cancelled: '已取消', draft: '待安排', processing: '进行中' } return status ? map[status] || status : '—' } export function getPlPlanArchiveStatusTagType(status?: string) { return status === 'archived' ? 'info' : 'danger' } export function getPlPlanArchiveStatusLabel(status?: string) { return status === 'archived' ? '已归档' : '未归档' } export function getPlPlanCompleteStatusLabel(status?: string) { return status === 'yes' ? '是' : '否' } export function getPlFreightTypeLabel(type?: string) { const map: Record = { by_weight: '按重量', by_trip: '按趟' } return type ? map[type] || type : '—' } export function getPlYesNoLabel(value?: number | boolean | null) { return value === 1 || value === true ? '是' : '否' } export function getPlHasInvoiceLabelFromSettlement(settlement?: { invoiceNo?: string | null invoiceDate?: string | null invoiceAmount?: number | null invoiceAttachmentUrls?: string | null } | null) { if (!settlement) return '—' const hasInvoice = Boolean(settlement.invoiceNo?.trim()) || Boolean(settlement.invoiceDate) || (settlement.invoiceAmount != null && Number(settlement.invoiceAmount) > 0) || Boolean(settlement.invoiceAttachmentUrls?.trim()) return hasInvoice ? '是' : '否' } /** 解析运输单司机回传附件(JSON 数组或逗号分隔) */ export function parsePlFeedbackImages(raw?: string | string[] | null): string[] { if (!raw) return [] if (Array.isArray(raw)) return raw.map((item) => String(item).trim()).filter(Boolean) const text = String(raw).trim() if (!text) return [] try { const parsed = JSON.parse(text) if (Array.isArray(parsed)) { return parsed.map((item) => String(item).trim()).filter(Boolean) } } catch { // fall through } return text .split(',') .map((item) => item.trim()) .filter(Boolean) } export function getPlRechargeTypeLabel(type: string) { return type === 'etc' ? 'ETC' : type === 'fuel' ? '加油' : type } export function getPlRechargeTypeTagType(type: string) { return type === 'etc' ? 'primary' : type === 'fuel' ? 'success' : 'info' } export function getPlRechargeApplyStatusLabel(status: string) { const map: Record = { pending: '待发放', disbursed: '已发放', cancelled: '已作废' } return map[status] || status } export function getPlRechargeApplyStatusTagType(status: string) { const map: Record = { pending: 'warning', disbursed: 'success', cancelled: 'info' } return map[status] || 'info' } export const PL_GAS_STATION_PRESETS = ['中石化', '线上etc-app', '百链'] export const PL_MAINTENANCE_TYPE_PRESETS = ['保养', '维修', '年检'] export function getPlMaintenanceTypeLabel(type: string) { return type || '—' } export function getPlMaintenanceTypeTagType(type: string) { const map: Record = { 保养: 'success', 维修: 'warning', 年检: 'primary' } return map[type] || 'info' } export function getPlMaintenanceApplyStatusLabel(status: string) { const map: Record = { pending: '待处理', processed: '已处理', cancelled: '已作废' } return map[status] || status } export function getPlMaintenanceApplyStatusTagType(status: string) { const map: Record = { pending: 'warning', processed: 'success', cancelled: 'info' } return map[status] || 'info' } export const PL_UREA_UNIT_PRESETS = ['吨桶'] export function getPlUreaApplyStatusLabel(status: string) { const map: Record = { pending: '待发放', disbursed: '已发放', cancelled: '已作废' } return map[status] || status } export function getPlUreaApplyStatusTagType(status: string) { const map: Record = { pending: 'warning', disbursed: 'success', cancelled: 'info' } return map[status] || 'info' } const PL_PERSONNEL_ROLE_SHORT_LABELS: Record = { driver: '驾', escort: '押' } /** 人员角色短标签(列表/摘要等紧凑展示) */ export function getPlPersonnelRoleShortLabel(role?: string | null) { if (!role) return '' return PL_PERSONNEL_ROLE_SHORT_LABELS[role] || role } /** 姓名 + 角色短标签,如 刘道银(驾) */ export function formatPlPersonnelNameWithRole( name?: string | null, role?: string | null, options?: { parentheses?: 'full' | 'half' } ) { const displayName = name?.trim() || '—' const roleLabel = getPlPersonnelRoleShortLabel(role) if (!roleLabel) return displayName const open = options?.parentheses === 'half' ? '(' : '(' const close = options?.parentheses === 'half' ? ')' : ')' return `${displayName}${open}${roleLabel}${close}` } /** 兼容历史 personnelSummary 快照中的完整角色文案 */ export function normalizePlPersonnelSummaryDisplay(text?: string | null) { if (!text?.trim()) return text ?? '' return text .replace(/\(司机\)/g, '(驾)') .replace(/\(押运员\)/g, '(押)') .replace(/(司机)/g, '(驾)') .replace(/(押运员)/g, '(押)') }