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
|
||||
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)
|
||||
if (type !== 'test') {
|
||||
authenticatePocketBase(pb, authData);
|
||||
}
|
||||
|
||||
let success = false;
|
||||
|
||||
|
@ -141,19 +143,51 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
|||
pocketbaseUrl: import.meta.env.POCKETBASE_URL
|
||||
});
|
||||
|
||||
// Get reimbursement details
|
||||
// Check if this is a test scenario
|
||||
const isTestData = data.reimbursementId?.includes('test') || data.reimbursementId === 'test-id';
|
||||
|
||||
let reimbursement, user;
|
||||
|
||||
if (isTestData) {
|
||||
console.log('🧪 Using test data for demonstration');
|
||||
// Use mock data for testing
|
||||
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);
|
||||
const reimbursement = await pb.collection('reimbursement').getOne(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);
|
||||
const user = await pb.collection('users').getOne(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 });
|
||||
}
|
||||
|
||||
// Get changed by user name if provided
|
||||
let changedByName = 'System';
|
||||
|
@ -177,7 +211,39 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
|||
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 {
|
||||
const statusOrder = ['submitted', 'under_review', 'approved', 'in_progress', 'paid'];
|
||||
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> = {
|
||||
submitted: '→',
|
||||
under_review: '○',
|
||||
under_review: '•',
|
||||
approved: '✓',
|
||||
rejected: '✗',
|
||||
in_progress: '◐',
|
||||
in_progress: '○',
|
||||
paid: '✓'
|
||||
};
|
||||
|
||||
|
@ -208,8 +274,11 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
|||
let progressBarHtml = `
|
||||
<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>
|
||||
<div style="display: flex; align-items: flex-start; justify-content: space-between; position: relative; max-width: 400px; margin: 0 auto;">
|
||||
<div style="position: absolute; left: 12px; right: 12px; top: 12px; height: 2px; background: #e2e8f0; z-index: 1;"></div>
|
||||
<table style="width: 100%; max-width: 500px; margin: 0 auto; border-collapse: collapse; position: relative;">
|
||||
<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) => {
|
||||
|
@ -245,51 +314,64 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
|||
lineColor = '#e2e8f0';
|
||||
}
|
||||
|
||||
// Status circle
|
||||
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="position: relative; z-index: 1; padding: 5px 0;">
|
||||
<div style="
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: ${backgroundColor};
|
||||
color: ${textColor};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border: 3px solid #f8fafc;
|
||||
box-shadow: none;
|
||||
margin: 0 auto 8px auto;
|
||||
">
|
||||
${statusIcons[status]}
|
||||
</div>
|
||||
<span style="
|
||||
font-size: 9px;
|
||||
<div style="
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: ${isCurrent ? (status === 'rejected' ? '#ef4444' : status === 'paid' ? '#10b981' : status === 'in_progress' ? '#f59e0b' : '#3b82f6') : isActive ? '#475569' : '#94a3b8'};
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
margin-top: 8px;
|
||||
max-width: 60px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
">
|
||||
${statusLabels[status]}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// Add colored line segment for active states
|
||||
if (index < statuses.length - 1 && isActive) {
|
||||
// Connecting line (except for the last status)
|
||||
if (index < statuses.length - 1) {
|
||||
const nextIsActive = (index + 1) <= currentIndex;
|
||||
const connectionColor = nextIsActive ? lineColor : '#e2e8f0';
|
||||
|
||||
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 += `
|
||||
</div>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
Loading…
Reference in a new issue