/**
* Builds the full HTML string for a CPAS violation PDF document.
* Matches the styling of the original HTML violation form.
*/
const TIERS = [
{ min: 0, max: 4, label: 'Tier 0-1 — Elite Standing', color: '#28a745' },
{ min: 5, max: 9, label: 'Tier 1 — Realignment', color: '#856404' },
{ min: 10, max: 14, label: 'Tier 2 — Administrative Lockdown', color: '#d9534f' },
{ min: 15, max: 19, label: 'Tier 3 — Verification', color: '#d9534f' },
{ min: 20, max: 24, label: 'Tier 4 — Risk Mitigation', color: '#c0392b' },
{ min: 25, max: 29, label: 'Tier 5 — Final Decision', color: '#c0392b' },
{ min: 30, max: 999,label: 'Tier 6 — Separation', color: '#721c24' },
];
function getTier(points) {
return TIERS.find(t => points >= t.min && points <= t.max) || TIERS[0];
}
function formatDate(d) {
if (!d) return '—';
const dt = new Date(d + 'T12:00:00');
return dt.toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
timeZone: 'America/Chicago'
});
}
function formatDateTime(d, t) {
const date = formatDate(d);
return t ? `${date} at ${t}` : date;
}
function row(label, value) {
return `
| ${label} |
${value || '—'} |
`;
}
function buildHtml(v, score) {
const activePts = score.active_points || 0;
const tier = getTier(activePts);
const newTotal = activePts + v.points;
const newTier = getTier(newTotal);
const tierChange = tier.label !== newTier.label;
const generatedAt = new Date().toLocaleString('en-US', {
timeZone: 'America/Chicago',
dateStyle: 'full', timeStyle: 'short'
});
// Map violation_type to handbook chapter reference (loaded from violations.js in frontend)
// Since we're in backend, we'll reconstruct key descriptions from the violation_name
// The database already stores violation_name (e.g., "Tardy Core Hours") and category
// Build a contextual description block
const contextBlock = v.details
? `
Context: ${v.details}
`
: '';
return `
⚠ CONFIDENTIAL — For authorized HR and management use only
Employee Information
${row('Employee Name', `${v.employee_name}`)}
${row('Department', v.department)}
${row('Supervisor', v.supervisor)}
${row('Witness / Documenting Officer', v.witness_name)}
Violation Details
${row('Violation Type', `${v.violation_name}`)}
${row('Category', v.category)}
${row('Policy Reference', 'Chapter 4, Section 5 — Comprehensive Professional Accountability System (CPAS)')}
${row('Incident Date / Time', formatDateTime(v.incident_date, v.incident_time))}
${v.location ? row('Location / Context', v.location) : ''}
${row('Submitted By', v.submitted_by || 'System')}
${v.details ? `
Incident Details:
${v.details}
` : ''}
CPAS Point Assessment
${v.points}
Points Assessed — This Violation
${activePts}
Active Points (Prior)
${tier.label}
+
${v.points}
Points — This Violation
=
${newTotal}
New Active Total
${newTier.label}
${tierChange ? `
⚠ Tier Escalation: This violation advances the employee from
${tier.label} to ${newTier.label}.
Review associated tier consequences per the Employee Handbook.
` : ''}
CPAS Tier Reference
| Points |
Tier |
${TIERS.map(t => `
| ${t.min === 30 ? '30+' : t.min + '–' + t.max} |
${t.label} |
`).join('')}
Employee Notice: CPAS points remain active for a rolling 90-day period from the date of each incident.
Accumulation of points may result in tier escalation and associated consequences as outlined in the Employee Handbook,
Chapter 4, Section 5. This document should be reviewed with the employee and signed by all parties.
Acknowledgement & Signatures
By signing below, the employee acknowledges receipt of this violation record.
Acknowledgement does not imply agreement. The employee may submit a written
response within 5 business days.
Supervisor / Documenting Officer Signature
`;
}
module.exports = buildHtml;