// Generates the same "Picklist" PDF that /generate-shipments produces, but // from an arbitrary set of m_inout document numbers (e.g. the currently-open // "Zu kommissionieren" shipments on the dashboard). // // Mirrors the data fetch + aggregation + sort of pages/generate-shipments.vue; the // pdfmake layout itself is the shared buildPicklistDocDefinition // (~/utils/picklistPdf.ts), so both code paths produce identical output. interface PicklistOptions { docNos: string[] sortBy?: 'locator' | 'value' | 'sku' | 'mpn' | 'upc' | 'name' | 'qty' | '' userName?: string title?: string } const loadPdfMake = async () => { const pdfMakeModule: any = await import('pdfmake/build/pdfmake.js') const pdfFontsModule: any = await import('pdfmake/build/vfs_fonts.js') const pdfMake = pdfMakeModule.default || pdfMakeModule let vfs: any = null if (pdfFontsModule.pdfMake?.vfs) vfs = pdfFontsModule.pdfMake.vfs else if (pdfFontsModule.default?.pdfMake?.vfs) vfs = pdfFontsModule.default.pdfMake.vfs else if (pdfFontsModule.default?.vfs) vfs = pdfFontsModule.default.vfs else if (pdfFontsModule.default) { const keys = Object.keys(pdfFontsModule.default) if (keys.some(k => k.endsWith('.ttf'))) vfs = pdfFontsModule.default } pdfMake.vfs = vfs return pdfMake } // Shared comparator: organization first, then the chosen field. Used both to sort // the aggregated picklist rows AND to derive the shipment print order, so the // shipments print in exactly the same order the picklist is sorted in. const lineComparator = (sortBy: string) => (a: any, b: any) => { const orgCompare = (a.AD_Org_ID?.identifier || '').localeCompare(b.AD_Org_ID?.identifier || '') if (orgCompare !== 0) return orgCompare if (!sortBy) return 0 switch (sortBy) { case 'value': return (a.M_Product_ID?.Value || '').localeCompare(b.M_Product_ID?.Value || '') case 'sku': return (a.M_Product_ID?.SKU || '').localeCompare(b.M_Product_ID?.SKU || '') case 'mpn': return (a.M_Product_ID?.mpn || '').localeCompare(b.M_Product_ID?.mpn || '') case 'upc': return (a.M_Product_ID?.UPC || '').localeCompare(b.M_Product_ID?.UPC || '') case 'name': return (a.M_Product_ID?.Name || '').localeCompare(b.M_Product_ID?.Name || '') case 'qty': return Number(b.QtyEntered) - Number(a.QtyEntered) case 'locator': { const xCompare = (a.M_Locator_ID?.X || '').toString().localeCompare((b.M_Locator_ID?.X || '').toString(), undefined, { numeric: true }) if (xCompare !== 0) return xCompare const yCompare = (a.M_Locator_ID?.Y || '').toString().localeCompare((b.M_Locator_ID?.Y || '').toString(), undefined, { numeric: true }) if (yCompare !== 0) return yCompare return (a.M_Locator_ID?.Z || '').toString().localeCompare((b.M_Locator_ID?.Z || '').toString(), undefined, { numeric: true }) } default: return 0 } } const sortPicklistData = (data: any[], sortBy: string) => [...data].sort(lineComparator(sortBy)) export const useGenerateOpenPicklistPdf = () => { const generate = async ({ docNos, sortBy = 'locator', userName = 'SUPERUSER', title = 'Picklist' }: PicklistOptions) => { if (!docNos?.length) return { ok: false, reason: 'no-docs' as const } const aggregated: any[] = [] const relatedDocNos: string[] = [] // Flat product lines tagged with their shipment + a docNo→shipment map, used to // derive the shipment print order (same comparator as the picklist). Does not // affect the picklist PDF itself. const orderLines: any[] = [] const shipmentByDocNo = new Map() for (const docNo of docNos) { if (!docNo) continue let res: any try { res = await $fetch('/api/processes/inouts/' + docNo) } catch (e) { continue } if (!res?.records?.[0]) continue if (!relatedDocNos.includes(docNo)) relatedDocNos.push(docNo) const docRes = res.records[0] if (!shipmentByDocNo.has(docNo)) shipmentByDocNo.set(docNo, { id: docRes.id, documentNo: docNo }) const productLines = (docRes.m_inoutline || []).filter((l: any) => l.M_Product_ID && l.M_Locator_ID && !l.C_Charge_ID) for (const drl of productLines) { orderLines.push({ ...drl, _docNo: docNo }) const existingIdx = aggregated.findIndex(i => i.AD_Org_ID?.id == drl.AD_Org_ID?.id && i.M_Product_ID?.Value == drl.M_Product_ID?.Value && i.M_Locator_ID?.X == drl.M_Locator_ID?.X && i.M_Locator_ID?.Y == drl.M_Locator_ID?.Y && i.M_Locator_ID?.Z == drl.M_Locator_ID?.Z ) if (existingIdx >= 0) { aggregated[existingIdx].QtyEntered = Number(aggregated[existingIdx].QtyEntered) + Number(drl.QtyEntered) } else { aggregated.push({ ...drl }) } } } if (!aggregated.length) return { ok: false, reason: 'no-lines' as const } const sorted = sortPicklistData(aggregated, sortBy) // Shipment print order — same comparator the picklist uses, first occurrence per // shipment, so the referenced shipments print in the picklist's order. const orderedShipments: { id: any, documentNo: string }[] = [] const seenDoc = new Set() for (const line of [...orderLines].sort(lineComparator(sortBy))) { if (seenDoc.has(line._docNo)) continue seenDoc.add(line._docNo) const sh = shipmentByDocNo.get(line._docNo) if (sh) orderedShipments.push(sh) } // Shipments without pickable product lines still belong in the batch — append them. for (const [doc, sh] of shipmentByDocNo) { if (!seenDoc.has(doc)) { seenDoc.add(doc); orderedShipments.push(sh) } } const today = new Date().toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }) const pdfMake = await loadPdfMake() // Same shared layout as the normal picklist (~/utils/picklistPdf.ts). const docDefinition: any = buildPicklistDocDefinition({ rows: sorted, relatedDocNos, today, title }) const stamp = new Date().toISOString().replace(/[-:T]/g, '_').slice(0, 19) const fileName = `${stamp}-PICKLIST-${userName}.pdf` // Return the pdfmake document handle instead of triggering a download here, so // the caller can preview it, download it (pdf.download), or push it to a server // printer (pdf.getBase64 → /api/print/picklist). See PicklistGenerateModal.vue. const pdf = pdfMake.createPdf(docDefinition) return { ok: true, pdf, fileName, count: aggregated.length, docs: relatedDocNos.length, orderedShipments } } return { generate } }