/** * Validation Rules Routes - FULL CRUD * * Validation rules are leaf nodes - no children, can be deleted directly * technique -> technique_validation_rule */ import { Router, Request, Response } from 'express'; import { query, queryOne, transaction } from '../config/database'; import { TechniqueValidationRule, ApiResponse } from '../types'; import { PoolClient } from 'pg'; import { internalError } from '../config/error-response'; const router = Router(); // Parameter type for validation rules const PARAMETER_TYPE_VALIDATION_RULE = 5; // Helper: Create parameter entry const createParameter = async (client: PoolClient): Promise => { const maxResult = await client.query('SELECT COALESCE(MAX(parameter_id), 0) + 1 as next_id FROM parameter'); const nextParamId = maxResult.rows[0].next_id; await client.query(` INSERT INTO parameter (parameter_id, parameter_type, valid_from, valid_to, created_date, updated_date) VALUES ($1, $2, '2026-01-01', '2999-01-01', CURRENT_DATE, CURRENT_DATE) `, [nextParamId, PARAMETER_TYPE_VALIDATION_RULE]); return nextParamId; }; // Helper: Get next rule_id const getNextRuleId = async (client: PoolClient): Promise => { const result = await client.query('SELECT COALESCE(MAX(technique_valid_rule_id), 0) + 1 as next_id FROM technique_validation_rule'); return result.rows[0].next_id; }; // GET all validation rules router.get('/', async (req: Request, res: Response) => { try { const rules = await query( 'SELECT * FROM technique_validation_rule ORDER BY technique_id, technique_valid_rule_id' ); res.json({ success: true, data: rules, count: rules.length } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' } as ApiResponse); } }); // GET validation rules with technique info router.get('/with-techniques', async (req: Request, res: Response) => { try { const rules = await query(` SELECT r.*, t.technique_name FROM technique_validation_rule r JOIN technique t ON r.technique_id = t.technique_id ORDER BY r.technique_id, r.technique_valid_rule_id `); res.json({ success: true, data: rules, count: rules.length }); } catch (error) { internalError(res, error); } }); // GET validation rules by technique_id router.get('/by-technique/:techniqueId', async (req: Request, res: Response) => { try { const rules = await query( 'SELECT * FROM technique_validation_rule WHERE technique_id = $1 ORDER BY technique_valid_rule_id', [req.params.techniqueId] ); res.json({ success: true, data: rules, count: rules.length } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Unknown error' } as ApiResponse); } }); // GET stats — declarat înainte de '/:id', altfel 'stats' e capturat ca :id router.get('/stats', async (req: Request, res: Response) => { try { const stats = await query(` SELECT (SELECT COUNT(*) FROM technique) as total_techniques, (SELECT COUNT(DISTINCT technique_id) FROM technique_validation_rule) as techniques_with_rules, (SELECT COUNT(*) FROM technique_validation_rule) as total_rules, (SELECT COUNT(*) FROM technique WHERE technique_id NOT IN (SELECT DISTINCT technique_id FROM technique_validation_rule)) as techniques_missing_rules `); res.json({ success: true, data: stats[0] }); } catch (error) { internalError(res, error); } }); // GET single validation rule by ID router.get('/:id', async (req: Request, res: Response) => { try { const rule = await queryOne( 'SELECT * FROM technique_validation_rule WHERE technique_valid_rule_id = $1', [req.params.id] ); if (!rule) { return res.status(404).json({ success: false, error: 'Regula de validare nu a fost găsită' }); } res.json({ success: true, data: rule }); } catch (error) { internalError(res, error); } }); // POST - Create single validation rule router.post('/', async (req: Request, res: Response) => { try { const { technique_id, rule_name, rule_value, description } = req.body; // Validation if (!technique_id || !rule_name || !rule_value) { return res.status(400).json({ success: false, error: 'Câmpuri obligatorii: technique_id, rule_name, rule_value' }); } // Verify technique exists const technique = await queryOne('SELECT technique_id FROM technique WHERE technique_id = $1', [technique_id]); if (!technique) { return res.status(400).json({ success: false, error: 'Tehnica specificată nu există' }); } const result = await transaction(async (client) => { // Create parameter entry const parameterId = await createParameter(client); // Get next rule ID const ruleId = await getNextRuleId(client); // Insert rule const insertResult = await client.query(` INSERT INTO technique_validation_rule (technique_valid_rule_id, technique_id, rule_name, rule_value, description, parameter_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `, [ruleId, technique_id, rule_name, rule_value, description || '', parameterId]); return insertResult.rows[0]; }); res.status(201).json({ success: true, data: result, message: 'Regula de validare a fost creată cu succes' }); } catch (error) { internalError(res, error); } }); // POST /bulk-for-technique - Create multiple rules for a single technique router.post('/bulk-for-technique/:techniqueId', async (req: Request, res: Response) => { try { const techniqueId = parseInt(req.params.techniqueId); const { rules } = req.body; if (!rules || !Array.isArray(rules) || rules.length === 0) { return res.status(400).json({ success: false, error: 'Câmp obligatoriu: rules (array de {rule_name, rule_value, description})' }); } // Verify technique exists const technique = await queryOne('SELECT technique_id FROM technique WHERE technique_id = $1', [techniqueId]); if (!technique) { return res.status(400).json({ success: false, error: 'Tehnica specificată nu există' }); } const result = await transaction(async (client) => { const created: any[] = []; for (const rule of rules) { if (!rule.rule_name || !rule.rule_value) { throw new Error(`Regulă invalidă: ${JSON.stringify(rule)}. Obligatoriu: rule_name, rule_value`); } // Create parameter entry const parameterId = await createParameter(client); // Get next rule ID const ruleId = await getNextRuleId(client); // Insert rule const insertResult = await client.query(` INSERT INTO technique_validation_rule (technique_valid_rule_id, technique_id, rule_name, rule_value, description, parameter_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING * `, [ruleId, techniqueId, rule.rule_name, rule.rule_value, rule.description || '', parameterId]); created.push(insertResult.rows[0]); } return created; }); res.status(201).json({ success: true, data: result, count: result.length, message: `${result.length} reguli de validare au fost create cu succes` }); } catch (error) { internalError(res, error); } }); // PUT - Update validation rule router.put('/:id', async (req: Request, res: Response) => { try { const id = req.params.id; const { technique_id, rule_name, rule_value, description } = req.body; // Check if rule exists const existing = await queryOne( 'SELECT * FROM technique_validation_rule WHERE technique_valid_rule_id = $1', [id] ); if (!existing) { return res.status(404).json({ success: false, error: 'Regula de validare nu a fost găsită' }); } // If changing technique_id, verify it exists if (technique_id) { const technique = await queryOne('SELECT technique_id FROM technique WHERE technique_id = $1', [technique_id]); if (!technique) { return res.status(400).json({ success: false, error: 'Tehnica specificată nu există' }); } } const result = await queryOne(` UPDATE technique_validation_rule SET technique_id = COALESCE($1, technique_id), rule_name = COALESCE($2, rule_name), rule_value = COALESCE($3, rule_value), description = COALESCE($4, description) WHERE technique_valid_rule_id = $5 RETURNING * `, [technique_id, rule_name, rule_value, description, id]); res.json({ success: true, data: result, message: 'Regula de validare a fost actualizată cu succes' }); } catch (error) { internalError(res, error); } }); // DELETE - Delete single validation rule router.delete('/:id', async (req: Request, res: Response) => { try { const id = req.params.id; // Check if rule exists const existing = await queryOne( 'SELECT * FROM technique_validation_rule WHERE technique_valid_rule_id = $1', [id] ); if (!existing) { return res.status(404).json({ success: false, error: 'Regula de validare nu a fost găsită' }); } // Delete (leaf node - no children to check) await query('DELETE FROM technique_validation_rule WHERE technique_valid_rule_id = $1', [id]); res.json({ success: true, message: 'Regula de validare a fost ștearsă cu succes', deleted: true }); } catch (error) { internalError(res, error); } }); // DELETE all validation rules for a technique router.delete('/by-technique/:techniqueId', async (req: Request, res: Response) => { try { const techniqueId = req.params.techniqueId; // Verify technique exists const technique = await queryOne('SELECT technique_id FROM technique WHERE technique_id = $1', [techniqueId]); if (!technique) { return res.status(404).json({ success: false, error: 'Tehnica specificată nu există' }); } const result = await query( 'DELETE FROM technique_validation_rule WHERE technique_id = $1 RETURNING *', [techniqueId] ); res.json({ success: true, data: result, count: result.length, message: `${result.length} reguli de validare au fost șterse pentru tehnica ${techniqueId}` }); } catch (error) { internalError(res, error); } }); export default router;