import { string } from 'alga-js' import refreshTokenHelper from "../../../utils/refreshTokenHelper" import forceLogoutHelper from "../../../utils/forceLogoutHelper" import errorHandlingHelper from "../../../utils/errorHandlingHelper" const handleFunc = async (event: any, authToken: any = null) => { let data: any = {} const config = useRuntimeConfig() const token = authToken ?? await getTokenHelper(event) const organizationId = getCookie(event, 'organizationId') const query = getQuery(event) // Get selected year from query parameter, default to current year const selectedYear = query.year ? parseInt(query.year as string) : new Date().getFullYear() // Fetch all fulfillment customers with their invoices, orders, and material receipts in one request for better performance const res: any = await event.context.fetch( `models/c_bpartner?$filter=${string.urlEncode('isFulfillmentCustomer eq true and IsActive eq true')}&$expand=C_Invoice.c_bpartner_id,C_Order.c_bpartner_id,M_InOut.c_bpartner_id&$orderby=${string.urlEncode('Name asc')}`, 'GET', token, null ) console.log('═══════════════════════════════════════════════════') console.log('FULFILLMENT CUSTOMERS API - DEBUG') console.log('═══════════════════════════════════════════════════') console.log('Selected Year:', selectedYear) console.log('Total customers found:', res?.records?.length || 0) if(res) { // Process the data to calculate metrics for each customer if(res.records && Array.isArray(res.records)) { res.records = res.records.map((customer: any) => { const allInvoices = customer['C_Invoice.c_bpartner_id'] || [] const orders = customer['C_Order.c_bpartner_id'] || [] const allInOuts = customer['M_InOut.c_bpartner_id'] || [] // Filter completed/closed invoices: IsActive = true, DocStatus = 'CO' or 'CL', IsSOTrx = true const invoices = allInvoices.filter((invoice: any) => { return invoice.IsActive === true && (invoice.DocStatus?.id === 'CO' || invoice.DocStatus?.id === 'CL') && invoice.IsSOTrx === true }) // Filter draft orders separately: IsActive = true, DocStatus = 'DR', IsSOTrx = true const draftOrders = orders.filter((order: any) => { return order.IsActive === true && order.DocStatus?.id === 'DR' && order.IsSOTrx === true }) // Filter material receipts: IsActive = true, DocStatus = 'CO' or 'CL', IsSOTrx = false (material receipts are purchases) // Also ensure the C_BPartner_ID matches the current customer const materialReceipts = allInOuts.filter((inout: any) => { const bpartnerMatch = inout.C_BPartner_ID?.id === customer.id || inout.C_BPartner_ID === customer.id || String(inout.C_BPartner_ID) === String(customer.id) return inout.IsActive === true && (inout.DocStatus?.id === 'CO' || inout.DocStatus?.id === 'CL') && inout.IsSOTrx === false && bpartnerMatch }) console.log('---------------------------------------------------') console.log('Customer:', customer.Name, '(ID:', customer.id, ')') console.log('Total invoices (raw):', allInvoices.length) console.log('Filtered invoices:', invoices.length) console.log('Total orders:', orders.length) console.log('Total material receipts (raw):', allInOuts.length) console.log('Filtered material receipts:', materialReceipts.length) if(invoices.length > 0) { console.log('Sample invoice:', { id: invoices[0].id, documentNo: invoices[0].DocumentNo, dateInvoiced: invoices[0].DateInvoiced, grandTotal: invoices[0].GrandTotal, docStatus: invoices[0].DocStatus?.id, isActive: invoices[0].IsActive, isSOTrx: invoices[0].IsSOTrx }) } // Calculate total income for all time (using TotalLines - without tax) const totalIncome = invoices.reduce((sum: number, invoice: any) => { return sum + (parseFloat(invoice.TotalLines) || 0) }, 0) // Calculate total income for selected year const yearIncome = invoices.reduce((sum: number, invoice: any) => { if(invoice.DateInvoiced) { const invoiceDate = new Date(invoice.DateInvoiced) if(invoiceDate.getFullYear() === selectedYear) { return sum + (parseFloat(invoice.TotalLines) || 0) } } return sum }, 0) // Calculate invoices by month for selected year const invoicesByMonth: any = {} invoices.forEach((invoice: any) => { // Use PeriodPerformanceDate for month assignment, fallback to DateInvoiced if empty/null const dateField = invoice.PeriodPerformanceDate || invoice.DateInvoiced if(dateField) { const invoiceDate = new Date(dateField) const invoiceYear = invoiceDate.getFullYear() const invoiceMonth = invoiceDate.getMonth() + 1 console.log(` Invoice ${invoice.DocumentNo}: ${dateField} (Year: ${invoiceYear}, Month: ${invoiceMonth}, Selected Year: ${selectedYear})`) if(invoiceYear === selectedYear) { const month = invoiceMonth // 1-12 const monthKey = month.toString() // Ensure string key for consistency if(!invoicesByMonth[monthKey]) { invoicesByMonth[monthKey] = { count: 0, total: 0, draftCount: 0, draftTotal: 0, invoices: [], draftOrders: [], orders: [], firstMaterialReceiptDate: null, firstMaterialReceiptId: null } } invoicesByMonth[monthKey].count++ // Accumulate raw values, will round later invoicesByMonth[monthKey].total += parseFloat(invoice.TotalLines) || 0 invoicesByMonth[monthKey].invoices.push({ id: invoice.id, documentNo: invoice.DocumentNo, dateInvoiced: invoice.DateInvoiced, totalLines: invoice.TotalLines, grandTotal: invoice.GrandTotal, docStatus: invoice.DocStatus, isPdfSent: invoice.isPdfSent || false, pdfSentTime: invoice.PdfSentTime || null }) console.log(` -> Added to month ${month} (key: ${monthKey})`) } } }) // Add draft orders by month for selected year draftOrders.forEach((order: any) => { // Use PeriodPerformanceDate for month assignment, fallback to DateOrdered if empty/null const dateField = order.PeriodPerformanceDate || order.DateOrdered if(dateField) { const orderDate = new Date(dateField) const orderYear = orderDate.getFullYear() const orderMonth = orderDate.getMonth() + 1 if(orderYear === selectedYear) { const month = orderMonth // 1-12 const monthKey = month.toString() if(!invoicesByMonth[monthKey]) { invoicesByMonth[monthKey] = { count: 0, total: 0, draftCount: 0, draftTotal: 0, invoices: [], draftOrders: [], orders: [], firstMaterialReceiptDate: null, firstMaterialReceiptId: null } } invoicesByMonth[monthKey].draftCount++ invoicesByMonth[monthKey].draftTotal += parseFloat(order.GrandTotal) || 0 invoicesByMonth[monthKey].draftOrders.push({ id: order.id, documentNo: order.DocumentNo, dateOrdered: order.DateOrdered, grandTotal: order.GrandTotal, docStatus: order.DocStatus }) } } }) // Process material receipts to find the first (earliest) one for all time for this business partner console.log(`Processing ${materialReceipts.length} material receipts for ${customer.Name}`) let firstMaterialReceipt = null if (materialReceipts.length > 0) { // Sort all material receipts by MovementDate to find the earliest one ever const sortedReceipts = [...materialReceipts].sort((a: any, b: any) => { return new Date(a.MovementDate).getTime() - new Date(b.MovementDate).getTime() }) firstMaterialReceipt = sortedReceipts[0] console.log(` -> First material receipt ever for ${customer.Name}: ${firstMaterialReceipt.DocumentNo} (${firstMaterialReceipt.MovementDate}), ID: ${firstMaterialReceipt.id}`) } // Store the first material receipt in the month where the first invoice/draft exists for the selected year if (firstMaterialReceipt) { // Find the first month with data in the selected year const monthsWithData = Object.keys(invoicesByMonth).map(k => parseInt(k)).sort((a, b) => a - b) if (monthsWithData.length > 0) { const firstMonthKey = monthsWithData[0].toString() invoicesByMonth[firstMonthKey].firstMaterialReceiptDate = firstMaterialReceipt.MovementDate invoicesByMonth[firstMonthKey].firstMaterialReceiptId = firstMaterialReceipt.id console.log(` -> Stored first material receipt in month ${firstMonthKey}`) } } console.log('Invoices by month:', Object.keys(invoicesByMonth).map(m => `Month ${m}: ${invoicesByMonth[m].count} invoices, total: ${invoicesByMonth[m].total}, ${invoicesByMonth[m].draftCount} drafts, draft total: ${invoicesByMonth[m].draftTotal}, first receipt: ${invoicesByMonth[m].firstMaterialReceiptDate || 'none'}`)) // Add draft orders to the month boxes orders.forEach((order: any) => { // Check if it's a fulfillment order by checking description if(order.Description && order.Description.includes('Fulfillment Invoice')) { const orderDate = new Date(order.DateOrdered) if(orderDate.getFullYear() === selectedYear) { const month = orderDate.getMonth() + 1 // 1-12 const monthKey = month.toString() // Ensure string key for consistency if(!invoicesByMonth[monthKey]) { invoicesByMonth[monthKey] = { count: 0, total: 0, draftCount: 0, draftTotal: 0, invoices: [], draftOrders: [], orders: [], firstMaterialReceiptDate: null, firstMaterialReceiptId: null } } invoicesByMonth[monthKey].orders.push({ id: order.id, documentNo: order.DocumentNo, dateOrdered: order.DateOrdered, grandTotal: order.GrandTotal, docStatus: order.DocStatus, description: order.Description }) console.log(` -> Added order to month ${month} (key: ${monthKey})`) } } }) // Round monthly totals to avoid floating-point precision issues Object.keys(invoicesByMonth).forEach(monthKey => { invoicesByMonth[monthKey].total = Math.round(invoicesByMonth[monthKey].total * 100) / 100 }) const result = { ...customer, metrics: { totalIncome: totalIncome, yearIncome: yearIncome, totalInvoices: invoices.length, invoicesByMonth } } console.log('Final metrics for', customer.Name, ':', { totalIncome: result.metrics.totalIncome, yearIncome: result.metrics.yearIncome, totalInvoices: result.metrics.totalInvoices, monthsWithData: Object.keys(result.metrics.invoicesByMonth) }) return result }) // Calculate monthly totals across all customers const monthlyTotals: any = {} for (let month = 1; month <= 12; month++) { monthlyTotals[month] = { total: 0, count: 0 } } res.records.forEach((customer: any) => { if (customer.metrics?.invoicesByMonth) { Object.keys(customer.metrics.invoicesByMonth).forEach(month => { const monthNum = parseInt(month) // Keep raw numbers for accurate totals, round only at display time monthlyTotals[monthNum].total += customer.metrics.invoicesByMonth[month].total || 0 monthlyTotals[monthNum].count += customer.metrics.invoicesByMonth[month].count || 0 }) } }) // Round monthly totals to 2 decimals to avoid floating-point precision issues Object.keys(monthlyTotals).forEach(month => { monthlyTotals[month].total = Math.round(monthlyTotals[month].total * 100) / 100 }) // Add monthly totals to response res.monthlyTotals = monthlyTotals res.selectedYear = selectedYear } data = res } console.log('═══════════════════════════════════════════════════') return data } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch(err: any) { try { let authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch(error) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })