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
This commit is contained in:
354
documentation/DATABASE_TRIGGERS_IMPLEMENTATION.md
Normal file
354
documentation/DATABASE_TRIGGERS_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,354 @@
|
||||
# 🔄 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
|
||||
Reference in New Issue
Block a user