feat: Enhance receipt processing in PDF generation with overall receipt numbering and improved path extraction for MinIO
This commit is contained in:
parent
a00b3918be
commit
6e99f4f783
|
|
@ -478,6 +478,7 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
|
||||
let totalReceiptImages = 0;
|
||||
let processedImages = 0;
|
||||
let currentReceiptNumber = 0; // Track overall receipt number across all expenses
|
||||
|
||||
// Count total receipt images for progress tracking
|
||||
expensesWithReceipts.forEach(expense => {
|
||||
|
|
@ -495,9 +496,11 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
// 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) {
|
||||
currentReceiptNumber++; // Increment overall receipt counter
|
||||
|
||||
if (receipt.url || receipt.signedUrl || 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}`);
|
||||
console.log(`[expenses/generate-pdf] Fetching receipt ${currentReceiptNumber}/${totalReceiptImages} (expense ${expense.Id}, receipt ${receiptIndex + 1}/${expense.Receipt.length})`);
|
||||
const imageBuffer = await fetchReceiptImage(receipt);
|
||||
|
||||
if (imageBuffer) {
|
||||
|
|
@ -517,10 +520,10 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
|
||||
doc.fillColor('#000000');
|
||||
|
||||
// Receipt header content
|
||||
// Receipt header content with overall numbering
|
||||
doc.fontSize(16)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
||||
.text(`Receipt Image ${currentReceiptNumber} of ${totalReceiptImages}`,
|
||||
70, 80, { align: 'left' });
|
||||
|
||||
doc.fontSize(14)
|
||||
|
|
@ -584,14 +587,14 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
}
|
||||
|
||||
} else {
|
||||
console.warn(`[expenses/generate-pdf] No image buffer received for receipt ${receiptIndex + 1} of expense ${expense.Id}`);
|
||||
console.warn(`[expenses/generate-pdf] No image buffer received for receipt ${currentReceiptNumber} of expense ${expense.Id}`);
|
||||
|
||||
// Add page with error message
|
||||
doc.addPage();
|
||||
|
||||
doc.fontSize(16)
|
||||
.font('Helvetica-Bold')
|
||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
||||
.text(`Receipt Image ${currentReceiptNumber} of ${totalReceiptImages}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.fontSize(14)
|
||||
|
|
@ -609,14 +612,14 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
}
|
||||
|
||||
} catch (imageError: any) {
|
||||
console.error(`[expenses/generate-pdf] Error processing receipt ${receiptIndex + 1} for expense ${expense.Id}:`, imageError);
|
||||
console.error(`[expenses/generate-pdf] Error processing receipt ${currentReceiptNumber} 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}` : ''}`,
|
||||
.text(`Receipt Image ${currentReceiptNumber} of ${totalReceiptImages}`,
|
||||
{ align: 'center' });
|
||||
|
||||
doc.fontSize(14)
|
||||
|
|
@ -637,7 +640,7 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
|||
doc.fillColor('#000000');
|
||||
}
|
||||
} else {
|
||||
console.log(`[expenses/generate-pdf] Skipping receipt ${receiptIndex + 1} for expense ${expense.Id} - no valid file path`);
|
||||
console.log(`[expenses/generate-pdf] Skipping receipt ${currentReceiptNumber} for expense ${expense.Id} - no valid file path`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -655,48 +658,60 @@ async function fetchReceiptImage(receipt: any): Promise<Buffer | null> {
|
|||
const client = getMinioClient();
|
||||
const bucketName = useRuntimeConfig().minio.bucketName;
|
||||
|
||||
// Determine the file path - try multiple possible locations
|
||||
let filePath = null;
|
||||
// Determine the file path - try multiple possible sources
|
||||
let rawPath = null;
|
||||
|
||||
// Try different receipt data structures
|
||||
if (receipt.url) {
|
||||
filePath = receipt.url;
|
||||
// Try different receipt data structures - prioritize signedUrl for S3 URLs
|
||||
if (receipt.signedUrl) {
|
||||
rawPath = receipt.signedUrl;
|
||||
} else if (receipt.url) {
|
||||
rawPath = receipt.url;
|
||||
} else if (receipt.directus_files_id?.filename_download) {
|
||||
filePath = receipt.directus_files_id.filename_download;
|
||||
rawPath = receipt.directus_files_id.filename_download;
|
||||
} else if (receipt.filename_download) {
|
||||
filePath = receipt.filename_download;
|
||||
rawPath = receipt.filename_download;
|
||||
} else if (receipt.id && receipt.filename_disk) {
|
||||
filePath = receipt.filename_disk;
|
||||
rawPath = receipt.filename_disk;
|
||||
} else if (typeof receipt === 'string') {
|
||||
filePath = receipt;
|
||||
rawPath = receipt;
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
if (!rawPath) {
|
||||
console.log('[expenses/generate-pdf] No file path found for receipt:', JSON.stringify(receipt, null, 2));
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('[expenses/generate-pdf] Fetching receipt image from path:', filePath);
|
||||
console.log('[expenses/generate-pdf] Raw path from receipt:', rawPath);
|
||||
|
||||
// Remove any URL prefixes if present
|
||||
if (filePath.includes('/files/')) {
|
||||
const parts = filePath.split('/files/');
|
||||
if (parts.length > 1) {
|
||||
filePath = parts[parts.length - 1];
|
||||
}
|
||||
// Extract MinIO path from S3 URL or use as-is if it's already a path
|
||||
let minioPath = extractMinioPath(rawPath);
|
||||
|
||||
if (!minioPath) {
|
||||
console.log('[expenses/generate-pdf] Could not extract MinIO path from:', rawPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure we're looking in the right place - sometimes files are in receipts/ folder
|
||||
console.log('[expenses/generate-pdf] Extracted MinIO path:', minioPath);
|
||||
|
||||
// Try multiple possible locations in MinIO
|
||||
const possiblePaths = [
|
||||
filePath,
|
||||
`receipts/${filePath}`,
|
||||
`expenses/${filePath}`,
|
||||
filePath.startsWith('receipts/') ? filePath : `receipts/${filePath}`
|
||||
minioPath,
|
||||
`receipts/${minioPath}`,
|
||||
`expenses/${minioPath}`,
|
||||
// Try without any folder prefix
|
||||
minioPath.split('/').pop() || minioPath,
|
||||
// Try in receipts folder with just filename
|
||||
`receipts/${minioPath.split('/').pop() || minioPath}`,
|
||||
// Try in expenses folder with just filename
|
||||
`expenses/${minioPath.split('/').pop() || minioPath}`
|
||||
];
|
||||
|
||||
for (const testPath of possiblePaths) {
|
||||
// Remove duplicates
|
||||
const uniquePaths = [...new Set(possiblePaths)];
|
||||
|
||||
for (const testPath of uniquePaths) {
|
||||
try {
|
||||
console.log('[expenses/generate-pdf] Trying path:', testPath);
|
||||
console.log('[expenses/generate-pdf] Trying MinIO path:', testPath);
|
||||
|
||||
// Check if object exists first
|
||||
await client.statObject(bucketName, testPath);
|
||||
|
|
@ -713,16 +728,16 @@ async function fetchReceiptImage(receipt: any): Promise<Buffer | null> {
|
|||
dataStream.on('error', reject);
|
||||
});
|
||||
|
||||
console.log('[expenses/generate-pdf] Successfully fetched image from:', testPath, 'Size:', imageBuffer.length);
|
||||
console.log('[expenses/generate-pdf] Successfully fetched image from MinIO path:', testPath, 'Size:', imageBuffer.length);
|
||||
return imageBuffer;
|
||||
|
||||
} catch (pathError) {
|
||||
console.log('[expenses/generate-pdf] Path not found:', testPath);
|
||||
console.log('[expenses/generate-pdf] MinIO path not found:', testPath);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[expenses/generate-pdf] Could not find image in any of the attempted paths');
|
||||
console.log('[expenses/generate-pdf] Could not find image in any of the attempted MinIO paths:', uniquePaths);
|
||||
return null;
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -731,6 +746,79 @@ async function fetchReceiptImage(receipt: any): Promise<Buffer | null> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the MinIO path from an S3 URL or return the path as-is
|
||||
*/
|
||||
function extractMinioPath(urlOrPath: string): string | null {
|
||||
try {
|
||||
// If it's already just a path, return it
|
||||
if (!urlOrPath.startsWith('http')) {
|
||||
return urlOrPath;
|
||||
}
|
||||
|
||||
// Parse the URL
|
||||
const url = new URL(urlOrPath);
|
||||
|
||||
// Extract the pathname (removes query parameters)
|
||||
let pathname = decodeURIComponent(url.pathname);
|
||||
|
||||
console.log('[expenses/generate-pdf] URL pathname:', pathname);
|
||||
|
||||
// For S3 URLs, we need to extract the part after the bucket name
|
||||
// Pattern: /database/nc/uploads/path/to/file.jpg
|
||||
// We want: uploads/path/to/file.jpg
|
||||
|
||||
// Remove leading slash
|
||||
if (pathname.startsWith('/')) {
|
||||
pathname = pathname.substring(1);
|
||||
}
|
||||
|
||||
// Look for common patterns
|
||||
if (pathname.includes('/uploads/')) {
|
||||
// Extract everything from 'uploads/' onwards
|
||||
const uploadsIndex = pathname.indexOf('uploads/');
|
||||
const extractedPath = pathname.substring(uploadsIndex);
|
||||
console.log('[expenses/generate-pdf] Extracted path from uploads pattern:', extractedPath);
|
||||
return extractedPath;
|
||||
}
|
||||
|
||||
if (pathname.includes('/nc/uploads/')) {
|
||||
// Extract everything from 'uploads/' onwards
|
||||
const uploadsIndex = pathname.indexOf('uploads/');
|
||||
const extractedPath = pathname.substring(uploadsIndex);
|
||||
console.log('[expenses/generate-pdf] Extracted path from nc/uploads pattern:', extractedPath);
|
||||
return extractedPath;
|
||||
}
|
||||
|
||||
if (pathname.includes('/database/')) {
|
||||
// Remove the database prefix
|
||||
const databaseIndex = pathname.indexOf('database/');
|
||||
const withoutDatabase = pathname.substring(databaseIndex + 'database/'.length);
|
||||
console.log('[expenses/generate-pdf] Extracted path after removing database prefix:', withoutDatabase);
|
||||
return withoutDatabase;
|
||||
}
|
||||
|
||||
// If no specific pattern found, return the pathname as-is
|
||||
console.log('[expenses/generate-pdf] Using pathname as-is:', pathname);
|
||||
return pathname;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[expenses/generate-pdf] Error parsing URL:', error);
|
||||
// If URL parsing fails, try to extract manually
|
||||
|
||||
// Remove query parameters manually
|
||||
const withoutQuery = urlOrPath.split('?')[0];
|
||||
|
||||
// Look for uploads pattern
|
||||
if (withoutQuery.includes('/uploads/')) {
|
||||
const uploadsIndex = withoutQuery.indexOf('/uploads/');
|
||||
return withoutQuery.substring(uploadsIndex + 1); // +1 to remove the leading slash
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function addFooter(doc: PDFKit.PDFDocument) {
|
||||
doc.fontSize(10)
|
||||
.fillColor('#666666')
|
||||
|
|
|
|||
Loading…
Reference in New Issue