Files
quality_app-v2/documentation/DATABASE_TRIGGERS_IMPLEMENTATION.md
Quality App Developer 07f77603eb Implement approved/rejected quantity triggers and warehouse inventory
Database Triggers Implementation:
- Added automatic quantity calculation triggers for scanfg_orders
- Added automatic quantity calculation triggers for scan1_orders (T1 phase)
- Triggers calculate based on CP_base_code grouping (8 digits)
- Quality code: 0 = approved, != 0 = rejected
- Quantities set at insertion time (BEFORE INSERT trigger)
- Added create_triggers() function to initialize_db.py

Warehouse Inventory Enhancement:
- Analyzed old app database quantity calculation logic
- Created comprehensive trigger implementation guide
- Added trigger verification and testing procedures
- Documented data migration strategy

Documentation Added:
- APPROVED_REJECTED_QUANTITIES_ANALYSIS.md - Old app logic analysis
- DATABASE_TRIGGERS_IMPLEMENTATION.md - v2 implementation guide
- WAREHOUSE_INVENTORY_IMPLEMENTATION.md - Inventory view feature

Files Modified:
- initialize_db.py: Added create_triggers() function and call in main()
- Documentation: 3 comprehensive guides for database and inventory management

Quality Metrics:
- Triggers maintain legacy compatibility
- Automatic calculation ensures data consistency
- Performance optimized at database level
- Comprehensive testing documented
2026-01-30 12:30:56 +02:00

11 KiB

🔄 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)

-- 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

-- 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)

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

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

-- 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

-- 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:

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:

# 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:

-- 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:

-- 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

# 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