backfill button and usage
Build and Push Docker Image / build (push) Successful in 16s

This commit is contained in:
2026-05-19 09:29:41 -05:00
parent 6ddc09aa71
commit 2d4920bd15
7 changed files with 171 additions and 5 deletions
+37 -1
View File
@@ -86,6 +86,11 @@ const s = {
fontSize: '9px', fontWeight: 700, background: '#0e2a2a', color: '#4db6ac',
border: '1px solid #1a4a4a', verticalAlign: 'middle',
},
backfillBtn: {
background: 'none', border: '1px solid #d4af37', color: '#ffd666',
borderRadius: '4px', padding: '4px 10px', fontSize: '11px',
cursor: 'pointer', fontWeight: 600,
},
};
export default function EmployeeModal({ employeeId, onClose }) {
@@ -156,6 +161,26 @@ export default function EmployeeModal({ employeeId, onClose }) {
}
};
const handleRecomputeSnapshots = async () => {
if (!window.confirm(
'Rebuild the "Prior Active Points" snapshot on every violation for this employee?\n\n' +
'Use this after back-dating a violation if older PDFs no longer reflect the correct prior-points total. ' +
'Existing PDFs will regenerate with up-to-date numbers.'
)) return;
try {
const r = await axios.post(`/api/employees/${employeeId}/recompute-snapshots`);
const { scanned, updated } = r.data;
if (updated === 0) {
toast.success(`Snapshots already up to date (${scanned} checked).`);
} else {
toast.success(`Updated ${updated} of ${scanned} snapshot${updated === 1 ? '' : 's'}.`);
}
load();
} catch (err) {
toast.error('Backfill failed: ' + (err.response?.data?.error || err.message));
}
};
const handleNegate = async ({ resolution_type, details, resolved_by }) => {
try {
await axios.patch(`/api/violations/${negating.id}/negate`, { resolution_type, details, resolved_by });
@@ -251,7 +276,18 @@ export default function EmployeeModal({ employeeId, onClose }) {
)}
{/* ── Active Violations ── */}
<div style={s.sectionHd}>Active Violations</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: '24px', marginBottom: '10px' }}>
<div style={{ ...s.sectionHd, marginTop: 0, marginBottom: 0 }}>Active Violations</div>
{violations.length > 0 && (
<button
style={s.backfillBtn}
onClick={handleRecomputeSnapshots}
title="Rebuild prior-points snapshot on each violation. Use after a back-dated insert if older PDFs show the wrong Prior Active Points."
>
Backfill Snapshots
</button>
)}
</div>
{active.length === 0 ? (
<div style={{ color: '#777990', fontStyle: 'italic', fontSize: '12px' }}>
No active violations on record.
+27
View File
@@ -164,6 +164,31 @@ Update name, department, or supervisor. Changes are logged to the audit trail.
#### Merge Duplicate
If the same employee exists under two names, use Merge to reassign all violations from the duplicate to the canonical record. The duplicate is then deleted. This cannot be undone.
#### Backfill Snapshots (repair tool)
Each violation stores a **prior-points snapshot** at submission time so its PDF always shows the score *as it was on the incident date*. Normally you never touch this — it's set on insert, refreshed automatically when a back-dated violation lands inside another violation's 90-day window, and otherwise locked. PDFs stay stable through negate/restore by design.
The **↻ Backfill Snapshots** button sits next to the **Active Violations** header in the profile modal. It rebuilds the snapshot on every violation for that employee using current data.
**When to use it:**
- After back-dating a violation, an older PDF still shows "Prior Active Points: 0" even though an earlier violation now clearly exists in the timeline.
- More generally: any time a regenerated PDF disagrees with what you see in the Point Expiration Timeline (the timeline is computed live; PDFs use the snapshot).
**When *not* to use it:**
- After a negate, restore, amend, or hard delete in normal workflow. The system intentionally keeps existing PDFs stable through those operations.
- As a routine maintenance step. If you keep needing it after ordinary back-dated inserts, that's a bug worth reporting — the auto-refresh should already be covering you.
**What clicking it does:**
1. Iterates every violation belonging to the employee (active and negated).
2. Recomputes each row's prior-points snapshot from the current set of non-negated violations in the 90 days before its incident date.
3. Writes only the rows that actually changed.
4. Records one entry in the audit log (action: \`violation_snapshots_recomputed\`, reason: \`manual_backfill\`) with the per-row before/after values.
A toast confirms the outcome — either *"Updated X of Y snapshots"* or *"Snapshots already up to date"*. Re-download any affected PDFs after running it; the new totals will appear immediately.
---
### Audit Log
@@ -217,6 +242,7 @@ Toasts auto-dismiss after a few seconds (errors persist longer). Each toast has
| Edit employee name / dept / supervisor | Yes | Logged to audit trail |
| Merge duplicate employees | Yes | Irreversible |
| Add / edit employee notes | Yes | Does not affect score |
| Recompute prior-points snapshot | Yes | Two paths only: auto (back-dated insert) or **↻ Backfill Snapshots** button. Never touched by negate, restore, amend, or hard delete |
---
@@ -237,6 +263,7 @@ Toasts auto-dismiss after a few seconds (errors persist longer). Each toast has
- In-app admin guide (this panel)
- Acknowledgment signature field — employee name + date on form and PDF
- Toast notification system — global feedback for all user actions
- Backfill Snapshots — per-employee repair tool that rebuilds the prior-points snapshot on every violation when older PDFs drift from current data
---