const express = require('express'); const { all, audit, now, one, parseJson, run, tx } = require('../db'); const { requireCapability } = require('../middleware/auth'); const { expandRuleSet } = require('../rule-catalog'); const router = express.Router(); function coerceRules(value) { if (value === undefined || value === null) return {}; if (typeof value === 'string') { try { return JSON.parse(value); } catch { const error = new Error('rules_json is not valid JSON'); error.status = 400; throw error; } } if (typeof value !== 'object') { const error = new Error('rules_json must be a JSON object'); error.status = 400; throw error; } return value; } function serialize(row) { return { ...row, active: Boolean(row.active), rules_json: parseJson(row.rules_json, {}) }; } router.get('/tax-rules', (req, res) => { const ruleSets = all('SELECT * FROM tax_rule_sets ORDER BY active DESC, effective_date DESC, id DESC').map(serialize); res.json({ ruleSets }); }); router.post('/tax-rules', requireCapability('finance.manage'), (req, res, next) => { try { const rules = coerceRules(req.body.rules_json ?? req.body.rules); const result = run( 'INSERT INTO tax_rule_sets (jurisdiction, name, effective_date, version, rules_json, active, source_note, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ req.body.jurisdiction || rules.jurisdiction || 'US-FED', req.body.name || rules.name || 'Untitled rule set', req.body.effective_date || rules.effectiveDate || rules.effective_date || new Date().toISOString().slice(0, 10), req.body.version || rules.version || '1.0', JSON.stringify(rules), req.body.active === false ? 0 : 1, req.body.source_note || rules.sourceNote || null, now() ] ); audit(req.user.id, 'create', 'tax_rule_set', result.lastInsertRowid, null, { ...req.body, rules_json: '[stored]' }); res.status(201).json({ ruleSet: serialize(one('SELECT * FROM tax_rule_sets WHERE id = ?', [result.lastInsertRowid])) }); } catch (error) { next(error); } }); router.put('/tax-rules/:id', requireCapability('finance.manage'), (req, res, next) => { try { const before = one('SELECT * FROM tax_rule_sets WHERE id = ?', [req.params.id]); if (!before) return res.status(404).json({ error: 'Rule set not found' }); const rules = req.body.rules_json !== undefined || req.body.rules !== undefined ? coerceRules(req.body.rules_json ?? req.body.rules) : parseJson(before.rules_json, {}); run( `UPDATE tax_rule_sets SET jurisdiction = ?, name = ?, effective_date = ?, version = ?, rules_json = ?, active = ?, source_note = ? WHERE id = ?`, [ req.body.jurisdiction ?? before.jurisdiction, req.body.name ?? before.name, req.body.effective_date ?? before.effective_date, req.body.version ?? before.version, JSON.stringify(rules), req.body.active === undefined ? before.active : (req.body.active ? 1 : 0), req.body.source_note ?? before.source_note, req.params.id ] ); audit(req.user.id, 'update', 'tax_rule_set', req.params.id, { ...before, rules_json: '[stored]' }, { ...req.body, rules_json: '[stored]' }); return res.json({ ruleSet: serialize(one('SELECT * FROM tax_rule_sets WHERE id = ?', [req.params.id])) }); } catch (error) { if (/UNIQUE/.test(error.message || '')) { return res.status(400).json({ error: 'A rule set with this jurisdiction and version already exists' }); } return next(error); } }); router.delete('/tax-rules/:id', requireCapability('finance.manage'), (req, res) => { const before = one('SELECT * FROM tax_rule_sets WHERE id = ?', [req.params.id]); if (!before) return res.status(404).json({ error: 'Rule set not found' }); run('DELETE FROM tax_rule_sets WHERE id = ?', [req.params.id]); audit(req.user.id, 'delete', 'tax_rule_set', req.params.id, { ...before, rules_json: '[stored]' }, null); return res.status(204).end(); }); router.post('/tax-rules/:id/activate', requireCapability('finance.manage'), (req, res) => { const target = one('SELECT * FROM tax_rule_sets WHERE id = ?', [req.params.id]); if (!target) return res.status(404).json({ error: 'Rule set not found' }); tx(() => { run('UPDATE tax_rule_sets SET active = 0 WHERE jurisdiction = ? AND id != ?', [target.jurisdiction, target.id]); run('UPDATE tax_rule_sets SET active = 1 WHERE id = ?', [target.id]); }); audit(req.user.id, 'activate', 'tax_rule_set', target.id, null, { jurisdiction: target.jurisdiction }); return res.json({ ruleSets: all('SELECT * FROM tax_rule_sets ORDER BY active DESC, effective_date DESC, id DESC').map(serialize) }); }); // Merge the generated 68-method depreciation catalog into a rule set (works on unsaved drafts too). router.post('/tax-rules/expand', requireCapability('finance.manage'), (req, res, next) => { try { const rules = coerceRules(req.body.rules_json ?? req.body.rules); return res.json({ rules_json: expandRuleSet(rules) }); } catch (error) { return next(error); } }); module.exports = router;