# 🔄 Database Triggers Implementation for v2 **Date:** January 30, 2026 **Status:** ✅ Ready for Implementation **Priority:** HIGH --- ## 📋 SQL Triggers for v2 scanfg_orders ### Current Situation The v2 application has the scanfg_orders table but: - ❌ No database triggers for automatic calculation - ❌ CP_base_code not extracted automatically - ❌ Quantities may be entered manually or not calculated ### Required Implementation #### 1. Add Generated Column (if not present) ```sql -- Check if cp_base_code column exists SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'scanfg_orders' AND COLUMN_NAME = 'cp_base_code'; -- If not exists, add it: ALTER TABLE scanfg_orders ADD COLUMN cp_base_code VARCHAR(10) GENERATED ALWAYS AS (SUBSTRING(CP_full_code, 1, 10)) STORED; -- Add index for performance CREATE INDEX idx_cp_base_code ON scanfg_orders(cp_base_code); ``` #### 2. Create Trigger for Automatic Quantity Calculation ```sql -- Drop existing trigger if present DROP TRIGGER IF EXISTS set_quantities_fg; -- Create new trigger CREATE TRIGGER set_quantities_fg BEFORE INSERT ON scanfg_orders FOR EACH ROW BEGIN -- Count how many APPROVED entries exist for this CP_base_code SET @approved = ( SELECT COUNT(*) FROM scanfg_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code = 0 ); -- Count how many REJECTED entries exist for this CP_base_code SET @rejected = ( SELECT COUNT(*) FROM scanfg_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code != 0 ); -- Set quantities based on this new row's quality_code IF NEW.quality_code = 0 THEN -- Approved scan: increment approved count SET NEW.approved_quantity = @approved + 1; SET NEW.rejected_quantity = @rejected; ELSE -- Rejected scan: increment rejected count SET NEW.approved_quantity = @approved; SET NEW.rejected_quantity = @rejected + 1; END IF; END; ``` #### 3. Same for scan1_orders (T1 Phase) ```sql DROP TRIGGER IF EXISTS set_quantities_scan1; CREATE TRIGGER set_quantities_scan1 BEFORE INSERT ON scan1_orders FOR EACH ROW BEGIN SET @approved = ( SELECT COUNT(*) FROM scan1_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code = 0 ); SET @rejected = ( SELECT COUNT(*) FROM scan1_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code != 0 ); IF NEW.quality_code = 0 THEN SET NEW.approved_quantity = @approved + 1; SET NEW.rejected_quantity = @rejected; ELSE SET NEW.approved_quantity = @approved; SET NEW.rejected_quantity = @rejected + 1; END IF; END; ``` --- ## 🔍 Verification Queries ### Check if Triggers Exist ```sql SELECT TRIGGER_NAME, TRIGGER_SCHEMA, TRIGGER_TABLE, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = 'quality_app_v2' AND TRIGGER_TABLE IN ('scanfg_orders', 'scan1_orders'); ``` ### Verify Trigger is Working ```sql -- Insert test record INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time) VALUES ('OP01', 'CP00000001-0001', 'OC01', 'OC02', 0, CURDATE(), CURTIME()); -- Check if quantities were set automatically SELECT Id, CP_full_code, quality_code, approved_quantity, rejected_quantity FROM scanfg_orders WHERE CP_full_code = 'CP00000001-0001'; -- Should show: approved_quantity = 1, rejected_quantity = 0 ``` ### Full Test Scenario ```sql -- Step 1: Insert approved record INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time) VALUES ('OP01', 'CP00000001-0001', 'OC01', 'OC02', 0, CURDATE(), CURTIME()); -- Expected: approved_qty=1, rejected_qty=0 -- Step 2: Insert another approved record INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time) VALUES ('OP02', 'CP00000001-0002', 'OC01', 'OC02', 0, CURDATE(), CURTIME()); -- Expected: approved_qty=2, rejected_qty=0 -- Step 3: Insert rejected record INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time) VALUES ('OP01', 'CP00000001-0003', 'OC01', 'OC02', 2, CURDATE(), CURTIME()); -- Expected: approved_qty=2, rejected_qty=1 -- Verify all records SELECT CP_full_code, quality_code, approved_quantity, rejected_quantity FROM scanfg_orders WHERE SUBSTRING(CP_full_code, 1, 10) = 'CP00000001' ORDER BY Id; ``` --- ## 🔧 Integration Points in Python Code ### 1. Database Initialization (initialize_db.py) Add trigger creation to the database setup: ```python def create_scan_triggers(): """Create triggers for automatic quantity calculation""" try: conn = get_db() cursor = conn.cursor() # Drop existing triggers cursor.execute("DROP TRIGGER IF EXISTS set_quantities_fg") cursor.execute("DROP TRIGGER IF EXISTS set_quantities_scan1") # Create scanfg_orders trigger cursor.execute(""" CREATE TRIGGER set_quantities_fg BEFORE INSERT ON scanfg_orders FOR EACH ROW BEGIN SET @approved = (SELECT COUNT(*) FROM scanfg_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code = 0); SET @rejected = (SELECT COUNT(*) FROM scanfg_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code != 0); IF NEW.quality_code = 0 THEN SET NEW.approved_quantity = @approved + 1; SET NEW.rejected_quantity = @rejected; ELSE SET NEW.approved_quantity = @approved; SET NEW.rejected_quantity = @rejected + 1; END IF; END """) logger.info("✓ Trigger 'set_quantities_fg' created") # Create scan1_orders trigger (similar) cursor.execute(""" CREATE TRIGGER set_quantities_scan1 BEFORE INSERT ON scan1_orders FOR EACH ROW BEGIN SET @approved = (SELECT COUNT(*) FROM scan1_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code = 0); SET @rejected = (SELECT COUNT(*) FROM scan1_orders WHERE SUBSTRING(CP_full_code, 1, 10) = SUBSTRING(NEW.CP_full_code, 1, 10) AND quality_code != 0); IF NEW.quality_code = 0 THEN SET NEW.approved_quantity = @approved + 1; SET NEW.rejected_quantity = @rejected; ELSE SET NEW.approved_quantity = @approved; SET NEW.rejected_quantity = @rejected + 1; END IF; END """) logger.info("✓ Trigger 'set_quantities_scan1' created") conn.commit() cursor.close() return True except Exception as e: logger.error(f"✗ Error creating triggers: {e}") return False ``` ### 2. FG Scan Form (fg_scan.html) Ensure quality_code is set correctly: ```python # In routes.py fg_scan endpoint quality_status = request.form.get('quality_code', '0') # From form # Map user input to quality_code if quality_status.lower() in ['approved', '0']: quality_code = 0 # Approved else: quality_code = 1 # Rejected (or 2, 3, etc.) # Insert record (trigger will auto-calculate quantities) cursor.execute(""" INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, box_id, location_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( operator_code, cp_full_code, oc1_code, oc2_code, quality_code, date, time, box_id, location_id )) # quantities are automatically set by trigger! ``` ### 3. Warehouse Inventory Display The quantities are now automatically available: ```sql -- In warehouse.py get_cp_inventory_list() SELECT s.CP_full_code, SUBSTRING(s.CP_full_code, 1, 10) as cp_base, SUM(s.approved_quantity) as total_approved, -- Auto-calculated SUM(s.rejected_quantity) as total_rejected, -- Auto-calculated ... FROM scanfg_orders s ... ``` --- ## 📊 Data Migration For existing records in the database: ```sql -- Recalculate quantities for all existing records UPDATE scanfg_orders s1 SET approved_quantity = ( SELECT COUNT(*) FROM scanfg_orders s2 WHERE SUBSTRING(s2.CP_full_code, 1, 10) = SUBSTRING(s1.CP_full_code, 1, 10) AND s2.quality_code = 0 AND s2.Id <= s1.Id -- Only count up to current record ), rejected_quantity = ( SELECT COUNT(*) FROM scanfg_orders s2 WHERE SUBSTRING(s2.CP_full_code, 1, 10) = SUBSTRING(s1.CP_full_code, 1, 10) AND s2.quality_code != 0 AND s2.Id <= s1.Id -- Only count up to current record ); ``` --- ## ✅ Checklist - [ ] Add cp_base_code generated column to scanfg_orders - [ ] Add cp_base_code generated column to scan1_orders - [ ] Create set_quantities_fg trigger - [ ] Create set_quantities_scan1 trigger - [ ] Test with sample inserts - [ ] Verify trigger working correctly - [ ] Update initialize_db.py to create triggers - [ ] Update db_schema_verifier.py to verify triggers exist - [ ] Test with production-like data volume - [ ] Document for team - [ ] Deploy to production --- ## 🚀 Execution Steps ### Step 1: Test Locally ```bash # Connect to test database mysql -h localhost -u root -p quality_app_v2 # Run verification query SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_TABLE IN ('scanfg_orders', 'scan1_orders'); ``` ### Step 2: Add to Schema Verifier Update `db_schema_verifier.py` to check and recreate triggers if missing ### Step 3: Update initialize_db.py Add trigger creation to database initialization sequence ### Step 4: Deploy - Restart application - Verify triggers created in database - Test with new FG scan entries --- ## 📝 Notes - Triggers execute **BEFORE INSERT** (before record is written to DB) - Quantities are **immutable** after insertion (set once) - Grouping is by **CP_base_code** (8 digits), not full code - Compatible with existing data and warehouse features - Maintains consistency with legacy application behavior --- **Priority:** HIGH **Effort:** MEDIUM **Impact:** Data Accuracy, Report Correctness