/** * GET /api/accounting/amazon-ads/booking?cInvoiceId=… * "Where is it booked?" — details of the iDempiere AP invoice an Amazon invoice * (Ads or seller fee) was imported as: document, org, vendor, dates, totals, * paid / Lexoffice state and every line with its charge (Kostenart) and tax. * Loaded on demand by the details dialog of the Amazon invoices page, so the list * route stays one bounded query. */ import { string } from 'alga-js' import { withRefresh } from '../../../utils/amazonAds/routeShared' import fetchHelper from '../../../utils/fetchHelper' import getTokenHelper from '../../../utils/getTokenHelper' const isYes = (v: any) => v === true || v === 'Y' || v === 'true' export default withRefresh(async (event, authToken = null) => { const token = authToken ?? await getTokenHelper(event) const cInvoiceId = Number(getQuery(event).cInvoiceId || 0) if (!cInvoiceId) return { status: 400, message: 'cInvoiceId fehlt' } const res: any = await fetchHelper(event, `models/c_invoice?$filter=${string.urlEncode(`C_Invoice_ID eq ${cInvoiceId}`)}&$top=1`, 'GET', token, null) const inv = res?.records?.[0] if (!inv?.id) return { status: 404, message: `Eingangsrechnung #${cInvoiceId} nicht gefunden` } let lines: any[] = [] let linesError: string | null = null try { const lres: any = await fetchHelper(event, `models/c_invoiceline?$filter=${string.urlEncode(`C_Invoice_ID eq ${cInvoiceId}`)}&$orderby=Line asc&$top=200`, 'GET', token, null) lines = (lres?.records || []).map((l: any) => ({ line: l.Line, description: l.Description || '', chargeId: l.C_Charge_ID?.id ?? null, charge: l.C_Charge_ID?.identifier ?? null, product: l.M_Product_ID?.identifier ?? null, taxId: l.C_Tax_ID?.id ?? null, tax: l.C_Tax_ID?.identifier ?? null, net: l.LineNetAmt ?? 0, taxAmt: l.TaxAmt ?? null, total: l.LineTotalAmt ?? null })) } catch (e: any) { linesError = e?.data?.detail || e?.message || String(e) } return { status: 200, booking: { id: inv.id, documentNo: inv.DocumentNo, poReference: inv.POReference || null, description: inv.Description || '', docStatus: inv.DocStatus?.id || inv.DocStatus || '', docStatusLabel: inv.DocStatus?.identifier || '', docType: inv.C_DocTypeTarget_ID?.identifier || inv.C_DocType_ID?.identifier || '', orgId: inv.AD_Org_ID?.id ?? null, org: inv.AD_Org_ID?.identifier ?? null, vendorId: inv.C_BPartner_ID?.id ?? null, vendor: inv.C_BPartner_ID?.identifier ?? null, dateInvoiced: inv.DateInvoiced || null, dateAcct: inv.DateAcct || null, currency: inv.C_Currency_ID?.identifier || 'EUR', paymentTerm: inv.C_PaymentTerm_ID?.identifier || null, totalLines: inv.TotalLines ?? null, grandTotal: inv.GrandTotal ?? null, isPaid: isYes(inv.IsPaid), posted: isYes(inv.Posted), lexofficeUploaded: isYes(inv.isUploadToLexoffice), lexofficeDate: inv.UploadDateLexoffice || null, created: inv.Created || null, createdBy: inv.CreatedBy?.identifier || null, // Storno: iDempiere links the reversal document (Reverse-Correct) on both sides isVoided: ['VO', 'RE'].includes(inv.DocStatus?.id || inv.DocStatus || ''), reversalId: inv.Reversal_ID?.id ?? null, // the FK identifier reads "__" — keep the document number only reversalDocumentNo: inv.Reversal_ID?.identifier ? String(inv.Reversal_ID.identifier).split('_')[0] : null }, lines, linesError } })