135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import { requireAuth } from '@/server/utils/auth';
|
|
import { getExpenseById } from '@/server/utils/nocodb';
|
|
import { processExpenseWithCurrency } from '@/server/utils/currency';
|
|
|
|
interface PDFOptions {
|
|
documentName: string;
|
|
subheader?: string;
|
|
groupBy: 'none' | 'payer' | 'category' | 'date';
|
|
includeReceipts: boolean;
|
|
includeSummary: boolean;
|
|
includeDetails: boolean;
|
|
pageFormat: 'A4' | 'Letter' | 'Legal';
|
|
includeProcessingFee?: boolean;
|
|
}
|
|
|
|
interface Expense {
|
|
Id: number;
|
|
'Establishment Name': string;
|
|
Price: string;
|
|
PriceNumber: number;
|
|
DisplayPrice: string;
|
|
PriceUSD?: number;
|
|
ConversionRate?: number;
|
|
Payer: string;
|
|
Category: string;
|
|
'Payment Method': string;
|
|
Time: string;
|
|
Contents?: string;
|
|
Receipt?: any[];
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
await requireAuth(event);
|
|
|
|
const body = await readBody(event);
|
|
const { expenseIds, options } = body;
|
|
|
|
if (!expenseIds || !Array.isArray(expenseIds) || expenseIds.length === 0) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Expense IDs are required'
|
|
});
|
|
}
|
|
|
|
if (!options || !options.documentName) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'PDF options with document name are required'
|
|
});
|
|
}
|
|
|
|
console.log('[expenses/generate-pdf] PDF generation requested for expenses:', expenseIds);
|
|
|
|
try {
|
|
// Fetch expense data
|
|
const expenses: Expense[] = [];
|
|
for (const expenseId of expenseIds) {
|
|
const expense = await getExpenseById(expenseId);
|
|
if (expense) {
|
|
const processedExpense = await processExpenseWithCurrency(expense);
|
|
expenses.push(processedExpense);
|
|
}
|
|
}
|
|
|
|
if (expenses.length === 0) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: 'No valid expenses found'
|
|
});
|
|
}
|
|
|
|
// Calculate totals to show the preview is working correctly
|
|
const totals = calculateTotals(expenses, options.includeProcessingFee);
|
|
|
|
console.log('[expenses/generate-pdf] Successfully calculated totals:', totals);
|
|
console.log('[expenses/generate-pdf] Options received:', options);
|
|
|
|
// For now, return a helpful error with the calculated information
|
|
throw createError({
|
|
statusCode: 501,
|
|
statusMessage: 'PDF generation is being upgraded',
|
|
message: `PDF generation is being upgraded! ✅ Your selection is ready:
|
|
|
|
📊 Summary:
|
|
• ${totals.count} expenses selected
|
|
• Total: €${totals.originalTotal.toFixed(2)}
|
|
• USD equivalent: $${totals.usdTotal.toFixed(2)}
|
|
${options.includeProcessingFee ? `• With 5% fee: €${totals.finalTotal.toFixed(2)}` : ''}
|
|
|
|
📋 Document: "${options.documentName}"
|
|
${options.subheader ? `📝 Subtitle: "${options.subheader}"` : ''}
|
|
🔗 Grouping: ${getGroupingLabel(options.groupBy)}
|
|
|
|
💡 Use CSV export for now, or contact support for manual PDF generation with these exact settings.`
|
|
});
|
|
|
|
} catch (error: any) {
|
|
// If it's our intentional error, re-throw it
|
|
if (error.statusCode === 501) {
|
|
throw error;
|
|
}
|
|
|
|
console.error('[expenses/generate-pdf] Error generating PDF:', error);
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: error.message || 'Failed to generate PDF'
|
|
});
|
|
}
|
|
});
|
|
|
|
function calculateTotals(expenses: Expense[], includeProcessingFee: boolean) {
|
|
const originalTotal = expenses.reduce((sum, exp) => sum + (exp.PriceNumber || 0), 0);
|
|
const usdTotal = expenses.reduce((sum, exp) => sum + (exp.PriceUSD || exp.PriceNumber || 0), 0);
|
|
|
|
const processingFee = includeProcessingFee ? originalTotal * 0.05 : 0;
|
|
const finalTotal = originalTotal + processingFee;
|
|
|
|
return {
|
|
originalTotal,
|
|
usdTotal,
|
|
processingFee,
|
|
finalTotal,
|
|
count: expenses.length
|
|
};
|
|
}
|
|
|
|
function getGroupingLabel(groupBy: string): string {
|
|
switch (groupBy) {
|
|
case 'payer': return 'By Person';
|
|
case 'category': return 'By Category';
|
|
case 'date': return 'By Date';
|
|
default: return 'No Grouping';
|
|
}
|
|
}
|