/** * POST /api/accounting/incoming-invoices/upload * Multipart: one file per request (the client uploads many in parallel, tracking * per-file upload % via XHR). Creates the CUST_IncomingInvoice staging record, * stores the PDF in Strapi (attached to that record), and kicks the extraction * pipeline in the BACKGROUND with the in-memory buffer (fire-and-forget), so * uploads stay fast for large batches. */ import refreshTokenHelper from '../../../utils/refreshTokenHelper' import errorHandlingHelper from '../../../utils/errorHandlingHelper' import { createInboxDocument, setInboxStrapiFile } from '../../../utils/inbox/inboxDb' import { getInboxScope, buildInboxCtx } from '../../../utils/inbox/scope' import { attachToStrapi } from '../../../utils/inbox/strapiAttach' import { processInboxDocument } from '../../../utils/inbox/processDocument' const ALLOWED = /pdf|xml|png|jpe?g|tiff?/i export default defineEventHandler(async (event) => { const scope = getInboxScope(event) if (!scope.orgId) return { status: 401, message: 'No organization context' } // Read the multipart body ONCE (a refresh-retry can't re-read it). const form = await readMultipartFormData(event) if (!form) return { status: 400, message: 'No file in request' } let buf: Buffer | null = null let filename = 'invoice.pdf' let mime = 'application/pdf' for (const part of form) { if (['file', 'files', 'attachment'].includes(part.name || '') && part.data?.length) { buf = part.data; filename = part.filename || filename; mime = (part as any).type || mime } } if (!buf || buf.length === 0) return { status: 400, message: 'Empty file' } if (!ALLOWED.test(mime) && !ALLOWED.test(filename)) return { status: 415, message: 'Unsupported file type (PDF/XML/image only)' } // createInboxDocument is the first REST write — safe to retry after refresh // (nothing persisted before it). Strapi attach + background run are best-effort. const persist = async (token: string) => { const ctx = buildInboxCtx(event, token) const id = await createInboxDocument(ctx, { filename, mimeType: mime, byteSize: buf!.length }) if (!id) return { status: 500, message: 'Could not create inbox record' } try { const att = await attachToStrapi(event, token, { tableName: 'CUST_IncomingInvoice', recordId: id, buffer: buf!, filename, mimeType: mime }) await setInboxStrapiFile(ctx, id, att.fileUrl, att.strapiAttachmentId) } catch (e: any) { console.warn('[Inbox] PDF Strapi attach failed (record kept):', e?.message || e) } processInboxDocument(ctx, id, buf!, { mimeType: mime, filename }).catch(() => {}) return { status: 200, id, filename, fileStatus: 'processing' } } try { return await persist(await getTokenHelper(event)) } catch (err: any) { try { return await persist(await refreshTokenHelper(event)) } catch (error: any) { return errorHandlingHelper(err?.data ?? err, error?.data ?? error) } } })