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 totalReceiptImages = 0;
|
||||||
let processedImages = 0;
|
let processedImages = 0;
|
||||||
|
let currentReceiptNumber = 0; // Track overall receipt number across all expenses
|
||||||
|
|
||||||
// Count total receipt images for progress tracking
|
// Count total receipt images for progress tracking
|
||||||
expensesWithReceipts.forEach(expense => {
|
expensesWithReceipts.forEach(expense => {
|
||||||
|
|
@ -495,9 +496,11 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||||
// Process receipt images - each gets its own page
|
// Process receipt images - each gets its own page
|
||||||
if (expense.Receipt && Array.isArray(expense.Receipt)) {
|
if (expense.Receipt && Array.isArray(expense.Receipt)) {
|
||||||
for (const [receiptIndex, receipt] of expense.Receipt.entries()) {
|
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 {
|
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);
|
const imageBuffer = await fetchReceiptImage(receipt);
|
||||||
|
|
||||||
if (imageBuffer) {
|
if (imageBuffer) {
|
||||||
|
|
@ -517,10 +520,10 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||||
|
|
||||||
doc.fillColor('#000000');
|
doc.fillColor('#000000');
|
||||||
|
|
||||||
// Receipt header content
|
// Receipt header content with overall numbering
|
||||||
doc.fontSize(16)
|
doc.fontSize(16)
|
||||||
.font('Helvetica-Bold')
|
.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' });
|
70, 80, { align: 'left' });
|
||||||
|
|
||||||
doc.fontSize(14)
|
doc.fontSize(14)
|
||||||
|
|
@ -584,14 +587,14 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} 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
|
// Add page with error message
|
||||||
doc.addPage();
|
doc.addPage();
|
||||||
|
|
||||||
doc.fontSize(16)
|
doc.fontSize(16)
|
||||||
.font('Helvetica-Bold')
|
.font('Helvetica-Bold')
|
||||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
.text(`Receipt Image ${currentReceiptNumber} of ${totalReceiptImages}`,
|
||||||
{ align: 'center' });
|
{ align: 'center' });
|
||||||
|
|
||||||
doc.fontSize(14)
|
doc.fontSize(14)
|
||||||
|
|
@ -609,14 +612,14 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (imageError: any) {
|
} 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
|
// Add page with error information
|
||||||
doc.addPage();
|
doc.addPage();
|
||||||
|
|
||||||
doc.fontSize(16)
|
doc.fontSize(16)
|
||||||
.font('Helvetica-Bold')
|
.font('Helvetica-Bold')
|
||||||
.text(`Receipt Image ${receiptIndex + 1}${expense.Receipt.length > 1 ? ` of ${expense.Receipt.length}` : ''}`,
|
.text(`Receipt Image ${currentReceiptNumber} of ${totalReceiptImages}`,
|
||||||
{ align: 'center' });
|
{ align: 'center' });
|
||||||
|
|
||||||
doc.fontSize(14)
|
doc.fontSize(14)
|
||||||
|
|
@ -637,7 +640,7 @@ async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) {
|
||||||
doc.fillColor('#000000');
|
doc.fillColor('#000000');
|
||||||
}
|
}
|
||||||
} else {
|
} 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 client = getMinioClient();
|
||||||
const bucketName = useRuntimeConfig().minio.bucketName;
|
const bucketName = useRuntimeConfig().minio.bucketName;
|
||||||
|
|
||||||
// Determine the file path - try multiple possible locations
|
// Determine the file path - try multiple possible sources
|
||||||
let filePath = null;
|
let rawPath = null;
|
||||||
|
|
||||||
// Try different receipt data structures
|
// Try different receipt data structures - prioritize signedUrl for S3 URLs
|
||||||
if (receipt.url) {
|
if (receipt.signedUrl) {
|
||||||
filePath = receipt.url;
|
rawPath = receipt.signedUrl;
|
||||||
|
} else if (receipt.url) {
|
||||||
|
rawPath = receipt.url;
|
||||||
} else if (receipt.directus_files_id?.filename_download) {
|
} 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) {
|
} else if (receipt.filename_download) {
|
||||||
filePath = receipt.filename_download;
|
rawPath = receipt.filename_download;
|
||||||
} else if (receipt.id && receipt.filename_disk) {
|
} else if (receipt.id && receipt.filename_disk) {
|
||||||
filePath = receipt.filename_disk;
|
rawPath = receipt.filename_disk;
|
||||||
} else if (typeof receipt === 'string') {
|
} 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));
|
console.log('[expenses/generate-pdf] No file path found for receipt:', JSON.stringify(receipt, null, 2));
|
||||||
return null;
|
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
|
// Extract MinIO path from S3 URL or use as-is if it's already a path
|
||||||
if (filePath.includes('/files/')) {
|
let minioPath = extractMinioPath(rawPath);
|
||||||
const parts = filePath.split('/files/');
|
|
||||||
if (parts.length > 1) {
|
if (!minioPath) {
|
||||||
filePath = parts[parts.length - 1];
|
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 = [
|
const possiblePaths = [
|
||||||
filePath,
|
minioPath,
|
||||||
`receipts/${filePath}`,
|
`receipts/${minioPath}`,
|
||||||
`expenses/${filePath}`,
|
`expenses/${minioPath}`,
|
||||||
filePath.startsWith('receipts/') ? filePath : `receipts/${filePath}`
|
// 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 {
|
try {
|
||||||
console.log('[expenses/generate-pdf] Trying path:', testPath);
|
console.log('[expenses/generate-pdf] Trying MinIO path:', testPath);
|
||||||
|
|
||||||
// Check if object exists first
|
// Check if object exists first
|
||||||
await client.statObject(bucketName, testPath);
|
await client.statObject(bucketName, testPath);
|
||||||
|
|
@ -713,16 +728,16 @@ async function fetchReceiptImage(receipt: any): Promise<Buffer | null> {
|
||||||
dataStream.on('error', reject);
|
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;
|
return imageBuffer;
|
||||||
|
|
||||||
} catch (pathError) {
|
} catch (pathError) {
|
||||||
console.log('[expenses/generate-pdf] Path not found:', testPath);
|
console.log('[expenses/generate-pdf] MinIO path not found:', testPath);
|
||||||
continue;
|
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;
|
return null;
|
||||||
|
|
||||||
} catch (error) {
|
} 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) {
|
function addFooter(doc: PDFKit.PDFDocument) {
|
||||||
doc.fontSize(10)
|
doc.fontSize(10)
|
||||||
.fillColor('#666666')
|
.fillColor('#666666')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue