Phase 3
This commit is contained in:
54
server.js
54
server.js
@@ -1,7 +1,8 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const db = require('./db/database');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const db = require('./db/database');
|
||||
const generatePdf = require('./pdf/generator');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
@@ -47,7 +48,7 @@ app.post('/api/employees', (req, res) => {
|
||||
res.status(201).json({ id: result.lastInsertRowid, name, department, supervisor });
|
||||
});
|
||||
|
||||
// ── Employee CPAS Score (rolling 90-day) ───────────────────────────────────
|
||||
// ── Employee CPAS Score ────────────────────────────────────────────────────
|
||||
app.get('/api/employees/:employeeId/score', (req, res) => {
|
||||
const row = db.prepare(
|
||||
'SELECT * FROM active_cpas_scores WHERE employee_id = ?'
|
||||
@@ -55,8 +56,7 @@ app.get('/api/employees/:employeeId/score', (req, res) => {
|
||||
res.json(row || { employee_id: req.params.employeeId, active_points: 0, violation_count: 0 });
|
||||
});
|
||||
|
||||
// ── Violation type usage counts for an employee (90-day window) ────────────
|
||||
// Returns { violation_type: count } so the frontend can badge the dropdown
|
||||
// ── Violation type counts (90-day) ─────────────────────────────────────────
|
||||
app.get('/api/employees/:employeeId/violation-counts', (req, res) => {
|
||||
const rows = db.prepare(`
|
||||
SELECT violation_type, COUNT(*) as count
|
||||
@@ -65,13 +65,12 @@ app.get('/api/employees/:employeeId/violation-counts', (req, res) => {
|
||||
AND incident_date >= DATE('now', '-90 days')
|
||||
GROUP BY violation_type
|
||||
`).all(req.params.employeeId);
|
||||
|
||||
const map = {};
|
||||
rows.forEach(r => { map[r.violation_type] = r.count; });
|
||||
res.json(map);
|
||||
});
|
||||
|
||||
// ── All-time violation type counts for recidivist point suggestion ─────────
|
||||
// ── Violation type counts (all-time) ───────────────────────────────────────
|
||||
app.get('/api/employees/:employeeId/violation-counts/alltime', (req, res) => {
|
||||
const rows = db.prepare(`
|
||||
SELECT violation_type, COUNT(*) as count, MAX(points) as max_points_used
|
||||
@@ -79,13 +78,12 @@ app.get('/api/employees/:employeeId/violation-counts/alltime', (req, res) => {
|
||||
WHERE employee_id = ?
|
||||
GROUP BY violation_type
|
||||
`).all(req.params.employeeId);
|
||||
|
||||
const map = {};
|
||||
rows.forEach(r => { map[r.violation_type] = { count: r.count, max_points_used: r.max_points_used }; });
|
||||
res.json(map);
|
||||
});
|
||||
|
||||
// ── Violation history for an employee ─────────────────────────────────────
|
||||
// ── Violation history ──────────────────────────────────────────────────────
|
||||
app.get('/api/violations/employee/:employeeId', (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
const rows = db.prepare(`
|
||||
@@ -127,6 +125,40 @@ app.post('/api/violations', (req, res) => {
|
||||
res.status(201).json({ id: result.lastInsertRowid });
|
||||
});
|
||||
|
||||
// ── PDF Generation ─────────────────────────────────────────────────────────
|
||||
// GET /api/violations/:id/pdf
|
||||
// Returns a binary PDF of the violation document
|
||||
app.get('/api/violations/:id/pdf', async (req, res) => {
|
||||
try {
|
||||
const violation = db.prepare(`
|
||||
SELECT v.*, e.name as employee_name, e.department, e.supervisor
|
||||
FROM violations v
|
||||
JOIN employees e ON e.id = v.employee_id
|
||||
WHERE v.id = ?
|
||||
`).get(req.params.id);
|
||||
|
||||
if (!violation) return res.status(404).json({ error: 'Violation not found' });
|
||||
|
||||
// Pull employee 90-day score for context block in PDF
|
||||
const score = db.prepare(
|
||||
'SELECT * FROM active_cpas_scores WHERE employee_id = ?'
|
||||
).get(violation.employee_id) || { active_points: 0, violation_count: 0 };
|
||||
|
||||
const pdfBuffer = await generatePdf(violation, score);
|
||||
|
||||
const safeName = violation.employee_name.replace(/[^a-z0-9]/gi, '_');
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="CPAS_${safeName}_${violation.incident_date}.pdf"`,
|
||||
'Content-Length': pdfBuffer.length,
|
||||
});
|
||||
res.end(pdfBuffer);
|
||||
} catch (err) {
|
||||
console.error('[PDF] Error:', err);
|
||||
res.status(500).json({ error: 'PDF generation failed', detail: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ── SPA fallback ───────────────────────────────────────────────────────────
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'client', 'dist', 'index.html'));
|
||||
|
||||
Reference in New Issue
Block a user