Fix math logic for timeline

This commit is contained in:
2026-05-19 00:33:08 -05:00
parent ba2b631e23
commit e2c352d518
44 changed files with 7660 additions and 22 deletions
+67 -21
View File
@@ -260,6 +260,33 @@ function getPriorActivePoints(employeeId, incidentDate) {
return row ? row.pts : 0;
}
// Helper: after a back-dated insert, refresh snapshots on any existing
// violations whose 90-day prior-window now includes the new (earlier)
// incident_date. Without this, their PDFs would still show the pre-backdate
// "Prior Active Points" and miss the inserted earlier violation.
// Snapshots are still immutable w.r.t. negate/restore — only timeline-
// rewriting events (back-dated inserts) trigger a refresh.
function recomputeSnapshotsAfter(employeeId, incidentDate) {
const affected = db.prepare(`
SELECT id, incident_date, prior_active_points
FROM violations
WHERE employee_id = ?
AND incident_date > ?
AND incident_date <= DATE(?, '+90 days')
`).all(employeeId, incidentDate, incidentDate);
const updateStmt = db.prepare('UPDATE violations SET prior_active_points = ? WHERE id = ?');
const changes = [];
for (const v of affected) {
const newPrior = getPriorActivePoints(employeeId, v.incident_date);
if (newPrior !== v.prior_active_points) {
updateStmt.run(newPrior, v.id);
changes.push({ id: v.id, incident_date: v.incident_date, old: v.prior_active_points, new: newPrior });
}
}
return changes;
}
// POST new violation
app.post('/api/violations', (req, res) => {
const {
@@ -275,32 +302,51 @@ app.post('/api/violations', (req, res) => {
}
const ptsInt = parseInt(points);
const priorPts = getPriorActivePoints(employee_id, incident_date);
const result = db.prepare(`
INSERT INTO violations (
employee_id, violation_type, violation_name, category,
points, incident_date, incident_time, location,
details, submitted_by, witness_name,
prior_active_points,
acknowledged_by, acknowledged_date,
amount
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
employee_id, violation_type, violation_name || violation_type,
category || 'General', ptsInt, incident_date,
incident_time || null, location || null,
details || null, submitted_by || null, witness_name || null,
priorPts,
acknowledged_by || null, acknowledged_date || null,
amount || null
);
// Insert + downstream snapshot refresh run in a single transaction so a
// failed recompute can't leave the system with a new violation and stale
// sibling snapshots.
const insertTxn = db.transaction(() => {
const priorPts = getPriorActivePoints(employee_id, incident_date);
const result = db.prepare(`
INSERT INTO violations (
employee_id, violation_type, violation_name, category,
points, incident_date, incident_time, location,
details, submitted_by, witness_name,
prior_active_points,
acknowledged_by, acknowledged_date,
amount
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
employee_id, violation_type, violation_name || violation_type,
category || 'General', ptsInt, incident_date,
incident_time || null, location || null,
details || null, submitted_by || null, witness_name || null,
priorPts,
acknowledged_by || null, acknowledged_date || null,
amount || null
);
const refreshed = recomputeSnapshotsAfter(employee_id, incident_date);
return { id: result.lastInsertRowid, refreshed };
});
audit('violation_created', 'violation', result.lastInsertRowid, submitted_by, {
const { id: newId, refreshed } = insertTxn();
audit('violation_created', 'violation', newId, submitted_by, {
employee_id, violation_type, points: ptsInt, incident_date,
});
res.status(201).json({ id: result.lastInsertRowid });
// Back-dated insert: log the snapshot refresh so the audit trail explains
// why downstream violations' PDFs now show different "Prior Active Points".
if (refreshed.length > 0) {
audit('violation_snapshots_recomputed', 'violation', newId, submitted_by, {
reason: 'backdated_insert',
trigger_incident_date: incident_date,
affected: refreshed,
});
}
res.status(201).json({ id: newId });
});
// ── Violation Amendment (edit) ───────────────────────────────────────────────