import { NextRequest, NextResponse } from 'next/server'; import { getAutotaskClient } from '@/lib/services/autotask-factory'; // Extract serial number from lineItemFullDescription function extractSerialNumber(description: string | null): string | null { if (!description) return null; // Look for "Serial Number(s)" followed by the serial const match = description.match(/Serial Number\(s\)\s+([A-Z0-9]+)/i); return match ? match[1] : null; } export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; const configItemId = searchParams.get('configItemId'); const invoiceId = searchParams.get('invoiceId'); // If invoice ID is provided, just return billing items for that invoice if (invoiceId) { const autotaskClient = getAutotaskClient(); const billingItems = await autotaskClient.queryEntity('BillingItems', { filter: [{ op: 'eq', field: 'invoiceID', value: parseInt(invoiceId) }], }); console.log(`Found ${billingItems.length} billing items for invoice ${invoiceId}`); return NextResponse.json({ configItem: null, billingItems, invoices: [], tickets: [], summary: { totalBilled: billingItems.reduce((sum: number, item: any) => sum + (item.totalAmount || 0), 0), purchaseDate: null, lastInvoiceDate: null, ticketCount: 0, }, }); } if (!configItemId) { return NextResponse.json( { error: 'Configuration Item ID or Invoice ID is required' }, { status: 400 } ); } const autotaskClient = getAutotaskClient(); // Get the configuration item const configItem = await autotaskClient.getConfigurationItemById(parseInt(configItemId)); if (!configItem) { return NextResponse.json( { error: 'Configuration item not found' }, { status: 404 } ); } // Get date range from query params const startDateParam = searchParams.get('startDate'); const endDateParam = searchParams.get('endDate'); const daysBackParam = searchParams.get('daysBack'); let startDateFilter: string; let endDateFilter: string; if (startDateParam && endDateParam) { // Use explicit date range startDateFilter = startDateParam; endDateFilter = endDateParam; console.log('Using explicit date range:', { startDateFilter, endDateFilter }); } else { // Fallback to daysBack (for backward compatibility) const daysBack = parseInt(daysBackParam || '90'); const dateAgo = new Date(); dateAgo.setDate(dateAgo.getDate() - daysBack); startDateFilter = dateAgo.toISOString().split('T')[0]; endDateFilter = new Date().toISOString().split('T')[0]; console.log('Using daysBack:', daysBack); } console.log('Searching for enrichment data:', { configItemId, companyID: configItem.companyID, serialNumber: configItem.serialNumber, startDate: startDateFilter, endDate: endDateFilter }); let invoices: any[] = []; try { invoices = await autotaskClient.queryEntity('Invoices', { filter: [ { op: 'eq', field: 'companyID', value: configItem.companyID }, { op: 'gte', field: 'invoiceDateTime', value: startDateFilter }, { op: 'lte', field: 'invoiceDateTime', value: endDateFilter } ], }); console.log(`Found ${invoices.length} invoices for company between ${startDateFilter} and ${endDateFilter}`); } catch (err) { console.error('Error fetching invoices:', err); } // Get billing items (line items) for all invoices - hardware only let billingItems: any[] = []; if (invoices.length > 0) { try { const invoiceIds = invoices.map(inv => inv.id); // Fetch only hardware billing items (type 3) to reduce data volume console.log(`Fetching hardware billing items (type 3) for company ${configItem.companyID}...`); const allBillingItems = await autotaskClient.queryEntity('BillingItems', { filter: [ { op: 'eq', field: 'companyID', value: configItem.companyID }, { op: 'gte', field: 'itemDate', value: startDateFilter }, { op: 'lte', field: 'itemDate', value: endDateFilter }, { op: 'eq', field: 'billingItemType', value: 3 } // Hardware only ], }); console.log(`Fetched ${allBillingItems.length} hardware billing items`); if (allBillingItems.length >= 500) { console.warn('⚠️ Hit API limit of 500 records - older purchases may not be included'); } // Enrich items (already filtered to hardware at API level) const enrichedItems = allBillingItems .map((item: any) => { const serialNumber = extractSerialNumber(item.lineItemFullDescription); const profit = (item.totalAmount || 0) - (item.ourCost || 0); const margin = item.totalAmount ? ((profit / item.totalAmount) * 100).toFixed(1) : '0'; return { ...item, extractedSerialNumber: serialNumber, profit, profitMargin: margin, }; }); // If we have a config item serial number, filter to matching items if (configItem.serialNumber) { console.log(`Looking for serial: ${configItem.serialNumber}`); console.log(`Extracted serials from billing items:`, enrichedItems.map((i: any) => i.extractedSerialNumber).filter(Boolean)); // Match if serial appears in extracted field OR anywhere in the description billingItems = enrichedItems.filter((item: any) => { const serial = configItem.serialNumber!.toLowerCase(); const extractedMatch = item.extractedSerialNumber && item.extractedSerialNumber.toLowerCase() === serial; const descriptionMatch = item.lineItemFullDescription && item.lineItemFullDescription.toLowerCase().includes(serial); return extractedMatch || descriptionMatch; }); console.log(`Found ${billingItems.length} billing items matching serial ${configItem.serialNumber}`); // If no exact match, return empty array (only show exact serial matches) if (billingItems.length === 0) { console.log(`No exact serial match found for ${configItem.serialNumber}`); } } else { billingItems = enrichedItems; console.log(`Found ${billingItems.length} hardware billing items (no serial filter)`); } } catch (err) { console.error('Error fetching billing items:', err); } } // Calculate summary const totalBilled = invoices.reduce((sum: number, invoice: any) => sum + (invoice.invoiceTotal || 0), 0 ); const lastInvoiceDate = invoices.length > 0 ? invoices.sort((a: any, b: any) => new Date(b.invoiceDateTime).getTime() - new Date(a.invoiceDateTime).getTime() )[0]?.invoiceDateTime : null; return NextResponse.json({ configItem, billingItems, invoices, tickets: [], summary: { totalBilled, purchaseDate: null, lastInvoiceDate, ticketCount: 0, }, }); } catch (error) { console.error('Error in config enrichment:', error); return NextResponse.json( { error: 'Failed to fetch enrichment data' }, { status: 500 } ); } }