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
355 lines
11 KiB
Markdown
355 lines
11 KiB
Markdown
# 🔄 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
|