feat: Enhance error handling and logging in expense and interest duplicate detection, add retry logic for document deletion, and improve PDF generation with detailed receipt processing
This commit is contained in:
@@ -78,6 +78,8 @@ export default defineEventHandler(async (event) => {
|
||||
* Find duplicate expenses based on multiple criteria
|
||||
*/
|
||||
function findDuplicateExpenses(expenses: any[]) {
|
||||
console.log('[EXPENSES] Starting duplicate detection for', expenses.length, 'expenses');
|
||||
|
||||
const duplicateGroups: Array<{
|
||||
id: string;
|
||||
expenses: any[];
|
||||
@@ -87,6 +89,7 @@ function findDuplicateExpenses(expenses: any[]) {
|
||||
}> = [];
|
||||
|
||||
const processedIds = new Set<number>();
|
||||
let comparisons = 0;
|
||||
|
||||
for (let i = 0; i < expenses.length; i++) {
|
||||
const expense1 = expenses[i];
|
||||
@@ -102,8 +105,13 @@ function findDuplicateExpenses(expenses: any[]) {
|
||||
if (processedIds.has(expense2.Id)) continue;
|
||||
|
||||
const similarity = calculateExpenseSimilarity(expense1, expense2);
|
||||
comparisons++;
|
||||
|
||||
if (similarity.score >= 0.8) {
|
||||
console.log(`[EXPENSES] Comparing ${expense1.Id} vs ${expense2.Id}: score=${similarity.score.toFixed(3)}, threshold=0.7`);
|
||||
|
||||
if (similarity.score >= 0.7) { // Lower threshold for expenses
|
||||
console.log(`[EXPENSES] MATCH FOUND! ${expense1.Id} vs ${expense2.Id} (score: ${similarity.score.toFixed(3)})`);
|
||||
console.log('[EXPENSES] Match reasons:', similarity.reasons);
|
||||
matches.push(expense2);
|
||||
processedIds.add(expense2.Id);
|
||||
similarity.reasons.forEach(r => matchReasons.add(r));
|
||||
|
||||
@@ -452,81 +452,202 @@ function groupExpenses(expenses: Expense[], groupBy: string): Record<string, Exp
|
||||
|
||||
async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||
console.log('[expenses/generate-pdf] Adding receipt images...');
|
||||
console.log('[expenses/generate-pdf] Total expenses to check:', expenses.length);
|
||||
|
||||
// Log receipt data structure for debugging
|
||||
expenses.forEach((expense, index) => {
|
||||
console.log(`[expenses/generate-pdf] Expense ${index + 1} (ID: ${expense.Id}):`, {
|
||||
establishment: expense['Establishment Name'],
|
||||
hasReceipt: !!expense.Receipt,
|
||||
receiptType: typeof expense.Receipt,
|
||||
receiptLength: Array.isArray(expense.Receipt) ? expense.Receipt.length : 'N/A',
|
||||
receiptData: expense.Receipt
|
||||
});
|
||||
});
|
||||
|
||||
const expensesWithReceipts = expenses.filter(expense =>
|
||||
expense.Receipt && Array.isArray(expense.Receipt) && expense.Receipt.length > 0
|
||||
);
|
||||
|
||||
console.log('[expenses/generate-pdf] Expenses with receipts:', expensesWithReceipts.length);
|
||||
|
||||
if (expensesWithReceipts.length === 0) {
|
||||
console.log('[expenses/generate-pdf] No receipts found to include');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new page for receipts
|
||||
doc.addPage();
|
||||
let totalReceiptImages = 0;
|
||||
let processedImages = 0;
|
||||
|
||||
doc.fontSize(18)
|
||||
.font('Helvetica-Bold')
|
||||
.text('Receipt Images', { align: 'center' });
|
||||
// Count total receipt images for progress tracking
|
||||
expensesWithReceipts.forEach(expense => {
|
||||
if (expense.Receipt && Array.isArray(expense.Receipt)) {
|
||||
totalReceiptImages += expense.Receipt.length;
|
||||
}
|
||||
});
|
||||
|
||||
doc.y += 20;
|
||||
console.log('[expenses/generate-pdf] Total receipt images to process:', totalReceiptImages);
|
||||
|
||||
for (const expense of expensesWithReceipts) {
|
||||
try {
|
||||
// Add expense header
|
||||
doc.fontSize(14)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt for: ${expense['Establishment Name']} - €${expense.PriceNumber?.toFixed(2)}`,
|
||||
{ align: 'left' });
|
||||
console.log('[expenses/generate-pdf] Processing receipts for expense:', expense.Id, expense['Establishment Name']);
|
||||
|
||||
doc.fontSize(12)
|
||||
.font('Helvetica')
|
||||
.text(`Date: ${expense.Time ? formatDate(expense.Time) : 'N/A'}`, { align: 'left' });
|
||||
|
||||
doc.y += 10;
|
||||
|
||||
// Process receipt images
|
||||
if (expense.Receipt) {
|
||||
for (const receipt of expense.Receipt) {
|
||||
if (receipt.url || receipt.directus_files_id?.filename_download) {
|
||||
// Process receipt images - each gets its own page
|
||||
if (expense.Receipt && Array.isArray(expense.Receipt)) {
|
||||
for (const [receiptIndex, receipt] of expense.Receipt.entries()) {
|
||||
if (receipt.url || receipt.directus_files_id?.filename_download || receipt.filename_download) {
|
||||
try {
|
||||
console.log(`[expenses/generate-pdf] Fetching receipt ${receiptIndex + 1}/${expense.Receipt.length} for expense ${expense.Id}`);
|
||||
const imageBuffer = await fetchReceiptImage(receipt);
|
||||
|
||||
if (imageBuffer) {
|
||||
// Check if we need a new page
|
||||
if (doc.y > doc.page.height - 400) {
|
||||
doc.addPage();
|
||||
doc.y = 60;
|
||||
// Add new page for each receipt image
|
||||
doc.addPage();
|
||||
|
||||
// Add header section for this receipt
|
||||
const headerHeight = 100;
|
||||
|
||||
// Header background
|
||||
doc.rect(60, 60, doc.page.width - 120, headerHeight)
|
||||
.fillColor('#f8f9fa')
|
||||
.fill()
|
||||
.strokeColor('#dee2e6')
|
||||
.lineWidth(1)
|
||||
.stroke();
|
||||
|
||||
doc.fillColor('#000000');
|
||||
|
||||
// Receipt header content
|
||||
doc.fontSize(16)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
||||
70, 80, { align: 'left' });
|
||||
|
||||
doc.fontSize(14)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`${expense['Establishment Name']} - €${expense.PriceNumber?.toFixed(2)}`,
|
||||
70, 105, { align: 'left' });
|
||||
|
||||
doc.fontSize(12)
|
||||
.font('Helvetica')
|
||||
.text(`Date: ${expense.Time ? formatDate(expense.Time) : 'N/A'}`,
|
||||
70, 125, { align: 'left' });
|
||||
|
||||
doc.fontSize(10)
|
||||
.fillColor('#666666')
|
||||
.text(`Payer: ${expense.Payer || 'N/A'} | Category: ${expense.Category || 'N/A'}`,
|
||||
70, 140, { align: 'left' });
|
||||
|
||||
doc.fillColor('#000000');
|
||||
|
||||
// Calculate available space for image (full page minus header and margins)
|
||||
const pageWidth = doc.page.width;
|
||||
const pageHeight = doc.page.height;
|
||||
const margin = 60;
|
||||
const imageStartY = 60 + headerHeight + 20; // Header + spacing
|
||||
|
||||
const maxImageWidth = pageWidth - (margin * 2);
|
||||
const maxImageHeight = pageHeight - imageStartY - margin;
|
||||
|
||||
console.log(`[expenses/generate-pdf] Adding large image - Max size: ${maxImageWidth}x${maxImageHeight}, Buffer size: ${imageBuffer.length} bytes`);
|
||||
|
||||
// Add the receipt image with maximum size
|
||||
try {
|
||||
doc.image(imageBuffer, margin, imageStartY, {
|
||||
fit: [maxImageWidth, maxImageHeight],
|
||||
align: 'center',
|
||||
valign: 'center'
|
||||
});
|
||||
|
||||
processedImages++;
|
||||
console.log(`[expenses/generate-pdf] Successfully added receipt image ${processedImages}/${totalReceiptImages}`);
|
||||
|
||||
} catch (imageEmbedError: any) {
|
||||
console.error('[expenses/generate-pdf] Error embedding image in PDF:', imageEmbedError);
|
||||
|
||||
// Add error message on the page
|
||||
doc.fontSize(14)
|
||||
.fillColor('#dc3545')
|
||||
.text('Receipt image could not be embedded', margin, imageStartY + 50, {
|
||||
align: 'center',
|
||||
width: maxImageWidth
|
||||
});
|
||||
|
||||
doc.fontSize(12)
|
||||
.fillColor('#6c757d')
|
||||
.text(`Error: ${imageEmbedError.message || 'Unknown error'}`, margin, imageStartY + 80, {
|
||||
align: 'center',
|
||||
width: maxImageWidth
|
||||
});
|
||||
|
||||
doc.fillColor('#000000');
|
||||
}
|
||||
|
||||
// Add image
|
||||
const maxWidth = 400;
|
||||
const maxHeight = 300;
|
||||
} else {
|
||||
console.warn(`[expenses/generate-pdf] No image buffer received for receipt ${receiptIndex + 1} of expense ${expense.Id}`);
|
||||
|
||||
doc.image(imageBuffer, {
|
||||
fit: [maxWidth, maxHeight],
|
||||
align: 'center'
|
||||
});
|
||||
// Add page with error message
|
||||
doc.addPage();
|
||||
|
||||
doc.y += 20;
|
||||
doc.fontSize(16)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.fontSize(14)
|
||||
.font('Helvetica')
|
||||
.text(`${expense['Establishment Name']} - €${expense.PriceNumber?.toFixed(2)}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.y += 50;
|
||||
|
||||
doc.fontSize(12)
|
||||
.fillColor('#dc3545')
|
||||
.text('Receipt image could not be loaded from storage', { align: 'center' });
|
||||
|
||||
doc.fillColor('#000000');
|
||||
}
|
||||
} catch (imageError) {
|
||||
console.error('[expenses/generate-pdf] Error adding receipt image:', imageError);
|
||||
|
||||
} catch (imageError: any) {
|
||||
console.error(`[expenses/generate-pdf] Error processing receipt ${receiptIndex + 1} for expense ${expense.Id}:`, imageError);
|
||||
|
||||
// Add page with error information
|
||||
doc.addPage();
|
||||
|
||||
doc.fontSize(16)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.fontSize(14)
|
||||
.font('Helvetica')
|
||||
.text(`${expense['Establishment Name']} - €${expense.PriceNumber?.toFixed(2)}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.y += 50;
|
||||
|
||||
doc.fontSize(12)
|
||||
.fillColor('#dc3545')
|
||||
.text('Error loading receipt image', { align: 'center' });
|
||||
|
||||
doc.fontSize(10)
|
||||
.fillColor('#666666')
|
||||
.text('Receipt image could not be loaded', { align: 'center' });
|
||||
.fillColor('#6c757d')
|
||||
.text(`${imageError.message || 'Unknown error'}`, { align: 'center' });
|
||||
|
||||
doc.fillColor('#000000');
|
||||
doc.y += 10;
|
||||
}
|
||||
} else {
|
||||
console.log(`[expenses/generate-pdf] Skipping receipt ${receiptIndex + 1} for expense ${expense.Id} - no valid file path`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doc.y += 20;
|
||||
} catch (error) {
|
||||
console.error('[expenses/generate-pdf] Error processing receipt for expense:', expense.Id, error);
|
||||
console.error('[expenses/generate-pdf] Error processing receipts for expense:', expense.Id, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[expenses/generate-pdf] Completed processing ${processedImages}/${totalReceiptImages} receipt images`);
|
||||
}
|
||||
|
||||
async function fetchReceiptImage(receipt: any): Promise<Buffer | null> {
|
||||
|
||||
Reference in New Issue
Block a user