Fix math logic for timeline
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'cpas.db');
|
||||
const dir = path.dirname(dbPath);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8');
|
||||
db.exec(schema);
|
||||
|
||||
// ── Migrations for existing DBs ──────────────────────────────────────────────
|
||||
const cols = db.prepare('PRAGMA table_info(violations)').all().map(c => c.name);
|
||||
if (!cols.includes('negated')) db.exec("ALTER TABLE violations ADD COLUMN negated INTEGER NOT NULL DEFAULT 0");
|
||||
if (!cols.includes('negated_at')) db.exec("ALTER TABLE violations ADD COLUMN negated_at DATETIME");
|
||||
if (!cols.includes('prior_active_points')) db.exec("ALTER TABLE violations ADD COLUMN prior_active_points INTEGER");
|
||||
if (!cols.includes('prior_tier_label')) db.exec("ALTER TABLE violations ADD COLUMN prior_tier_label TEXT");
|
||||
if (!cols.includes('acknowledged_by')) db.exec("ALTER TABLE violations ADD COLUMN acknowledged_by TEXT");
|
||||
if (!cols.includes('acknowledged_date')) db.exec("ALTER TABLE violations ADD COLUMN acknowledged_date TEXT");
|
||||
|
||||
// Employee notes column (free-text, does not affect scoring)
|
||||
const empCols = db.prepare('PRAGMA table_info(employees)').all().map(c => c.name);
|
||||
if (!empCols.includes('notes')) db.exec("ALTER TABLE employees ADD COLUMN notes TEXT");
|
||||
|
||||
// Ensure resolutions table exists
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS violation_resolutions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
violation_id INTEGER NOT NULL REFERENCES violations(id) ON DELETE CASCADE,
|
||||
resolution_type TEXT NOT NULL,
|
||||
details TEXT,
|
||||
resolved_by TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// ── Feature: Violation Amendments ────────────────────────────────────────────
|
||||
// Stores a field-level diff every time a violation's editable fields are changed.
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS violation_amendments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
violation_id INTEGER NOT NULL REFERENCES violations(id) ON DELETE CASCADE,
|
||||
changed_by TEXT,
|
||||
field_name TEXT NOT NULL,
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// ── Feature: Audit Log ───────────────────────────────────────────────────────
|
||||
// Append-only record of every write action across the system.
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
action TEXT NOT NULL,
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id INTEGER,
|
||||
performed_by TEXT,
|
||||
details TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// ── Feature: Custom Violation Types ──────────────────────────────────────────
|
||||
// Persisted violation type definitions created via the UI. type_key is prefixed
|
||||
// with 'custom_' to prevent collisions with hardcoded violation keys.
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS violation_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type_key TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT NOT NULL DEFAULT 'Custom',
|
||||
chapter TEXT,
|
||||
description TEXT,
|
||||
min_points INTEGER NOT NULL DEFAULT 1,
|
||||
max_points INTEGER NOT NULL DEFAULT 1,
|
||||
fields TEXT NOT NULL DEFAULT '["description"]',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// Recreate view so it always filters negated rows
|
||||
db.exec(`DROP VIEW IF EXISTS active_cpas_scores;
|
||||
CREATE VIEW active_cpas_scores AS
|
||||
SELECT
|
||||
employee_id,
|
||||
SUM(points) AS active_points,
|
||||
COUNT(*) AS violation_count
|
||||
FROM violations
|
||||
WHERE negated = 0
|
||||
AND incident_date >= DATE('now', '-90 days')
|
||||
GROUP BY employee_id;`);
|
||||
|
||||
console.log('[DB] Connected:', dbPath);
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
department TEXT,
|
||||
supervisor TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS violations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
employee_id INTEGER NOT NULL REFERENCES employees(id),
|
||||
violation_type TEXT NOT NULL,
|
||||
violation_name TEXT NOT NULL,
|
||||
category TEXT NOT NULL DEFAULT 'General',
|
||||
points INTEGER NOT NULL,
|
||||
incident_date TEXT NOT NULL,
|
||||
incident_time TEXT,
|
||||
location TEXT,
|
||||
details TEXT,
|
||||
submitted_by TEXT,
|
||||
witness_name TEXT,
|
||||
negated INTEGER NOT NULL DEFAULT 0,
|
||||
negated_at DATETIME,
|
||||
prior_active_points INTEGER, -- snapshot at time of logging
|
||||
prior_tier_label TEXT, -- optional human-readable tier
|
||||
acknowledged_by TEXT, -- employee name who acknowledged receipt
|
||||
acknowledged_date TEXT, -- date of acknowledgment (YYYY-MM-DD)
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS violation_resolutions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
violation_id INTEGER NOT NULL REFERENCES violations(id) ON DELETE CASCADE,
|
||||
resolution_type TEXT NOT NULL,
|
||||
details TEXT,
|
||||
resolved_by TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Active score: only non-negated violations in rolling 90 days
|
||||
CREATE VIEW IF NOT EXISTS active_cpas_scores AS
|
||||
SELECT
|
||||
employee_id,
|
||||
SUM(points) AS active_points,
|
||||
COUNT(*) AS violation_count
|
||||
FROM violations
|
||||
WHERE negated = 0
|
||||
AND incident_date >= DATE('now', '-90 days')
|
||||
GROUP BY employee_id;
|
||||
Reference in New Issue
Block a user