2026-03-06 12:19:55 -06:00
const TIERS = [
2026-03-06 14:12:00 -06:00
{ 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' },
2026-03-06 12:19:55 -06:00
];
2026-03-06 14:38:43 -06:00
function getTier ( points ) { return TIERS . find ( t => points >= t . min && points <= t . max ) || TIERS [ 0 ]; }
2026-03-06 12:19:55 -06:00
function formatDate ( d ) {
2026-03-06 14:12:00 -06:00
if ( ! d ) return '—' ;
const dt = new Date ( d + 'T12:00:00' );
2026-03-06 14:38:43 -06:00
return dt . toLocaleDateString ( 'en-US' , { weekday : 'long' , year : 'numeric' , month : 'long' , day : 'numeric' , timeZone : 'America/Chicago' });
2026-03-06 12:19:55 -06:00
}
function formatDateTime ( d , t ) {
2026-03-06 14:12:00 -06:00
const date = formatDate ( d );
return t ? ` ${ date } at ${ t } ` : date ;
2026-03-06 12:19:55 -06:00
}
function row ( label , value ) {
2026-03-06 14:12:00 -06:00
return `
<tr>
<td style="font-weight:600; color:#555; width:200px; padding:8px 12px; border-bottom:1px solid #eee; white-space:nowrap;"> ${ label } </td>
<td style="padding:8px 12px; border-bottom:1px solid #eee; color:#222;"> ${ value || '—' } </td>
</tr>` ;
2026-03-06 12:19:55 -06:00
}
function buildHtml ( v , score ) {
2026-03-06 14:38:43 -06:00
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 ;
2026-03-06 12:19:55 -06:00
2026-03-06 14:38:43 -06:00
const generatedAt = new Date (). toLocaleString ( 'en-US' , { timeZone : 'America/Chicago' , dateStyle : 'full' , timeStyle : 'short' });
2026-03-06 12:19:55 -06:00
2026-03-06 14:12:00 -06:00
return `<!DOCTYPE html>
2026-03-06 12:19:55 -06:00
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
2026-03-06 14:12:00 -06:00
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 13px; color: #222; background: #fff; }
2026-03-06 14:38:43 -06:00
.header { background: linear-gradient(135deg, #000000, #151622); color: white; padding: 22px 32px; display: flex; align-items: center; justify-content: space-between; }
2026-03-06 14:12:00 -06:00
.header-left { display: flex; align-items: center; }
2026-03-06 14:38:43 -06:00
.logo { height: 28px; margin-right: 12px; }
2026-03-06 14:12:00 -06:00
.header h1 { font-size: 20px; letter-spacing: 0.5px; }
.header p { font-size: 11px; opacity: 0.85; margin-top: 3px; }
.doc-id { text-align: right; font-size: 11px; opacity: 0.8; }
.section { margin: 20px 0; }
2026-03-06 14:38:43 -06:00
.section-title { font-size: 14px; font-weight: 700; color: white; background: #000000; padding: 8px 14px; border-radius: 4px 4px 0 0; margin-bottom: 0; }
2026-03-06 14:12:00 -06:00
table { width: 100%; border-collapse: collapse; border: 1px solid #ddd; border-top: none; }
2026-03-06 14:38:43 -06:00
.score-box { display: flex; gap: 20px; flex-wrap: wrap; background: #f8f9fa; border: 1px solid #ddd; border-radius: 6px; padding: 16px 20px; margin: 20px 0; }
2026-03-06 14:12:00 -06:00
.score-cell { flex: 1; min-width: 120px; text-align: center; }
2026-03-06 14:38:43 -06:00
.score-num { font-size: 28px; font-weight: 800; }
.score-lbl { font-size: 11px; color: #666; margin-top: 2px; }
.tier-badge { display: inline-block; padding: 5px 14px; border-radius: 14px; font-size: 12px; font-weight: 700; border: 2px solid currentColor; }
.tier-change { background: #fff3cd; border: 2px solid #ffc107; border-radius: 6px; padding: 12px 16px; margin: 16px 0; font-size: 12px; color: #856404; }
.points-display { background: #fff3cd; border: 2px solid #ffc107; border-radius: 6px; padding: 16px; margin: 16px 0; text-align: center; }
2026-03-06 14:12:00 -06:00
.points-display .pts { font-size: 36px; font-weight: 800; color: #d4af37; }
.points-display .lbl { font-size: 12px; color: #666; }
2026-03-06 14:38:43 -06:00
.sig-section { margin-top: 50px; page-break-inside: avoid; }
.sig-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; margin-top: 32px; }
.sig-block { border-top: 1.5px solid #333; padding-top: 10px; min-height: 80px; }
2026-03-06 14:12:00 -06:00
.sig-label { font-size: 11px; color: #555; font-weight: 600; }
2026-03-06 14:38:43 -06:00
.sig-date-block { border-top: 1.5px solid #333; padding-top: 10px; min-height: 60px; margin-top: 36px; }
.footer-bar { margin-top: 40px; padding: 10px 0; border-top: 2px solid #000000; font-size: 10px; color: #888; text-align: center; }
.confidential { background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; padding: 6px 12px; font-size: 11px; color: #721c24; font-weight: 600; text-align: center; margin-bottom: 16px; }
.notice { background: #e7f3ff; border-left: 4px solid #2196F3; padding: 10px 14px; margin: 16px 0; font-size: 12px; }
.policy-context { background: #f8f9fa; border-left: 3px solid #667eea; padding: 12px 16px; margin: 12px 0; font-size: 12px; color: #444; border-radius: 4px; }
2026-03-06 12:19:55 -06:00
</style>
</head>
<body>
<div class="header">
2026-03-06 14:12:00 -06:00
<div class="header-left">
<img src="/static/mpm-logo.png" class="logo" />
<div>
<h1>CPAS Individual Violation Record</h1>
<p>Message Point Media — Comprehensive Professional Accountability System</p>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:12:00 -06:00
</div>
<div class="doc-id">
Document ID: CPAS- ${ v . id . toString (). padStart ( 5 , '0' ) } <br />
Generated: ${ generatedAt }
</div>
2026-03-06 12:19:55 -06:00
</div>
<div style="padding: 0 4px;">
2026-03-06 14:38:43 -06:00
<div class="confidential" style="margin-top:16px;">⚠ CONFIDENTIAL — For authorized HR and management use only</div>
2026-03-06 12:19:55 -06:00
<div class="section">
2026-03-06 14:12:00 -06:00
<div class="section-title">Employee Information</div>
<table>
${ row ( 'Employee Name' , `<strong> ${ v . employee_name } </strong>` ) }
${ row ( 'Department' , v . department ) }
${ row ( 'Supervisor' , v . supervisor ) }
${ row ( 'Witness / Documenting Officer' , v . witness_name ) }
</table>
2026-03-06 12:19:55 -06:00
</div>
<div class="section">
2026-03-06 14:12:00 -06:00
<div class="section-title">Violation Details</div>
<table>
${ row ( 'Violation Type' , `<strong> ${ v . violation_name } </strong>` ) }
${ 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' ) }
</table>
2026-03-06 14:38:43 -06:00
${ v . details ? `<div class="policy-context"><strong>Incident Details:</strong><br /> ${ v . details } </div>` : '' }
2026-03-06 12:19:55 -06:00
</div>
<div class="section">
2026-03-06 14:12:00 -06:00
<div class="section-title">CPAS Point Assessment</div>
2026-03-06 14:38:43 -06:00
<div class="points-display"><div class="pts"> ${ v . points } </div><div class="lbl">Points Assessed — This Violation</div></div>
2026-03-06 14:12:00 -06:00
<div class="score-box">
<div class="score-cell">
<div class="score-num" style="color: ${ tier . color } ;"> ${ activePts } </div>
<div class="score-lbl">Active Points (Prior)</div>
2026-03-06 14:38:43 -06:00
<div style="margin-top:6px;"><span class="tier-badge" style="color: ${ tier . color } ;"> ${ tier . label } </span></div>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:12:00 -06:00
<div class="score-cell" style="font-size:28px; font-weight:300; color:#ccc; line-height:1.8;">+</div>
<div class="score-cell">
<div class="score-num" style="color:#d4af37;"> ${ v . points } </div>
<div class="score-lbl">Points — This Violation</div>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:12:00 -06:00
<div class="score-cell" style="font-size:28px; font-weight:300; color:#ccc; line-height:1.8;">=</div>
<div class="score-cell">
<div class="score-num" style="color: ${ newTier . color } ;"> ${ newTotal } </div>
<div class="score-lbl">New Active Total</div>
2026-03-06 14:38:43 -06:00
<div style="margin-top:6px;"><span class="tier-badge" style="color: ${ newTier . color } ;"> ${ newTier . label } </span></div>
2026-03-06 14:12:00 -06:00
</div>
</div>
2026-03-06 14:38:43 -06:00
${ tierChange ? `<div class="tier-change"><strong>⚠ Tier Escalation:</strong> This violation advances the employee from <strong> ${ tier . label } </strong> to <strong> ${ newTier . label } </strong>.</div>` : '' }
2026-03-06 12:19:55 -06:00
</div>
<div class="section">
2026-03-06 14:12:00 -06:00
<div class="section-title">CPAS Tier Reference</div>
<table>
2026-03-06 14:38:43 -06:00
<tr style="background:#f8f9fa;"><th style="padding:7px 12px; text-align:left; font-size:12px;">Points</th><th style="padding:7px 12px; text-align:left; font-size:12px;">Tier</th></tr>
${ TIERS . map ( t => `<tr style=" ${ newTotal >= t . min && newTotal <= t . max ? 'background:#fff3cd; font-weight:700;' : '' } "><td style="padding:6px 12px; border-bottom:1px solid #eee; font-size:12px;"> ${ t . min === 30 ? '30+' : t . min + '– ' + t . max } </td><td style="padding:6px 12px; border-bottom:1px solid #eee; font-size:12px; color: ${ t . color } ;"> ${ t . label } </td></tr>` ). join ( '' ) }
2026-03-06 14:12:00 -06:00
</table>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:38:43 -06:00
<div class="notice"><strong>Employee Notice:</strong> 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.</div>
2026-03-06 12:19:55 -06:00
<div class="sig-section">
2026-03-06 14:12:00 -06:00
<div class="section-title" style="background:#000000;">Acknowledgement & Signatures</div>
2026-03-06 14:38:43 -06:00
<div style="padding: 20px 0;">
<p style="font-size:12px; color:#555; margin-bottom:32px; line-height:1.6;">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.</p>
2026-03-06 14:12:00 -06:00
<div class="sig-grid">
<div>
<div class="sig-block"><div class="sig-label">Employee Signature</div></div>
<div class="sig-date-block"><div class="sig-label">Date</div></div>
</div>
<div>
<div class="sig-block"><div class="sig-label">Supervisor / Documenting Officer Signature</div></div>
<div class="sig-date-block"><div class="sig-label">Date</div></div>
</div>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:12:00 -06:00
</div>
2026-03-06 12:19:55 -06:00
</div>
2026-03-06 14:38:43 -06:00
<div class="footer-bar">CPAS Violation Record — Document ID: CPAS- ${ v . id . toString (). padStart ( 5 , '0' ) } | ${ v . employee_name } | Incident: ${ v . incident_date } | Message Point Media Internal Use Only</div>
2026-03-06 12:19:55 -06:00
2026-03-06 14:12:00 -06:00
</div>
2026-03-06 12:19:55 -06:00
</body>
</html>` ;
}
module . exports = buildHtml ;