diff --git a/web-admin/src/utils/pl-batch-print.ts b/web-admin/src/utils/pl-batch-print.ts new file mode 100644 index 000000000..6b2a06490 --- /dev/null +++ b/web-admin/src/utils/pl-batch-print.ts @@ -0,0 +1,109 @@ +// web-admin/src/utils/pl-batch-print.ts +import type { Plan, PlanProduct } from '@/api/pl' +import type { PlPrintSlipCommand } from '@/utils/pl-display' +import { canPrintSlipCommand } from '@/utils/pl-display' + +export type SlipPlanProduct = PlanProduct & { slipPlanNo?: string } + +export function normalizeCustomerName(name?: string | null) { + return String(name || '').trim() +} + +export function canBatchPrintSlip( + plans: Array>, + command: PlPrintSlipCommand +): { ok: boolean; reason?: string } { + if (!plans?.length) return { ok: false, reason: '请先勾选运输计划' } + + if (plans.length === 1) { + if (!canPrintSlipCommand(plans[0].planType, command)) { + return { ok: false, reason: '当前单据类型不支持打印该单据' } + } + return { ok: true } + } + + if (command === 'inbound') { + if (!plans.every((p) => p.planType === 'inbound')) { + return { ok: false, reason: '合打入库单要求:均为入库类计划' } + } + const c = normalizeCustomerName(plans[0].customerName) + const d = plans[0].planDate + if (!c || !d || !plans.every((p) => normalizeCustomerName(p.customerName) === c && p.planDate === d)) { + return { + ok: false, + reason: '合打入库单要求:客户名称一致且计划日期一致,且均为入库类计划' + } + } + return { ok: true } + } + + if (command === 'delivery') { + if (!plans.every((p) => p.planType === 'outbound')) { + return { ok: false, reason: '合打送(提)货单要求:均为出库类计划' } + } + const c = normalizeCustomerName(plans[0].customerName) + const d = plans[0].planDate + if (!c || !d || !plans.every((p) => normalizeCustomerName(p.customerName) === c && p.planDate === d)) { + return { + ok: false, + reason: '合打送(提)货单要求:客户名称一致且计划日期一致,且均为出库类计划' + } + } + return { ok: true } + } + + if (command === 'subpack') { + if (!plans.every((p) => canPrintSlipCommand(p.planType, 'subpack'))) { + return { ok: false, reason: '分装单仅支持出库类计划' } + } + return { ok: true } + } + + return { ok: false, reason: '未知单据类型' } +} + +export function mergeBatchPrintRemarks(plans: Array>) { + return plans + .map((p) => { + const text = String(p.remark || '').trim() + if (!text) return '' + return `${p.planNo}:${text}` + }) + .filter(Boolean) + .join('\n') +} + +/** 备注写入 HTML 模版时换行 */ +export function formatRemarkForSlipHtml(remark?: string | null) { + return String(remark || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\n/g, '
') +} + +/** 合打视图:首条为抬头基准;产品展开并打 slipPlanNo;清空车辆;备注合并 */ +export function buildBatchPrintPlanView(plans: Plan[]): Plan { + if (!plans.length) throw new Error('plans empty') + const base = { ...plans[0] } + const products: SlipPlanProduct[] = [] + for (const p of plans) { + for (const prod of p.products || []) { + products.push({ ...prod, slipPlanNo: p.planNo }) + } + } + base.products = products + base.remark = mergeBatchPrintRemarks(plans) + base.licensePlate = undefined + base.vehicleId = undefined + base.driverId = undefined + base.driverName = undefined + base.trailerNo = undefined + base.personnelSummary = undefined + return base +} + +export function normalizePlanIds(planIdOrIds: string | string[]): string[] { + const ids = Array.isArray(planIdOrIds) ? planIdOrIds : [planIdOrIds] + return ids.map((id) => String(id || '').trim()).filter(Boolean) +}