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 formatPlVehiclePlateOptionLabel( licensePlate?: string | null, trailerNo?: string | null ) { const plate = licensePlate?.trim() || '' const trailer = trailerNo?.trim() || '' 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 getPlanProductModel(product: { model?: string | null }) { return product.model || '—' } export function getPlanProductPackageSpec(product: { packageSpec?: string | null }) { return 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 getPlanProductActualQuantity(product: { actualQty?: number | string | null }) { const qty = product.actualQty if (qty === undefined || qty === null || qty === '') return '—' return String(qty) } /** 列表「入库数量」列 */ export function getPlanProductInboundQuantity(product: { inboundQty?: number | string | null }) { const qty = product.inboundQty if (qty === undefined || qty === null || qty === '') return '—' return String(qty) } /** 列表「出库数量」列 */ export function getPlanProductOutboundQuantity(product: { outboundQty?: number | string | null }) { const qty = product.outboundQty 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 const PL_PLAN_COMPANY_OPTIONS = [ { value: 'shanxia', label: '安庆山峡' }, { value: 'yundong', label: '安徽云栋' } ] as const 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' /** 运输计划可打印的单据命令(不区分单据类型,由用户自行选择) */ export const PL_PRINT_SLIP_COMMANDS: PlPrintSlipCommand[] = ['inbound', 'delivery', 'subpack'] export function getPrintableSlipCommands(_planType?: string): PlPrintSlipCommand[] { return PL_PRINT_SLIP_COMMANDS } export function canPrintSlipCommand( _planType: string | undefined, _command: PlPrintSlipCommand ) { return true } export type PlPlanTypeTab = 'all' | 'inbound' | 'outbound' | 'direct' /** 运输计划列表 Tab(单据类型前增加「待安排」「已安排」「草稿中」) */ export type PlPlanListTab = PlPlanTypeTab | 'pending_arrange' | 'arranged' | 'drafting' /** 危化品证上传页 Tab(在单据类型后增加「未上传证书」) */ export type PlPlanCertTab = PlPlanTypeTab | 'cert_incomplete' export const PL_PLAN_TYPE_TABS: { label: string; name: PlPlanTypeTab }[] = [ { label: '全部', name: 'all' }, { label: '入库单', name: 'inbound' }, { label: '出库单', name: 'outbound' }, { label: '直销单', name: 'direct' } ] export const PL_PLAN_LIST_TABS: { label: string; name: PlPlanListTab }[] = [ { label: '待安排', name: 'pending_arrange' }, { label: '已安排', name: 'arranged' }, { label: '草稿中', name: 'drafting' }, ...PL_PLAN_TYPE_TABS ] export const PL_PLAN_CERT_TABS: { label: string; name: PlPlanCertTab }[] = [ { label: '未上传证书', name: 'cert_incomplete' }, ...PL_PLAN_TYPE_TABS ] export function isPlPlanFollowStatusListTab( tab: PlPlanListTab | string ): tab is 'pending_arrange' | 'arranged' | 'drafting' { return tab === 'pending_arrange' || tab === 'arranged' || tab === 'drafting' } export function plPlanTypeTabToFilter(tab: PlPlanTypeTab): string { return tab === 'all' ? '' : tab } export function plPlanListTabToFilters(tab: PlPlanListTab): { planType: string followStatus?: string } { if (tab === 'pending_arrange') { return { planType: '', followStatus: 'pending_arrange' } } if (tab === 'arranged') { return { planType: '', followStatus: 'arranged' } } if (tab === 'drafting') { return { planType: '', followStatus: 'drafting' } } return { planType: plPlanTypeTabToFilter(tab) } } /** 危化品证上传页:证书齐备筛选选项 */ export const PL_PLAN_HAZARD_CERT_READY_FILTER_OPTIONS = [ { label: '未齐备', value: '0' }, { label: '已齐备', value: '1' } ] as const export function plPlanCertTabToFilters(tab: PlPlanCertTab): { planType: string certUploadStatus?: string hazardCertReady?: string } { if (tab === 'cert_incomplete') { return { planType: '', certUploadStatus: 'incomplete', hazardCertReady: '0' } } return { planType: plPlanTypeTabToFilter(tab), hazardCertReady: '' } } 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: 'drafting' }, { label: '等通知', value: 'wait_notice' }, { label: '待安排', value: 'pending_arrange' }, { label: '已安排', value: 'arranged' }, { label: '已完成', value: 'completed' }, { label: '已取消', value: 'cancelled' } ] as const /** 运输计划/危化品证等列表:跟进状态默认勾选(不含已取消) */ export const PL_PLAN_DEFAULT_FOLLOW_STATUSES = PL_PLAN_FOLLOW_STATUS_OPTIONS.filter( (opt) => opt.value !== 'cancelled' ).map((opt) => opt.value) export function getPlPlanFollowStatusTagType(status?: string) { const map: Record = { drafting: 'info', 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 = { drafting: '草稿中', wait_notice: '等通知', pending_arrange: '待安排', arranged: '已安排', completed: '已完成', cancelled: '已取消', draft: '待安排', processing: '进行中' } return status ? map[status] || status : '—' } export function getPlPlanArchiveStatusTagType(status?: string) { if (status === 'archived') return 'info' if (status === 'draft') return 'warning' return 'danger' } export function getPlPlanArchiveStatusLabel(status?: string) { if (status === 'archived') return '已归档' if (status === 'draft') return '已暂存' return '未归档' } 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) { // primary/success 同为翠绿,油卡用 warning(琥珀)区分 return type === 'etc' ? 'primary' : type === 'fuel' ? 'warning' : 'info' } export function getPlFuelCategoryLabel(category: string) { const map: Record = { bailian: '百链', gas_station: '加油站', shanxia_factory: '山峡' } return map[category] || category || '—' } export function getPlFuelCategoryTagType(category: string) { // 注意:本项目 global 样式下 primary 与 success 同为翠绿,不可并用区分类别 const map: Record = { bailian: 'primary', gas_station: 'warning', shanxia_factory: 'info' } return map[category] || 'info' } export function getPlFuelRegisterStatusLabel(status: string) { const map: Record = { normal: '正常', cancelled: '已作废' } return map[status] || status || '—' } export function getPlFuelRegisterStatusTagType(status: string) { const map: Record = { normal: 'success', cancelled: 'info' } return map[status] || 'info' } /** 附件 URL 逗号串 → 非空个数 */ export function countPlAttachmentUrls(raw?: string | null): number { return String(raw || '') .split(',') .map((s) => s.trim()) .filter(Boolean).length } /** 加油登记发微信群纯文本(含库内序号、附件个数) */ export function buildFuelRegisterCopyText(row: { seqNo?: number | null registerNo?: string | null fuelCategory?: string | null registerDate?: string | null registrantName?: string | null licensePlate?: string | null odometerKm?: number | null fuelLiters?: number | null fuelTons?: number | null attachmentUrls?: string | null remark?: string | null }): string { const dash = '—' const seq = row.seqNo != null && Number.isFinite(Number(row.seqNo)) ? String(row.seqNo) : dash const liters = row.fuelLiters != null && Number.isFinite(Number(row.fuelLiters)) ? Number(row.fuelLiters).toFixed(2) : dash let tons = dash if (row.fuelTons != null && Number.isFinite(Number(row.fuelTons))) { tons = Number(row.fuelTons).toFixed(3) } else if (row.fuelLiters != null && Number.isFinite(Number(row.fuelLiters))) { tons = (Number(row.fuelLiters) / 1176).toFixed(3) } const odometer = row.odometerKm != null && Number.isFinite(Number(row.odometerKm)) ? `${Number(row.odometerKm).toFixed(1)} KM` : dash const plate = row.licensePlate?.trim() || dash const lines = [ `【${plate} - 加油登记】`, `序号:${seq}`, `登记单号:${row.registerNo || dash}`, `加油类别:${getPlFuelCategoryLabel(String(row.fuelCategory || '')) || dash}`, `日期:${row.registerDate || dash}`, `登记人:${row.registrantName || dash}`, `车牌号:${plate}`, `期初公里数:${odometer}`, `加油升数:${liters === dash ? dash : `${liters} L`}`, `加油/吨:${tons}`, `附件个数:${countPlAttachmentUrls(row.attachmentUrls)}`, `备注:${row.remark?.trim() || dash}` ] return lines.join('\n') } export function getPlRechargeApplyStatusLabel(status: string) { const map: Record = { draft: '草稿', pending: '待发放', in_approval: '审批中', rejected: '已驳回', disbursed: '已发放', cancelled: '已作废' } return map[status] || status } export function getPlRechargeApplyStatusTagType(status: string) { const map: Record = { draft: 'info', pending: 'warning', in_approval: 'warning', rejected: 'danger', disbursed: 'success', cancelled: 'info' } return map[status] || 'info' } export function getPlRechargeApprovalStatusLabel(status: string) { const map: Record = { approved: '通过', rejected: '驳回', disbursed: '已发放' } return map[status] || status } export function getPlRechargeApprovalStatusTagType(status: string) { const map: Record = { approved: 'success', rejected: 'danger', disbursed: 'primary' } return map[status] || 'info' } /** ISO 日期时间展示:将「T」换成空格,如 2026-09-27 17:13:51 */ export function formatPlDateTime(value?: string | null, empty = '—') { if (value == null || value === '') return empty const text = String(value).replace('T', ' ') return text.length > 19 ? text.slice(0, 19) : text } /** 充值审批节点 → BxApprovalFlowTabs / ApprovalFlowView */ export function mapVehicleRechargeApprovalFlowToNodes( flow: Array<{ id: string nodeName?: string nodeOrder?: number approvalRound?: number | null approvalStatus: string approvalOpinion?: string | null approverName?: string approvalTime?: string | null }>, options?: { withRoundPrefix?: boolean } ) { return flow.map((item, index) => { const baseName = item.nodeName || `节点${item.nodeOrder ?? index + 1}` const nodeName = options?.withRoundPrefix && item.approvalRound ? `第${item.approvalRound}轮 · ${baseName}` : baseName return { id: item.id as unknown as number, nodeName, nodeOrder: item.nodeOrder ?? index + 1, approvalStatus: item.approvalStatus, approvalOpinion: item.approvalOpinion, approverName: item.approverName || '', approvalTime: item.approvalTime ? formatPlDateTime(item.approvalTime, '') : item.approvalTime } }) } 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, '(押)') }