import QRCode from "qrcode"; const PRINT_TITLE = "销售发货单"; const escapeHtml = (value) => String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); const toNumber = (value) => { const num = Number(value); return Number.isFinite(num) ? num : 0; }; const formatDisplayDate = (value) => { if (!value) return ""; const date = new Date(value); if (Number.isNaN(date.getTime())) return String(value); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}/${month}/${day}`; }; const getItemArea = (item) => toNumber(item?.area || item?.settleTotalArea || item?.actualTotalArea); const getOrderNo = (data, row, item) => item?.salesContractNo || item?.orderNo || data?.salesContractNo || row?.salesContractNo || ""; const PRODUCT_NAME_FIELD_KEYS = [ "productDescription", "productName", "name", "title", "goodsName", "materialName", "glassName", ]; const PRODUCT_NAME_LIST_FIELD_KEYS = [ "productDescriptionList", "productNameList", "productDescriptions", "productNames", "nameList", "goodsNameList", "detailProductNames", "productInfoList", ]; const SPECIFICATION_FIELD_KEYS = ["specificationModel", "specification", "model", "spec"]; const normalizeNameList = (value) => { if (Array.isArray(value)) { return value .flatMap((item) => normalizeNameList(item)) .map((item) => String(item).trim()) .filter(Boolean); } if (value && typeof value === "object") { const objectListNames = PRODUCT_NAME_LIST_FIELD_KEYS.flatMap((key) => normalizeNameList(value?.[key])); if (objectListNames.length) return objectListNames; return PRODUCT_NAME_FIELD_KEYS.flatMap((key) => normalizeNameList(value?.[key])); } if (typeof value === "string") { const text = value.trim(); if (!text) return []; const parts = text .split(/[,\n,、;;]/) .map((item) => item.trim()) .filter(Boolean); return parts.length > 1 ? parts : [text]; } if (value === null || value === undefined) return []; const text = String(value).trim(); return text ? [text] : []; }; const extractNameListByKeys = (source, keys) => { if (!source || typeof source !== "object") return []; return keys.flatMap((key) => normalizeNameList(source?.[key])); }; const resolveProductName = (item, fallbackNames, index) => { const itemNames = [ ...extractNameListByKeys(item, PRODUCT_NAME_LIST_FIELD_KEYS), ...extractNameListByKeys(item, PRODUCT_NAME_FIELD_KEYS), ]; if (itemNames.length > 1) { return itemNames[index] || itemNames[0] || ""; } return itemNames[0] || fallbackNames[index] || fallbackNames[0] || ""; }; const resolveSpecificationModel = (...sources) => { for (const source of sources) { if (!source || typeof source !== "object") continue; for (const key of SPECIFICATION_FIELD_KEYS) { const value = source?.[key]; if (value !== undefined && value !== null && String(value).trim()) { return String(value).trim(); } } } return ""; }; const splitItemsByPage = (items, pageSize) => { const list = Array.isArray(items) ? items : []; if (list.length === 0) return [[]]; const pages = []; for (let i = 0; i < list.length; i += pageSize) { pages.push(list.slice(i, i + pageSize)); } return pages; }; const normalizeInvoiceData = (raw, selectedRow) => { const data = raw ?? {}; const groups = Array.isArray(data.groups) ? data.groups : Array.isArray(data.groupList) ? data.groupList : []; const dataLevelNames = [ ...extractNameListByKeys(data, PRODUCT_NAME_LIST_FIELD_KEYS), ...extractNameListByKeys(data, PRODUCT_NAME_FIELD_KEYS), ]; const items = groups.length ? groups.flatMap((group) => { const groupItems = Array.isArray(group?.items) ? group.items : []; const groupNames = [ ...extractNameListByKeys(group, PRODUCT_NAME_LIST_FIELD_KEYS), ...extractNameListByKeys(group, PRODUCT_NAME_FIELD_KEYS), ]; return groupItems.map((item, index) => ({ ...item, // 优先使用明细自身产品名,兼容“名称数组/分隔字符串”的接口格式 productDescription: resolveProductName(item, groupNames, index), specificationModel: resolveSpecificationModel(item, group, data), salesContractNo: group?.salesContractNo || item?.salesContractNo || "", widthHeight: item?.widthHeight || "", })); }) : (Array.isArray(data.items) ? data.items : []).map((item, index) => ({ ...item, productDescription: resolveProductName(item, dataLevelNames, index), specificationModel: resolveSpecificationModel(item, data, selectedRow), widthHeight: item?.widthHeight || "", })); return { ...data, items, customerName: data.customerName || selectedRow?.customerName || "", contactPerson: data.contactPerson || selectedRow?.contactPerson || "", contactPhone: data.contactPhone || selectedRow?.contactPhone || "", deliveryAddress: data.companyAddress || data.deliveryAddress || data.shippingAddress || selectedRow?.deliveryAddress || "", shipmentNo: data.externalOrderNo || data.shipmentNo || "", register: data.orderMaker || data.register || selectedRow?.entryPersonName || "", registerDate: data.executionDate || data.registerDate || data.entryDate || selectedRow?.entryDate || "", }; }; const groupByProduct = (items, data, row) => { const list = Array.isArray(items) ? items : []; const map = new Map(); list.forEach((item) => { const key = `${item?.productDescription || ""}__${item?.specificationModel || ""}__${getOrderNo( data, row, item )}`; if (!map.has(key)) { map.set(key, { productName: item?.productDescription || "", specificationModel: item?.specificationModel || "", orderNo: getOrderNo(data, row, item), items: [], }); } map.get(key).items.push(item); }); return Array.from(map.values()); }; const renderItemRows = (items, startIndex) => items .map((item, idx) => { const sizeText = item?.widthHeight ? escapeHtml(item.widthHeight) : item?.width || item?.height ? `${escapeHtml(item?.width)} * ${escapeHtml(item?.height)}` : ""; return ` ${startIndex + idx + 1} ${escapeHtml(item?.floorCode)} ${sizeText} ${toNumber(item?.quantity) || ""} ${getItemArea(item) ? getItemArea(item).toFixed(2) : ""} ${escapeHtml(item?.remark)} ${escapeHtml(item?.processRequirement)} `; }) .join(""); export const printSalesDeliveryNote = async (rawData, selectedRow = {}, ledgerIds = null) => { const data = normalizeInvoiceData(rawData, selectedRow); const allItems = Array.isArray(data.items) ? data.items : []; const pageSize = 18; const itemPages = splitItemsByPage(allItems, pageSize); const totalPages = itemPages.length; const ids = Array.isArray(ledgerIds) && ledgerIds.length > 0 ? ledgerIds : selectedRow?.id !== undefined && selectedRow?.id !== null && selectedRow?.id !== "" ? [selectedRow.id] : []; const qrLedgerId = selectedRow?.id !== undefined && selectedRow?.id !== null && selectedRow?.id !== "" ? selectedRow.id : ids.length ? ids[0] : ""; const qrSalesContractNo = String(data?.salesContractNo || selectedRow?.salesContractNo || "").trim(); const qrPayload = JSON.stringify({ id: qrLedgerId, salesContractNo: qrSalesContractNo, type: "XS", }); let qrDataUrl = ""; try { qrDataUrl = await QRCode.toDataURL(qrPayload, { width: 160, margin: 1 }); } catch { qrDataUrl = ""; } const printWindow = window.open("", "_blank", "width=1200,height=900"); if (!printWindow) { throw new Error("浏览器拦截了弹窗,请允许弹窗后重试"); } const html = ` ${PRINT_TITLE} ${itemPages .map((pageItems, pageIndex) => { const pageGroups = groupByProduct(pageItems, data, selectedRow); let serial = pageIndex * pageSize; const totalQty = toNumber(data.totalQuantity) || allItems.reduce((s, it) => s + toNumber(it?.quantity), 0); const totalArea = toNumber(data.totalArea) || allItems.reduce((s, it) => s + getItemArea(it), 0); return `
鹤壁天沐钢化玻璃厂
第${pageIndex + 1}页,共${totalPages}页
对方单号: ${escapeHtml(data.deliveryNo || data.shippingNo || selectedRow.expressNumber || "")}
销售发货单
${qrDataUrl ? `二维码` : ""}
发货单号: ${escapeHtml(data.shipmentNo || data.deliveryNo || "")}
${ pageGroups.length ? pageGroups .map((group) => { const subQty = group.items.reduce((s, it) => s + toNumber(it?.quantity), 0); const subArea = group.items.reduce((s, it) => s + getItemArea(it), 0); const rows = renderItemRows(group.items, serial); serial += group.items.length; return ` ${rows} `; }) .join("") : `` } ${ pageIndex === totalPages - 1 ? ` ` : "" }
客户名称: ${escapeHtml(data.customerName || selectedRow.customerName || "")} 联系人: ${escapeHtml(data.contactPerson || selectedRow.contactPerson || "")}
发货地址: ${escapeHtml(data.deliveryAddress || data.shippingAddress || selectedRow.deliveryAddress || "")} 联系电话: ${escapeHtml(data.contactPhone || selectedRow.contactPhone || "")}
序号 楼层编号 宽(弧长)*高 数量 面积 备注 加工要求
产品名称: ${escapeHtml(group.productName)}${group.specificationModel ? ` 规格型号: ${escapeHtml(group.specificationModel)}` : ""} 订单编号: ${escapeHtml(group.orderNo)}
小计: ${subQty || ""} ${subArea ? subArea.toFixed(2) : ""}
暂无明细
合计: ${totalQty || ""} ${totalArea ? totalArea.toFixed(2) : ""}
${ pageIndex === totalPages - 1 ? ` ` : "" }
`; }) .join("")} `; printWindow.document.write(html); printWindow.document.close(); printWindow.onload = () => { setTimeout(() => { printWindow.focus(); printWindow.print(); printWindow.close(); }, 300); }; };