| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 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 : []; |
| | | if (!groups.length) return data; |
| | | |
| | | const items = groups.flatMap((group) => |
| | | (Array.isArray(group?.items) ? group.items : []).map((item) => ({ |
| | | ...item, |
| | | productDescription: group?.productName || item?.productDescription || "", |
| | | salesContractNo: group?.salesContractNo || item?.salesContractNo || "", |
| | | 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 || ""}__${getOrderNo(data, row, item)}`; |
| | | if (!map.has(key)) { |
| | | map.set(key, { |
| | | productName: item?.productDescription || "", |
| | | 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 ` |
| | | <tr> |
| | | <td>${startIndex + idx + 1}</td> |
| | | <td class="left">${escapeHtml(item?.floorCode)}</td> |
| | | <td>${sizeText}</td> |
| | | <td>${toNumber(item?.quantity) || ""}</td> |
| | | <td>${getItemArea(item) ? getItemArea(item).toFixed(2) : ""}</td> |
| | | <td class="left">${escapeHtml(item?.remark)}</td> |
| | | <td class="left">${escapeHtml(item?.processRequirement)}</td> |
| | | </tr> |
| | | `; |
| | | }) |
| | | .join(""); |
| | | |
| | | export const printSalesDeliveryNote = (rawData, selectedRow = {}) => { |
| | | 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 printWindow = window.open("", "_blank", "width=1200,height=900"); |
| | | if (!printWindow) { |
| | | throw new Error("æµè§å¨æ¦æªäºå¼¹çªï¼è¯·å
许弹çªåéè¯"); |
| | | } |
| | | |
| | | const html = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8" /> |
| | | <title>${PRINT_TITLE}</title> |
| | | <style> |
| | | body { margin: 0; padding: 0; font-family: "SimSun", serif; color: #222; } |
| | | .page { width: 198mm; margin: 0 auto; padding: 4mm 4mm 6mm; box-sizing: border-box; page-break-after: always; } |
| | | .page:last-child { page-break-after: auto; } |
| | | .head-top { |
| | | display: grid; |
| | | grid-template-columns: 1fr auto 1fr; |
| | | align-items: end; |
| | | margin-bottom: 1px; |
| | | } |
| | | .factory { |
| | | grid-column: 2; |
| | | text-align: center; |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | } |
| | | .page-mark { |
| | | grid-column: 3; |
| | | justify-self: end; |
| | | font-size: 12px; |
| | | margin-right: 8mm; |
| | | margin-bottom: 1px; |
| | | } |
| | | .head-mid { |
| | | display: grid; |
| | | grid-template-columns: 1fr auto 1fr; |
| | | align-items: center; |
| | | margin-bottom: 2px; |
| | | } |
| | | .head-mid-left { font-size: 13px; text-align: left; } |
| | | .head-mid-title { font-size: 20px; font-weight: 700; text-align: center; } |
| | | .head-mid-right { font-size: 13px; text-align: right; padding-right: 8mm; } |
| | | table { width: 100%; border-collapse: collapse; table-layout: fixed; border: 1px solid #222; } |
| | | td, th { border: 1px solid #222; padding: 2px 4px; font-size: 13px; text-align: center; vertical-align: middle; } |
| | | .left { text-align: left; } |
| | | .group-title td { font-weight: 700; } |
| | | .subtotal td, .total-row td { font-weight: 700; } |
| | | .empty td { height: 120px; color: #666; } |
| | | .footer { margin-top: 6px; display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px; font-size: 13px; } |
| | | @media print { |
| | | @page { size: A4 portrait; margin: 8mm; } |
| | | .page { width: 100%; margin: 0; padding: 0; } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | ${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 ` |
| | | <div class="page"> |
| | | <div class="head-top"> |
| | | <div></div> |
| | | <div class="factory">鹤å£å¤©æ²é¢åç»çå</div> |
| | | <div class="page-mark">第${pageIndex + 1}页,å
±${totalPages}页</div> |
| | | </div> |
| | | <div class="head-mid"> |
| | | <div class="head-mid-left">对æ¹åå·: ${escapeHtml(data.deliveryNo || data.shippingNo || selectedRow.expressNumber || "")}</div> |
| | | <div class="head-mid-title">éå®åè´§å</div> |
| | | <div class="head-mid-right">åè´§åå·: ${escapeHtml(data.shipmentNo || data.deliveryNo || "")}</div> |
| | | </div> |
| | | <table> |
| | | <tr> |
| | | <td class="left" colspan="4">客æ·åç§°: ${escapeHtml(data.customerName || selectedRow.customerName || "")}</td> |
| | | <td class="left" colspan="3">è系人: ${escapeHtml(data.contactPerson || selectedRow.contactPerson || "")}</td> |
| | | </tr> |
| | | <tr> |
| | | <td class="left" colspan="4">åè´§å°å: ${escapeHtml(data.deliveryAddress || data.shippingAddress || selectedRow.deliveryAddress || "")}</td> |
| | | <td class="left" colspan="3">èç³»çµè¯: ${escapeHtml(data.contactPhone || selectedRow.contactPhone || "")}</td> |
| | | </tr> |
| | | <tr> |
| | | <th style="width:8%;">åºå·</th> |
| | | <th style="width:22%;">楼å±ç¼å·</th> |
| | | <th style="width:20%;">宽(å¼§é¿)*é«</th> |
| | | <th style="width:10%;">æ°é</th> |
| | | <th style="width:12%;">é¢ç§¯</th> |
| | | <th style="width:10%;">夿³¨</th> |
| | | <th style="width:18%;">å å·¥è¦æ±</th> |
| | | </tr> |
| | | ${ |
| | | 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 ` |
| | | <tr class="group-title"> |
| | | <td colspan="5" class="left">产ååç§°: ${escapeHtml(group.productName)}</td> |
| | | <td colspan="2" class="left">订åç¼å·: ${escapeHtml(group.orderNo)}</td> |
| | | </tr> |
| | | ${rows} |
| | | <tr class="subtotal"> |
| | | <td colspan="3">å°è®¡:</td> |
| | | <td>${subQty || ""}</td> |
| | | <td>${subArea ? subArea.toFixed(2) : ""}</td> |
| | | <td colspan="2"></td> |
| | | </tr> |
| | | `; |
| | | }) |
| | | .join("") |
| | | : `<tr class="empty"><td colspan="7">ææ æç»</td></tr>` |
| | | } |
| | | ${ |
| | | pageIndex === totalPages - 1 |
| | | ? ` |
| | | <tr class="total-row"> |
| | | <td colspan="3">å计:</td> |
| | | <td>${totalQty || ""}</td> |
| | | <td>${totalArea ? totalArea.toFixed(2) : ""}</td> |
| | | <td colspan="2"></td> |
| | | </tr> |
| | | ` |
| | | : "" |
| | | } |
| | | </table> |
| | | ${ |
| | | pageIndex === totalPages - 1 |
| | | ? ` |
| | | <div class="footer"> |
| | | <div>å¶ å å: ${escapeHtml(data.register || selectedRow.entryPersonName || "")}</div> |
| | | <div>å¶åæ¥æ: ${escapeHtml(formatDisplayDate(data.registerDate || data.entryDate || selectedRow.entryDate))}</div> |
| | | <div>客æ·ç¾å:</div> |
| | | <div>ç¾æ¶æ¥æ:</div> |
| | | </div> |
| | | ` |
| | | : "" |
| | | } |
| | | </div> |
| | | `; |
| | | }) |
| | | .join("")} |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | printWindow.document.write(html); |
| | | printWindow.document.close(); |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.focus(); |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | }, 300); |
| | | }; |
| | | }; |