import { string } from 'alga-js' // Per-order invoice generation via the iDempiere process c_invoice_generate. // Extracted from pages/generate-invoices-from-orders.vue so the marketplace // orders-check pages (Amazon/eBay) can create invoices for selected rows with // the exact same semantics. The process response carries no invoice id — only // log lines — so the DocumentNo is parsed out of the logs and the invoice is // then resolved via the generic c_invoice filter endpoint. export const useInvoiceGenerate = () => { const headers = useRequestHeaders(['cookie']) const extractDocumentNo = (str: any): string => { const s = String(str ?? '') // English session: "... Processed: 1000216" const candidate = s.split('Processed:')?.[1]?.trim() ?? '' if (candidate !== '') return candidate // Language-agnostic fallback — iDempiere translates the label per session // language, but the log line always ends with ": ". Doc numbers // may be non-numeric, so take the last token after the colon. const m = s.match(/:\s*(\S+)\s*$/) return m ? m[1] : '' } const lookupInvoice = async (docNo: string, orderId: number | null = null): Promise => { if (!docNo) return null const fetchOne = async (filter: string) => { const res: any = await $fetch(`/api/filters/c_invoice/${string.urlEncode(filter)}`, { headers }) return res?.records?.[0] || null } try { // AND the order id into the filter so a DocumentNo shared with another // doctype can't resolve to the wrong record; the filter endpoint orders // c_invoice_id desc, so records[0] is the newest on the fallback path too. let rec = await fetchOne( `DocumentNo eq '${docNo}'` + (orderId ? ` and C_Order_ID eq ${orderId}` : '') ) if (!rec && orderId) rec = await fetchOne(`DocumentNo eq '${docNo}'`) if (rec) { return { invoiceId: rec.id, invoiceDocNo: rec.DocumentNo, invoiceDate: rec.DateInvoiced, amount: rec.GrandTotal, currency: rec.C_Currency_ID?.identifier || '', docStatus: rec.DocStatus?.id || rec.DocStatus?.identifier || rec.DocStatus || '' } } } catch (e) {} return null } // Runs the process for ONE order. Never throws — callers can loop safely. // status: 'success' = doc no parsed + invoice resolved; 'warning' = process ok // but no doc no (nothing generated) or lookup missed; 'error' = process failed. const generateInvoiceForOrder = async ({ orderId, organizationId, dateInvoiced, docAction = 'CO' }: { orderId: number organizationId: number | string dateInvoiced: string docAction?: string }): Promise => { try { const res: any = await $fetch('/api/processes/c_invoice_generate', { method: 'POST', headers, body: { dateInvoiced, organizationId, orderId, docAction, consolidateDocument: false } }) if (Number(res?.status) === 200 && Number(res?.logs?.length || 0) >= 1) { const docNos: string[] = [] for (const log of res.logs || []) { const extracted = extractDocumentNo(log?.msg ?? log) if (extracted && !docNos.includes(extracted)) docNos.push(extracted) } if (docNos.length) { const invoice = await lookupInvoice(docNos[0], orderId) return { status: invoice ? 'success' : 'warning', docNos, invoice, summary: res.summary || '', message: res.summary || (invoice ? 'Rechnung erstellt' : 'Rechnung erstellt (Details nicht auflösbar)') } } return { status: 'warning', docNos: [], invoice: null, summary: res.summary || '', message: res.summary || 'Keine Rechnung erzeugt' } } return { status: 'error', docNos: [], invoice: null, summary: res?.summary || '', message: res?.message || res?.summary || 'Rechnungserstellung fehlgeschlagen' } } catch (err: any) { return { status: 'error', docNos: [], invoice: null, summary: '', message: err?.data?.message || err?.message || 'Rechnungserstellung fehlgeschlagen' } } } return { extractDocumentNo, lookupInvoice, generateInvoiceForOrder } }