fix: attendance print - return proper data structure with records, leave balances, and fund stats
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -529,43 +529,120 @@ export async function getPrintData(monthStr: string, filterUserId: number | null
|
||||
|
||||
const records = await prisma.attendance.findMany({
|
||||
where,
|
||||
include: { users: { select: { id: true, first_name: true, last_name: true } } },
|
||||
orderBy: [{ user_id: 'asc' }, { shift_date: 'asc' }],
|
||||
include: {
|
||||
users: { select: { id: true, first_name: true, last_name: true } },
|
||||
attendance_project_logs: {
|
||||
orderBy: { started_at: 'asc' },
|
||||
},
|
||||
},
|
||||
orderBy: [{ users: { last_name: 'asc' } }, { shift_date: 'asc' }],
|
||||
});
|
||||
|
||||
const userTotals: Record<string, { name: string; worked: number; vacation: number; sick: number; holiday: number; unpaid: number }> = {};
|
||||
const fundHours = countWorkingDays(yr, mo - 1) * 8;
|
||||
|
||||
// Load project names for enrichment
|
||||
const projectIds = [...new Set(records.flatMap(r => (r as any).attendance_project_logs?.map((l: any) => l.project_id) || []).filter(Boolean))];
|
||||
const projectMap: Record<number, string> = {};
|
||||
if (projectIds.length > 0) {
|
||||
const projects = await prisma.projects.findMany({
|
||||
where: { id: { in: projectIds } },
|
||||
select: { id: true, name: true, project_number: true },
|
||||
});
|
||||
for (const p of projects) {
|
||||
projectMap[p.id] = p.project_number ? `${p.project_number} – ${p.name}` : (p.name || `#${p.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Group records by user and calculate totals
|
||||
const userTotals: Record<string, Record<string, unknown>> = {};
|
||||
for (const rec of records) {
|
||||
const uid = String(rec.user_id);
|
||||
if (!userTotals[uid]) {
|
||||
const u = rec.users;
|
||||
userTotals[uid] = { name: u ? `${u.first_name} ${u.last_name}`.trim() : `User #${uid}`, worked: 0, vacation: 0, sick: 0, holiday: 0, unpaid: 0 };
|
||||
const u = (rec as any).users;
|
||||
userTotals[uid] = {
|
||||
name: u ? `${u.first_name} ${u.last_name}`.trim() : `User #${uid}`,
|
||||
minutes: 0,
|
||||
records: [],
|
||||
vacation_hours: 0,
|
||||
sick_hours: 0,
|
||||
holiday_hours: 0,
|
||||
unpaid_hours: 0,
|
||||
fund: fundHours,
|
||||
worked_hours: 0,
|
||||
covered: 0,
|
||||
missing: 0,
|
||||
overtime: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Build record with project_logs for frontend
|
||||
const projectLogs = (rec as any).attendance_project_logs?.map((log: any) => ({
|
||||
project_id: log.project_id,
|
||||
project_name: projectMap[log.project_id] || null,
|
||||
hours: log.hours,
|
||||
minutes: log.minutes,
|
||||
started_at: log.started_at,
|
||||
ended_at: log.ended_at,
|
||||
})) || [];
|
||||
|
||||
(userTotals[uid].records as unknown[]).push({
|
||||
...rec,
|
||||
project_logs: projectLogs,
|
||||
project_name: projectLogs.length > 0 ? projectLogs[0].project_name : null,
|
||||
});
|
||||
|
||||
const lt = (rec.leave_type as string) || 'work';
|
||||
if (lt !== 'work') {
|
||||
const hrs = Number(rec.leave_hours) || 8;
|
||||
if (lt === 'vacation') userTotals[uid].vacation += hrs;
|
||||
else if (lt === 'sick') userTotals[uid].sick += hrs;
|
||||
else if (lt === 'holiday') userTotals[uid].holiday += hrs;
|
||||
else if (lt === 'unpaid') userTotals[uid].unpaid += hrs;
|
||||
if (lt === 'vacation') (userTotals[uid].vacation_hours as number) += hrs;
|
||||
else if (lt === 'sick') (userTotals[uid].sick_hours as number) += hrs;
|
||||
else if (lt === 'holiday') (userTotals[uid].holiday_hours as number) += hrs;
|
||||
else if (lt === 'unpaid') (userTotals[uid].unpaid_hours as number) += hrs;
|
||||
} else if (rec.arrival_time && rec.departure_time) {
|
||||
userTotals[uid].worked += calcWorkedHours(rec.arrival_time, rec.departure_time, rec.break_start, rec.break_end);
|
||||
const mins = calcWorkedHours(rec.arrival_time, rec.departure_time, rec.break_start, rec.break_end) * 60;
|
||||
(userTotals[uid].minutes as number) += Math.round(mins);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate fund coverage per user
|
||||
for (const uid of Object.keys(userTotals)) {
|
||||
userTotals[uid].worked = Math.round(userTotals[uid].worked * 10) / 10;
|
||||
const ut = userTotals[uid];
|
||||
const workedH = Math.round((ut.minutes as number) / 60 * 10) / 10;
|
||||
ut.worked_hours = workedH;
|
||||
const covered = workedH + (ut.vacation_hours as number) + (ut.sick_hours as number) + (ut.holiday_hours as number);
|
||||
ut.covered = Math.round(covered * 10) / 10;
|
||||
ut.missing = Math.max(0, Math.round(((ut.fund as number) - covered) * 10) / 10);
|
||||
ut.overtime = Math.max(0, Math.round((covered - (ut.fund as number)) * 10) / 10);
|
||||
}
|
||||
|
||||
const bizDays = countWorkingDays(yr, mo - 1);
|
||||
// Leave balances
|
||||
const leaveBalances: Record<string, Record<string, number>> = {};
|
||||
const balanceRecords = await prisma.leave_balances.findMany({ where: { year: yr } });
|
||||
for (const bal of balanceRecords) {
|
||||
const uid = String(bal.user_id);
|
||||
leaveBalances[uid] = {
|
||||
vacation_total: Number(bal.vacation_total) || 160,
|
||||
vacation_remaining: (Number(bal.vacation_total) || 160) - (Number(bal.vacation_used) || 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Selected user name
|
||||
let selectedUserName = '';
|
||||
if (filterUserId) {
|
||||
const u = users.find(u => u.id === filterUserId);
|
||||
if (u) selectedUserName = `${u.first_name} ${u.last_name}`.trim();
|
||||
}
|
||||
|
||||
return {
|
||||
user_totals: userTotals,
|
||||
leave_balances: leaveBalances,
|
||||
users: users.map(u => ({ id: u.id, name: `${u.first_name} ${u.last_name}`.trim() })),
|
||||
month: monthStr,
|
||||
month_name: `${MONTH_NAMES[mo - 1]} ${yr}`,
|
||||
selected_user: filterUserId,
|
||||
selected_user_name: selectedUserName,
|
||||
year: yr,
|
||||
fund: { business_days: bizDays, hours: bizDays * 8 },
|
||||
fund: { business_days: countWorkingDays(yr, mo - 1), hours: fundHours },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user