Update send-reimbursement-email.ts
This commit is contained in:
parent
8f6b9806a9
commit
b831e89f49
1 changed files with 136 additions and 54 deletions
|
@ -40,8 +40,10 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
// Initialize services
|
// Initialize services
|
||||||
const { pb, resend, fromEmail, replyToEmail } = await initializeEmailServices();
|
const { pb, resend, fromEmail, replyToEmail } = await initializeEmailServices();
|
||||||
|
|
||||||
// Authenticate with PocketBase if auth data is provided
|
// Authenticate with PocketBase if auth data is provided (skip for test emails)
|
||||||
authenticatePocketBase(pb, authData);
|
if (type !== 'test') {
|
||||||
|
authenticatePocketBase(pb, authData);
|
||||||
|
}
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
|
|
||||||
|
@ -141,19 +143,51 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
pocketbaseUrl: import.meta.env.POCKETBASE_URL
|
pocketbaseUrl: import.meta.env.POCKETBASE_URL
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get reimbursement details
|
// Check if this is a test scenario
|
||||||
console.log('🔍 Fetching reimbursement details for:', data.reimbursementId);
|
const isTestData = data.reimbursementId?.includes('test') || data.reimbursementId === 'test-id';
|
||||||
const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId);
|
|
||||||
console.log('✅ Reimbursement fetched:', { id: reimbursement.id, title: reimbursement.title });
|
|
||||||
|
|
||||||
// Get submitter user details
|
let reimbursement, user;
|
||||||
console.log('👤 Fetching user details for:', reimbursement.submitted_by);
|
|
||||||
const user = await pb.collection('users').getOne(reimbursement.submitted_by);
|
if (isTestData) {
|
||||||
if (!user || !user.email) {
|
console.log('🧪 Using test data for demonstration');
|
||||||
console.error('❌ User not found or no email:', reimbursement.submitted_by);
|
// Use mock data for testing
|
||||||
return false;
|
reimbursement = {
|
||||||
|
id: data.reimbursementId,
|
||||||
|
title: 'Test Reimbursement Request',
|
||||||
|
total_amount: 125.50,
|
||||||
|
date_of_purchase: new Date().toISOString(),
|
||||||
|
department: 'general',
|
||||||
|
payment_method: 'Personal Card',
|
||||||
|
status: data.previousStatus || 'submitted',
|
||||||
|
submitted_by: 'test-user-id',
|
||||||
|
audit_notes: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
user = {
|
||||||
|
id: 'test-user-id',
|
||||||
|
name: 'Test User',
|
||||||
|
email: data.additionalContext?.testEmail || 'test@example.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Test data prepared:', {
|
||||||
|
reimbursementTitle: reimbursement.title,
|
||||||
|
userEmail: user.email
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Get real reimbursement details
|
||||||
|
console.log('🔍 Fetching reimbursement details for:', data.reimbursementId);
|
||||||
|
reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId);
|
||||||
|
console.log('✅ Reimbursement fetched:', { id: reimbursement.id, title: reimbursement.title });
|
||||||
|
|
||||||
|
// Get submitter user details
|
||||||
|
console.log('👤 Fetching user details for:', reimbursement.submitted_by);
|
||||||
|
user = await pb.collection('users').getOne(reimbursement.submitted_by);
|
||||||
|
if (!user || !user.email) {
|
||||||
|
console.error('❌ User not found or no email:', reimbursement.submitted_by);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log('✅ User fetched:', { id: user.id, name: user.name, email: user.email });
|
||||||
}
|
}
|
||||||
console.log('✅ User fetched:', { id: user.id, name: user.name, email: user.email });
|
|
||||||
|
|
||||||
// Get changed by user name if provided
|
// Get changed by user name if provided
|
||||||
let changedByName = 'System';
|
let changedByName = 'System';
|
||||||
|
@ -177,7 +211,39 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
status: data.newStatus
|
status: data.newStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper function to generate status progress bar HTML
|
// Add audit note when reimbursement is declined (skip for test data)
|
||||||
|
if (data.newStatus === 'rejected' && !isTestData) {
|
||||||
|
try {
|
||||||
|
console.log('📝 Adding audit note for declined reimbursement...');
|
||||||
|
|
||||||
|
// Prepare audit note content
|
||||||
|
let auditNote = `Status changed to REJECTED by ${changedByName}`;
|
||||||
|
if (data.additionalContext?.rejectionReason) {
|
||||||
|
auditNote += `\nRejection Reason: ${data.additionalContext.rejectionReason}`;
|
||||||
|
}
|
||||||
|
auditNote += `\nDate: ${new Date().toLocaleString()}`;
|
||||||
|
|
||||||
|
// Get existing audit notes or initialize empty string
|
||||||
|
const existingNotes = reimbursement.audit_notes || '';
|
||||||
|
const updatedNotes = existingNotes
|
||||||
|
? `${existingNotes}\n\n--- DECLINE RECORD ---\n${auditNote}`
|
||||||
|
: `--- DECLINE RECORD ---\n${auditNote}`;
|
||||||
|
|
||||||
|
// Update the reimbursement record with the new audit notes
|
||||||
|
await pb.collection('reimbursement').update(data.reimbursementId, {
|
||||||
|
audit_notes: updatedNotes
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ Audit note added successfully for declined reimbursement');
|
||||||
|
} catch (auditError) {
|
||||||
|
console.error('❌ Failed to add audit note for declined reimbursement:', auditError);
|
||||||
|
// Don't fail the entire email process if audit note fails
|
||||||
|
}
|
||||||
|
} else if (data.newStatus === 'rejected' && isTestData) {
|
||||||
|
console.log('🧪 Skipping audit note update for test data');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to generate status progress bar HTML (email-compatible)
|
||||||
function generateStatusProgressBar(currentStatus: string): string {
|
function generateStatusProgressBar(currentStatus: string): string {
|
||||||
const statusOrder = ['submitted', 'under_review', 'approved', 'in_progress', 'paid'];
|
const statusOrder = ['submitted', 'under_review', 'approved', 'in_progress', 'paid'];
|
||||||
const rejectedStatus = ['submitted', 'under_review', 'rejected'];
|
const rejectedStatus = ['submitted', 'under_review', 'rejected'];
|
||||||
|
@ -187,10 +253,10 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
|
|
||||||
const statusIcons: Record<string, string> = {
|
const statusIcons: Record<string, string> = {
|
||||||
submitted: '→',
|
submitted: '→',
|
||||||
under_review: '○',
|
under_review: '•',
|
||||||
approved: '✓',
|
approved: '✓',
|
||||||
rejected: '✗',
|
rejected: '✗',
|
||||||
in_progress: '◐',
|
in_progress: '○',
|
||||||
paid: '✓'
|
paid: '✓'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -208,8 +274,11 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
let progressBarHtml = `
|
let progressBarHtml = `
|
||||||
<div style="background: #f8fafc; padding: 30px 20px; border-radius: 8px; margin: 20px 0; border: 1px solid #e2e8f0;">
|
<div style="background: #f8fafc; padding: 30px 20px; border-radius: 8px; margin: 20px 0; border: 1px solid #e2e8f0;">
|
||||||
<h3 style="margin: 0 0 30px 0; color: #1e293b; font-size: 16px; font-weight: 600; text-align: center;">Request Progress</h3>
|
<h3 style="margin: 0 0 30px 0; color: #1e293b; font-size: 16px; font-weight: 600; text-align: center;">Request Progress</h3>
|
||||||
<div style="display: flex; align-items: flex-start; justify-content: space-between; position: relative; max-width: 400px; margin: 0 auto;">
|
<table style="width: 100%; max-width: 500px; margin: 0 auto; border-collapse: collapse; position: relative;">
|
||||||
<div style="position: absolute; left: 12px; right: 12px; top: 12px; height: 2px; background: #e2e8f0; z-index: 1;"></div>
|
<tr style="position: relative;">
|
||||||
|
<td colspan="${statuses.length * 2 - 1}" style="height: 2px; background: #e2e8f0; position: absolute; top: 21px; left: 0; right: 0; z-index: 3;"></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="position: relative; z-index: 1;">
|
||||||
`;
|
`;
|
||||||
|
|
||||||
statuses.forEach((status, index) => {
|
statuses.forEach((status, index) => {
|
||||||
|
@ -245,51 +314,64 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
lineColor = '#e2e8f0';
|
lineColor = '#e2e8f0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status circle
|
||||||
progressBarHtml += `
|
progressBarHtml += `
|
||||||
<div style="display: flex; flex-direction: column; align-items: center; position: relative; z-index: 10;">
|
<td style="text-align: center; padding: 0; vertical-align: top; position: relative; width: ${100/statuses.length}%;">
|
||||||
<div style="
|
<div style="position: relative; z-index: 1; padding: 5px 0;">
|
||||||
width: 24px;
|
<div style="
|
||||||
height: 24px;
|
width: 32px;
|
||||||
border-radius: 50%;
|
height: 32px;
|
||||||
background: ${backgroundColor};
|
border-radius: 50%;
|
||||||
color: ${textColor};
|
background: ${backgroundColor};
|
||||||
display: flex;
|
color: ${textColor};
|
||||||
align-items: center;
|
text-align: center;
|
||||||
justify-content: center;
|
line-height: 32px;
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: bold;
|
||||||
border: 2px solid white;
|
border: 3px solid #f8fafc;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: none;
|
||||||
line-height: 1;
|
margin: 0 auto 8px auto;
|
||||||
text-align: center;
|
">
|
||||||
">
|
${statusIcons[status]}
|
||||||
${statusIcons[status]}
|
</div>
|
||||||
</div>
|
<div style="
|
||||||
<span style="
|
font-size: 11px;
|
||||||
font-size: 9px;
|
font-weight: 600;
|
||||||
font-weight: 600;
|
color: ${isCurrent ? (status === 'rejected' ? '#ef4444' : status === 'paid' ? '#10b981' : status === 'in_progress' ? '#f59e0b' : '#3b82f6') : isActive ? '#475569' : '#94a3b8'};
|
||||||
color: ${isCurrent ? (status === 'rejected' ? '#ef4444' : status === 'paid' ? '#10b981' : status === 'in_progress' ? '#f59e0b' : '#3b82f6') : isActive ? '#475569' : '#94a3b8'};
|
text-align: center;
|
||||||
text-align: center;
|
line-height: 1.2;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-top: 8px;
|
">
|
||||||
max-width: 60px;
|
${statusLabels[status]}
|
||||||
line-height: 1.2;
|
</div>
|
||||||
">
|
</div>
|
||||||
${statusLabels[status]}
|
</td>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add colored line segment for active states
|
// Connecting line (except for the last status)
|
||||||
if (index < statuses.length - 1 && isActive) {
|
if (index < statuses.length - 1) {
|
||||||
|
const nextIsActive = (index + 1) <= currentIndex;
|
||||||
|
const connectionColor = nextIsActive ? lineColor : '#e2e8f0';
|
||||||
|
|
||||||
progressBarHtml += `
|
progressBarHtml += `
|
||||||
<div style="position: absolute; left: ${12 + (index * (376 / (statuses.length - 1)))}px; width: ${376 / (statuses.length - 1)}px; top: 12px; height: 2px; background: ${lineColor}; z-index: 2;"></div>
|
<td style="padding: 0; vertical-align: top; position: relative; width: 20px;">
|
||||||
|
<div style="
|
||||||
|
height: 2px;
|
||||||
|
background: ${connectionColor};
|
||||||
|
position: absolute;
|
||||||
|
top: 21px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 3;
|
||||||
|
"></div>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
progressBarHtml += `
|
progressBarHtml += `
|
||||||
</div>
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue