diff options
Diffstat (limited to 'backend/database/dev')
| -rw-r--r-- | backend/database/dev/beepzone-full-dump.sql | 2214 | ||||
| -rw-r--r-- | backend/database/dev/beepzone-schema-consolidated-backup.sql | 1882 | ||||
| -rw-r--r-- | backend/database/dev/beepzone-schema-dump.sql | 2081 | ||||
| -rwxr-xr-x | backend/database/dev/export-clean-schema.sh | 32 | ||||
| -rwxr-xr-x | backend/database/dev/export-full-dump.sh | 37 |
5 files changed, 6246 insertions, 0 deletions
diff --git a/backend/database/dev/beepzone-full-dump.sql b/backend/database/dev/beepzone-full-dump.sql new file mode 100644 index 0000000..dd5f22e --- /dev/null +++ b/backend/database/dev/beepzone-full-dump.sql @@ -0,0 +1,2214 @@ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +DROP TABLE IF EXISTS `asset_change_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `asset_change_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_name` varchar(50) NOT NULL, + `action` enum('INSERT','UPDATE','DELETE') NOT NULL, + `record_id` int(11) NOT NULL, + `changed_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Only fields that actually changed' CHECK (json_valid(`changed_fields`)), + `old_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`old_values`)), + `new_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`new_values`)), + `changed_at` timestamp NULL DEFAULT current_timestamp(), + `changed_by_id` int(11) DEFAULT NULL, + `changed_by_username` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_table_action` (`table_name`,`action`), + KEY `idx_timestamp` (`changed_at`), + KEY `idx_record` (`record_id`), + KEY `idx_user` (`changed_by_id`), + CONSTRAINT `asset_change_log_ibfk_1` FOREIGN KEY (`changed_by_id`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=221 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `asset_change_log` WRITE; +/*!40000 ALTER TABLE `asset_change_log` DISABLE KEYS */; +INSERT INTO `asset_change_log` VALUES (1,'assets','INSERT',1,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DESK-1\", \"asset_numeric_id\": 75650012, \"asset_type\": \"N\", \"name\": \"Desktop Computer 1\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-1\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:46',1,'admin'),(2,'assets','INSERT',2,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DESK-2\", \"asset_numeric_id\": 34081098, \"asset_type\": \"N\", \"name\": \"Desktop Computer 2\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-2\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:46',1,'admin'),(3,'assets','INSERT',3,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DESK-3\", \"asset_numeric_id\": 64139289, \"asset_type\": \"N\", \"name\": \"Desktop Computer 3\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-3\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:46',1,'admin'),(4,'assets','INSERT',4,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DESK-4\", \"asset_numeric_id\": 40933134, \"asset_type\": \"N\", \"name\": \"Desktop Computer 4\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-4\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:46',1,'admin'),(5,'assets','INSERT',5,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DESK-5\", \"asset_numeric_id\": 80850219, \"asset_type\": \"N\", \"name\": \"Desktop Computer 5\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-5\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:47',1,'admin'),(6,'assets','INSERT',6,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"LAPTOP-1\", \"asset_numeric_id\": 65938365, \"asset_type\": \"B\", \"name\": \"Laptop 1\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-1\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:47',1,'admin'),(7,'assets','INSERT',7,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"LAPTOP-2\", \"asset_numeric_id\": 90756157, \"asset_type\": \"B\", \"name\": \"Laptop 2\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-2\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:47',1,'admin'),(8,'assets','INSERT',8,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"LAPTOP-3\", \"asset_numeric_id\": 97073776, \"asset_type\": \"B\", \"name\": \"Laptop 3\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-3\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:47',1,'admin'),(9,'assets','INSERT',9,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"OSC-001\", \"asset_numeric_id\": 40246871, \"asset_type\": \"L\", \"name\": \"Digital Oscilloscope\", \"category_id\": 3, \"manufacturer\": \"Tektronix\", \"model\": \"TBS2104B\", \"serial_number\": \"SN-OSC-001\", \"zone_id\": 7, \"status\": \"Good\", \"price\": 1899.99, \"purchase_date\": \"2024-01-10\", \"quantity_used\": 0, \"supplier_id\": 2, \"lendable\": 1, \"minimum_role_for_lending\": 50, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:48',1,'admin'),(10,'assets','INSERT',10,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"DMM-001\", \"asset_numeric_id\": 96642470, \"asset_type\": \"L\", \"name\": \"Digital Multimeter\", \"category_id\": 3, \"manufacturer\": \"Fluke\", \"model\": \"117\", \"serial_number\": \"SN-DMM-001\", \"zone_id\": 7, \"status\": \"Good\", \"price\": 199.99, \"purchase_date\": \"2024-01-10\", \"quantity_used\": 0, \"supplier_id\": 2, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:48',1,'admin'),(11,'assets','INSERT',11,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_available\", \"quantity_total\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"CABLE-ETH-001\", \"asset_numeric_id\": 57348031, \"asset_type\": \"C\", \"name\": \"Ethernet Cables (Box of 50)\", \"category_id\": 4, \"manufacturer\": \"Generic\", \"model\": \"Cat6 5ft\", \"serial_number\": \"BATCH-001\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 149.99, \"purchase_date\": \"2024-03-01\", \"quantity_available\": 50, \"quantity_total\": 50, \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:43:48',1,'admin'),(12,'assets','UPDATE',6,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 1, \"due_date\": \"2025-10-20\"}','2025-10-20 11:43:51',3,'staff1'),(13,'assets','UPDATE',9,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-10-15\"}','2025-10-20 11:43:51',2,'manager1'),(14,'assets','UPDATE',3,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-10-20 11:43:52',1,'admin'),(15,'assets','UPDATE',6,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 1, \"due_date\": \"2025-10-20\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 1}','2025-10-20 11:43:54',3,'staff1'),(16,'assets','UPDATE',10,'[\"status\", \"lending_status\"]','{\"status\": \"Good\", \"lending_status\": \"Available\"}','{\"status\": \"Faulty\", \"lending_status\": null}','2025-10-20 11:43:54',3,'staff1'),(17,'assets','UPDATE',10,'[\"status\", \"lending_status\"]','{\"status\": \"Faulty\", \"lending_status\": null}','{\"status\": \"Good\", \"lending_status\": \"Available\"}','2025-10-20 11:43:55',2,'manager1'),(18,'assets','INSERT',12,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"MOUSE-1\", \"asset_numeric_id\": 12309104, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 1\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-1\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 2}','2025-10-20 11:43:57',2,'manager1'),(19,'assets','INSERT',13,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"MOUSE-2\", \"asset_numeric_id\": 56755484, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 2\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-2\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 2}','2025-10-20 11:43:57',2,'manager1'),(20,'assets','INSERT',14,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"MOUSE-3\", \"asset_numeric_id\": 85179628, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 3\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-3\", \"zone_id\": 6, \"status\": \"Good\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 2}','2025-10-20 11:43:57',2,'manager1'),(21,'assets','INSERT',15,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"zone_plus\", \"zone_note\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"CLARIFY-1\", \"asset_numeric_id\": 26824546, \"asset_type\": \"N\", \"name\": \"Valid Clarify Test\", \"category_id\": 2, \"zone_id\": 5, \"zone_plus\": \"Clarify\", \"zone_note\": \"Needs verification of exact location in lab\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 2}','2025-10-20 11:43:59',2,'manager1'),(22,'assets','UPDATE',6,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-13\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:00',2,'manager1'),(23,'assets','UPDATE',7,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-13\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:00',2,'manager1'),(24,'assets','UPDATE',6,'[\"last_audit\"]','{\"last_audit\": \"2025-10-13\"}','{\"last_audit\": \"2025-10-20\"}','2025-10-20 11:44:03',2,'manager1'),(25,'assets','UPDATE',7,'[\"last_audit\"]','{\"last_audit\": \"2025-10-13\"}','{\"last_audit\": \"2025-10-20\"}','2025-10-20 11:44:04',2,'manager1'),(26,'assets','UPDATE',8,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:04',2,'manager1'),(27,'assets','UPDATE',11,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:04',2,'manager1'),(28,'assets','UPDATE',12,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:05',2,'manager1'),(29,'assets','UPDATE',13,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:05',2,'manager1'),(30,'assets','UPDATE',14,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\"}','2025-10-20 11:44:05',2,'manager1'),(31,'assets','UPDATE',10,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Attention\"}','2025-10-20 11:44:07',2,'manager1'),(32,'assets','UPDATE',10,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Attention\"}','2025-10-20 11:44:07',2,'manager1'),(33,'assets','UPDATE',2,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Missing\"}','2025-10-20 11:44:09',2,'manager1'),(34,'assets','UPDATE',2,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-10-20 11:44:09',2,'manager1'),(35,'assets','UPDATE',12,'[\"last_audit_status\"]','{\"last_audit_status\": \"Good\"}','{\"last_audit_status\": \"Faulty\"}','2025-10-20 11:44:10',2,'manager1'),(36,'assets','UPDATE',12,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Faulty\"}','2025-10-20 11:44:10',2,'manager1'),(37,'assets','INSERT',16,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ROUTER-1\", \"asset_numeric_id\": 49096117, \"asset_type\": \"N\", \"name\": \"Network Router\", \"category_id\": 6, \"zone_id\": 4, \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:44:15',1,'admin'),(38,'assets','INSERT',17,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"zone_id\", \"status\", \"expiry_date\", \"quantity_total\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"notes\", \"created_by\"]',NULL,'{\"asset_tag\": \"LIC-OFFICE-365\", \"asset_numeric_id\": 38735916, \"asset_type\": \"L\", \"name\": \"Office 365 Business Premium License\", \"zone_id\": 4, \"status\": \"Good\", \"expiry_date\": \"2026-03-15\", \"quantity_total\": 10, \"quantity_used\": 3, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"notes\": \"Annual subscription - 10 user licenses\", \"created_by\": 1}','2025-10-20 11:44:16',1,'admin'),(39,'assets','INSERT',18,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"zone_id\", \"status\", \"quantity_total\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"notes\", \"created_by\"]',NULL,'{\"asset_tag\": \"CONS-MARKERS\", \"asset_numeric_id\": 79433402, \"asset_type\": \"C\", \"name\": \"Whiteboard Markers Pack\", \"zone_id\": 5, \"status\": \"Good\", \"quantity_total\": 50, \"quantity_used\": 10, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"notes\": \"Box of 50 markers, track deployment to classrooms\", \"created_by\": 1}','2025-10-20 11:44:16',1,'admin'),(40,'assets','UPDATE',18,'[\"quantity_used\"]','{\"quantity_used\": 10}','{\"quantity_used\": 45}','2025-10-20 11:44:17',1,'admin'),(41,'assets','INSERT',19,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"zone_id\", \"zone_plus\", \"zone_note\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"FLOAT-PROJECTOR\", \"asset_numeric_id\": 80464972, \"asset_type\": \"N\", \"name\": \"Mobile Projector\", \"zone_id\": 1, \"zone_plus\": \"Floating Global\", \"zone_note\": \"Can be moved between any locations\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-20 11:44:18',1,'admin'),(42,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 11:44:36',NULL,NULL),(43,'assets','UPDATE',7,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 11:44:36',NULL,NULL),(44,'assets','UPDATE',8,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 11:44:36',NULL,NULL),(45,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 11:46:36',1,'admin'),(46,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 11:48:36',1,'admin'),(47,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 11:50:36',1,'admin'),(48,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 11:52:36',1,'admin'),(49,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 11:54:36',1,'admin'),(50,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 11:56:36',1,'admin'),(51,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 11:58:36',1,'admin'),(52,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:00:36',1,'admin'),(53,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:02:36',1,'admin'),(54,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:04:36',1,'admin'),(55,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:06:54',NULL,NULL),(56,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:08:54',NULL,NULL),(57,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:10:09',NULL,NULL),(58,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:11:33',NULL,NULL),(59,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:13:33',NULL,NULL),(60,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:15:33',1,'admin'),(61,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:17:33',1,'admin'),(62,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:19:33',1,'admin'),(63,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:21:33',1,'admin'),(64,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:23:33',1,'admin'),(65,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:25:33',1,'admin'),(66,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Overdue\"}','2025-10-20 12:27:33',1,'admin'),(67,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-10-20 12:29:33',1,'admin'),(68,'assets','INSERT',20,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"test\", \"asset_numeric_id\": 15540884, \"asset_type\": \"N\", \"name\": \"test\", \"category_id\": 1, \"manufacturer\": \"test\", \"model\": \"test\", \"zone_id\": 7, \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-10-22 10:36:03',1,'admin'),(69,'assets','UPDATE',20,'[\"name\"]','{\"name\": \"test\"}','{\"name\": \"asdasd\"}','2025-10-22 11:59:03',1,'admin'),(70,'assets','UPDATE',9,'[\"zone_id\"]','{\"zone_id\": 7}','{\"zone_id\": 4}','2025-10-22 12:04:21',1,'admin'),(71,'assets','UPDATE',20,'[\"name\"]','{\"name\": \"asdasd\"}','{\"name\": \"asdasdasd\"}','2025-10-22 12:09:18',1,'admin'),(72,'assets','UPDATE',20,'[\"model\"]','{\"model\": \"test\"}','{\"model\": \"test222\"}','2025-10-22 12:45:46',1,'admin'),(73,'assets','UPDATE',20,'[\"name\"]','{\"name\": \"asdasdasd\"}','{\"name\": \"asdasdasd2222\"}','2025-10-22 12:46:02',1,'admin'),(74,'assets','UPDATE',20,'[\"asset_tag\"]','{\"asset_tag\": \"test\"}','{\"asset_tag\": \"test2\"}','2025-10-22 12:57:24',1,'admin'),(75,'assets','UPDATE',20,'[\"asset_tag\"]','{\"asset_tag\": \"test2\"}','{\"asset_tag\": \"test\"}','2025-10-22 13:01:08',1,'admin'),(76,'assets','UPDATE',20,'[\"asset_tag\"]','{\"asset_tag\": \"test\"}','{\"asset_tag\": \"test1\"}','2025-10-22 13:01:14',1,'admin'),(77,'assets','UPDATE',20,'[\"asset_tag\"]','{\"asset_tag\": \"test1\"}','{\"asset_tag\": \"test2\"}','2025-10-22 13:01:21',1,'admin'),(78,'assets','UPDATE',20,'[\"asset_tag\"]','{\"asset_tag\": \"test2\"}','{\"asset_tag\": \"test3\"}','2025-10-22 13:01:28',1,'admin'),(79,'assets','UPDATE',1,'[\"asset_tag\"]','{\"asset_tag\": \"DESK-1\"}','{\"asset_tag\": \"DESK-12\"}','2025-10-22 13:41:25',1,'admin'),(80,'assets','UPDATE',5,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Retired\"}','2025-10-22 14:34:17',1,'admin'),(81,'assets','UPDATE',5,'[\"category_id\"]','{\"category_id\": 2}','{\"category_id\": 3}','2025-10-22 14:59:45',1,'admin'),(82,'assets','UPDATE',19,'[\"category_id\"]','{\"category_id\": null}','{\"category_id\": 1}','2025-10-22 14:59:52',1,'admin'),(83,'assets','UPDATE',18,'[\"category_id\"]','{\"category_id\": null}','{\"category_id\": 4}','2025-10-22 14:59:59',1,'admin'),(84,'assets','UPDATE',17,'[\"category_id\"]','{\"category_id\": null}','{\"category_id\": 6}','2025-10-22 15:00:05',1,'admin'),(85,'assets','UPDATE',20,'[\"lendable\"]','{\"lendable\": 0}','{\"lendable\": 1}','2025-10-23 08:43:07',1,'admin'),(86,'assets','UPDATE',16,'[\"lendable\"]','{\"lendable\": 0}','{\"lendable\": 1}','2025-10-23 12:38:36',1,'admin'),(87,'assets','UPDATE',6,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-10-23 14:10:33',1,'admin'),(88,'assets','UPDATE',12,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-10-24 06:51:21',1,'admin'),(89,'assets','UPDATE',13,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-10-24 11:59:06',1,'admin'),(92,'assets','UPDATE',6,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-10-27 09:56:24',NULL,NULL),(93,'assets','UPDATE',12,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-10-27 09:56:24',NULL,NULL),(94,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Available\"}','2025-10-27 10:09:28',1,'admin'),(95,'assets','UPDATE',8,'[\"lending_status\"]','{\"lending_status\": \"Stolen\"}','{\"lending_status\": \"Available\"}','2025-10-27 10:10:04',1,'admin'),(96,'assets','UPDATE',7,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Available\"}','2025-10-27 10:15:09',1,'admin'),(97,'assets','UPDATE',1,'[\"lending_status\"]','{\"lending_status\": null}','{\"lending_status\": \"Available\"}','2025-10-27 10:17:22',1,'admin'),(98,'assets','UPDATE',6,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Available\"}','2025-10-27 10:46:10',1,'admin'),(99,'assets','UPDATE',13,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-11-03 14:40:22',NULL,NULL),(100,'assets','UPDATE',8,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-11-03 14:44:10',1,'admin'),(101,'assets','UPDATE',12,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Stolen\"}','2025-11-09 00:00:06',NULL,NULL),(102,'assets','UPDATE',8,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-11-11 08:55:56',NULL,NULL),(103,'assets','UPDATE',14,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-11-11 09:00:13',1,'admin'),(104,'assets','UPDATE',6,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-11-12 10:32:00',1,'admin'),(105,'assets','UPDATE',14,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Overdue\"}','2025-11-13 00:01:11',NULL,NULL),(106,'assets','INSERT',21,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"TECH-sb-GFLAB-101-01\", \"asset_numeric_id\": 91584504, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 13:03:55',1,'admin'),(107,'assets','INSERT',22,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"PS39-sb-108-01\", \"asset_numeric_id\": 31979434, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 13:30:16',1,'admin'),(108,'assets','INSERT',24,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"PS39-sb-108-02\", \"asset_numeric_id\": 72311629, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 13:39:32',1,'admin'),(109,'assets','UPDATE',6,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Retired\"}','2025-11-13 15:18:06',1,'admin'),(110,'assets','UPDATE',6,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Available\"}','2025-11-13 15:21:47',1,'admin'),(111,'assets','DELETE',6,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"LAPTOP-1\", \"asset_numeric_id\": 65938365, \"asset_type\": \"B\", \"name\": \"Laptop 1\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-1\", \"zone_id\": 6, \"status\": \"Retired\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"previous_borrower_id\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:47\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 15:21:47\", \"last_modified_by\": 1}',NULL,'2025-11-13 15:21:58',1,'admin'),(112,'assets','DELETE',20,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"test3\", \"asset_numeric_id\": 15540884, \"asset_type\": \"N\", \"name\": \"asdasdasd2222\", \"category_id\": 1, \"manufacturer\": \"test\", \"model\": \"test222\", \"zone_id\": 7, \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-22 10:36:03\", \"created_by\": 1, \"last_modified_date\": \"2025-10-23 08:43:07\", \"last_modified_by\": 1}',NULL,'2025-11-13 15:24:16',1,'admin'),(113,'assets','DELETE',4,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"DESK-4\", \"asset_numeric_id\": 40933134, \"asset_type\": \"N\", \"name\": \"Desktop Computer 4\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-4\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:46\", \"created_by\": 1, \"last_modified_date\": \"2025-10-20 11:43:46\"}',NULL,'2025-11-13 15:24:25',1,'admin'),(114,'assets','DELETE',3,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"DESK-3\", \"asset_numeric_id\": 64139289, \"asset_type\": \"N\", \"name\": \"Desktop Computer 3\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-3\", \"zone_id\": 5, \"status\": \"Missing\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:46\", \"created_by\": 1, \"last_modified_date\": \"2025-10-20 11:43:52\", \"last_modified_by\": 1}',NULL,'2025-11-13 15:24:42',1,'admin'),(115,'assets','DELETE',5,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"DESK-5\", \"asset_numeric_id\": 80850219, \"asset_type\": \"N\", \"name\": \"Desktop Computer 5\", \"category_id\": 3, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-5\", \"zone_id\": 5, \"status\": \"Retired\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:47\", \"created_by\": 1, \"last_modified_date\": \"2025-10-22 14:59:45\", \"last_modified_by\": 1}',NULL,'2025-11-13 15:24:49',1,'admin'),(116,'assets','DELETE',10,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"DMM-001\", \"asset_numeric_id\": 96642470, \"asset_type\": \"L\", \"name\": \"Digital Multimeter\", \"category_id\": 3, \"manufacturer\": \"Fluke\", \"model\": \"117\", \"serial_number\": \"SN-DMM-001\", \"zone_id\": 7, \"status\": \"Attention\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Attention\", \"price\": 199.99, \"purchase_date\": \"2024-01-10\", \"quantity_used\": 0, \"supplier_id\": 2, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:48\", \"created_by\": 1, \"last_modified_date\": \"2025-10-20 11:44:07\", \"last_modified_by\": 2}',NULL,'2025-11-13 15:25:01',1,'admin'),(117,'assets','DELETE',2,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"DESK-2\", \"asset_numeric_id\": 34081098, \"asset_type\": \"N\", \"name\": \"Desktop Computer 2\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-2\", \"zone_id\": 5, \"status\": \"Missing\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Missing\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:46\", \"created_by\": 1, \"last_modified_date\": \"2025-10-20 11:44:09\", \"last_modified_by\": 2}',NULL,'2025-11-13 15:25:04',1,'admin'),(118,'assets','DELETE',11,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_available\", \"quantity_total\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"CABLE-ETH-001\", \"asset_numeric_id\": 57348031, \"asset_type\": \"C\", \"name\": \"Ethernet Cables (Box of 50)\", \"category_id\": 4, \"manufacturer\": \"Generic\", \"model\": \"Cat6 5ft\", \"serial_number\": \"BATCH-001\", \"zone_id\": 6, \"status\": \"Good\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 149.99, \"purchase_date\": \"2024-03-01\", \"quantity_available\": 50, \"quantity_total\": 50, \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:48\", \"created_by\": 1, \"last_modified_date\": \"2025-10-20 11:44:04\", \"last_modified_by\": 2}',NULL,'2025-11-13 15:25:08',1,'admin'),(119,'assets','DELETE',15,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"zone_plus\", \"zone_note\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"CLARIFY-1\", \"asset_numeric_id\": 26824546, \"asset_type\": \"N\", \"name\": \"Valid Clarify Test\", \"category_id\": 2, \"zone_id\": 5, \"zone_plus\": \"Clarify\", \"zone_note\": \"Needs verification of exact location in lab\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:59\", \"created_by\": 2, \"last_modified_date\": \"2025-10-20 11:43:59\"}',NULL,'2025-11-13 15:25:12',1,'admin'),(120,'assets','DELETE',21,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"TECH-sb-GFLAB-101-01\", \"asset_numeric_id\": 91584504, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-11-13 13:03:55\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 13:03:55\"}',NULL,'2025-11-13 15:25:16',1,'admin'),(121,'assets','DELETE',24,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"PS39-sb-108-02\", \"asset_numeric_id\": 72311629, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-11-13 13:39:32\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 13:39:32\"}',NULL,'2025-11-13 15:26:36',1,'admin'),(122,'assets','DELETE',22,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"PS39-sb-108-01\", \"asset_numeric_id\": 31979434, \"asset_type\": \"N\", \"name\": \"CTouch Smartboard Riva R2\", \"category_id\": 7, \"manufacturer\": \"Cunt Touch\", \"model\": \"Riva R2\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 123123.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-11-13 13:30:16\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 13:30:16\"}',NULL,'2025-11-13 15:26:38',1,'admin'),(123,'assets','INSERT',25,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"lendable\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"cbl-hdmi-001\", \"asset_numeric_id\": 32094651, \"asset_type\": \"N\", \"name\": \"Floating HDMI Cables\", \"category_id\": 9, \"zone_id\": 11, \"status\": \"Good\", \"price\": 20.00, \"purchase_date\": \"2025-11-13\", \"lendable\": 1, \"lending_status\": \"Available\", \"no_scan\": \"Yes\", \"created_by\": 1}','2025-11-13 15:30:09',1,'admin'),(124,'assets','UPDATE',13,'[\"lending_status\", \"previous_borrower_id\"]','{\"lending_status\": \"Overdue\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"previous_borrower_id\": 5}','2025-11-13 15:53:36',1,'admin'),(125,'assets','UPDATE',8,'[\"lending_status\", \"previous_borrower_id\"]','{\"lending_status\": \"Overdue\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"previous_borrower_id\": 3}','2025-11-13 15:54:12',1,'admin'),(126,'assets','UPDATE',12,'[\"lending_status\", \"previous_borrower_id\"]','{\"lending_status\": \"Stolen\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"previous_borrower_id\": 1}','2025-11-13 16:09:31',1,'admin'),(127,'assets','UPDATE',7,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-19\"}','2025-11-13 16:13:07',1,'admin'),(128,'assets','UPDATE',9,'[\"lending_status\"]','{\"lending_status\": \"Available\"}','{\"lending_status\": \"Borrowed\"}','2025-11-13 16:15:33',NULL,NULL),(129,'assets','UPDATE',7,'[\"lending_status\", \"current_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3}','{\"lending_status\": \"Available\", \"current_borrower_id\": null}','2025-11-13 16:19:51',NULL,NULL),(130,'assets','UPDATE',7,'[\"due_date\", \"previous_borrower_id\"]','{\"due_date\": \"2025-11-19\", \"previous_borrower_id\": null}','{\"due_date\": null, \"previous_borrower_id\": 3}','2025-11-13 16:20:17',1,'admin'),(131,'assets','UPDATE',9,'[\"lending_status\", \"current_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3}','{\"lending_status\": \"Available\", \"current_borrower_id\": null}','2025-11-13 16:21:02',NULL,NULL),(132,'assets','UPDATE',9,'[\"due_date\"]','{\"due_date\": \"2025-10-15\"}','{\"due_date\": null}','2025-11-13 16:21:10',NULL,NULL),(133,'assets','UPDATE',9,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-27\"}','2025-11-13 16:21:33',1,'admin'),(134,'assets','UPDATE',9,'[\"asset_type\"]','{\"asset_type\": \"L\"}','{\"asset_type\": \"N\"}','2025-11-13 16:23:28',1,'admin'),(135,'assets','UPDATE',12,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 4, \"due_date\": \"2025-11-27\"}','2025-11-13 16:27:31',1,'admin'),(136,'assets','UPDATE',14,'[\"lending_status\"]','{\"lending_status\": \"Overdue\"}','{\"lending_status\": \"Available\"}','2025-11-13 16:32:16',1,'admin'),(137,'assets','INSERT',26,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"lendable\", \"lending_status\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"cbl-hdmi-002\", \"asset_numeric_id\": 18368995, \"asset_type\": \"N\", \"name\": \"Floating HDMI Cables\", \"category_id\": 9, \"zone_id\": 11, \"status\": \"Good\", \"price\": 20.00, \"purchase_date\": \"2025-11-13\", \"lendable\": 1, \"lending_status\": \"Available\", \"no_scan\": \"Yes\", \"created_by\": 1}','2025-11-13 18:26:54',1,'admin'),(138,'assets','INSERT',27,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-01\", \"asset_numeric_id\": 66843174, \"asset_type\": \"N\", \"name\": \"Generic Monitor\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 18:51:25',1,'admin'),(139,'assets','INSERT',28,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-02\", \"asset_numeric_id\": 75155874, \"asset_type\": \"N\", \"name\": \"Generic Monitor\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:09:42',1,'admin'),(140,'assets','INSERT',29,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 95324750, \"asset_type\": \"N\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:17:12',1,'admin'),(141,'assets','DELETE',29,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 95324750, \"asset_type\": \"N\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:17:12\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:17:12\"}',NULL,'2025-11-13 19:19:42',1,'admin'),(142,'assets','INSERT',30,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 79796373, \"asset_type\": \"N\", \"name\": \"Generic Monitor (Copy)\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:25:07',1,'admin'),(143,'assets','DELETE',30,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 79796373, \"asset_type\": \"N\", \"name\": \"Generic Monitor (Copy)\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:25:07\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:25:07\"}',NULL,'2025-11-13 19:25:15',1,'admin'),(144,'assets','INSERT',31,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 66329180, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:26:38',1,'admin'),(145,'assets','INSERT',32,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-04\", \"asset_numeric_id\": 69590527, \"asset_type\": \"N\", \"name\": \"Dell Monitor (Copy)\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:27:38',1,'admin'),(146,'assets','DELETE',32,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-04\", \"asset_numeric_id\": 69590527, \"asset_type\": \"N\", \"name\": \"Dell Monitor (Copy)\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:27:38\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:27:38\"}',NULL,'2025-11-13 19:36:33',1,'admin'),(147,'assets','INSERT',33,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-04\", \"asset_numeric_id\": 82402163, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:36:52',1,'admin'),(148,'assets','INSERT',34,'[\"asset_numeric_id\", \"asset_type\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_numeric_id\": 29878524, \"asset_type\": \"N\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:39:23',1,'admin'),(149,'assets','DELETE',34,'[\"asset_numeric_id\", \"asset_type\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_numeric_id\": 29878524, \"asset_type\": \"N\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:39:23\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:39:23\"}',NULL,'2025-11-13 19:39:30',1,'admin'),(150,'assets','INSERT',35,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-05\", \"asset_numeric_id\": 62556220, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:39:42',1,'admin'),(151,'assets','INSERT',36,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-06\", \"asset_numeric_id\": 47928367, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:41:10',1,'admin'),(152,'assets','INSERT',37,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-07\", \"asset_numeric_id\": 94709833, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:45:14',1,'admin'),(153,'assets','DELETE',37,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-07\", \"asset_numeric_id\": 94709833, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:45:14\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:45:14\"}',NULL,'2025-11-13 19:45:33',1,'admin'),(154,'assets','DELETE',36,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-06\", \"asset_numeric_id\": 47928367, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:41:10\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:41:10\"}',NULL,'2025-11-13 19:45:36',1,'admin'),(155,'assets','DELETE',35,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-05\", \"asset_numeric_id\": 62556220, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:39:42\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:39:42\"}',NULL,'2025-11-13 19:45:38',1,'admin'),(156,'assets','DELETE',33,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-04\", \"asset_numeric_id\": 82402163, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:36:52\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:36:52\"}',NULL,'2025-11-13 19:45:41',1,'admin'),(157,'assets','DELETE',31,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-03\", \"asset_numeric_id\": 66329180, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"manufacturer\": \"Dell\", \"model\": \"P2723D\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 199.90, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:26:38\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:26:38\"}',NULL,'2025-11-13 19:45:43',1,'admin'),(158,'assets','DELETE',28,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-02\", \"asset_numeric_id\": 75155874, \"asset_type\": \"N\", \"name\": \"Generic Monitor\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 19:09:42\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 19:09:42\"}',NULL,'2025-11-13 19:45:45',1,'admin'),(159,'assets','DELETE',27,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\"]','{\"asset_tag\": \"ps39-mon-108-01\", \"asset_numeric_id\": 66843174, \"asset_type\": \"N\", \"name\": \"Generic Monitor\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_date\": \"2025-11-13 18:51:25\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 18:51:25\"}',NULL,'2025-11-13 19:45:47',1,'admin'),(160,'assets','INSERT',38,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"purchase_date\", \"warranty_until\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-01\", \"asset_numeric_id\": 51229246, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"zone_id\": 11, \"status\": \"Good\", \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:46:33',1,'admin'),(161,'assets','INSERT',39,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"warranty_until\", \"supplier_id\", \"lendable\", \"no_scan\", \"created_by\"]',NULL,'{\"asset_tag\": \"ps39-mon-108-02\", \"asset_numeric_id\": 97355732, \"asset_type\": \"N\", \"name\": \"Dell Monitor\", \"category_id\": 10, \"model\": \"jyiuyiu\", \"serial_number\": \"7657865756\", \"zone_id\": 11, \"status\": \"Good\", \"price\": 21423.00, \"purchase_date\": \"2025-11-13\", \"warranty_until\": \"2027-11-13\", \"supplier_id\": 1, \"lendable\": 0, \"no_scan\": \"No\", \"created_by\": 1}','2025-11-13 19:48:05',1,'admin'),(162,'assets','DELETE',14,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"MOUSE-3\", \"asset_numeric_id\": 85179628, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 3\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-3\", \"zone_id\": 6, \"status\": \"Good\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:57\", \"created_by\": 2, \"last_modified_date\": \"2025-11-13 16:32:16\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:20:55',1,'admin'),(163,'assets','DELETE',7,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"LAPTOP-2\", \"asset_numeric_id\": 90756157, \"asset_type\": \"B\", \"name\": \"Laptop 2\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-2\", \"zone_id\": 6, \"status\": \"Good\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"previous_borrower_id\": 3, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:47\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 16:20:17\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:20:59',1,'admin'),(164,'assets','DELETE',8,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"LAPTOP-3\", \"asset_numeric_id\": 97073776, \"asset_type\": \"B\", \"name\": \"Laptop 3\", \"category_id\": 2, \"manufacturer\": \"HP\", \"model\": \"EliteBook 840\", \"serial_number\": \"SN-LAPTOP-3\", \"zone_id\": 6, \"status\": \"Good\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 1299.99, \"purchase_date\": \"2024-02-01\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 1, \"minimum_role_for_lending\": 25, \"lending_status\": \"Available\", \"previous_borrower_id\": 3, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:47\", \"created_by\": 1, \"last_modified_date\": \"2025-11-13 15:54:12\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:02',1,'admin'),(165,'assets','DELETE',13,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"MOUSE-2\", \"asset_numeric_id\": 56755484, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 2\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-2\", \"zone_id\": 6, \"status\": \"Good\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Good\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"previous_borrower_id\": 5, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:57\", \"created_by\": 2, \"last_modified_date\": \"2025-11-13 15:53:36\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:05',1,'admin'),(166,'assets','UPDATE',12,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 4, \"due_date\": \"2025-11-27\", \"previous_borrower_id\": 1}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 4}','2025-11-14 15:21:13',1,'admin'),(167,'assets','UPDATE',9,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-27\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 3}','2025-11-14 15:21:16',1,'admin'),(168,'assets','DELETE',12,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"last_audit\", \"last_audit_status\", \"price\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"MOUSE-1\", \"asset_numeric_id\": 12309104, \"asset_type\": \"N\", \"name\": \"Wireless Mouse 1\", \"category_id\": 2, \"manufacturer\": \"Logitech\", \"model\": \"MX Master 3\", \"serial_number\": \"SN-MOUSE-1\", \"zone_id\": 6, \"status\": \"Faulty\", \"last_audit\": \"2025-10-20\", \"last_audit_status\": \"Faulty\", \"price\": 99.99, \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"previous_borrower_id\": 4, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:57\", \"created_by\": 2, \"last_modified_date\": \"2025-11-14 15:21:13\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:37',1,'admin'),(169,'assets','DELETE',9,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"previous_borrower_id\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"OSC-001\", \"asset_numeric_id\": 40246871, \"asset_type\": \"N\", \"name\": \"Digital Oscilloscope\", \"category_id\": 3, \"manufacturer\": \"Tektronix\", \"model\": \"TBS2104B\", \"serial_number\": \"SN-OSC-001\", \"zone_id\": 4, \"status\": \"Good\", \"price\": 1899.99, \"purchase_date\": \"2024-01-10\", \"quantity_used\": 0, \"supplier_id\": 2, \"lendable\": 1, \"minimum_role_for_lending\": 50, \"lending_status\": \"Available\", \"previous_borrower_id\": 3, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:48\", \"created_by\": 1, \"last_modified_date\": \"2025-11-14 15:21:16\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:37',1,'admin'),(170,'assets','DELETE',1,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"manufacturer\", \"model\", \"serial_number\", \"zone_id\", \"status\", \"price\", \"purchase_date\", \"quantity_used\", \"supplier_id\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"DESK-12\", \"asset_numeric_id\": 75650012, \"asset_type\": \"N\", \"name\": \"Desktop Computer 1\", \"category_id\": 2, \"manufacturer\": \"Dell\", \"model\": \"OptiPlex 7090\", \"serial_number\": \"SN-DESK-1\", \"zone_id\": 5, \"status\": \"Good\", \"price\": 899.99, \"purchase_date\": \"2024-01-15\", \"quantity_used\": 0, \"supplier_id\": 1, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:43:46\", \"created_by\": 1, \"last_modified_date\": \"2025-10-27 10:17:22\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:53',1,'admin'),(171,'assets','DELETE',19,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"zone_plus\", \"zone_note\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"FLOAT-PROJECTOR\", \"asset_numeric_id\": 80464972, \"asset_type\": \"N\", \"name\": \"Mobile Projector\", \"category_id\": 1, \"zone_id\": 1, \"zone_plus\": \"Floating Global\", \"zone_note\": \"Can be moved between any locations\", \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:44:18\", \"created_by\": 1, \"last_modified_date\": \"2025-10-22 14:59:52\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:53',1,'admin'),(172,'assets','DELETE',18,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"quantity_total\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"lending_status\", \"no_scan\", \"notes\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"CONS-MARKERS\", \"asset_numeric_id\": 79433402, \"asset_type\": \"C\", \"name\": \"Whiteboard Markers Pack\", \"category_id\": 4, \"zone_id\": 5, \"status\": \"Good\", \"quantity_total\": 50, \"quantity_used\": 45, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"lending_status\": \"Available\", \"no_scan\": \"No\", \"notes\": \"Box of 50 markers, track deployment to classrooms\", \"created_date\": \"2025-10-20 11:44:16\", \"created_by\": 1, \"last_modified_date\": \"2025-10-27 10:11:39\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:53',1,'admin'),(173,'assets','DELETE',17,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"expiry_date\", \"quantity_total\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"notes\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"LIC-OFFICE-365\", \"asset_numeric_id\": 38735916, \"asset_type\": \"L\", \"name\": \"Office 365 Business Premium License\", \"category_id\": 6, \"zone_id\": 4, \"status\": \"Good\", \"expiry_date\": \"2026-03-15\", \"quantity_total\": 10, \"quantity_used\": 3, \"lendable\": 0, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"notes\": \"Annual subscription - 10 user licenses\", \"created_date\": \"2025-10-20 11:44:16\", \"created_by\": 1, \"last_modified_date\": \"2025-10-22 15:00:05\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:53',1,'admin'),(174,'assets','DELETE',16,'[\"asset_tag\", \"asset_numeric_id\", \"asset_type\", \"name\", \"category_id\", \"zone_id\", \"status\", \"quantity_used\", \"lendable\", \"minimum_role_for_lending\", \"no_scan\", \"created_date\", \"created_by\", \"last_modified_date\", \"last_modified_by\"]','{\"asset_tag\": \"ROUTER-1\", \"asset_numeric_id\": 49096117, \"asset_type\": \"N\", \"name\": \"Network Router\", \"category_id\": 6, \"zone_id\": 4, \"status\": \"Good\", \"quantity_used\": 0, \"lendable\": 1, \"minimum_role_for_lending\": 1, \"no_scan\": \"No\", \"created_date\": \"2025-10-20 11:44:15\", \"created_by\": 1, \"last_modified_date\": \"2025-10-23 12:38:36\", \"last_modified_by\": 1}',NULL,'2025-11-14 15:21:53',1,'admin'),(175,'assets','UPDATE',25,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-21\"}','2025-11-14 19:28:38',1,'admin'),(176,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-21\"}','2025-11-14 21:47:39',1,'admin'),(177,'assets','UPDATE',25,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-21\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 3}','2025-11-14 21:48:14',1,'admin'),(178,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3, \"due_date\": \"2025-11-21\", \"previous_borrower_id\": null}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 3}','2025-11-14 21:49:25',1,'admin'),(179,'assets','UPDATE',25,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 4, \"due_date\": \"2025-11-21\"}','2025-11-14 21:51:07',1,'admin'),(180,'assets','UPDATE',25,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 4, \"due_date\": \"2025-11-21\", \"previous_borrower_id\": 3}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 4}','2025-11-14 21:51:11',1,'admin'),(181,'assets','UPDATE',38,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-11-14\", \"last_audit_status\": \"Good\"}','2025-11-14 22:42:57',1,'admin'),(182,'assets','UPDATE',39,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-11-14\", \"last_audit_status\": \"Good\"}','2025-11-14 22:42:57',1,'admin'),(183,'assets','UPDATE',25,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-11-14\", \"last_audit_status\": \"Good\"}','2025-11-14 22:42:57',1,'admin'),(184,'assets','UPDATE',26,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": null, \"last_audit_status\": null}','{\"last_audit\": \"2025-11-14\", \"last_audit_status\": \"Good\"}','2025-11-14 22:42:57',1,'admin'),(185,'assets','UPDATE',26,'[\"no_scan\"]','{\"no_scan\": \"Yes\"}','{\"no_scan\": \"Ask\"}','2025-11-14 23:06:14',1,'admin'),(186,'assets','UPDATE',26,'[\"last_audit_status\"]','{\"last_audit_status\": \"Good\"}','{\"last_audit_status\": \"Missing\"}','2025-11-14 23:10:15',1,'admin'),(187,'assets','UPDATE',26,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-11-14 23:10:15',1,'admin'),(188,'assets','UPDATE',26,'[\"status\"]','{\"status\": \"Missing\"}','{\"status\": \"Good\"}','2025-11-14 23:21:14',1,'admin'),(189,'assets','UPDATE',26,'[\"last_audit_status\"]','{\"last_audit_status\": \"Missing\"}','{\"last_audit_status\": \"Good\"}','2025-11-14 23:25:16',1,'admin'),(190,'assets','UPDATE',38,'[\"audit_task_id\"]','{\"audit_task_id\": null}','{\"audit_task_id\": 1}','2025-11-15 01:18:32',1,'admin'),(191,'assets','UPDATE',25,'[\"last_audit\"]','{\"last_audit\": \"2025-11-14\"}','{\"last_audit\": \"2025-11-15\"}','2025-11-15 01:19:04',1,'admin'),(192,'assets','UPDATE',38,'[\"last_audit\"]','{\"last_audit\": \"2025-11-14\"}','{\"last_audit\": \"2025-11-15\"}','2025-11-15 01:29:14',1,'admin'),(193,'assets','UPDATE',39,'[\"last_audit\"]','{\"last_audit\": \"2025-11-14\"}','{\"last_audit\": \"2025-11-15\"}','2025-11-15 01:29:14',1,'admin'),(194,'assets','UPDATE',26,'[\"last_audit\"]','{\"last_audit\": \"2025-11-14\"}','{\"last_audit\": \"2025-11-15\"}','2025-11-15 01:29:14',1,'admin'),(195,'assets','UPDATE',25,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 5, \"due_date\": \"2026-01-01\"}','2025-12-03 15:44:27',1,'admin'),(196,'assets','UPDATE',38,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": \"2025-11-15\", \"last_audit_status\": \"Good\"}','{\"last_audit\": \"2025-12-03\", \"last_audit_status\": \"Faulty\"}','2025-12-03 15:55:10',1,'admin'),(197,'assets','UPDATE',38,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Faulty\"}','2025-12-03 15:55:10',1,'admin'),(198,'assets','UPDATE',39,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": \"2025-11-15\", \"last_audit_status\": \"Good\"}','{\"last_audit\": \"2025-12-03\", \"last_audit_status\": \"Missing\"}','2025-12-03 15:55:10',1,'admin'),(199,'assets','UPDATE',39,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-12-03 15:55:10',1,'admin'),(200,'assets','UPDATE',25,'[\"last_audit\"]','{\"last_audit\": \"2025-11-15\"}','{\"last_audit\": \"2025-12-03\"}','2025-12-03 15:55:10',1,'admin'),(201,'assets','UPDATE',26,'[\"last_audit\"]','{\"last_audit\": \"2025-11-15\"}','{\"last_audit\": \"2025-12-03\"}','2025-12-03 15:55:10',1,'admin'),(202,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 5, \"due_date\": \"2025-12-11\"}','2025-12-08 10:58:17',1,'admin'),(203,'assets','UPDATE',38,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": \"2025-12-03\", \"last_audit_status\": \"Faulty\"}','{\"last_audit\": \"2025-12-08\", \"last_audit_status\": \"Missing\"}','2025-12-08 11:00:50',1,'admin'),(204,'assets','UPDATE',38,'[\"status\"]','{\"status\": \"Faulty\"}','{\"status\": \"Missing\"}','2025-12-08 11:00:50',1,'admin'),(205,'assets','UPDATE',39,'[\"last_audit\", \"last_audit_status\"]','{\"last_audit\": \"2025-12-03\", \"last_audit_status\": \"Missing\"}','{\"last_audit\": \"2025-12-08\", \"last_audit_status\": \"Good\"}','2025-12-08 11:00:50',1,'admin'),(206,'assets','UPDATE',39,'[\"status\"]','{\"status\": \"Missing\"}','{\"status\": \"Good\"}','2025-12-08 11:00:50',1,'admin'),(207,'assets','UPDATE',25,'[\"last_audit\"]','{\"last_audit\": \"2025-12-03\"}','{\"last_audit\": \"2025-12-08\"}','2025-12-08 11:00:50',1,'admin'),(208,'assets','UPDATE',26,'[\"last_audit\"]','{\"last_audit\": \"2025-12-03\"}','{\"last_audit\": \"2025-12-08\"}','2025-12-08 11:00:50',1,'admin'),(209,'assets','UPDATE',38,'[\"last_audit_status\"]','{\"last_audit_status\": \"Missing\"}','{\"last_audit_status\": \"Attention\"}','2025-12-08 11:08:13',1,'admin'),(210,'assets','UPDATE',38,'[\"status\"]','{\"status\": \"Missing\"}','{\"status\": \"Attention\"}','2025-12-08 11:08:13',1,'admin'),(211,'assets','UPDATE',39,'[\"last_audit_status\"]','{\"last_audit_status\": \"Good\"}','{\"last_audit_status\": \"Missing\"}','2025-12-08 11:08:13',1,'admin'),(212,'assets','UPDATE',39,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-12-08 11:08:13',1,'admin'),(213,'assets','UPDATE',26,'[\"last_audit_status\"]','{\"last_audit_status\": \"Good\"}','{\"last_audit_status\": \"Missing\"}','2025-12-08 11:08:13',1,'admin'),(214,'assets','UPDATE',26,'[\"status\"]','{\"status\": \"Good\"}','{\"status\": \"Missing\"}','2025-12-08 11:08:13',1,'admin'),(215,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"due_date\", \"previous_borrower_id\"]','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 5, \"due_date\": \"2025-12-11\", \"previous_borrower_id\": 3}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null, \"previous_borrower_id\": 5}','2025-12-10 14:29:13',1,'admin'),(216,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 3}','2025-12-10 16:37:29',1,'admin'),(217,'assets','UPDATE',26,'[\"status\"]','{\"status\": \"Missing\"}','{\"status\": \"Good\"}','2025-12-10 16:40:16',1,'admin'),(218,'assets','UPDATE',26,'[\"lending_status\"]','{\"lending_status\": \"Borrowed\"}','{\"lending_status\": \"Deployed\"}','2025-12-10 16:42:23',1,'admin'),(219,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"previous_borrower_id\"]','{\"lending_status\": \"Deployed\", \"current_borrower_id\": 3, \"previous_borrower_id\": 5}','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"previous_borrower_id\": 3}','2025-12-10 16:55:09',1,'admin'),(220,'assets','UPDATE',26,'[\"lending_status\", \"current_borrower_id\", \"due_date\"]','{\"lending_status\": \"Available\", \"current_borrower_id\": null, \"due_date\": null}','{\"lending_status\": \"Borrowed\", \"current_borrower_id\": 5, \"due_date\": \"2025-12-14\"}','2025-12-11 17:01:32',1,'admin'); +/*!40000 ALTER TABLE `asset_change_log` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `assets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `assets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `asset_tag` varchar(200) DEFAULT NULL, + `asset_numeric_id` int(11) NOT NULL CHECK (`asset_numeric_id` between 10000000 and 99999999), + `asset_type` enum('N','B','L','C') NOT NULL, + `name` varchar(255) DEFAULT NULL, + `category_id` int(11) DEFAULT NULL, + `manufacturer` varchar(200) DEFAULT NULL, + `model` varchar(200) DEFAULT NULL, + `serial_number` varchar(200) DEFAULT NULL, + `zone_id` int(11) DEFAULT NULL, + `zone_plus` enum('Floating Local','Floating Global','Clarify') DEFAULT NULL, + `zone_note` text DEFAULT NULL, + `status` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT 'Good', + `last_audit` date DEFAULT NULL, + `last_audit_status` varchar(100) DEFAULT NULL, + `price` decimal(12,2) DEFAULT NULL CHECK (`price` is null or `price` >= 0), + `purchase_date` date DEFAULT NULL, + `warranty_until` date DEFAULT NULL, + `expiry_date` date DEFAULT NULL, + `quantity_available` int(11) DEFAULT NULL, + `quantity_total` int(11) DEFAULT NULL, + `quantity_used` int(11) DEFAULT 0, + `supplier_id` int(11) DEFAULT NULL, + `lendable` tinyint(1) DEFAULT 0, + `minimum_role_for_lending` int(11) DEFAULT 1 CHECK (`minimum_role_for_lending` >= 1 and `minimum_role_for_lending` <= 100), + `lending_status` enum('Available','Deployed','Borrowed','Overdue','Illegally Handed Out','Stolen') DEFAULT NULL, + `current_borrower_id` int(11) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `previous_borrower_id` int(11) DEFAULT NULL, + `audit_task_id` int(11) DEFAULT NULL, + `label_template_id` int(11) DEFAULT NULL, + `no_scan` enum('Yes','Ask','No') DEFAULT 'No', + `notes` text DEFAULT NULL, + `additional_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`additional_fields`)), + `file_attachment` mediumblob DEFAULT NULL, + `created_date` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_date` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `asset_numeric_id` (`asset_numeric_id`), + UNIQUE KEY `asset_tag` (`asset_tag`), + KEY `supplier_id` (`supplier_id`), + KEY `current_borrower_id` (`current_borrower_id`), + KEY `previous_borrower_id` (`previous_borrower_id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_asset_tag` (`asset_tag`), + KEY `idx_asset_numeric` (`asset_numeric_id`), + KEY `idx_type` (`asset_type`), + KEY `idx_status` (`status`), + KEY `idx_zone` (`zone_id`), + KEY `idx_category` (`category_id`), + KEY `idx_lendable` (`lendable`), + KEY `idx_lending_status` (`lending_status`), + KEY `idx_label_template` (`label_template_id`), + CONSTRAINT `assets_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`), + CONSTRAINT `assets_ibfk_2` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`), + CONSTRAINT `assets_ibfk_3` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_4` FOREIGN KEY (`current_borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_5` FOREIGN KEY (`previous_borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_6` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_7` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_8` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_asset_label_template` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `assets` WRITE; +/*!40000 ALTER TABLE `assets` DISABLE KEYS */; +INSERT INTO `assets` VALUES (25,'cbl-hdmi-001',32094651,'N','Floating HDMI Cables',9,NULL,NULL,NULL,11,NULL,NULL,'Good','2025-12-08','Good',20.00,'2025-11-13',NULL,NULL,NULL,NULL,NULL,NULL,1,NULL,'Borrowed',5,'2026-01-01',4,NULL,NULL,'Yes',NULL,NULL,NULL,'2025-11-13 15:30:09',1,'2025-12-08 11:08:13',1),(26,'cbl-hdmi-002',18368995,'N','Floating HDMI Cables',9,NULL,NULL,NULL,11,NULL,NULL,'Good','2025-12-08','Missing',20.00,'2025-11-13',NULL,NULL,NULL,NULL,NULL,NULL,1,NULL,'Borrowed',5,'2025-12-14',3,NULL,7,'Ask',NULL,NULL,NULL,'2025-11-13 18:26:54',1,'2025-12-11 17:01:32',1),(38,'ps39-mon-108-01',51229246,'N','Dell Monitor',10,NULL,NULL,NULL,11,NULL,NULL,'Attention','2025-12-08','Attention',NULL,'2025-11-13','2027-11-13',NULL,NULL,NULL,NULL,NULL,0,NULL,NULL,NULL,NULL,NULL,1,7,'No',NULL,NULL,NULL,'2025-11-13 19:46:33',1,'2025-12-08 11:08:13',1),(39,'ps39-mon-108-02',97355732,'N','Dell Monitor',10,NULL,'jyiuyiu','7657865756',11,NULL,NULL,'Missing','2025-12-08','Missing',21423.00,'2025-11-13','2027-11-13',NULL,NULL,NULL,NULL,1,0,NULL,NULL,NULL,NULL,NULL,NULL,7,'No',NULL,NULL,NULL,'2025-11-13 19:48:05',1,'2025-12-08 11:08:13',1); +/*!40000 ALTER TABLE `assets` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_before_insert_meta +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.created_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.created_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_zone_plus_insert +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_insert_log +AFTER INSERT ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE set_fields_array JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields + SET set_fields_array = JSON_ARRAY(); + SET new_vals = JSON_OBJECT(); + + -- Always log these core fields + IF NEW.asset_tag IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_tag'); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF NEW.asset_numeric_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_numeric_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF NEW.asset_type IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_type'); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF NEW.name IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'name'); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF NEW.category_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'category_id'); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF NEW.manufacturer IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'manufacturer'); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF NEW.model IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'model'); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF NEW.serial_number IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'serial_number'); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF NEW.zone_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_id'); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF NEW.zone_plus IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_plus'); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF NEW.zone_note IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_note'); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF NEW.status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF NEW.last_audit IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit'); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF NEW.last_audit_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit_status'); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF NEW.price IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'price'); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF NEW.purchase_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'purchase_date'); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF NEW.warranty_until IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'warranty_until'); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF NEW.expiry_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'expiry_date'); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF NEW.quantity_available IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_available'); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF NEW.quantity_total IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_total'); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF NEW.quantity_used IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_used'); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF NEW.supplier_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'supplier_id'); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF NEW.lendable IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lendable'); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF NEW.minimum_role_for_lending IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'minimum_role_for_lending'); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF NEW.lending_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lending_status'); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF NEW.current_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'current_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF NEW.due_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'due_date'); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF NEW.previous_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'previous_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF NEW.audit_task_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'audit_task_id'); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF NEW.no_scan IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'no_scan'); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF NEW.notes IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'notes'); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF NEW.additional_fields IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'additional_fields'); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + IF NEW.created_by IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'created_by'); + SET new_vals = JSON_SET(new_vals, '$.created_by', NEW.created_by); + END IF; + + -- Log the INSERT with only the fields that were set + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'INSERT', + NEW.id, + set_fields_array, + new_vals, + @current_user_id, + username + ); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_before_update_meta +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + SET NEW.last_modified_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.last_modified_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER prevent_lend_non_lendable_assets +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + -- Check if trying to set lending_status to any borrowed state on a non-lendable asset + IF (NEW.lendable = FALSE OR NEW.lendable IS NULL) AND + NEW.lending_status IN ('Borrowed', 'Deployed', 'Overdue') AND + (OLD.lending_status NOT IN ('Borrowed', 'Deployed', 'Overdue') OR OLD.lending_status IS NULL) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot lend asset that is marked as non-lendable. Set lendable=TRUE first.'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_zone_plus_update +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_update_log +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE changed_fields_array JSON; + DECLARE old_vals JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with changed fields + SET changed_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + SET new_vals = JSON_OBJECT(); + + IF OLD.asset_tag <=> NEW.asset_tag IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF OLD.asset_numeric_id <=> NEW.asset_numeric_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF OLD.asset_type <=> NEW.asset_type IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF OLD.name <=> NEW.name IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF OLD.category_id <=> NEW.category_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF OLD.manufacturer <=> NEW.manufacturer IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF OLD.model <=> NEW.model IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF OLD.serial_number <=> NEW.serial_number IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF OLD.zone_id <=> NEW.zone_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF OLD.zone_plus <=> NEW.zone_plus IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF OLD.zone_note <=> NEW.zone_note IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF OLD.last_audit <=> NEW.last_audit IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF OLD.last_audit_status <=> NEW.last_audit_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF OLD.price <=> NEW.price IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF OLD.purchase_date <=> NEW.purchase_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF OLD.warranty_until <=> NEW.warranty_until IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF OLD.expiry_date <=> NEW.expiry_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF OLD.quantity_available <=> NEW.quantity_available IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF OLD.quantity_total <=> NEW.quantity_total IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF OLD.quantity_used <=> NEW.quantity_used IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF OLD.supplier_id <=> NEW.supplier_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF OLD.lendable <=> NEW.lendable IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF OLD.minimum_role_for_lending <=> NEW.minimum_role_for_lending IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF OLD.lending_status <=> NEW.lending_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF OLD.current_borrower_id <=> NEW.current_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF OLD.due_date <=> NEW.due_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF OLD.previous_borrower_id <=> NEW.previous_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF OLD.audit_task_id <=> NEW.audit_task_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF OLD.no_scan <=> NEW.no_scan IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF OLD.notes <=> NEW.notes IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF OLD.additional_fields <=> NEW.additional_fields IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + -- Only log if there were actual changes (excluding auto-updated fields) + IF JSON_LENGTH(changed_fields_array) > 0 THEN + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'UPDATE', + NEW.id, + changed_fields_array, + old_vals, + new_vals, + @current_user_id, + username + ); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_asset_issues +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + DECLARE issue_severity ENUM('Critical', 'High', 'Medium', 'Low'); + DECLARE detection_trigger_name VARCHAR(100); + + -- Check for lending_status changes to problematic states + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + + -- Determine issue details based on lending_status + CASE NEW.lending_status + WHEN 'Overdue' THEN + SET issue_title = CONCAT('Asset Overdue: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Overdue. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'LENDING_OVERDUE'; + + WHEN 'Illegally Handed Out' THEN + SET issue_title = CONCAT('Asset Illegally Handed Out: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Illegally Handed Out. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_ILLEGAL'; + + WHEN 'Stolen' THEN + SET issue_title = CONCAT('Asset Stolen: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Stolen (14+ days overdue). Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_STOLEN'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Urgent', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Check for status changes to problematic states + IF OLD.status != NEW.status AND NEW.status IN ('Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'Expired') THEN + + -- Determine issue details based on status + CASE NEW.status + WHEN 'Attention' THEN + SET issue_title = CONCAT('Asset Needs Attention: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Attention. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_ATTENTION'; + + WHEN 'Faulty' THEN + SET issue_title = CONCAT('Asset Faulty: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Faulty. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'STATUS_FAULTY'; + + WHEN 'Missing' THEN + SET issue_title = CONCAT('Asset Missing: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Missing. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'STATUS_MISSING'; + + WHEN 'Retired' THEN + SET issue_title = CONCAT('Asset Retired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Retired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Low'; + SET detection_trigger_name = 'STATUS_RETIRED'; + + WHEN 'In Repair' THEN + SET issue_title = CONCAT('Asset In Repair: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to In Repair. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_IN_REPAIR'; + + WHEN 'Expired' THEN + SET issue_title = CONCAT('Asset Expired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Expired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_EXPIRED'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Auto-resolve issues when status becomes Good again + IF OLD.status != NEW.status AND NEW.status = 'Good' AND OLD.status IN ('Faulty', 'Missing', 'In Repair', 'Expired') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Automatically Fixed', + solution_plus = CONCAT('Asset status automatically changed from ', OLD.status, ' to Good'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('STATUS_FAULTY', 'STATUS_MISSING', 'STATUS_IN_REPAIR', 'STATUS_EXPIRED'); + END IF; + + -- Auto-resolve overdue/stolen/illegal issues when item is returned (lending_status becomes Available) + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status = 'Available' + AND OLD.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Asset was returned - lending status changed from ', OLD.lending_status, ' to Available'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('LENDING_OVERDUE', 'LENDING_ILLEGAL', 'LENDING_STOLEN'); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER prevent_delete_borrowed_assets +BEFORE DELETE ON assets +FOR EACH ROW +BEGIN + IF OLD.lending_status IN ('Borrowed', 'Deployed', 'Overdue') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot delete asset that is currently borrowed or deployed, maybe update to retired or unmanaged before'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_delete_log +AFTER DELETE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE deleted_fields_array JSON; + DECLARE old_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields (for restore capability) + SET deleted_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + + IF OLD.asset_tag IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + END IF; + + IF OLD.asset_numeric_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + END IF; + + IF OLD.asset_type IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + END IF; + + IF OLD.name IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + END IF; + + IF OLD.category_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + END IF; + + IF OLD.manufacturer IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + END IF; + + IF OLD.model IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + END IF; + + IF OLD.serial_number IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + END IF; + + IF OLD.zone_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + END IF; + + IF OLD.zone_plus IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + END IF; + + IF OLD.zone_note IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + END IF; + + IF OLD.status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + + IF OLD.last_audit IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + END IF; + + IF OLD.last_audit_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + END IF; + + IF OLD.price IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + END IF; + + IF OLD.purchase_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + END IF; + + IF OLD.warranty_until IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + END IF; + + IF OLD.expiry_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + END IF; + + IF OLD.quantity_available IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + END IF; + + IF OLD.quantity_total IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + END IF; + + IF OLD.quantity_used IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + END IF; + + IF OLD.supplier_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + END IF; + + IF OLD.lendable IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + END IF; + + IF OLD.minimum_role_for_lending IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + END IF; + + IF OLD.lending_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + END IF; + + IF OLD.current_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + END IF; + + IF OLD.due_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + END IF; + + IF OLD.previous_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + END IF; + + IF OLD.audit_task_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + END IF; + + IF OLD.no_scan IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + END IF; + + IF OLD.notes IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + END IF; + + IF OLD.additional_fields IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + END IF; + + -- Always capture metadata fields for restore + IF OLD.created_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_date'); + SET old_vals = JSON_SET(old_vals, '$.created_date', OLD.created_date); + END IF; + + IF OLD.created_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_by'); + SET old_vals = JSON_SET(old_vals, '$.created_by', OLD.created_by); + END IF; + + IF OLD.last_modified_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_date'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_date', OLD.last_modified_date); + END IF; + + IF OLD.last_modified_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_by'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_by', OLD.last_modified_by); + END IF; + + -- Log the DELETE with only non-NULL fields + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'DELETE', + OLD.id, + deleted_fields_array, + old_vals, + @current_user_id, + username + ); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `audit_tasks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `audit_tasks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_name` varchar(200) NOT NULL, + `json_sequence` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`json_sequence`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `audit_tasks` WRITE; +/*!40000 ALTER TABLE `audit_tasks` DISABLE KEYS */; +INSERT INTO `audit_tasks` VALUES (1,'Basic Device Check','[{\"actions\":{\"no\":{\"end_audit\":true,\"set_status\":\"Faulty\"},\"yes\":{\"next_step\":2}},\"question\":\"Does the device power on?\",\"step\":1,\"type\":\"yes_no\"},{\"actions\":{\"Damaged\":{\"next_step\":3},\"Good\":{\"end_audit\":true,\"set_status\":\"Good\"}},\"options\":[\"Good\",\"Damaged\"],\"question\":\"Physical condition?\",\"step\":2,\"type\":\"multiple_choice\"},{\"actions\":{\"any\":{\"end_audit\":true,\"set_additional_fields\":{\"damage_notes\":\"{user_input}\"},\"set_status\":\"Attention\"}},\"question\":\"Describe the damage\",\"step\":3,\"type\":\"text_input\"}]','2025-10-20 11:43:38','2025-11-15 01:20:50'); +/*!40000 ALTER TABLE `audit_tasks` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `borrowers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `borrowers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `phone_number` varchar(50) DEFAULT NULL, + `class_name` varchar(100) DEFAULT NULL, + `role` varchar(100) DEFAULT NULL, + `notes` text DEFAULT NULL, + `added_by` int(11) NOT NULL, + `added_date` timestamp NULL DEFAULT current_timestamp(), + `banned` tinyint(1) DEFAULT 0, + `unban_fine` decimal(10,2) DEFAULT 0.00, + `last_unban_by` int(11) DEFAULT NULL, + `last_unban_date` date DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `added_by` (`added_by`), + KEY `last_unban_by` (`last_unban_by`), + KEY `idx_name` (`name`), + KEY `idx_banned` (`banned`), + CONSTRAINT `borrowers_ibfk_1` FOREIGN KEY (`added_by`) REFERENCES `users` (`id`), + CONSTRAINT `borrowers_ibfk_2` FOREIGN KEY (`last_unban_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `borrowers` WRITE; +/*!40000 ALTER TABLE `borrowers` DISABLE KEYS */; +INSERT INTO `borrowers` VALUES (1,'Alice Johnson','alice@school.edu','+1-555-1001','CS-301','Student','NEVER LEND TO AGAIN DOESNT RETURN SHIT !!!',1,'2025-10-20 11:43:45',1,69.00,1,'2025-11-13'),(2,'Bob Smith','bob@school.edu','+1-555-1002','EE-201','Student',NULL,1,'2025-10-20 11:43:45',1,25.00,NULL,NULL),(3,'Carol Teacher','carol@school.edu','+1-555-1003',NULL,'Faculty',NULL,1,'2025-10-20 11:43:45',0,0.00,NULL,NULL),(4,'Forgetful Fred','fred@test.local','+1-555-9999',NULL,'Student',NULL,1,'2025-10-20 11:44:24',0,0.00,NULL,NULL),(5,'Missgeburt Fredie',NULL,NULL,'Hurensohn','Student','Bruh',1,'2025-10-24 06:52:20',0,0.00,1,'2025-12-03'); +/*!40000 ALTER TABLE `borrowers` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER borrowers_before_insert_meta +BEFORE INSERT ON borrowers +FOR EACH ROW +BEGIN + IF NEW.added_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.added_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER borrowers_before_update_meta +BEFORE UPDATE ON borrowers +FOR EACH ROW +BEGIN + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + IF NEW.last_unban_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.last_unban_by = @current_user_id; + END IF; + IF NEW.last_unban_date IS NULL THEN + SET NEW.last_unban_date = CURDATE(); + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_borrower_issues +AFTER UPDATE ON borrowers +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + + -- Auto-detect when borrower gets banned + IF OLD.banned = FALSE AND NEW.banned = TRUE THEN + SET issue_title = CONCAT('Borrower Banned: ', NEW.name); + SET issue_description = CONCAT('Borrower has been banned. Name: ', NEW.name, CASE WHEN NEW.unban_fine > 0 THEN CONCAT(', Unban Fine: $', NEW.unban_fine) ELSE '' END); + + INSERT INTO issue_tracker ( + issue_type, borrower_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Borrower Issue', NEW.id, issue_title, issue_description, 'High', 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, 'BORROWER_BANNED', NOW() + ); + END IF; + + -- Auto-resolve when borrower gets unbanned + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Borrower unbanned on ', COALESCE(NEW.last_unban_date, CURDATE()), CASE WHEN NEW.last_unban_by IS NOT NULL THEN CONCAT(' by user ID ', NEW.last_unban_by) ELSE '' END), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, NEW.last_unban_by, 1) + WHERE borrower_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger = 'BORROWER_BANNED'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `categories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `categories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `category_name` varchar(200) NOT NULL, + `category_description` text DEFAULT NULL, + `parent_id` int(11) DEFAULT NULL, + `category_code` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_parent` (`parent_id`), + KEY `idx_code` (`category_code`), + CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `categories` WRITE; +/*!40000 ALTER TABLE `categories` DISABLE KEYS */; +INSERT INTO `categories` VALUES (7,'Smartboard',NULL,NULL,'sb'),(8,'Adapters',NULL,NULL,'ada'),(9,'Cables',NULL,NULL,'cbl'),(10,'Monitors',NULL,NULL,'mon'); +/*!40000 ALTER TABLE `categories` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `issue_tracker`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `issue_tracker` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `issue_type` enum('Asset Issue','Borrower Issue','System Issue','Maintenance','Other') NOT NULL, + `asset_id` int(11) DEFAULT NULL, + `borrower_id` int(11) DEFAULT NULL, + `title` varchar(255) NOT NULL, + `description` text NOT NULL, + `severity` enum('Critical','High','Medium','Low') DEFAULT NULL, + `priority` enum('Urgent','High','Normal','Low') DEFAULT 'Normal', + `status` enum('Open','In Progress','Resolved','Closed','On Hold') DEFAULT 'Open', + `solution` enum('Fixed','Replaced','Clarify','No Action Needed','Deferred','Items Returned','Automatically Fixed') DEFAULT NULL, + `solution_plus` text DEFAULT NULL, + `replacement_asset_id` int(11) DEFAULT NULL, + `reported_by` int(11) NOT NULL, + `assigned_to` int(11) DEFAULT NULL, + `resolved_by` int(11) DEFAULT NULL, + `cost` decimal(10,2) DEFAULT NULL, + `created_date` datetime NOT NULL DEFAULT current_timestamp(), + `updated_date` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `resolved_date` datetime DEFAULT NULL, + `notes` text DEFAULT NULL, + `auto_detected` tinyint(1) DEFAULT 0, + `detection_trigger` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `replacement_asset_id` (`replacement_asset_id`), + KEY `reported_by` (`reported_by`), + KEY `assigned_to` (`assigned_to`), + KEY `resolved_by` (`resolved_by`), + KEY `idx_issue_type` (`issue_type`), + KEY `idx_asset` (`asset_id`), + KEY `idx_borrower` (`borrower_id`), + KEY `idx_severity` (`severity`), + KEY `idx_status` (`status`), + KEY `idx_created_date` (`created_date`), + KEY `idx_auto_detected` (`auto_detected`), + CONSTRAINT `issue_tracker_ibfk_1` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_ibfk_2` FOREIGN KEY (`borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_ibfk_3` FOREIGN KEY (`replacement_asset_id`) REFERENCES `assets` (`id`) ON DELETE SET NULL, + CONSTRAINT `issue_tracker_ibfk_4` FOREIGN KEY (`reported_by`) REFERENCES `users` (`id`), + CONSTRAINT `issue_tracker_ibfk_5` FOREIGN KEY (`assigned_to`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `issue_tracker_ibfk_6` FOREIGN KEY (`resolved_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `issue_tracker` WRITE; +/*!40000 ALTER TABLE `issue_tracker` DISABLE KEYS */; +INSERT INTO `issue_tracker` VALUES (4,'Borrower Issue',NULL,2,'Borrower Banned: Bob Smith','Borrower has been banned. Name: Bob Smith, Unban Fine: $25.00','High','Normal','Open',NULL,NULL,NULL,2,NULL,NULL,NULL,'2025-10-20 11:43:55','2025-10-20 11:43:55',NULL,NULL,1,'BORROWER_BANNED'),(5,'System Issue',NULL,NULL,'Audit: Missing Assets in Unknown Zone','Full zone audit completed with 3 missing assets. Expected: 5, Found: 2. Audit ID: 1','High','High','Open',NULL,NULL,NULL,2,NULL,NULL,NULL,'2025-10-20 11:44:01','2025-10-20 11:44:01',NULL,'Physical Audit ID: 1 in zone: 5',1,'AUDIT_MISSING_ASSETS'),(43,'Borrower Issue',NULL,1,'Borrower Banned: Alice Johnson','Borrower has been banned. Name: Alice Johnson, Unban Fine: $1000.00','High','Normal','Resolved','Items Returned','Borrower unbanned on 2025-11-13 by user ID 1',NULL,1,NULL,1,NULL,'2025-11-11 09:19:22','2025-11-13 16:01:00','2025-11-13 16:01:00',NULL,1,'BORROWER_BANNED'),(44,'Borrower Issue',NULL,5,'Borrower Banned: Missgeburt Fredie','Borrower has been banned. Name: Missgeburt Fredie, Unban Fine: $4293847.00','High','Normal','Resolved','Items Returned','Borrower unbanned on 2025-12-03 by user ID 1',NULL,1,NULL,1,NULL,'2025-11-12 10:32:36','2025-12-03 15:41:20','2025-12-03 15:41:20',NULL,1,'BORROWER_BANNED'),(47,'Borrower Issue',NULL,1,'Borrower Banned: Alice Johnson','Borrower has been banned. Name: Alice Johnson, Unban Fine: $69.00','High','Normal','Open',NULL,NULL,NULL,1,NULL,NULL,NULL,'2025-11-13 16:09:28','2025-11-13 16:09:28',NULL,NULL,1,'BORROWER_BANNED'),(48,'Asset Issue',26,NULL,'Asset Missing: Floating HDMI Cables','Asset status changed to Missing. Asset: 18368995 (Floating HDMI Cables)','Critical','Normal','Resolved','Automatically Fixed','Asset status automatically changed from Missing to Good',NULL,1,NULL,1,NULL,'2025-11-14 23:10:15','2025-11-14 23:21:14','2025-11-14 23:21:14',NULL,1,'STATUS_MISSING'),(49,'Asset Issue',38,NULL,'Asset Faulty: Dell Monitor','Asset status changed to Faulty. Asset: 51229246 (Dell Monitor)','High','Normal','Open',NULL,NULL,NULL,1,NULL,NULL,NULL,'2025-12-03 15:55:10','2025-12-03 15:55:10',NULL,NULL,1,'STATUS_FAULTY'),(50,'Asset Issue',39,NULL,'Asset Missing: Dell Monitor','Asset status changed to Missing. Asset: 97355732 (Dell Monitor)','Critical','Normal','Resolved','Automatically Fixed','Asset status automatically changed from Missing to Good',NULL,1,NULL,1,NULL,'2025-12-03 15:55:10','2025-12-08 11:00:50','2025-12-08 11:00:50',NULL,1,'STATUS_MISSING'),(51,'Asset Issue',38,NULL,'Asset Missing: Dell Monitor','Asset status changed to Missing. Asset: 51229246 (Dell Monitor)','Critical','Normal','Open',NULL,NULL,NULL,1,NULL,NULL,NULL,'2025-12-08 11:00:50','2025-12-08 11:00:50',NULL,NULL,1,'STATUS_MISSING'),(52,'Asset Issue',38,NULL,'Asset Needs Attention: Dell Monitor','Asset status changed to Attention. Asset: 51229246 (Dell Monitor)','Medium','Normal','Open',NULL,NULL,NULL,1,NULL,NULL,NULL,'2025-12-08 11:08:13','2025-12-08 11:08:13',NULL,NULL,1,'STATUS_ATTENTION'),(53,'Asset Issue',39,NULL,'Asset Missing: Dell Monitor','Asset status changed to Missing. Asset: 97355732 (Dell Monitor)','Critical','Normal','Open',NULL,NULL,NULL,1,NULL,NULL,NULL,'2025-12-08 11:08:13','2025-12-08 11:08:13',NULL,NULL,1,'STATUS_MISSING'),(54,'Asset Issue',26,NULL,'Asset Missing: Floating HDMI Cables','Asset status changed to Missing. Asset: 18368995 (Floating HDMI Cables)','Critical','Normal','Resolved','Automatically Fixed','Asset status automatically changed from Missing to Good',NULL,1,NULL,1,NULL,'2025-12-08 11:08:13','2025-12-10 16:40:16','2025-12-10 16:40:16',NULL,1,'STATUS_MISSING'); +/*!40000 ALTER TABLE `issue_tracker` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_before_insert_meta +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + IF NEW.reported_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.reported_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_issue_tracker_insert +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Asset Issue requires asset_id + IF NEW.issue_type = 'Asset Issue' AND NEW.asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'asset_id is required for Asset Issue type'; + END IF; + + -- Borrower Issue requires borrower_id + IF NEW.issue_type = 'Borrower Issue' AND NEW.borrower_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'borrower_id is required for Borrower Issue type'; + END IF; + + -- Auto-set resolved_date when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_date IS NULL THEN + SET NEW.resolved_date = NOW(); + END IF; + + -- Auto-set resolved_by when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_insert_log +AFTER INSERT ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE set_fields JSON DEFAULT JSON_ARRAY(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Build JSON of non-NULL inserted fields + IF NEW.issue_type IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'issue_type'); + SET new_vals = JSON_SET(new_vals, '$.issue_type', NEW.issue_type); + END IF; + IF NEW.asset_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'asset_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_id', NEW.asset_id); + END IF; + IF NEW.borrower_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.borrower_id', NEW.borrower_id); + END IF; + IF NEW.title IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'title'); + SET new_vals = JSON_SET(new_vals, '$.title', NEW.title); + END IF; + IF NEW.severity IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'severity'); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF NEW.status IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, new_values, changed_by) + VALUES (NEW.id, 'INSERT', set_fields, new_vals, COALESCE(@current_user_id, NEW.reported_by)); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_issue_tracker_update +BEFORE UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Auto-set resolved_date when status changes to Resolved or Closed + IF OLD.status NOT IN ('Resolved', 'Closed') AND NEW.status IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; + END IF; + + -- Clear resolved_date when status changes away from Resolved/Closed + IF OLD.status IN ('Resolved', 'Closed') AND NEW.status NOT IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NULL; + SET NEW.resolved_by = NULL; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_update_log +AFTER UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE changed_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Track all changed fields + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + IF OLD.severity <=> NEW.severity IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'severity'); + SET old_vals = JSON_SET(old_vals, '$.severity', OLD.severity); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF OLD.priority <=> NEW.priority IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'priority'); + SET old_vals = JSON_SET(old_vals, '$.priority', OLD.priority); + SET new_vals = JSON_SET(new_vals, '$.priority', NEW.priority); + END IF; + IF OLD.solution <=> NEW.solution IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + SET new_vals = JSON_SET(new_vals, '$.solution', NEW.solution); + END IF; + IF OLD.assigned_to <=> NEW.assigned_to IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'assigned_to'); + SET old_vals = JSON_SET(old_vals, '$.assigned_to', OLD.assigned_to); + SET new_vals = JSON_SET(new_vals, '$.assigned_to', NEW.assigned_to); + END IF; + IF OLD.resolved_by <=> NEW.resolved_by IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'resolved_by'); + SET old_vals = JSON_SET(old_vals, '$.resolved_by', OLD.resolved_by); + SET new_vals = JSON_SET(new_vals, '$.resolved_by', NEW.resolved_by); + END IF; + + -- Only log if something actually changed + IF JSON_LENGTH(changed_fields) > 0 THEN + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, new_values, changed_by) + VALUES (NEW.id, 'UPDATE', changed_fields, old_vals, new_vals, COALESCE(@current_user_id, OLD.reported_by)); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_before_delete +BEFORE DELETE ON issue_tracker +FOR EACH ROW +BEGIN + -- If issue is not already resolved/closed, update it before deletion + IF OLD.status NOT IN ('Resolved', 'Closed') THEN + -- Can't UPDATE in a BEFORE DELETE trigger, so we just ensure it was marked resolved + -- This will prevent accidental deletion of open issues + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete open issues. Please close or resolve the issue first.'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_delete_log +AFTER DELETE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE deleted_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + + -- Log all fields from deleted issue + IF OLD.issue_type IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'issue_type'); + SET old_vals = JSON_SET(old_vals, '$.issue_type', OLD.issue_type); + END IF; + IF OLD.asset_id IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'asset_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_id', OLD.asset_id); + END IF; + IF OLD.title IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'title'); + SET old_vals = JSON_SET(old_vals, '$.title', OLD.title); + END IF; + IF OLD.status IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + IF OLD.solution IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, changed_by) + VALUES (OLD.id, 'DELETE', deleted_fields, old_vals, COALESCE(@current_user_id, OLD.reported_by)); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `issue_tracker_change_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `issue_tracker_change_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `issue_id` int(11) NOT NULL, + `change_type` enum('INSERT','UPDATE','DELETE') NOT NULL, + `changed_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`changed_fields`)), + `old_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`old_values`)), + `new_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`new_values`)), + `changed_by` int(11) DEFAULT NULL, + `change_date` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `changed_by` (`changed_by`), + KEY `idx_issue` (`issue_id`), + KEY `idx_change_type` (`change_type`), + KEY `idx_change_date` (`change_date`), + CONSTRAINT `issue_tracker_change_log_ibfk_1` FOREIGN KEY (`issue_id`) REFERENCES `issue_tracker` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_change_log_ibfk_2` FOREIGN KEY (`changed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `issue_tracker_change_log` WRITE; +/*!40000 ALTER TABLE `issue_tracker_change_log` DISABLE KEYS */; +INSERT INTO `issue_tracker_change_log` VALUES (6,4,'INSERT','[\"issue_type\", \"borrower_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Borrower Issue\", \"borrower_id\": 2, \"title\": \"Borrower Banned: Bob Smith\", \"severity\": \"High\", \"status\": \"Open\"}',2,'2025-10-20 11:43:55'),(7,5,'INSERT','[\"issue_type\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"System Issue\", \"title\": \"Audit: Missing Assets in Unknown Zone\", \"severity\": \"High\", \"status\": \"Open\"}',2,'2025-10-20 11:44:01'),(72,43,'INSERT','[\"issue_type\", \"borrower_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Borrower Issue\", \"borrower_id\": 1, \"title\": \"Borrower Banned: Alice Johnson\", \"severity\": \"High\", \"status\": \"Open\"}',1,'2025-11-11 09:19:22'),(73,44,'INSERT','[\"issue_type\", \"borrower_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Borrower Issue\", \"borrower_id\": 5, \"title\": \"Borrower Banned: Missgeburt Fredie\", \"severity\": \"High\", \"status\": \"Open\"}',1,'2025-11-12 10:32:36'),(78,43,'UPDATE','[\"status\", \"solution\", \"resolved_by\"]','{\"status\": \"Open\", \"solution\": null, \"resolved_by\": null}','{\"status\": \"Resolved\", \"solution\": \"Items Returned\", \"resolved_by\": 1}',1,'2025-11-13 16:01:00'),(79,47,'INSERT','[\"issue_type\", \"borrower_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Borrower Issue\", \"borrower_id\": 1, \"title\": \"Borrower Banned: Alice Johnson\", \"severity\": \"High\", \"status\": \"Open\"}',1,'2025-11-13 16:09:28'),(83,48,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 26, \"title\": \"Asset Missing: Floating HDMI Cables\", \"severity\": \"Critical\", \"status\": \"Open\"}',1,'2025-11-14 23:10:15'),(84,48,'UPDATE','[\"status\", \"solution\", \"resolved_by\"]','{\"status\": \"Open\", \"solution\": null, \"resolved_by\": null}','{\"status\": \"Resolved\", \"solution\": \"Automatically Fixed\", \"resolved_by\": 1}',1,'2025-11-14 23:21:14'),(85,44,'UPDATE','[\"status\", \"solution\", \"resolved_by\"]','{\"status\": \"Open\", \"solution\": null, \"resolved_by\": null}','{\"status\": \"Resolved\", \"solution\": \"Items Returned\", \"resolved_by\": 1}',1,'2025-12-03 15:41:20'),(86,49,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 38, \"title\": \"Asset Faulty: Dell Monitor\", \"severity\": \"High\", \"status\": \"Open\"}',1,'2025-12-03 15:55:10'),(87,50,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 39, \"title\": \"Asset Missing: Dell Monitor\", \"severity\": \"Critical\", \"status\": \"Open\"}',1,'2025-12-03 15:55:10'),(88,51,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 38, \"title\": \"Asset Missing: Dell Monitor\", \"severity\": \"Critical\", \"status\": \"Open\"}',1,'2025-12-08 11:00:50'),(89,50,'UPDATE','[\"status\", \"solution\", \"resolved_by\"]','{\"status\": \"Open\", \"solution\": null, \"resolved_by\": null}','{\"status\": \"Resolved\", \"solution\": \"Automatically Fixed\", \"resolved_by\": 1}',1,'2025-12-08 11:00:50'),(90,52,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 38, \"title\": \"Asset Needs Attention: Dell Monitor\", \"severity\": \"Medium\", \"status\": \"Open\"}',1,'2025-12-08 11:08:13'),(91,53,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 39, \"title\": \"Asset Missing: Dell Monitor\", \"severity\": \"Critical\", \"status\": \"Open\"}',1,'2025-12-08 11:08:13'),(92,54,'INSERT','[\"issue_type\", \"asset_id\", \"title\", \"severity\", \"status\"]',NULL,'{\"issue_type\": \"Asset Issue\", \"asset_id\": 26, \"title\": \"Asset Missing: Floating HDMI Cables\", \"severity\": \"Critical\", \"status\": \"Open\"}',1,'2025-12-08 11:08:13'),(93,54,'UPDATE','[\"status\", \"solution\", \"resolved_by\"]','{\"status\": \"Open\", \"solution\": null, \"resolved_by\": null}','{\"status\": \"Resolved\", \"solution\": \"Automatically Fixed\", \"resolved_by\": 1}',1,'2025-12-10 16:40:16'); +/*!40000 ALTER TABLE `issue_tracker_change_log` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `label_templates`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `label_templates` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `template_code` varchar(100) NOT NULL COMMENT 'Unique code like "CABLE"', + `template_name` varchar(200) NOT NULL COMMENT 'Human readable name', + `layout_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Universal label design: SVG graphics, auto-populated field placeholders, styling' CHECK (json_valid(`layout_json`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `template_code` (`template_code`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_template_code` (`template_code`), + KEY `idx_template_name` (`template_name`), + CONSTRAINT `label_templates_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `label_templates_ibfk_2` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `label_templates` WRITE; +/*!40000 ALTER TABLE `label_templates` DISABLE KEYS */; +INSERT INTO `label_templates` VALUES (5,'DEF','Default-Zonebound-Label','{\"background\":\"#FFFFFF\",\"elements\":[{\"data\":\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCAyMDAgODAiPjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iODAiIGZpbGw9IiNmZmZmZmYiLz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjgwIiByeD0iOCIgcnk9IjgiIGZpbGw9IiMyMjIyMjIiLz48Y2lyY2xlIGN4PSI0MCIgY3k9IjQwIiByPSIyMiIgZmlsbD0iIzAwRDA4NCIvPjxyZWN0IHg9Ijc1IiB5PSIyNSIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMCIgZmlsbD0iI2ZmZmZmZiIvPjxyZWN0IHg9Ijc1IiB5PSI0NSIgd2lkdGg9IjcwIiBoZWlnaHQ9IjEwIiBmaWxsPSIjZmZmZmZmIi8+PC9zdmc+\",\"height\":10.0,\"type\":\"svg\",\"width\":18.0,\"x\":4.0,\"y\":3.0},{\"color\":\"#000000\",\"field\":\"BeepZone Demo\",\"fontSize\":12.0,\"maxWidth\":22.0,\"type\":\"text\",\"wrap\":false,\"x\":24.0,\"y\":5.0},{\"color\":\"#000000\",\"field\":\"TAG: {{asset_tag}}\",\"fontSize\":10.0,\"type\":\"text\",\"x\":4.0,\"y\":18.0},{\"color\":\"#000000\",\"field\":\"ZONE: {{zone_code}}\",\"fontSize\":10.0,\"type\":\"text\",\"x\":4.0,\"y\":23.0},{\"field\":\"{{asset_numeric_id}}\",\"showText\":false,\"size\":22.0,\"type\":\"datamatrix\",\"x\":4.0,\"y\":28.0},{\"color\":\"#000000\",\"field\":\"ID: {{asset_numeric_id}}\",\"fontSize\":9.0,\"type\":\"text\",\"x\":28.0,\"y\":29.0},{\"color\":\"#000000\",\"field\":\"{{name}}\",\"fontSize\":9.0,\"maxWidth\":20.0,\"type\":\"text\",\"wrap\":true,\"x\":28.0,\"y\":34.0}]}','2025-11-09 12:02:11',NULL,'2025-11-09 20:56:34',NULL),(6,'ZONE','Zone Sign','{\"background\":\"#F8F9FA\",\"elements\":[{\"fill\":\"#1976D2\",\"height\":12.0,\"type\":\"rect\",\"width\":100.0,\"x\":0.0,\"y\":0.0},{\"color\":\"#FFFFFF\",\"field\":\"ZONE IDENTIFICATION\",\"fontSize\":14.0,\"fontWeight\":\"bold\",\"type\":\"text\",\"x\":5.0,\"y\":2.0},{\"color\":\"#000000\",\"field\":\"{{zone_name}}\",\"fontSize\":16.0,\"fontWeight\":\"bold\",\"maxWidth\":60.0,\"type\":\"text\",\"wrap\":false,\"x\":5.0,\"y\":18.0},{\"color\":\"#666666\",\"field\":\"Zone Code: {{zone_code}}\",\"fontSize\":12.0,\"type\":\"text\",\"x\":5.0,\"y\":25.0},{\"field\":\"ZONE:{{zone_code}}\",\"showText\":false,\"size\":24.0,\"type\":\"qrcode\",\"x\":70.0,\"y\":15.0}],\"space\":{\"height\":40.0,\"width\":100.0}}','2025-11-09 21:08:18',NULL,'2025-11-09 21:08:18',NULL),(7,'DEFIMMO','Default ImmoRSS','{\"background\":\"#FFFFFF\",\"elements\":[{\"data\":\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJiIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNDYuMzggNDQuODYiPjxnIGlkPSJjIj48cGF0aCBkPSJNMTU2LjMsNDNsLS4wMi0zNy4xYy42MS0yLjg0LTUuNS0yLjktNS45OC0zLjM0LTEuNDMtMS4zMiwzLjUxLTEuNTcsMy45NS0xLjYsNS43Ny0uMzcsMTguNjYtLjUxLDIzLjUxLDIuMDksNy41Nyw0LjA2LDcuNDksMTYuNTQtLjMxLDE4LjkyLTEuNDMuNDQtMy43Ni0uNDctMy40OC45OGwxMC40NCwyMC4wNS02LjktLjA0LTEwLjAzLTE4LjI0Yy0xLTEuODEtNC4yLTEuNzItNC40NC40NC0uMzgsMy4zNi0uMTcsOS45Mi0uMTEsMTMuNTgsMCwuNzguNzksNC4yNy4xNiw0LjI3aC02Ljc5Wk0xNzIuMjQsMTkuNTdjLjQyLS4yNC43OC0uNTYsMS4yNS0uOTQsMi44My0yLjMsMy40MS03LjkxLDEuNTktMTEtLjIyLS4zOS0uOTYtMS43LTIuNDgtMi42My0uNDQtLjI3LS45NC0uNDQtMS40Ni0uNS0zLjMyLS40LTYuMDUtLjIzLTYuNzIsMC0uNTkuMjEtMS4wOCwxLjM3LTEuMTgsMS45OC0uMiwxLjIzLS4yLDEzLjExLjE3LDEzLjc4LjQ2LjgzLDcuMDcuMzgsOC4wNi0uMi4yOS0uMTcuMjctLjE5LjQyLS4yOGwuMzUtLjIxWiIvPjxwYXRoIGQ9Ik0yNC4wNyw0MC45OGMwLTIuMDEuOTYtMjEuMTEtMS4yMy0yMy4yNi0uODEtLjc5LTUuMjEtLjIzLTMuOTItMi4xOC4zNy0uNTYsMTAuMTYtMi4yMSwxMC45NS0xLjcyLDEuMzcuODUtLjk0LDEuNjkuMzksMi4wOS41MS4xNSw0Ljg4LTEuNjIsNi40MS0yLjA1LDEuMy0uMzYsNi4wOS0uMjgsNy4zNS4zOCwxLjQ0Ljc1LDIuNDEsMi44LDIuNjcsMi45My41NS4yNSwxLjU4LTIuNCw2LjE0LTMuMjgsMi4xMi0uMzIsNy4wMy0uNzIsOC43MS45Miw0LjU1LDQuNDMsMi40NywyMC40OCwyLjgxLDI3LjEuMDIuMzQtLjAyLS4yMi0uMTMsMS4wOGgtNi4xNnYtMjEuMDFjMC0yLjIyLTMuMDYtNC44LTUuMjEtNC4yNy0yLjc4LjYxLTQuNjgsNC44MS00LjgsNS4xNC0xLjE4LDMuMy0uODIsMTQuODItLjY0LDE4Ljc5LjAyLjQ2LjAyLDEuMjMuMDEsMS4yOC0xLjgyLjAzLTUuMzguMTEtNi40MS4wNnYtMjAuNjljMC0uNC0uNTUtMy44OC0xLjk3LTQuMTktMS41MS0xLjQ2LTQuNjYuNTUtNS43NywxLjgyLTUuMjksNi4wMS0yLjgyLDE1LjY4LTMsMjMuMDZoLTYuMTdzLS4wMy0yLjAxLS4wMy0yLjAxWiIvPjxwYXRoIGQ9Ik0xOTkuNDYsMTUuMzJjMy44OSwzLjE0LDkuNDksNC42LDEyLjkyLDguNDEsMS44NCwyLjA0LDQuMTMsNC4xMiwzLjY1LDkuOTctMS40NSw4LjAyLTExLjA0LDEwLjk3LTE4LjE5LDEwLjU3LTQuNzgtLjQzLTguODQtMS45My05LjE4LTIuMTItLjEtLjA1LjIxLTEuMDguMjEtMS4yLDAtLjE3LjgtMi4zOS45My0yLjQ5LjUzLS40MSw1LjM0LDEuMjIsNi42MywxLjU0LDUuMzYsMS4zMywxMS44NCwxLjE1LDEyLjMtNS43MnMtOC44My05LjQ0LTEzLjItMTIuMzNjLTIuMzQtMS41NS01LjkyLTUuMzctNi41Mi04LjE1LS42Ni0zLjA3Ljk1LTkuMzcsMy44Ny0xMS4xOSwxLjgtMS4xMiw0LjY3LTIuMjMsMTAuMS0yLjMzLDQuMTMuMDUsOC4zOC43Miw5LjAyLDEuMDksMS4wOC42MywxLjIxLDMuNzMuNTcsNC4yMS0xLjAxLjc2LTE1LTUuODgtMTYuMzcsMS45NS0uNzMsNC4xNS40LDUuNDYsMy4yOCw3Ljc5aC0uMDJaIi8+PHBhdGggZD0iTTE0MC4zNCwxMi44OWM5Ljc1LDMuNTcsMTAuNDEsMjUuMDIuODEsMjguNTQtMTUuMjEsNS41Ny0yMS44LTYuNTctMTkuMzEtMjAuMjYuNDYtMS41Ny40Ni0xLjU5Ljg4LTIuNC4zNC0uODMuMzUtLjgzLjgxLTEuNTcuMDItLjA0LjY3LS45OC45OS0xLjI1LDQuMTQtMy41MiwxMC41OC00Ljk4LDE1LjgyLTMuMDZoMFpNMTQwLjk3LDMxLjk1Yy4wOS01LjU1LDEuMDYtMjAuOS04Ljc4LTE3LjE1LTIuOTUsMS4xMi0zLjEsMy4yNC0zLjIzLDMuNjQtLjA5LjEtLjMxLjc5LS4zNywxLjI3LS4xLjE3LS40MSwzLjM2LS40NiwzLjk3LS40MSw1LjczLS4xOSwxNy4zNCw4LjAyLDE2LjIzLDQuMTQtLjU2LDQuMy00Ljk1LDQuODItNy45NloiLz48cGF0aCBkPSJNNi43MSw0M2MtLjIzLS4wOS0uNS0uNDctLjQ1LS43NmwtLjA2LTM1LjcyQzUuMTksMS43OS0xLjI1LDUuNDcuMjEsMS44MS42Ljg0LDEwLjg5LDEuNjQsMTIuMzgsMGMuODEtLjA1LDEuMTcuMzEsMS4xNCwxLjEybC0uMTcsNDEuODdoLTYuNjRaIi8+PHBhdGggZD0iTTc0LjYyLDQwLjk2YzAtMi4wMS45Ni0yMS4xMS0xLjIzLTIzLjI2LS44MS0uNzktNS4yMS0uMjMtMy45Mi0yLjE4LjM3LS41NiwxMC4xNi0yLjIxLDEwLjk1LTEuNzIsMS4zNy44NS0uOTQsMS42OS4zOSwyLjA5LjUxLjE1LDQuODgtMS42Miw2LjQxLTIuMDUsMS4zLS4zNiw2LjA5LS4yOCw3LjM1LjM4LDEuNDQuNzUsMi40MSwyLjgsMi42NywyLjkzLjU1LjI1LDEuNTgtMi40LDYuMTQtMy4yOCwyLjEyLS4zMiw3LjAzLS43Miw4LjcxLjkyLDQuNTUsNC40MywyLjQ3LDIwLjQ4LDIuODEsMjcuMS4wMi4zNC0uMDItLjIyLS4xMywxLjA4aC02LjE2di0yMS4wMWMwLTIuMjItMy4wNi00LjgtNS4yMS00LjI3LTIuNzguNjEtNC42OCw0LjgxLTQuOCw1LjE0LTEuMTgsMy4zLS44MiwxNC44Mi0uNjQsMTguNzkuMDIuNDYuMDIsMS4yMy4wMSwxLjI4LTEuODIuMDMtNS4zOC4xMS02LjQxLjA2di0yMC42OWMwLS40LS41NS0zLjg4LTEuOTctNC4xOS0xLjUxLTEuNDYtNC42Ni41NS01Ljc3LDEuODItNS4yOSw2LjAxLTIuODIsMTUuNjgtMywyMy4wNmgtNi4xN3MtLjAzLTIuMDEtLjAzLTIuMDFaIi8+PHBhdGggZD0iTTIyOS43NSwxNS44OGMzLjg5LDMuMTQsOS40OSw0LjYsMTIuOTIsOC40MSwxLjg0LDIuMDQsNC4xMyw0LjEyLDMuNjUsOS45Ny0xLjQ1LDguMDItMTEuMDQsMTAuOTctMTguMTksMTAuNTctNC43OC0uNDMtOC44NC0xLjkzLTkuMTgtMi4xMi0uMS0uMDUuMjEtMS4wOC4yMS0xLjIsMC0uMTcuOC0yLjM5LjkzLTIuNDkuNTMtLjQxLDUuMzQsMS4yMiw2LjYzLDEuNTQsNS4zNiwxLjMzLDExLjg0LDEuMTUsMTIuMy01Ljcycy04LjgzLTkuNDQtMTMuMi0xMi4zM2MtMi4zNC0xLjU1LTUuOTItNS4zNy02LjUyLTguMTUtLjY2LTMuMDcuOTUtOS4zNywzLjg3LTExLjE5LDEuOC0xLjEyLDQuNjctMi4yMywxMC4xLTIuMzMsNC4xMy4wNSw4LjM4LjcyLDkuMDIsMS4wOSwxLjA4LjYzLDEuMjEsMy43My41Nyw0LjIxLTEuMDEuNzYtMTUtNS44OC0xNi4zNywxLjk1LS43Myw0LjE1LjQsNS40NiwzLjI4LDcuNzloLS4wMloiLz48L2c+PC9zdmc+\",\"height\":20.0,\"type\":\"svg\",\"width\":40.0,\"x\":4.0,\"y\":3.0},{\"color\":\"#000000\",\"field\":\"Inventory\",\"fontSize\":12.0,\"maxWidth\":22.0,\"type\":\"text\",\"wrap\":false,\"x\":48.0,\"y\":5.0},{\"color\":\"#000000\",\"field\":\"TAG: {{asset_tag}}\",\"fontSize\":10.0,\"type\":\"text\",\"x\":4.0,\"y\":18.0},{\"color\":\"#000000\",\"field\":\"ZONE: {{zone_code}}\",\"fontSize\":10.0,\"type\":\"text\",\"x\":4.0,\"y\":23.0},{\"field\":\"{{asset_numeric_id}}\",\"showText\":false,\"size\":22.0,\"type\":\"datamatrix\",\"x\":4.0,\"y\":28.0},{\"color\":\"#000000\",\"field\":\"ID: {{asset_numeric_id}}\",\"fontSize\":9.0,\"type\":\"text\",\"x\":28.0,\"y\":29.0},{\"color\":\"#000000\",\"field\":\"{{name}}\",\"fontSize\":9.0,\"maxWidth\":20.0,\"type\":\"text\",\"wrap\":true,\"x\":28.0,\"y\":34.0}]}','2025-11-13 18:31:09',NULL,'2025-11-13 18:33:43',NULL); +/*!40000 ALTER TABLE `label_templates` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `lending_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `lending_history` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `asset_id` int(11) NOT NULL, + `borrower_id` int(11) NOT NULL, + `checkout_date` datetime NOT NULL DEFAULT current_timestamp(), + `due_date` date DEFAULT NULL, + `return_date` datetime DEFAULT NULL, + `checked_out_by` int(11) DEFAULT NULL, + `checked_in_by` int(11) DEFAULT NULL, + `notes` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `checked_out_by` (`checked_out_by`), + KEY `checked_in_by` (`checked_in_by`), + KEY `idx_asset` (`asset_id`), + KEY `idx_borrower` (`borrower_id`), + KEY `idx_checkout_date` (`checkout_date`), + KEY `idx_return_date` (`return_date`), + CONSTRAINT `lending_history_ibfk_1` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `lending_history_ibfk_2` FOREIGN KEY (`borrower_id`) REFERENCES `borrowers` (`id`), + CONSTRAINT `lending_history_ibfk_3` FOREIGN KEY (`checked_out_by`) REFERENCES `users` (`id`), + CONSTRAINT `lending_history_ibfk_4` FOREIGN KEY (`checked_in_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `lending_history` WRITE; +/*!40000 ALTER TABLE `lending_history` DISABLE KEYS */; +INSERT INTO `lending_history` VALUES (17,25,3,'2025-11-14 20:28:38','2025-11-21','2025-11-14 22:48:14',1,1,NULL),(18,26,3,'2025-11-14 22:47:39','2025-11-21','2025-11-14 22:49:25',1,1,NULL),(19,25,4,'2025-11-14 22:51:07','2025-11-21','2025-11-14 22:51:11',1,1,NULL),(20,25,5,'2025-12-03 16:44:27','2026-01-01',NULL,1,NULL,'Permanent Lending for Work usage'),(21,26,5,'2025-12-08 11:58:17','2025-12-11','2025-12-10 15:29:13',1,1,'Penis Fotze'),(22,26,3,'2025-12-10 17:37:29',NULL,'2025-12-10 17:55:09',1,1,NULL),(23,26,5,'2025-12-11 19:55:25','2025-12-14',NULL,1,NULL,NULL); +/*!40000 ALTER TABLE `lending_history` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER lending_history_before_insert_meta +BEFORE INSERT ON lending_history +FOR EACH ROW +BEGIN + IF NEW.checked_out_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_out_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER lending_history_before_update_meta +BEFORE UPDATE ON lending_history +FOR EACH ROW +BEGIN + IF OLD.return_date IS NULL AND NEW.return_date IS NOT NULL THEN + IF NEW.checked_in_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_in_by = @current_user_id; + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `physical_audit_logs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `physical_audit_logs` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `physical_audit_id` int(11) NOT NULL COMMENT 'Reference to the audit session', + `asset_id` int(11) NOT NULL, + `audit_date` datetime NOT NULL DEFAULT current_timestamp(), + `audited_by` int(11) NOT NULL, + `status_found` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT 'Good', + `audit_task_id` int(11) DEFAULT NULL COMMENT 'Which audit task was run on this asset', + `audit_task_responses` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'User responses to the JSON sequence questions' CHECK (json_valid(`audit_task_responses`)), + `exception_type` enum('wrong-zone','unexpected-asset','damaged','missing-label','other') DEFAULT NULL, + `exception_details` text DEFAULT NULL COMMENT 'Details about the exception found', + `found_in_zone_id` int(11) DEFAULT NULL COMMENT 'Which zone the asset was actually found in (if different from expected)', + `auditor_action` enum('physical-move','virtual-update','no-action') DEFAULT NULL COMMENT 'What the auditor chose to do about wrong-zone assets', + `notes` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `found_in_zone_id` (`found_in_zone_id`), + KEY `idx_physical_audit` (`physical_audit_id`), + KEY `idx_asset` (`asset_id`), + KEY `idx_audit_date` (`audit_date`), + KEY `idx_audited_by` (`audited_by`), + KEY `idx_status_found` (`status_found`), + KEY `idx_exception_type` (`exception_type`), + CONSTRAINT `physical_audit_logs_ibfk_1` FOREIGN KEY (`physical_audit_id`) REFERENCES `physical_audits` (`id`) ON DELETE CASCADE, + CONSTRAINT `physical_audit_logs_ibfk_2` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `physical_audit_logs_ibfk_3` FOREIGN KEY (`audited_by`) REFERENCES `users` (`id`), + CONSTRAINT `physical_audit_logs_ibfk_4` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL, + CONSTRAINT `physical_audit_logs_ibfk_5` FOREIGN KEY (`found_in_zone_id`) REFERENCES `zones` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `physical_audit_logs` WRITE; +/*!40000 ALTER TABLE `physical_audit_logs` DISABLE KEYS */; +INSERT INTO `physical_audit_logs` VALUES (14,7,38,'2025-11-14 22:42:57',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(15,7,39,'2025-11-14 22:42:57',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(16,7,25,'2025-11-14 22:42:57',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(17,7,26,'2025-11-14 22:42:57',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(18,9,38,'2025-11-14 22:57:48',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(19,9,25,'2025-11-14 22:57:48',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(20,9,26,'2025-11-14 22:57:48',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(21,10,38,'2025-11-14 23:08:21',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(22,10,39,'2025-11-14 23:08:21',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(23,10,25,'2025-11-14 23:08:21',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(24,10,26,'2025-11-14 23:08:21',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(25,12,38,'2025-11-14 23:10:15',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(26,12,39,'2025-11-14 23:10:15',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(27,12,25,'2025-11-14 23:10:15',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(28,12,26,'2025-11-14 23:10:15',1,'Missing',NULL,NULL,'other','Marked missing during audit',11,NULL,NULL),(29,13,38,'2025-11-14 23:19:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(30,13,39,'2025-11-14 23:19:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(31,13,25,'2025-11-14 23:19:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(32,13,26,'2025-11-14 23:19:06',1,'Missing',NULL,NULL,NULL,NULL,11,NULL,NULL),(33,14,38,'2025-11-14 23:25:16',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(34,14,39,'2025-11-14 23:25:16',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(35,14,25,'2025-11-14 23:25:16',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(36,14,26,'2025-11-14 23:25:16',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(37,15,38,'2025-11-14 23:29:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(38,15,39,'2025-11-14 23:29:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(39,15,25,'2025-11-14 23:29:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(40,15,26,'2025-11-14 23:29:06',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(41,16,38,'2025-11-14 23:31:01',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(42,16,39,'2025-11-14 23:31:01',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(43,16,25,'2025-11-14 23:31:01',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(44,16,26,'2025-11-14 23:31:01',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(45,17,25,'2025-11-15 01:19:04',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(46,18,38,'2025-11-15 01:29:14',1,'Good',1,'{\"responses\":[{\"answer\":\"Yes\",\"question\":\"Does the device power on?\",\"step\":1},{\"answer\":\"Good\",\"question\":\"Physical condition?\",\"step\":2}]}',NULL,NULL,11,NULL,NULL),(47,18,39,'2025-11-15 01:29:14',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(48,18,25,'2025-11-15 01:29:14',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(49,18,26,'2025-11-15 01:29:14',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(50,19,25,'2025-11-15 04:03:42',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(51,20,25,'2025-11-15 04:04:23',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(52,21,25,'2025-11-15 04:05:11',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(53,22,25,'2025-11-15 04:07:13',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(54,23,38,'2025-12-03 15:55:10',1,'Faulty',1,'{\"responses\":[{\"answer\":\"No\",\"question\":\"Does the device power on?\",\"step\":1}]}',NULL,NULL,11,NULL,NULL),(55,23,39,'2025-12-03 15:55:10',1,'Missing',NULL,NULL,'other','Marked missing during audit',11,NULL,NULL),(56,23,25,'2025-12-03 15:55:10',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(57,23,26,'2025-12-03 15:55:10',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(58,24,38,'2025-12-08 11:00:50',1,'Missing',1,NULL,'other','Marked missing during audit',11,NULL,NULL),(59,24,39,'2025-12-08 11:00:50',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(60,24,25,'2025-12-08 11:00:50',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(61,24,26,'2025-12-08 11:00:50',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(62,25,38,'2025-12-08 11:08:13',1,'Attention',1,'{\"additional_fields\":{\"damage_notes\":\"penis\"},\"responses\":[{\"answer\":\"Yes\",\"question\":\"Does the device power on?\",\"step\":1},{\"answer\":\"Damaged\",\"question\":\"Physical condition?\",\"step\":2},{\"answer\":\"penis\",\"question\":\"Describe the damage\",\"step\":3}]}',NULL,NULL,11,NULL,NULL),(63,25,39,'2025-12-08 11:08:13',1,'Missing',NULL,NULL,'other','Marked missing during audit',11,NULL,NULL),(64,25,25,'2025-12-08 11:08:13',1,'Good',NULL,NULL,NULL,NULL,11,NULL,NULL),(65,25,26,'2025-12-08 11:08:13',1,'Missing',NULL,NULL,'other','Marked missing during audit',11,NULL,NULL); +/*!40000 ALTER TABLE `physical_audit_logs` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER physical_audit_logs_before_insert_meta +BEFORE INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + -- Auto-populate audited_by from session variable if not provided + IF NEW.audited_by IS NULL OR NEW.audited_by = 0 THEN + SET NEW.audited_by = COALESCE(@current_user_id, 1); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER update_assets_found +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + UPDATE physical_audits + SET assets_found = assets_found + 1 + WHERE id = NEW.physical_audit_id; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER update_asset_from_audit +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + DECLARE current_status VARCHAR(100); + + -- Update asset's last_audit date + UPDATE assets + SET last_audit = DATE(NEW.audit_date), + last_audit_status = NEW.status_found + WHERE id = NEW.asset_id; + + -- Compare found status with current asset status + SELECT status INTO current_status FROM assets WHERE id = NEW.asset_id LIMIT 1; + + IF NEW.status_found != current_status THEN + UPDATE assets + SET status = NEW.status_found + WHERE id = NEW.asset_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `physical_audits`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `physical_audits` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `audit_type` enum('full-zone','spot-check') NOT NULL, + `zone_id` int(11) DEFAULT NULL COMMENT 'Zone being audited (NULL for spot-check audits)', + `audit_name` varchar(255) DEFAULT NULL COMMENT 'Custom name for the audit session', + `started_by` int(11) NOT NULL, + `started_at` datetime NOT NULL DEFAULT current_timestamp(), + `completed_at` datetime DEFAULT NULL, + `status` enum('in-progress','all-good','timeout','attention','cancelled') DEFAULT 'in-progress', + `timeout_minutes` int(11) DEFAULT NULL COMMENT 'Timeout setting used for this audit', + `issues_found` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Array of issues: missing_assets, moved_assets, damaged_assets, etc.' CHECK (json_valid(`issues_found`)), + `assets_expected` int(11) DEFAULT NULL COMMENT 'Total assets expected to be found in zone', + `assets_found` int(11) DEFAULT 0 COMMENT 'Total assets actually found and scanned', + `notes` text DEFAULT NULL, + `cancelled_reason` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_audit_type` (`audit_type`), + KEY `idx_zone` (`zone_id`), + KEY `idx_status` (`status`), + KEY `idx_started_at` (`started_at`), + KEY `idx_started_by` (`started_by`), + CONSTRAINT `physical_audits_ibfk_1` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`), + CONSTRAINT `physical_audits_ibfk_2` FOREIGN KEY (`started_by`) REFERENCES `users` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `physical_audits` WRITE; +/*!40000 ALTER TABLE `physical_audits` DISABLE KEYS */; +INSERT INTO `physical_audits` VALUES (3,'spot-check',NULL,NULL,2,'2025-10-20 11:44:07','2025-10-13 18:35:00','attention',NULL,NULL,NULL,1,NULL,NULL),(4,'spot-check',NULL,NULL,2,'2025-10-20 11:44:09','2025-10-13 18:40:00','attention',NULL,NULL,NULL,1,NULL,NULL),(5,'spot-check',NULL,NULL,2,'2025-10-20 11:44:10','2025-10-13 19:00:00','attention',NULL,NULL,NULL,1,NULL,NULL),(6,'spot-check',NULL,NULL,2,'2025-10-20 11:44:11','2025-10-13 19:05:00','all-good',NULL,NULL,NULL,1,NULL,NULL),(7,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 22:42:27','2025-11-14 22:42:56','all-good',60,NULL,4,6,NULL,NULL),(8,'spot-check',NULL,'Spot Check 2025-11-14 22:44',1,'2025-11-14 22:44:13','2025-11-14 22:44:39','attention',NULL,'{\"exceptions\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"details\":null,\"name\":\"Dell Monitor\",\"type\":\"spot-check\"}],\"unexpected_assets\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"name\":\"Dell Monitor\",\"status\":\"Good\"}]}',0,0,NULL,NULL),(9,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 22:57:14','2025-11-14 22:57:47','cancelled',60,NULL,4,4,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-14 22:57:47'),(10,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:06:24','2025-11-14 23:08:21','all-good',60,NULL,4,7,NULL,NULL),(11,'spot-check',NULL,'Spot Check 2025-11-14 23:08',1,'2025-11-14 23:08:59','2025-11-14 23:09:04','cancelled',NULL,NULL,0,0,NULL,'Spot check cancelled at 2025-11-14 23:09:04'),(12,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:09:25','2025-11-14 23:10:15','attention',60,'{\"exceptions\":[{\"asset_id\":26,\"asset_tag\":\"cbl-hdmi-002\",\"details\":\"Marked missing during audit\",\"name\":\"Floating HDMI Cables\",\"type\":\"other\"}],\"missing_assets\":[{\"asset_id\":26,\"asset_tag\":\"cbl-hdmi-002\",\"name\":\"Floating HDMI Cables\"}]}',4,6,NULL,NULL),(13,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:18:39','2025-11-14 23:19:06','all-good',60,NULL,3,6,NULL,NULL),(14,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:24:56','2025-11-14 23:25:16','all-good',60,NULL,4,6,NULL,NULL),(15,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:28:48','2025-11-14 23:29:06','all-good',60,NULL,4,6,NULL,NULL),(16,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-14 23:30:47','2025-11-14 23:31:01','all-good',60,NULL,4,6,NULL,NULL),(17,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 01:18:44','2025-11-15 01:19:04','cancelled',60,NULL,4,1,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-15 01:19:04'),(18,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 01:28:49','2025-11-15 01:29:14','all-good',60,NULL,4,6,NULL,NULL),(19,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 12:23:36','2025-11-15 12:24:35','cancelled',60,NULL,4,1,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-15 12:24:35'),(20,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 12:25:00','2025-11-15 12:25:16','cancelled',60,NULL,4,1,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-15 12:25:16'),(21,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 12:25:49','2025-11-15 12:26:04','cancelled',60,NULL,4,1,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-15 12:26:04'),(22,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-11-15 12:27:57','2025-11-15 12:28:06','cancelled',60,NULL,4,1,NULL,'Audit cancelled for zone Buero IT und so at 2025-11-15 12:28:06'),(23,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-12-03 15:54:18','2025-12-03 15:55:10','attention',60,'{\"attention_assets\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"name\":\"Dell Monitor\",\"status\":\"Faulty\"}],\"exceptions\":[{\"asset_id\":39,\"asset_tag\":\"ps39-mon-108-02\",\"details\":\"Marked missing during audit\",\"name\":\"Dell Monitor\",\"type\":\"other\"}],\"missing_assets\":[{\"asset_id\":39,\"asset_tag\":\"ps39-mon-108-02\",\"name\":\"Dell Monitor\"}]}',4,5,NULL,NULL),(24,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-12-08 10:58:41','2025-12-08 11:00:50','attention',60,'{\"exceptions\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"details\":\"Marked missing during audit\",\"name\":\"Dell Monitor\",\"type\":\"other\"}],\"missing_assets\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"name\":\"Dell Monitor\"}]}',3,5,NULL,NULL),(25,'full-zone',11,'Zone Buero IT und so Audit',1,'2025-12-08 11:07:50','2025-12-08 11:08:13','attention',60,'{\"attention_assets\":[{\"asset_id\":38,\"asset_tag\":\"ps39-mon-108-01\",\"name\":\"Dell Monitor\",\"status\":\"Attention\"}],\"exceptions\":[{\"asset_id\":39,\"asset_tag\":\"ps39-mon-108-02\",\"details\":\"Marked missing during audit\",\"name\":\"Dell Monitor\",\"type\":\"other\"},{\"asset_id\":26,\"asset_tag\":\"cbl-hdmi-002\",\"details\":\"Marked missing during audit\",\"name\":\"Floating HDMI Cables\",\"type\":\"other\"}],\"missing_assets\":[{\"asset_id\":39,\"asset_tag\":\"ps39-mon-108-02\",\"name\":\"Dell Monitor\"}]}',3,5,NULL,NULL); +/*!40000 ALTER TABLE `physical_audits` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER calculate_assets_expected +BEFORE INSERT ON physical_audits +FOR EACH ROW +BEGIN + DECLARE expected_count INT DEFAULT 0; + DECLARE v_timeout INT; + + -- For full-zone audits, calculate expected assets in the zone + IF NEW.audit_type = 'full-zone' AND NEW.zone_id IS NOT NULL THEN + SELECT COUNT(*) INTO expected_count + FROM assets + WHERE zone_id = NEW.zone_id + AND status NOT IN ('Missing', 'Retired'); + + SET NEW.assets_expected = expected_count; + END IF; + + -- Set timeout from zone settings if not specified + IF NEW.timeout_minutes IS NULL AND NEW.zone_id IS NOT NULL THEN + SELECT audit_timeout_minutes INTO v_timeout + FROM zones + WHERE id = NEW.zone_id + LIMIT 1; + + IF v_timeout IS NOT NULL THEN + SET NEW.timeout_minutes = v_timeout; + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_audit_issues +AFTER UPDATE ON physical_audits +FOR EACH ROW +BEGIN + DECLARE missing_count INT DEFAULT 0; + DECLARE zone_name VARCHAR(200); + + -- Only process when audit status changes to completed states + IF OLD.status = 'in-progress' AND NEW.status IN ('all-good', 'attention', 'timeout') THEN + + -- Get zone name for reporting + IF NEW.zone_id IS NOT NULL THEN + SELECT zone_name INTO zone_name FROM zones WHERE id = NEW.zone_id; + END IF; + + -- For full-zone audits, check for missing assets + IF NEW.audit_type = 'full-zone' AND NEW.assets_expected IS NOT NULL THEN + SET missing_count = GREATEST(0, NEW.assets_expected - NEW.assets_found); + END IF; + + -- Create issue for missing assets + IF missing_count > 0 THEN + INSERT INTO issue_tracker ( + issue_type, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date, notes + ) + VALUES ( + 'System Issue', + CONCAT('Audit: Missing Assets in ', COALESCE(zone_name, 'Unknown Zone')), + CONCAT('Full zone audit completed with ', missing_count, ' missing assets. Expected: ', NEW.assets_expected, ', Found: ', NEW.assets_found, '. Audit ID: ', NEW.id), + CASE WHEN missing_count >= 5 THEN 'Critical' WHEN missing_count >= 2 THEN 'High' ELSE 'Medium' END, + 'High', 'Open', + NEW.started_by, TRUE, 'AUDIT_MISSING_ASSETS', NOW(), + CONCAT('Physical Audit ID: ', NEW.id, ' in zone: ', COALESCE(zone_name, NEW.zone_id)) + ); + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +DROP TABLE IF EXISTS `print_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `print_history` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `entity_type` enum('Asset','Template','Borrower','Zone','Report','Custom') NOT NULL, + `entity_id` int(11) DEFAULT NULL COMMENT 'ID of the asset/template/borrower/zone (NULL for reports)', + `label_template_id` int(11) DEFAULT NULL, + `printer_id` int(11) DEFAULT NULL, + `quantity` int(11) DEFAULT 1, + `print_status` enum('Success','Failed','Cancelled','Queued') NOT NULL, + `error_message` text DEFAULT NULL, + `rendered_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'The actual data that was sent to printer (for debugging)' CHECK (json_valid(`rendered_data`)), + `printed_at` timestamp NULL DEFAULT current_timestamp(), + `printed_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `label_template_id` (`label_template_id`), + KEY `idx_entity` (`entity_type`,`entity_id`), + KEY `idx_printed_at` (`printed_at`), + KEY `idx_printed_by` (`printed_by`), + KEY `idx_printer` (`printer_id`), + KEY `idx_status` (`print_status`), + CONSTRAINT `print_history_ibfk_1` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL, + CONSTRAINT `print_history_ibfk_2` FOREIGN KEY (`printer_id`) REFERENCES `printer_settings` (`id`) ON DELETE SET NULL, + CONSTRAINT `print_history_ibfk_3` FOREIGN KEY (`printed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `print_history` WRITE; +/*!40000 ALTER TABLE `print_history` DISABLE KEYS */; +/*!40000 ALTER TABLE `print_history` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `printer_settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `printer_settings` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `printer_name` varchar(200) NOT NULL, + `description` text DEFAULT NULL, + `log` tinyint(1) DEFAULT 1 COMMENT 'Log all print jobs to this printer', + `can_be_used_for_reports` tinyint(1) DEFAULT 0 COMMENT 'Can this printer be used for printing reports', + `min_powerlevel_to_use` int(11) NOT NULL DEFAULT 75 COMMENT 'Minimum role power level required to use this printer', + `printer_plugin` enum('Ptouch','Brother','Zebra','System','PDF','Network','Custom') NOT NULL COMMENT 'Which printer plugin the client should send printer_settings to', + `printer_settings` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Printer-specific settings: connection, paper size, DPI, margins, etc.' CHECK (json_valid(`printer_settings`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_printer_name` (`printer_name`), + KEY `idx_printer_plugin` (`printer_plugin`), + KEY `idx_min_powerlevel` (`min_powerlevel_to_use`), + KEY `idx_can_reports` (`can_be_used_for_reports`), + CONSTRAINT `printer_settings_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `printer_settings_ibfk_2` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `CONSTRAINT_1` CHECK (`min_powerlevel_to_use` >= 1 and `min_powerlevel_to_use` <= 100) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `printer_settings` WRITE; +/*!40000 ALTER TABLE `printer_settings` DISABLE KEYS */; +INSERT INTO `printer_settings` VALUES (1,'PDF Export','Export labels as PDF files for manual printing',1,1,1,'PDF','{\n \"output_path\": \"/tmp/beepzone_labels/\",\n \"paper_size\": \"A4\",\n \"dpi\": 300,\n \"color_mode\": \"color\",\n \"auto_open\": false,\n \"filename_pattern\": \"label_{{entity_type}}_{{entity_id}}_{{timestamp}}.pdf\"\n}','2025-11-07 17:35:20',NULL,'2025-11-07 17:35:20',NULL),(2,'Brother P-Touch Label Printer','Standard Brother label printer',1,0,25,'Ptouch','{\n \"connection_type\": \"USB\",\n \"device_path\": \"/dev/usb/lp0\",\n \"tape_widths\": [\"6mm\", \"9mm\", \"12mm\", \"18mm\", \"24mm\"],\n \"default_tape_width\": \"12mm\",\n \"print_quality\": \"normal\",\n \"auto_cut\": true\n}','2025-11-07 17:35:20',NULL,'2025-11-07 17:35:20',NULL),(4,'System_ZQ510_2x3inch','Zebra ZQ510 Hooked Up via USB to Computer',0,0,75,'System','{\"auto_scale\":false,\"center\":\"both\",\"center_disabled\":true,\"color\":false,\"copies\":1,\"custom_height_mm\":76.2,\"custom_width_mm\":50.8,\"duplex\":false,\"margins\":{\"bottom\":5.0,\"left\":5.0,\"right\":5.0,\"top\":5.0},\"orientation\":\"landscape\",\"paper_size\":\"Custom\",\"printer_name\":\"ZQ510\",\"quality\":\"high\",\"scale\":1.0,\"show_dialog_if_unfound\":true}','2025-11-09 12:01:22',NULL,'2025-12-11 11:39:06',NULL),(5,'Laser Printer at home','Thierrys laser printer at home lol',0,1,75,'System','{\"auto_scale\":false,\"center\":\"both\",\"center_disabled\":true,\"color\":true,\"compatibility_mode\":true,\"copies\":1,\"custom_height_mm\":null,\"custom_width_mm\":null,\"duplex\":false,\"margins\":{\"bottom\":20.0,\"left\":20.0,\"right\":20.0,\"top\":20.0},\"orientation\":\"landscape\",\"paper_size\":\"A4\",\"printer_name\":\"ps450-print-105-01\",\"quality\":\"high\",\"scale\":1.0,\"show_dialog_if_unfound\":true}','2025-11-09 20:22:24',NULL,'2025-12-11 12:47:11',NULL),(6,'System_TD-4550D-4xXinch','Brother TD-4550D Printer Hooked up via USB',0,0,75,'System','{\"auto_scale\":false,\"center\":\"both\",\"center_disabled\":false,\"color\":false,\"copies\":1,\"custom_height_mm\":50,\"custom_width_mm\":103,\"duplex\":false,\"margins\":{\"bottom\":5.0,\"left\":5.0,\"right\":5.0,\"top\":5.0},\"orientation\":\"portrait\",\"paper_size\":\"Custom\",\"printer_name\":\"Brother TD-4550DNWB\",\"quality\":\"high\",\"scale\":1.0,\"show_dialog_if_unfound\":true}','2025-12-11 10:14:21',NULL,'2025-12-11 11:25:09',NULL),(7,'System_TD-4550D-Landscapetest','Brother TD-4550D Printer Hooked up via USB but using Extremle Large Landscape Labels',0,0,75,'System','{\"auto_scale\":false,\"center\":\"both\",\"center_disabled\":false,\"color\":false,\"copies\":1,\"custom_height_mm\":200,\"custom_width_mm\":103,\"duplex\":false,\"margins\":{\"bottom\":5.0,\"left\":5.0,\"right\":5.0,\"top\":5.0},\"orientation\":\"landscape\",\"paper_size\":\"Custom\",\"printer_name\":\"Brother TD-4550DNWB\",\"quality\":\"high\",\"scale\":1.0,\"show_dialog_if_unfound\":true}','2025-12-11 10:57:20',NULL,'2025-12-11 11:35:52',NULL),(8,'System_ZQ510_2x3-Portraittest','Zebra ZQ510 Hooked Up via USB to Computer',0,0,75,'System','{\"auto_scale\":false,\"center\":\"both\",\"center_disabled\":true,\"color\":false,\"copies\":1,\"custom_height_mm\":76.2,\"custom_width_mm\":50.8,\"duplex\":false,\"margins\":{\"bottom\":5.0,\"left\":5.0,\"right\":5.0,\"top\":5.0},\"orientation\":\"portrait\",\"paper_size\":\"Custom\",\"printer_name\":\"ZQ510\",\"quality\":\"high\",\"scale\":1.0,\"show_dialog_if_unfound\":true}','2025-12-11 11:54:23',NULL,'2025-12-11 11:54:23',NULL); +/*!40000 ALTER TABLE `printer_settings` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `roles` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL, + `power` int(11) NOT NULL CHECK (`power` >= 1 and `power` <= 100), + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `roles` WRITE; +/*!40000 ALTER TABLE `roles` DISABLE KEYS */; +INSERT INTO `roles` VALUES (1,'Administrator',100,'2025-10-20 11:43:38'),(2,'Manager',75,'2025-10-20 11:43:38'),(3,'Staff',50,'2025-10-20 11:43:38'),(4,'User',25,'2025-10-20 11:43:38'); +/*!40000 ALTER TABLE `roles` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `suppliers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `suppliers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `contact` varchar(200) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `phone` varchar(50) DEFAULT NULL, + `website` varchar(255) DEFAULT NULL, + `notes` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `suppliers` WRITE; +/*!40000 ALTER TABLE `suppliers` DISABLE KEYS */; +INSERT INTO `suppliers` VALUES (1,'TechSupply Co','Sales Team','sales@techsupply.com','+1-555-0100',NULL,NULL,'2025-10-20 11:43:44'),(3,'Digitect',NULL,'support@digitec.ch',NULL,'https://digitec.ch/',NULL,'2025-11-14 21:31:48'); +/*!40000 ALTER TABLE `suppliers` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `templates`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `templates` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `template_code` varchar(50) DEFAULT NULL, + `asset_tag_generation_string` varchar(500) DEFAULT NULL, + `description` text DEFAULT NULL, + `active` tinyint(1) DEFAULT 1, + `asset_type` enum('N','B','L','C') DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `category_id` int(11) DEFAULT NULL, + `manufacturer` varchar(200) DEFAULT NULL, + `model` varchar(200) DEFAULT NULL, + `zone_id` int(11) DEFAULT NULL, + `zone_plus` enum('Floating Local','Floating Global','Clarify') DEFAULT NULL, + `zone_note` text DEFAULT NULL, + `status` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT NULL, + `price` decimal(12,2) DEFAULT NULL CHECK (`price` is null or `price` >= 0), + `purchase_date` date DEFAULT NULL COMMENT 'Default purchase date for assets created from this template', + `purchase_date_now` tinyint(1) DEFAULT 0 COMMENT 'Auto-set purchase date to current date when creating assets', + `warranty_until` date DEFAULT NULL, + `warranty_auto` tinyint(1) DEFAULT 0 COMMENT 'Auto-calculate warranty_until from purchase_date', + `warranty_auto_amount` int(11) DEFAULT NULL COMMENT 'Number of days/years for warranty calculation', + `warranty_auto_unit` enum('days','years') DEFAULT 'years' COMMENT 'Unit for warranty auto-calculation', + `expiry_date` date DEFAULT NULL, + `expiry_auto` tinyint(1) DEFAULT 0 COMMENT 'Auto-calculate expiry_date from purchase_date', + `expiry_auto_amount` int(11) DEFAULT NULL COMMENT 'Number of days/years for expiry calculation', + `expiry_auto_unit` enum('days','years') DEFAULT 'years' COMMENT 'Unit for expiry auto-calculation', + `quantity_total` int(11) DEFAULT NULL, + `quantity_used` int(11) DEFAULT NULL, + `supplier_id` int(11) DEFAULT NULL, + `lendable` tinyint(1) DEFAULT NULL, + `lending_status` enum('Available','Borrowed','Overdue','Deployed','Illegally Handed Out','Stolen') DEFAULT 'Available' COMMENT 'Default lending status for assets created from this template', + `minimum_role_for_lending` int(11) DEFAULT NULL, + `audit_task_id` int(11) DEFAULT NULL, + `label_template_id` int(11) DEFAULT NULL, + `no_scan` enum('Yes','Ask','No') DEFAULT NULL, + `notes` text DEFAULT NULL, + `additional_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`additional_fields`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `template_code` (`template_code`), + KEY `category_id` (`category_id`), + KEY `zone_id` (`zone_id`), + KEY `supplier_id` (`supplier_id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `idx_template_code` (`template_code`), + KEY `idx_label_template` (`label_template_id`), + KEY `idx_asset_tag_generation` (`asset_tag_generation_string`), + CONSTRAINT `fk_template_label_template` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_2` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_3` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_4` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `templates` WRITE; +/*!40000 ALTER TABLE `templates` DISABLE KEYS */; +INSERT INTO `templates` VALUES (1,'TEST',NULL,NULL,1,'N','Test',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,NULL,0,NULL,'years',NULL,0,NULL,'years',NULL,NULL,NULL,NULL,'Available',NULL,NULL,NULL,'No',NULL,NULL,'2025-11-10 00:54:59'),(2,'CTSBRR2','{BUILDINGCODE}-{CATEGORYCODE}-{FLOORCODE}{ROOMCODE}-{ZONEASC}','Cunt',1,'N','CTouch Smartboard Riva R2',7,'Cunt Touch','Riva R2',NULL,NULL,NULL,'Good',123123.00,NULL,1,NULL,1,2,'years',NULL,0,NULL,'years',NULL,NULL,1,NULL,'Available',NULL,NULL,5,'No',NULL,NULL,'2025-11-10 00:56:35'),(3,'hdmi','{CATEGORYCODE}-hdmi-{GLOBALASC}','Regular HDMI Cable 1-3 Meters',1,'N','Lendable HDMI Cable',9,NULL,NULL,11,NULL,NULL,'Good',20.00,NULL,1,NULL,0,NULL,'years',NULL,0,NULL,'years',NULL,NULL,NULL,1,'Available',NULL,NULL,5,'Yes',NULL,NULL,'2025-11-13 15:29:34'),(4,'mon','{BUILDINGCODE}-{CATEGORYCODE}-{FLOORCODE}{ROOMCODE}-{ZONEASC}','Generic Monitor',1,'N','Generic Monitor',10,NULL,NULL,NULL,NULL,NULL,'Good',NULL,NULL,1,NULL,1,2,'years',NULL,0,NULL,'years',NULL,NULL,NULL,0,NULL,NULL,NULL,7,'No',NULL,NULL,'2025-11-13 18:39:13'); +/*!40000 ALTER TABLE `templates` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `username` varchar(100) NOT NULL, + `password` varchar(255) NOT NULL, + `pin_code` varchar(8) DEFAULT NULL, + `login_string` varchar(255) DEFAULT NULL, + `role_id` int(11) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `phone` varchar(50) DEFAULT NULL, + `notes` text DEFAULT NULL, + `active` tinyint(1) DEFAULT 1, + `last_login_date` datetime DEFAULT NULL, + `created_date` timestamp NULL DEFAULT current_timestamp(), + `password_reset_token` varchar(255) DEFAULT NULL, + `password_reset_expiry` datetime DEFAULT NULL, + `preferences` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'User personalization settings: common (all clients) + client-specific (web, mobile, desktop)' CHECK (json_valid(`preferences`)), + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + KEY `role_id` (`role_id`), + KEY `idx_username` (`username`), + KEY `idx_login_string` (`login_string`), + CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; +INSERT INTO `users` VALUES (1,'Adminier','admin','$2a$12$gZiN2xX8ZdkmlInWIw/CM.OC76XnepAQLCHghCx9hKYSgdZdkxDjW',NULL,NULL,1,'admin@beepzone.local',NULL,NULL,1,'2025-12-12 20:46:53','2025-10-20 11:43:38',NULL,NULL,NULL),(2,'Sarah Manager','manager1','$2a$12$1IZfvE1ocoxSp4z6N1qy4.lbGquGx9mecBtVWmmUa01wBgrAfCWf.','1234','RFID_MGR_001',2,'manager@test.local',NULL,NULL,1,'2025-10-20 11:44:12','2025-10-20 11:43:39',NULL,NULL,NULL),(3,'John Staff','staff1','$2a$12$1IZfvE1ocoxSp4z6N1qy4.lbGquGx9mecBtVWmmUa01wBgrAfCWf.','5678','RFID_STAFF_001',3,'staff@test.local',NULL,NULL,1,'2025-10-20 11:44:14','2025-10-20 11:43:40',NULL,NULL,'{\"common\":{\"language\":\"fr\"},\"web\":{\"theme\":\"light\"}}'),(4,'Emily Student','student1','$2a$12$1IZfvE1ocoxSp4z6N1qy4.lbGquGx9mecBtVWmmUa01wBgrAfCWf.','9999','RFID_STUDENT_001',4,'student@test.local',NULL,NULL,1,'2025-10-20 11:43:42','2025-10-20 11:43:41',NULL,NULL,NULL); +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; +DROP TABLE IF EXISTS `zones`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `zones` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `zone_name` varchar(200) NOT NULL, + `zone_notes` text DEFAULT NULL, + `zone_type` enum('Building','Floor','Room','Storage Area') NOT NULL, + `zone_code` varchar(50) DEFAULT NULL, + `mini_code` varchar(50) DEFAULT NULL, + `parent_id` int(11) DEFAULT NULL, + `include_in_parent` tinyint(1) DEFAULT 1, + `audit_timeout_minutes` int(11) DEFAULT 60 COMMENT 'Audit timeout in minutes for this zone', + PRIMARY KEY (`id`), + KEY `idx_parent` (`parent_id`), + KEY `idx_type` (`zone_type`), + CONSTRAINT `zones_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `zones` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +LOCK TABLES `zones` WRITE; +/*!40000 ALTER TABLE `zones` DISABLE KEYS */; +INSERT INTO `zones` VALUES (9,'Plattenstrasse 39',NULL,'Building','ps39','ps39',NULL,0,120),(10,'1st Floor',NULL,'Floor','PS39-1','1',9,1,60),(11,'Buero IT und so ',NULL,'Room','PS39-108','08',10,0,60),(12,'Ausleihschrank',NULL,'Storage Area','PS39-108AS','AS',11,1,60),(13,'Server Raum',NULL,'Room','ps39-109','09',10,1,60); +/*!40000 ALTER TABLE `zones` ENABLE KEYS */; +UNLOCK TABLES; +-- +-- WARNING: can't read the INFORMATION_SCHEMA.libraries table. It's most probably an old server 12.0.2-MariaDB-ubu2404. +-- + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/backend/database/dev/beepzone-schema-consolidated-backup.sql b/backend/database/dev/beepzone-schema-consolidated-backup.sql new file mode 100644 index 0000000..e91043c --- /dev/null +++ b/backend/database/dev/beepzone-schema-consolidated-backup.sql @@ -0,0 +1,1882 @@ +-- BeepZone Database Schema v0.0.8 (Consolidated) +-- MariaDB/MySQL Compatible +-- Created: 2025-12-13 +-- Includes: Complete schema with triggers, asset change logging, printing, templates, zones +-- +-- AUTO-POPULATION TRIGGERS: +-- The following fields are auto-populated from @current_user_id if not provided: +-- • assets.created_by (on INSERT) +-- • assets.last_modified_by (on UPDATE) +-- • borrowers.added_by (on INSERT) +-- • borrowers.last_unban_by (on UPDATE when unbanning) +-- • lending_history.checked_out_by (on INSERT) +-- • lending_history.checked_in_by (on UPDATE when returning) +-- • issue_tracker.reported_by (on INSERT) +-- • physical_audit_logs.audited_by (on INSERT) +-- Your API proxy should set @current_user_id before executing queries. + +-- Drop tables if they exist (in reverse order of dependencies) +DROP TABLE IF EXISTS print_history; +DROP TABLE IF EXISTS issue_tracker_change_log; +DROP TABLE IF EXISTS asset_change_log; +DROP TABLE IF EXISTS issue_tracker; +DROP TABLE IF EXISTS physical_audit_logs; +DROP TABLE IF EXISTS physical_audits; +DROP TABLE IF EXISTS lending_history; +DROP TABLE IF EXISTS templates; +DROP TABLE IF EXISTS assets; +DROP TABLE IF EXISTS borrowers; +DROP TABLE IF EXISTS audit_tasks; +DROP TABLE IF EXISTS suppliers; +DROP TABLE IF EXISTS zones; +DROP TABLE IF EXISTS categories; +DROP TABLE IF EXISTS label_templates; +DROP TABLE IF EXISTS printer_settings; +DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS roles; + +-- ============================================ +-- Roles Table +-- ============================================ +CREATE TABLE roles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + power INT NOT NULL CHECK (power >= 1 AND power <= 100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Users Table +-- ============================================ +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(200) NOT NULL, + username VARCHAR(100) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + pin_code VARCHAR(8) NULL, + login_string VARCHAR(255) NULL, + role_id INT NOT NULL, + email VARCHAR(255) NULL, + phone VARCHAR(50) NULL, + notes TEXT NULL, + active BOOLEAN DEFAULT TRUE, + last_login_date DATETIME NULL, + created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + password_reset_token VARCHAR(255) NULL, + password_reset_expiry DATETIME NULL, + FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT, + INDEX idx_username (username), + INDEX idx_login_string (login_string) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Categories Table +-- ============================================ +CREATE TABLE categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + category_name VARCHAR(200) NOT NULL, + category_description TEXT NULL, + parent_id INT NULL, + category_code VARCHAR(50) NULL, + FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE RESTRICT, + INDEX idx_parent (parent_id), + INDEX idx_code (category_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Zones Table +-- ============================================ +CREATE TABLE zones ( + id INT AUTO_INCREMENT PRIMARY KEY, + zone_name VARCHAR(200) NOT NULL, + zone_notes TEXT NULL, + zone_type ENUM('Building', 'Floor', 'Room', 'Storage Area') NOT NULL, + zone_code VARCHAR(50) NOT NULL COMMENT 'Full hierarchical code (e.g., PS52-1-108)', + mini_code VARCHAR(50) NOT NULL COMMENT 'Local short code for this node (e.g., PS52, 1, 108)', + parent_id INT NULL, + include_in_parent BOOLEAN DEFAULT TRUE, + audit_timeout_minutes INT DEFAULT 60 COMMENT 'Audit timeout in minutes for this zone', + FOREIGN KEY (parent_id) REFERENCES zones(id) ON DELETE RESTRICT, + INDEX idx_parent (parent_id), + INDEX idx_type (zone_type), + UNIQUE INDEX uq_zone_code (zone_code), + INDEX idx_parent_type_mini (parent_id, zone_type, mini_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Suppliers Table +-- ============================================ +CREATE TABLE suppliers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(200) NOT NULL, + contact VARCHAR(200) NULL, + email VARCHAR(255) NULL, + phone VARCHAR(50) NULL, + website VARCHAR(255) NULL, + notes TEXT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Audit Tasks Table +-- ============================================ +CREATE TABLE audit_tasks ( + id INT AUTO_INCREMENT PRIMARY KEY, + task_name VARCHAR(200) NOT NULL, + json_sequence JSON NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Borrowers Table +-- ============================================ +CREATE TABLE borrowers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(200) NOT NULL, + email VARCHAR(255) NULL, + phone_number VARCHAR(50) NULL, + class_name VARCHAR(100) NULL, + role VARCHAR(100) NULL, + notes TEXT NULL, + added_by INT NOT NULL, + added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + banned BOOLEAN DEFAULT FALSE, + unban_fine DECIMAL(10, 2) DEFAULT 0.00, + last_unban_by INT NULL, + last_unban_date DATE NULL, + FOREIGN KEY (added_by) REFERENCES users(id) ON DELETE RESTRICT, + FOREIGN KEY (last_unban_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_name (name), + INDEX idx_banned (banned) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Assets Table (Sexy Edition v2) +-- ============================================ +CREATE TABLE assets ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_tag VARCHAR(200) NULL UNIQUE, + asset_numeric_id INT NOT NULL UNIQUE CHECK (asset_numeric_id BETWEEN 10000000 AND 99999999), + asset_type ENUM('N', 'B', 'L', 'C') NOT NULL, + name VARCHAR(255) NULL, + category_id INT NULL, + manufacturer VARCHAR(200) NULL, + model VARCHAR(200) NULL, + serial_number VARCHAR(200) NULL, + zone_id INT NULL, + zone_plus ENUM('Floating Local', 'Floating Global', 'Clarify') NULL, + zone_note TEXT NULL, + status ENUM('Good', 'Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'In Transit', 'Expired', 'Unmanaged') DEFAULT 'Good', + last_audit DATE NULL, + last_audit_status VARCHAR(100) NULL, + price DECIMAL(12, 2) NULL CHECK (price IS NULL OR price >= 0), + purchase_date DATE NULL, + warranty_until DATE NULL, + expiry_date DATE NULL, + quantity_available INT NULL, + quantity_total INT NULL, + quantity_used INT DEFAULT 0, + supplier_id INT NULL, + lendable BOOLEAN DEFAULT FALSE, + minimum_role_for_lending INT DEFAULT 1 CHECK (minimum_role_for_lending >= 1 AND minimum_role_for_lending <= 100), + lending_status ENUM('Available', 'Deployed', 'Borrowed', 'Overdue', 'Illegally Handed Out', 'Stolen') NULL, + current_borrower_id INT NULL, + due_date DATE NULL, + previous_borrower_id INT NULL, + audit_task_id INT NULL, + label_template_id INT NULL COMMENT 'Label template to use for this asset', + no_scan ENUM('Yes', 'Ask', 'No') DEFAULT 'No', + notes TEXT NULL, + additional_fields JSON NULL, + file_attachment MEDIUMBLOB NULL, + created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INT NULL, + last_modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + last_modified_by INT NULL, + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE RESTRICT, + FOREIGN KEY (zone_id) REFERENCES zones(id) ON DELETE RESTRICT, + FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL, + FOREIGN KEY (current_borrower_id) REFERENCES borrowers(id) ON DELETE SET NULL, + FOREIGN KEY (previous_borrower_id) REFERENCES borrowers(id) ON DELETE SET NULL, + FOREIGN KEY (audit_task_id) REFERENCES audit_tasks(id) ON DELETE SET NULL, + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (last_modified_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_asset_tag (asset_tag), + INDEX idx_asset_numeric (asset_numeric_id), + INDEX idx_type (asset_type), + INDEX idx_status (status), + INDEX idx_zone (zone_id), + INDEX idx_category (category_id), + INDEX idx_lendable (lendable), + INDEX idx_lending_status (lending_status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Templates Table (with new sexy columns) +-- ============================================ +CREATE TABLE templates ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_code VARCHAR(50) NULL UNIQUE, + asset_tag_generation_string VARCHAR(500) NULL, + description TEXT NULL, + active BOOLEAN DEFAULT TRUE, + asset_type ENUM('N', 'B', 'L', 'C') NULL, + name VARCHAR(255) NULL, + category_id INT NULL, + manufacturer VARCHAR(200) NULL, + model VARCHAR(200) NULL, + zone_id INT NULL, + zone_plus ENUM('Floating Local', 'Floating Global', 'Clarify') NULL, + zone_note TEXT NULL, + status ENUM('Good', 'Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'In Transit', 'Expired', 'Unmanaged') NULL, + price DECIMAL(12, 2) NULL CHECK (price IS NULL OR price >= 0), + purchase_date DATE NULL COMMENT 'Default purchase date for assets created from this template', + purchase_date_now BOOLEAN DEFAULT FALSE COMMENT 'Auto-set purchase date to current date when creating assets', + warranty_until DATE NULL, + warranty_auto BOOLEAN DEFAULT FALSE COMMENT 'Auto-calculate warranty_until from purchase_date', + warranty_auto_amount INT NULL COMMENT 'Number of days/years for warranty calculation', + warranty_auto_unit ENUM('days', 'years') DEFAULT 'years' COMMENT 'Unit for warranty auto-calculation', + expiry_date DATE NULL, + expiry_auto BOOLEAN DEFAULT FALSE COMMENT 'Auto-calculate expiry_date from purchase_date', + expiry_auto_amount INT NULL COMMENT 'Number of days/years for expiry calculation', + expiry_auto_unit ENUM('days', 'years') DEFAULT 'years' COMMENT 'Unit for expiry auto-calculation', + quantity_total INT NULL, + quantity_used INT NULL, + supplier_id INT NULL, + lendable BOOLEAN NULL, + lending_status ENUM('Available', 'Borrowed', 'Overdue', 'Deployed', 'Illegally Handed Out', 'Stolen') DEFAULT 'Available' COMMENT 'Default lending status for assets created from this template', + minimum_role_for_lending INT NULL, + audit_task_id INT NULL, + label_template_id INT NULL COMMENT 'Default label template for assets created from this template', + no_scan ENUM('Yes', 'Ask', 'No') NULL, + notes TEXT NULL, + additional_fields JSON NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL, + FOREIGN KEY (zone_id) REFERENCES zones(id) ON DELETE SET NULL, + FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL, + FOREIGN KEY (audit_task_id) REFERENCES audit_tasks(id) ON DELETE SET NULL, + INDEX idx_template_code (template_code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Lending History Table +-- ============================================ +CREATE TABLE lending_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + asset_id INT NOT NULL, + borrower_id INT NOT NULL, + checkout_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + due_date DATE NULL, + return_date DATETIME NULL, + checked_out_by INT NULL, + checked_in_by INT NULL, + notes TEXT NULL, + FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE, + FOREIGN KEY (borrower_id) REFERENCES borrowers(id) ON DELETE RESTRICT, + FOREIGN KEY (checked_out_by) REFERENCES users(id) ON DELETE RESTRICT, + FOREIGN KEY (checked_in_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_asset (asset_id), + INDEX idx_borrower (borrower_id), + INDEX idx_checkout_date (checkout_date), + INDEX idx_return_date (return_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Physical Audits Table +-- ============================================ +CREATE TABLE physical_audits ( + id INT AUTO_INCREMENT PRIMARY KEY, + audit_type ENUM('full-zone', 'spot-check') NOT NULL, + zone_id INT NULL COMMENT 'Zone being audited (NULL for spot-check audits)', + audit_name VARCHAR(255) NULL COMMENT 'Custom name for the audit session', + started_by INT NOT NULL, + started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + completed_at DATETIME NULL, + status ENUM('in-progress', 'all-good', 'timeout', 'attention', 'cancelled') DEFAULT 'in-progress', + timeout_minutes INT NULL COMMENT 'Timeout setting used for this audit', + issues_found JSON NULL COMMENT 'Array of issues: missing_assets, moved_assets, damaged_assets, etc.', + assets_expected INT NULL COMMENT 'Total assets expected to be found in zone', + assets_found INT DEFAULT 0 COMMENT 'Total assets actually found and scanned', + notes TEXT NULL, + cancelled_reason TEXT NULL, + FOREIGN KEY (zone_id) REFERENCES zones(id) ON DELETE RESTRICT, + FOREIGN KEY (started_by) REFERENCES users(id) ON DELETE RESTRICT, + INDEX idx_audit_type (audit_type), + INDEX idx_zone (zone_id), + INDEX idx_status (status), + INDEX idx_started_at (started_at), + INDEX idx_started_by (started_by) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Physical Audit Logs Table +-- ============================================ +CREATE TABLE physical_audit_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + physical_audit_id INT NOT NULL COMMENT 'Reference to the audit session', + asset_id INT NOT NULL, + audit_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + audited_by INT NOT NULL, + status_found ENUM('Good', 'Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'In Transit', 'Expired', 'Unmanaged') DEFAULT 'Good', + audit_task_id INT NULL COMMENT 'Which audit task was run on this asset', + audit_task_responses JSON NULL COMMENT 'User responses to the JSON sequence questions', + exception_type ENUM('wrong-zone', 'unexpected-asset', 'damaged', 'missing-label', 'other') NULL, + exception_details TEXT NULL COMMENT 'Details about the exception found', + found_in_zone_id INT NULL COMMENT 'Which zone the asset was actually found in (if different from expected)', + auditor_action ENUM('physical-move', 'virtual-update', 'no-action') NULL COMMENT 'What the auditor chose to do about wrong-zone assets', + notes TEXT NULL, + FOREIGN KEY (physical_audit_id) REFERENCES physical_audits(id) ON DELETE CASCADE, + FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE, + FOREIGN KEY (audited_by) REFERENCES users(id) ON DELETE RESTRICT, + FOREIGN KEY (audit_task_id) REFERENCES audit_tasks(id) ON DELETE SET NULL, + FOREIGN KEY (found_in_zone_id) REFERENCES zones(id) ON DELETE SET NULL, + INDEX idx_physical_audit (physical_audit_id), + INDEX idx_asset (asset_id), + INDEX idx_audit_date (audit_date), + INDEX idx_audited_by (audited_by), + INDEX idx_status_found (status_found), + INDEX idx_exception_type (exception_type) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Issue Tracker Table +-- ============================================ +CREATE TABLE issue_tracker ( + id INT AUTO_INCREMENT PRIMARY KEY, + issue_type ENUM('Asset Issue', 'Borrower Issue', 'System Issue', 'Maintenance', 'Other') NOT NULL, + asset_id INT NULL, + borrower_id INT NULL, + title VARCHAR(255) NOT NULL, + description TEXT NOT NULL, + severity ENUM('Critical', 'High', 'Medium', 'Low') NULL, + priority ENUM('Urgent', 'High', 'Normal', 'Low') DEFAULT 'Normal', + status ENUM('Open', 'In Progress', 'Resolved', 'Closed', 'On Hold') DEFAULT 'Open', + solution ENUM('Fixed', 'Replaced', 'Clarify', 'No Action Needed', 'Deferred', 'Items Returned', 'Automatically Fixed') NULL, + solution_plus TEXT NULL, + replacement_asset_id INT NULL, + reported_by INT NOT NULL, + assigned_to INT NULL, + resolved_by INT NULL, + cost DECIMAL(10, 2) NULL, + created_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + resolved_date DATETIME NULL, + notes TEXT NULL, + auto_detected BOOLEAN DEFAULT FALSE, + detection_trigger VARCHAR(100) NULL, + FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE, + FOREIGN KEY (borrower_id) REFERENCES borrowers(id) ON DELETE CASCADE, + FOREIGN KEY (replacement_asset_id) REFERENCES assets(id) ON DELETE SET NULL, + FOREIGN KEY (reported_by) REFERENCES users(id) ON DELETE RESTRICT, + FOREIGN KEY (assigned_to) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (resolved_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_issue_type (issue_type), + INDEX idx_asset (asset_id), + INDEX idx_borrower (borrower_id), + INDEX idx_severity (severity), + INDEX idx_status (status), + INDEX idx_created_date (created_date), + INDEX idx_auto_detected (auto_detected) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Issue Tracker Change Log Table +-- ============================================ +CREATE TABLE issue_tracker_change_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + issue_id INT NOT NULL, + change_type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, + changed_fields JSON NULL, + old_values JSON NULL, + new_values JSON NULL, + changed_by INT NULL, + change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (issue_id) REFERENCES issue_tracker(id) ON DELETE CASCADE, + FOREIGN KEY (changed_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_issue (issue_id), + INDEX idx_change_type (change_type), + INDEX idx_change_date (change_date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Asset Change Log Table +-- ============================================ +CREATE TABLE asset_change_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(50) NOT NULL, + action ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, + record_id INT NOT NULL, + changed_fields JSON NULL COMMENT 'Only fields that actually changed', + old_values JSON NULL, + new_values JSON NULL, + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changed_by_id INT NULL, + changed_by_username VARCHAR(100) NULL, + FOREIGN KEY (changed_by_id) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_table_action (table_name, action), + INDEX idx_timestamp (changed_at), + INDEX idx_record (record_id), + INDEX idx_user (changed_by_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Label Templates Table +-- ============================================ +CREATE TABLE label_templates ( + id INT AUTO_INCREMENT PRIMARY KEY, + template_code VARCHAR(100) NOT NULL UNIQUE COMMENT 'Unique code like "CABLE"', + template_name VARCHAR(200) NOT NULL COMMENT 'Human readable name', + layout_json JSON NOT NULL COMMENT 'Universal label design: graphics, auto-populated field placeholders, styling with space dimensions', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INT NULL, + last_modified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + last_modified_by INT NULL, + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (last_modified_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_template_code (template_code), + INDEX idx_template_name (template_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Printer Settings Table +-- ============================================ +CREATE TABLE printer_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + printer_name VARCHAR(200) NOT NULL, + description TEXT NULL, + log BOOLEAN DEFAULT TRUE COMMENT 'Log all print jobs to this printer', + can_be_used_for_reports BOOLEAN DEFAULT FALSE COMMENT 'Can this printer be used for printing reports', + min_powerlevel_to_use INT NOT NULL DEFAULT 75 COMMENT 'Minimum role power level required to use this printer', + printer_plugin ENUM('Ptouch', 'Brother', 'Zebra', 'System', 'PDF', 'Network', 'Custom') NOT NULL COMMENT 'Which printer plugin the client should send printer_settings to', + printer_settings JSON NOT NULL COMMENT 'Printer-specific settings: connection, paper size, DPI, margins, etc.', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INT NULL, + last_modified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + last_modified_by INT NULL, + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (last_modified_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_printer_name (printer_name), + INDEX idx_printer_plugin (printer_plugin), + INDEX idx_min_powerlevel (min_powerlevel_to_use), + INDEX idx_can_reports (can_be_used_for_reports), + CHECK (min_powerlevel_to_use >= 1 AND min_powerlevel_to_use <= 100) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- Print History Table (Labels & Reports) +-- ============================================ +CREATE TABLE print_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + entity_type ENUM('Asset', 'Template', 'Borrower', 'Zone', 'Report', 'Custom') NOT NULL, + entity_id INT NULL COMMENT 'ID of the asset/template/borrower/zone (NULL for reports)', + label_template_id INT NULL, + printer_id INT NULL, + quantity INT DEFAULT 1, + print_status ENUM('Success', 'Failed', 'Cancelled', 'Queued') NOT NULL, + error_message TEXT NULL, + rendered_data JSON NULL COMMENT 'The actual data that was sent to printer (for debugging)', + printed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + printed_by INT NULL, + FOREIGN KEY (label_template_id) REFERENCES label_templates(id) ON DELETE SET NULL, + FOREIGN KEY (printer_id) REFERENCES printer_settings(id) ON DELETE SET NULL, + FOREIGN KEY (printed_by) REFERENCES users(id) ON DELETE SET NULL, + INDEX idx_entity (entity_type, entity_id), + INDEX idx_printed_at (printed_at), + INDEX idx_printed_by (printed_by), + INDEX idx_printer (printer_id), + INDEX idx_status (print_status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; + +-- ============================================ +-- TRIGGERS FOR ASSETS TABLE +-- ============================================ + +DELIMITER // + +-- Trigger: Auto-populate created_by on INSERT +DROP TRIGGER IF EXISTS assets_before_insert_meta// +CREATE TRIGGER assets_before_insert_meta +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.created_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.created_by = @current_user_id; + END IF; +END// + +-- Trigger: Auto-update last_modified_date and last_modified_by +DROP TRIGGER IF EXISTS assets_before_update_meta// +CREATE TRIGGER assets_before_update_meta +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + SET NEW.last_modified_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.last_modified_by = @current_user_id; + END IF; +END// + +-- Trigger: Log INSERT operations (only non-NULL fields for efficiency) +DROP TRIGGER IF EXISTS assets_after_insert_log// +CREATE TRIGGER assets_after_insert_log +AFTER INSERT ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE set_fields_array JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields + SET set_fields_array = JSON_ARRAY(); + SET new_vals = JSON_OBJECT(); + + -- Always log these core fields + IF NEW.asset_tag IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_tag'); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF NEW.asset_numeric_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_numeric_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF NEW.asset_type IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_type'); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF NEW.name IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'name'); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF NEW.category_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'category_id'); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF NEW.manufacturer IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'manufacturer'); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF NEW.model IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'model'); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF NEW.serial_number IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'serial_number'); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF NEW.zone_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_id'); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF NEW.zone_plus IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_plus'); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF NEW.zone_note IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_note'); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF NEW.status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF NEW.last_audit IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit'); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF NEW.last_audit_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit_status'); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF NEW.price IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'price'); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF NEW.purchase_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'purchase_date'); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF NEW.warranty_until IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'warranty_until'); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF NEW.expiry_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'expiry_date'); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF NEW.quantity_available IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_available'); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF NEW.quantity_total IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_total'); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF NEW.quantity_used IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_used'); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF NEW.supplier_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'supplier_id'); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF NEW.lendable IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lendable'); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF NEW.minimum_role_for_lending IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'minimum_role_for_lending'); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF NEW.lending_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lending_status'); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF NEW.current_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'current_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF NEW.due_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'due_date'); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF NEW.previous_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'previous_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF NEW.audit_task_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'audit_task_id'); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF NEW.no_scan IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'no_scan'); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF NEW.notes IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'notes'); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF NEW.additional_fields IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'additional_fields'); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + IF NEW.created_by IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'created_by'); + SET new_vals = JSON_SET(new_vals, '$.created_by', NEW.created_by); + END IF; + + -- Log the INSERT with only the fields that were set + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'INSERT', + NEW.id, + set_fields_array, + new_vals, + @current_user_id, + username + ); +END// + +-- Trigger: Log UPDATE operations (only changed fields) +DROP TRIGGER IF EXISTS assets_after_update_log// +CREATE TRIGGER assets_after_update_log +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE changed_fields_array JSON; + DECLARE old_vals JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with changed fields + SET changed_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + SET new_vals = JSON_OBJECT(); + + IF OLD.asset_tag <=> NEW.asset_tag IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF OLD.asset_numeric_id <=> NEW.asset_numeric_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF OLD.asset_type <=> NEW.asset_type IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF OLD.name <=> NEW.name IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF OLD.category_id <=> NEW.category_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF OLD.manufacturer <=> NEW.manufacturer IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF OLD.model <=> NEW.model IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF OLD.serial_number <=> NEW.serial_number IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF OLD.zone_id <=> NEW.zone_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF OLD.zone_plus <=> NEW.zone_plus IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF OLD.zone_note <=> NEW.zone_note IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF OLD.last_audit <=> NEW.last_audit IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF OLD.last_audit_status <=> NEW.last_audit_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF OLD.price <=> NEW.price IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF OLD.purchase_date <=> NEW.purchase_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF OLD.warranty_until <=> NEW.warranty_until IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF OLD.expiry_date <=> NEW.expiry_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF OLD.quantity_available <=> NEW.quantity_available IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF OLD.quantity_total <=> NEW.quantity_total IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF OLD.quantity_used <=> NEW.quantity_used IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF OLD.supplier_id <=> NEW.supplier_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF OLD.lendable <=> NEW.lendable IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF OLD.minimum_role_for_lending <=> NEW.minimum_role_for_lending IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF OLD.lending_status <=> NEW.lending_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF OLD.current_borrower_id <=> NEW.current_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF OLD.due_date <=> NEW.due_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF OLD.previous_borrower_id <=> NEW.previous_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF OLD.audit_task_id <=> NEW.audit_task_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF OLD.no_scan <=> NEW.no_scan IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF OLD.notes <=> NEW.notes IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF OLD.additional_fields <=> NEW.additional_fields IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + -- Only log if there were actual changes (excluding auto-updated fields) + IF JSON_LENGTH(changed_fields_array) > 0 THEN + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'UPDATE', + NEW.id, + changed_fields_array, + old_vals, + new_vals, + @current_user_id, + username + ); + END IF; +END// + +-- Trigger: Log DELETE operations (only non-NULL fields for efficiency, but preserve all data for restore) +DROP TRIGGER IF EXISTS assets_after_delete_log// +CREATE TRIGGER assets_after_delete_log +AFTER DELETE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE deleted_fields_array JSON; + DECLARE old_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields (for restore capability) + SET deleted_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + + IF OLD.asset_tag IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + END IF; + + IF OLD.asset_numeric_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + END IF; + + IF OLD.asset_type IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + END IF; + + IF OLD.name IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + END IF; + + IF OLD.category_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + END IF; + + IF OLD.manufacturer IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + END IF; + + IF OLD.model IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + END IF; + + IF OLD.serial_number IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + END IF; + + IF OLD.zone_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + END IF; + + IF OLD.zone_plus IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + END IF; + + IF OLD.zone_note IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + END IF; + + IF OLD.status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + + IF OLD.last_audit IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + END IF; + + IF OLD.last_audit_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + END IF; + + IF OLD.price IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + END IF; + + IF OLD.purchase_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + END IF; + + IF OLD.warranty_until IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + END IF; + + IF OLD.expiry_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + END IF; + + IF OLD.quantity_available IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + END IF; + + IF OLD.quantity_total IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + END IF; + + IF OLD.quantity_used IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + END IF; + + IF OLD.supplier_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + END IF; + + IF OLD.lendable IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + END IF; + + IF OLD.minimum_role_for_lending IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + END IF; + + IF OLD.lending_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + END IF; + + IF OLD.current_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + END IF; + + IF OLD.due_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + END IF; + + IF OLD.previous_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + END IF; + + IF OLD.audit_task_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + END IF; + + IF OLD.no_scan IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + END IF; + + IF OLD.notes IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + END IF; + + IF OLD.additional_fields IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + END IF; + + -- Always capture metadata fields for restore + IF OLD.created_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_date'); + SET old_vals = JSON_SET(old_vals, '$.created_date', OLD.created_date); + END IF; + + IF OLD.created_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_by'); + SET old_vals = JSON_SET(old_vals, '$.created_by', OLD.created_by); + END IF; + + IF OLD.last_modified_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_date'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_date', OLD.last_modified_date); + END IF; + + IF OLD.last_modified_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_by'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_by', OLD.last_modified_by); + END IF; + + -- Log the DELETE with only non-NULL fields + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'DELETE', + OLD.id, + deleted_fields_array, + old_vals, + @current_user_id, + username + ); +END// + +-- ============================================ +-- BUSINESS LOGIC TRIGGERS +-- ============================================ + +-- Trigger: Prevent lending non-lendable assets +DROP TRIGGER IF EXISTS prevent_lend_non_lendable_assets// +CREATE TRIGGER prevent_lend_non_lendable_assets +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + -- Check if trying to set lending_status to any borrowed state on a non-lendable asset + IF (NEW.lendable = FALSE OR NEW.lendable IS NULL) AND + NEW.lending_status IN ('Borrowed', 'Deployed', 'Overdue') AND + (OLD.lending_status NOT IN ('Borrowed', 'Deployed', 'Overdue') OR OLD.lending_status IS NULL) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot lend asset that is marked as non-lendable. Set lendable=TRUE first.'; + END IF; +END// + +-- Trigger: Prevent deleting borrowed items +DROP TRIGGER IF EXISTS prevent_delete_borrowed_assets// +CREATE TRIGGER prevent_delete_borrowed_assets +BEFORE DELETE ON assets +FOR EACH ROW +BEGIN + IF OLD.lending_status IN ('Borrowed', 'Deployed', 'Overdue') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot delete asset that is currently borrowed or deployed, maybe update to retired or unmanaged before'; + END IF; +END// + +-- Trigger: Validate zone_plus requires zone_note for 'Clarify' +DROP TRIGGER IF EXISTS validate_zone_plus_insert// +CREATE TRIGGER validate_zone_plus_insert +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END// + +DROP TRIGGER IF EXISTS validate_zone_plus_update// +CREATE TRIGGER validate_zone_plus_update +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END// + +-- ============================================ +-- BORROWERS TABLE TRIGGERS +-- ============================================ + +-- Trigger: Auto-populate added_by on INSERT +DROP TRIGGER IF EXISTS borrowers_before_insert_meta// +CREATE TRIGGER borrowers_before_insert_meta +BEFORE INSERT ON borrowers +FOR EACH ROW +BEGIN + IF NEW.added_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.added_by = @current_user_id; + END IF; +END// + +-- Trigger: Auto-populate last_unban_by on UPDATE when unbanning +DROP TRIGGER IF EXISTS borrowers_before_update_meta// +CREATE TRIGGER borrowers_before_update_meta +BEFORE UPDATE ON borrowers +FOR EACH ROW +BEGIN + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + IF NEW.last_unban_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.last_unban_by = @current_user_id; + END IF; + IF NEW.last_unban_date IS NULL THEN + SET NEW.last_unban_date = CURDATE(); + END IF; + END IF; +END// + +-- ============================================ +-- LENDING HISTORY TRIGGERS +-- ============================================ + +-- Trigger: Auto-populate checked_out_by on INSERT +DROP TRIGGER IF EXISTS lending_history_before_insert_meta// +CREATE TRIGGER lending_history_before_insert_meta +BEFORE INSERT ON lending_history +FOR EACH ROW +BEGIN + IF NEW.checked_out_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_out_by = @current_user_id; + END IF; +END// + +-- Trigger: Auto-populate checked_in_by on UPDATE when returning +DROP TRIGGER IF EXISTS lending_history_before_update_meta// +CREATE TRIGGER lending_history_before_update_meta +BEFORE UPDATE ON lending_history +FOR EACH ROW +BEGIN + IF OLD.return_date IS NULL AND NEW.return_date IS NOT NULL THEN + IF NEW.checked_in_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_in_by = @current_user_id; + END IF; + END IF; +END// + +-- ============================================ +-- ISSUE TRACKER TRIGGERS +-- ============================================ + +-- Trigger: Auto-populate reported_by on INSERT +DROP TRIGGER IF EXISTS issue_tracker_before_insert_meta// +CREATE TRIGGER issue_tracker_before_insert_meta +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + IF NEW.reported_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.reported_by = @current_user_id; + END IF; +END// + +-- Trigger: Validate issue_tracker business rules on INSERT +DROP TRIGGER IF EXISTS validate_issue_tracker_insert// +CREATE TRIGGER validate_issue_tracker_insert +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Asset Issue requires asset_id + IF NEW.issue_type = 'Asset Issue' AND NEW.asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'asset_id is required for Asset Issue type'; + END IF; + + -- Borrower Issue requires borrower_id + IF NEW.issue_type = 'Borrower Issue' AND NEW.borrower_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'borrower_id is required for Borrower Issue type'; + END IF; + + -- Auto-set resolved_date when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_date IS NULL THEN + SET NEW.resolved_date = NOW(); + END IF; + + -- Auto-set resolved_by when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; +END// + +-- Trigger: Validate issue_tracker business rules on UPDATE +DROP TRIGGER IF EXISTS validate_issue_tracker_update// +CREATE TRIGGER validate_issue_tracker_update +BEFORE UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Auto-set resolved_date when status changes to Resolved or Closed + IF OLD.status NOT IN ('Resolved', 'Closed') AND NEW.status IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; + END IF; + + -- Clear resolved_date when status changes away from Resolved/Closed + IF OLD.status IN ('Resolved', 'Closed') AND NEW.status NOT IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NULL; + SET NEW.resolved_by = NULL; + END IF; +END// + +-- Trigger: Auto-resolve issue before DELETE +DROP TRIGGER IF EXISTS issue_tracker_before_delete// +CREATE TRIGGER issue_tracker_before_delete +BEFORE DELETE ON issue_tracker +FOR EACH ROW +BEGIN + -- If issue is not already resolved/closed, update it before deletion + IF OLD.status NOT IN ('Resolved', 'Closed') THEN + -- Can't UPDATE in a BEFORE DELETE trigger, so we just ensure it was marked resolved + -- This will prevent accidental deletion of open issues + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete open issues. Please close or resolve the issue first.'; + END IF; +END// + +-- Trigger: Log issue_tracker INSERT operations +DROP TRIGGER IF EXISTS issue_tracker_after_insert_log// +CREATE TRIGGER issue_tracker_after_insert_log +AFTER INSERT ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE set_fields JSON DEFAULT JSON_ARRAY(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Build JSON of non-NULL inserted fields + IF NEW.issue_type IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'issue_type'); + SET new_vals = JSON_SET(new_vals, '$.issue_type', NEW.issue_type); + END IF; + IF NEW.asset_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'asset_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_id', NEW.asset_id); + END IF; + IF NEW.borrower_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.borrower_id', NEW.borrower_id); + END IF; + IF NEW.title IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'title'); + SET new_vals = JSON_SET(new_vals, '$.title', NEW.title); + END IF; + IF NEW.severity IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'severity'); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF NEW.status IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, new_values, changed_by) + VALUES (NEW.id, 'INSERT', set_fields, new_vals, COALESCE(@current_user_id, NEW.reported_by)); +END// + +-- Trigger: Log issue_tracker UPDATE operations +DROP TRIGGER IF EXISTS issue_tracker_after_update_log// +CREATE TRIGGER issue_tracker_after_update_log +AFTER UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE changed_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Track all changed fields + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + IF OLD.severity <=> NEW.severity IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'severity'); + SET old_vals = JSON_SET(old_vals, '$.severity', OLD.severity); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF OLD.priority <=> NEW.priority IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'priority'); + SET old_vals = JSON_SET(old_vals, '$.priority', OLD.priority); + SET new_vals = JSON_SET(new_vals, '$.priority', NEW.priority); + END IF; + IF OLD.solution <=> NEW.solution IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + SET new_vals = JSON_SET(new_vals, '$.solution', NEW.solution); + END IF; + IF OLD.assigned_to <=> NEW.assigned_to IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'assigned_to'); + SET old_vals = JSON_SET(old_vals, '$.assigned_to', OLD.assigned_to); + SET new_vals = JSON_SET(new_vals, '$.assigned_to', NEW.assigned_to); + END IF; + IF OLD.resolved_by <=> NEW.resolved_by IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'resolved_by'); + SET old_vals = JSON_SET(old_vals, '$.resolved_by', OLD.resolved_by); + SET new_vals = JSON_SET(new_vals, '$.resolved_by', NEW.resolved_by); + END IF; + + -- Only log if something actually changed + IF JSON_LENGTH(changed_fields) > 0 THEN + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, new_values, changed_by) + VALUES (NEW.id, 'UPDATE', changed_fields, old_vals, new_vals, COALESCE(@current_user_id, OLD.reported_by)); + END IF; +END// + +-- Trigger: Log issue_tracker DELETE operations +DROP TRIGGER IF EXISTS issue_tracker_after_delete_log// +CREATE TRIGGER issue_tracker_after_delete_log +AFTER DELETE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE deleted_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + + -- Log all fields from deleted issue + IF OLD.issue_type IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'issue_type'); + SET old_vals = JSON_SET(old_vals, '$.issue_type', OLD.issue_type); + END IF; + IF OLD.asset_id IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'asset_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_id', OLD.asset_id); + END IF; + IF OLD.title IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'title'); + SET old_vals = JSON_SET(old_vals, '$.title', OLD.title); + END IF; + IF OLD.status IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + IF OLD.solution IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, changed_by) + VALUES (OLD.id, 'DELETE', deleted_fields, old_vals, COALESCE(@current_user_id, OLD.reported_by)); +END// + +-- Trigger: Auto-detect asset issues when status becomes problematic +DROP TRIGGER IF EXISTS auto_detect_asset_issues// +CREATE TRIGGER auto_detect_asset_issues +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + DECLARE issue_severity ENUM('Critical', 'High', 'Medium', 'Low'); + DECLARE detection_trigger_name VARCHAR(100); + + -- Check for lending_status changes to problematic states + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + + -- Determine issue details based on lending_status + CASE NEW.lending_status + WHEN 'Overdue' THEN + SET issue_title = CONCAT('Asset Overdue: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Overdue. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'LENDING_OVERDUE'; + + WHEN 'Illegally Handed Out' THEN + SET issue_title = CONCAT('Asset Illegally Handed Out: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Illegally Handed Out. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_ILLEGAL'; + + WHEN 'Stolen' THEN + SET issue_title = CONCAT('Asset Stolen: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Stolen (14+ days overdue). Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_STOLEN'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Urgent', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Check for status changes to problematic states + IF OLD.status != NEW.status AND NEW.status IN ('Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'Expired') THEN + + -- Determine issue details based on status + CASE NEW.status + WHEN 'Attention' THEN + SET issue_title = CONCAT('Asset Needs Attention: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Attention. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_ATTENTION'; + + WHEN 'Faulty' THEN + SET issue_title = CONCAT('Asset Faulty: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Faulty. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'STATUS_FAULTY'; + + WHEN 'Missing' THEN + SET issue_title = CONCAT('Asset Missing: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Missing. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'STATUS_MISSING'; + + WHEN 'Retired' THEN + SET issue_title = CONCAT('Asset Retired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Retired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Low'; + SET detection_trigger_name = 'STATUS_RETIRED'; + + WHEN 'In Repair' THEN + SET issue_title = CONCAT('Asset In Repair: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to In Repair. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_IN_REPAIR'; + + WHEN 'Expired' THEN + SET issue_title = CONCAT('Asset Expired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Expired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_EXPIRED'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Auto-resolve issues when status becomes Good again + IF OLD.status != NEW.status AND NEW.status = 'Good' AND OLD.status IN ('Faulty', 'Missing', 'In Repair', 'Expired') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Automatically Fixed', + solution_plus = CONCAT('Asset status automatically changed from ', OLD.status, ' to Good'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('STATUS_FAULTY', 'STATUS_MISSING', 'STATUS_IN_REPAIR', 'STATUS_EXPIRED'); + END IF; + + -- Auto-resolve overdue/stolen/illegal issues when item is returned (lending_status becomes Available) + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status = 'Available' + AND OLD.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Asset was returned - lending status changed from ', OLD.lending_status, ' to Available'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('LENDING_OVERDUE', 'LENDING_ILLEGAL', 'LENDING_STOLEN'); + END IF; +END// + +-- Trigger: Auto-detect borrower issues when borrower is banned +DROP TRIGGER IF EXISTS auto_detect_borrower_issues// +CREATE TRIGGER auto_detect_borrower_issues +AFTER UPDATE ON borrowers +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + + -- Auto-detect when borrower gets banned + IF OLD.banned = FALSE AND NEW.banned = TRUE THEN + SET issue_title = CONCAT('Borrower Banned: ', NEW.name); + SET issue_description = CONCAT('Borrower has been banned. Name: ', NEW.name, CASE WHEN NEW.unban_fine > 0 THEN CONCAT(', Unban Fine: $', NEW.unban_fine) ELSE '' END); + + INSERT INTO issue_tracker ( + issue_type, borrower_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Borrower Issue', NEW.id, issue_title, issue_description, 'High', 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, 'BORROWER_BANNED', NOW() + ); + END IF; + + -- Auto-resolve when borrower gets unbanned + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Borrower unbanned on ', COALESCE(NEW.last_unban_date, CURDATE()), CASE WHEN NEW.last_unban_by IS NOT NULL THEN CONCAT(' by user ID ', NEW.last_unban_by) ELSE '' END), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, NEW.last_unban_by, 1) + WHERE borrower_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger = 'BORROWER_BANNED'; + END IF; +END// + +-- ============================================ +-- PHYSICAL AUDIT TRIGGERS (Simplified) +-- ============================================ + +-- Trigger: Auto-calculate assets_expected when starting full-zone audit +DROP TRIGGER IF EXISTS calculate_assets_expected// +CREATE TRIGGER calculate_assets_expected +BEFORE INSERT ON physical_audits +FOR EACH ROW +BEGIN + DECLARE expected_count INT DEFAULT 0; + DECLARE v_timeout INT; + + -- For full-zone audits, calculate expected assets in the zone + IF NEW.audit_type = 'full-zone' AND NEW.zone_id IS NOT NULL THEN + SELECT COUNT(*) INTO expected_count + FROM assets + WHERE zone_id = NEW.zone_id + AND status NOT IN ('Missing', 'Retired'); + + SET NEW.assets_expected = expected_count; + END IF; + + -- Set timeout from zone settings if not specified + IF NEW.timeout_minutes IS NULL AND NEW.zone_id IS NOT NULL THEN + SELECT audit_timeout_minutes INTO v_timeout + FROM zones + WHERE id = NEW.zone_id + LIMIT 1; + + IF v_timeout IS NOT NULL THEN + SET NEW.timeout_minutes = v_timeout; + END IF; + END IF; +END// + +-- Trigger: Auto-populate audited_by from session user +DROP TRIGGER IF EXISTS physical_audit_logs_before_insert_meta// +CREATE TRIGGER physical_audit_logs_before_insert_meta +BEFORE INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + -- Auto-populate audited_by from session variable if not provided + IF NEW.audited_by IS NULL OR NEW.audited_by = 0 THEN + SET NEW.audited_by = COALESCE(@current_user_id, 1); + END IF; +END// + +-- Trigger: Update assets_found counter when asset is audited +DROP TRIGGER IF EXISTS update_assets_found// +CREATE TRIGGER update_assets_found +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + UPDATE physical_audits + SET assets_found = assets_found + 1 + WHERE id = NEW.physical_audit_id; +END// + +-- Trigger: Simple audit issue detection +DROP TRIGGER IF EXISTS auto_detect_audit_issues// +CREATE TRIGGER auto_detect_audit_issues +AFTER UPDATE ON physical_audits +FOR EACH ROW +BEGIN + DECLARE missing_count INT DEFAULT 0; + DECLARE zone_name VARCHAR(200); + + -- Only process when audit status changes to completed states + IF OLD.status = 'in-progress' AND NEW.status IN ('all-good', 'attention', 'timeout') THEN + + -- Get zone name for reporting + IF NEW.zone_id IS NOT NULL THEN + SELECT zone_name INTO zone_name FROM zones WHERE id = NEW.zone_id; + END IF; + + -- For full-zone audits, check for missing assets + IF NEW.audit_type = 'full-zone' AND NEW.assets_expected IS NOT NULL THEN + SET missing_count = GREATEST(0, NEW.assets_expected - NEW.assets_found); + END IF; + + -- Create issue for missing assets + IF missing_count > 0 THEN + INSERT INTO issue_tracker ( + issue_type, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date, notes + ) + VALUES ( + 'System Issue', + CONCAT('Audit: Missing Assets in ', COALESCE(zone_name, 'Unknown Zone')), + CONCAT('Full zone audit completed with ', missing_count, ' missing assets. Expected: ', NEW.assets_expected, ', Found: ', NEW.assets_found, '. Audit ID: ', NEW.id), + CASE WHEN missing_count >= 5 THEN 'Critical' WHEN missing_count >= 2 THEN 'High' ELSE 'Medium' END, + 'High', 'Open', + NEW.started_by, TRUE, 'AUDIT_MISSING_ASSETS', NOW(), + CONCAT('Physical Audit ID: ', NEW.id, ' in zone: ', COALESCE(zone_name, NEW.zone_id)) + ); + END IF; + END IF; +END// + +-- Trigger: Basic asset audit update +DROP TRIGGER IF EXISTS update_asset_from_audit// +CREATE TRIGGER update_asset_from_audit +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + DECLARE current_status VARCHAR(100); + + -- Update asset's last_audit date + UPDATE assets + SET last_audit = DATE(NEW.audit_date), + last_audit_status = NEW.status_found + WHERE id = NEW.asset_id; + + -- Compare found status with current asset status + SELECT status INTO current_status FROM assets WHERE id = NEW.asset_id LIMIT 1; + + IF NEW.status_found != current_status THEN + UPDATE assets + SET status = NEW.status_found + WHERE id = NEW.asset_id; + END IF; +END// + +DELIMITER ; + +-- End of clean triggers file + +-- ============================================ +-- USAGE NOTES +-- ============================================ + +/* +HOW TO USE FROM YOUR RUST PROXY: + +Before any INSERT/UPDATE/DELETE operation, set the user context: + SET @current_user_id = 123; + +Then execute your query normally. The triggers will automatically: +1. Auto-populate user tracking fields (if not explicitly provided): + • assets.created_by (on INSERT) + • assets.last_modified_by (on UPDATE) + • borrowers.added_by (on INSERT) + • borrowers.last_unban_by (on UPDATE when unbanning) + • issue_tracker.reported_by (on INSERT) +2. Log changes to asset_change_log EFFICIENTLY: + • INSERT: Only logs fields that were actually set (non-NULL) + • UPDATE: Only logs fields that actually changed + • DELETE: Only logs fields that had values (non-NULL) for restore capability +3. Include user_id and username in logs +4. Update last_modified_by and last_modified_date automatically +5. Enforce business rules (prevent deleting borrowed items, validate zone_plus, etc.) +6. Auto-calculate quantities for lendable items with quantity tracking +7. Auto-detect and track issues in issue_tracker +8. Manage physical audits with automatic issue detection + +NOTE: You can still explicitly provide user tracking fields in your queries if needed. + The triggers only set them if they are NULL and @current_user_id is available. + +EFFICIENCY: Change logging only captures non-NULL/changed fields, reducing storage by ~80% + for typical operations. The 'changed_fields' JSON array shows exactly what was + set/changed/deleted, making audit logs cleaner and more queryable. + +PHYSICAL AUDIT WORKFLOW: +1. Start audit: INSERT INTO physical_audits (audit_type, zone_id, started_by) VALUES ('full-zone', 1, 123); +2. Scan assets: INSERT INTO physical_audit_logs (physical_audit_id, asset_id, audited_by, status_found, audit_task_id, audit_task_responses, exception_type, found_in_zone_id, auditor_action) VALUES (...); +3. Complete audit: UPDATE physical_audits SET status = 'all-good' (or 'attention') WHERE id = audit_id; +4. System automatically creates issues for missing/moved/damaged assets + +WRONG-ZONE ASSET HANDLING: +When asset found in wrong zone (exception_type = 'wrong-zone'), auditor has 3 options: +- auditor_action = 'physical-move': Auditor will physically move item to correct zone (no issue created) +- auditor_action = 'virtual-update': Update asset's zone in system to where found (auto-detects label reprinting needs) +- auditor_action = NULL: Creates standard follow-up issue for later resolution + +LABEL REPRINTING DETECTION: +System automatically detects if asset_tag contains location info when doing virtual-update: +- Checks if asset_tag contains old zone name or room codes +- Checks for common label patterns (e.g., "MB101-001", "RoomA-Device") +- Creates 'Maintenance' issue with 'Low' priority for label reprinting if needed + +CROSS-AUDIT RECONCILIATION: +System automatically resolves "missing asset" issues from previous audits when assets are found: +- When asset is scanned in any audit, checks if it was missing from previous completed audits +- Auto-resolves related missing asset issues with solution 'Automatically Fixed' +- Logs reconciliation activity in asset_change_log for audit trail +- Prevents false "missing" reports when assets are just in different locations + +ISSUE TRACKER FEATURES: +- Auto-creates issues for problematic asset status changes +- Auto-resolves issues when assets return to Good status +- Tracks borrower ban/unban cycles +- Comprehensive audit issue detection and tracking +- Intelligent cross-audit reconciliation to prevent false missing asset reports + +Example queries in asset_change_log: +- changed_fields: ["status", "zone_id"] (array of field names that changed) +- old_values: {"status": "Good", "zone_id": 5} +- new_values: {"status": "Faulty", "zone_id": 7} + +Example audit_task_responses JSON: +{"step_1_answer": "yes", "step_2_answer": "Good", "damage_notes": "Minor scratches on case"} + +Example issues_found JSON in physical_audits: +{"missing_assets": 2, "moved_assets": 5, "damaged_assets": 1, "total_issues": 8} +*/ + +-- ============================================ +-- End of Schema +-- ============================================
\ No newline at end of file diff --git a/backend/database/dev/beepzone-schema-dump.sql b/backend/database/dev/beepzone-schema-dump.sql new file mode 100644 index 0000000..3acb5e5 --- /dev/null +++ b/backend/database/dev/beepzone-schema-dump.sql @@ -0,0 +1,2081 @@ +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `asset_change_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `table_name` varchar(50) NOT NULL, + `action` enum('INSERT','UPDATE','DELETE') NOT NULL, + `record_id` int(11) NOT NULL, + `changed_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Only fields that actually changed' CHECK (json_valid(`changed_fields`)), + `old_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`old_values`)), + `new_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`new_values`)), + `changed_at` timestamp NULL DEFAULT current_timestamp(), + `changed_by_id` int(11) DEFAULT NULL, + `changed_by_username` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_table_action` (`table_name`,`action`), + KEY `idx_timestamp` (`changed_at`), + KEY `idx_record` (`record_id`), + KEY `idx_user` (`changed_by_id`), + CONSTRAINT `asset_change_log_ibfk_1` FOREIGN KEY (`changed_by_id`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `assets` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `asset_tag` varchar(200) DEFAULT NULL, + `asset_numeric_id` int(11) NOT NULL CHECK (`asset_numeric_id` between 10000000 and 99999999), + `asset_type` enum('N','B','L','C') NOT NULL, + `name` varchar(255) DEFAULT NULL, + `category_id` int(11) DEFAULT NULL, + `manufacturer` varchar(200) DEFAULT NULL, + `model` varchar(200) DEFAULT NULL, + `serial_number` varchar(200) DEFAULT NULL, + `zone_id` int(11) DEFAULT NULL, + `zone_plus` enum('Floating Local','Floating Global','Clarify') DEFAULT NULL, + `zone_note` text DEFAULT NULL, + `status` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT 'Good', + `last_audit` date DEFAULT NULL, + `last_audit_status` varchar(100) DEFAULT NULL, + `price` decimal(12,2) DEFAULT NULL CHECK (`price` is null or `price` >= 0), + `purchase_date` date DEFAULT NULL, + `warranty_until` date DEFAULT NULL, + `expiry_date` date DEFAULT NULL, + `quantity_available` int(11) DEFAULT NULL, + `quantity_total` int(11) DEFAULT NULL, + `quantity_used` int(11) DEFAULT 0, + `supplier_id` int(11) DEFAULT NULL, + `lendable` tinyint(1) DEFAULT 0, + `minimum_role_for_lending` int(11) DEFAULT 1 CHECK (`minimum_role_for_lending` >= 1 and `minimum_role_for_lending` <= 100), + `lending_status` enum('Available','Deployed','Borrowed','Overdue','Illegally Handed Out','Stolen') DEFAULT NULL, + `current_borrower_id` int(11) DEFAULT NULL, + `due_date` date DEFAULT NULL, + `previous_borrower_id` int(11) DEFAULT NULL, + `audit_task_id` int(11) DEFAULT NULL, + `label_template_id` int(11) DEFAULT NULL, + `no_scan` enum('Yes','Ask','No') DEFAULT 'No', + `notes` text DEFAULT NULL, + `additional_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`additional_fields`)), + `file_attachment` mediumblob DEFAULT NULL, + `created_date` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_date` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `asset_numeric_id` (`asset_numeric_id`), + UNIQUE KEY `asset_tag` (`asset_tag`), + KEY `supplier_id` (`supplier_id`), + KEY `current_borrower_id` (`current_borrower_id`), + KEY `previous_borrower_id` (`previous_borrower_id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_asset_tag` (`asset_tag`), + KEY `idx_asset_numeric` (`asset_numeric_id`), + KEY `idx_type` (`asset_type`), + KEY `idx_status` (`status`), + KEY `idx_zone` (`zone_id`), + KEY `idx_category` (`category_id`), + KEY `idx_lendable` (`lendable`), + KEY `idx_lending_status` (`lending_status`), + KEY `idx_label_template` (`label_template_id`), + CONSTRAINT `assets_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`), + CONSTRAINT `assets_ibfk_2` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`), + CONSTRAINT `assets_ibfk_3` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_4` FOREIGN KEY (`current_borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_5` FOREIGN KEY (`previous_borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_6` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_7` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `assets_ibfk_8` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_asset_label_template` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_before_insert_meta +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.created_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.created_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_zone_plus_insert +BEFORE INSERT ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_insert_log +AFTER INSERT ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE set_fields_array JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields + SET set_fields_array = JSON_ARRAY(); + SET new_vals = JSON_OBJECT(); + + -- Always log these core fields + IF NEW.asset_tag IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_tag'); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF NEW.asset_numeric_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_numeric_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF NEW.asset_type IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'asset_type'); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF NEW.name IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'name'); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF NEW.category_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'category_id'); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF NEW.manufacturer IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'manufacturer'); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF NEW.model IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'model'); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF NEW.serial_number IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'serial_number'); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF NEW.zone_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_id'); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF NEW.zone_plus IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_plus'); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF NEW.zone_note IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'zone_note'); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF NEW.status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF NEW.last_audit IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit'); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF NEW.last_audit_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'last_audit_status'); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF NEW.price IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'price'); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF NEW.purchase_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'purchase_date'); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF NEW.warranty_until IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'warranty_until'); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF NEW.expiry_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'expiry_date'); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF NEW.quantity_available IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_available'); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF NEW.quantity_total IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_total'); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF NEW.quantity_used IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'quantity_used'); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF NEW.supplier_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'supplier_id'); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF NEW.lendable IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lendable'); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF NEW.minimum_role_for_lending IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'minimum_role_for_lending'); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF NEW.lending_status IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'lending_status'); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF NEW.current_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'current_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF NEW.due_date IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'due_date'); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF NEW.previous_borrower_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'previous_borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF NEW.audit_task_id IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'audit_task_id'); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF NEW.no_scan IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'no_scan'); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF NEW.notes IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'notes'); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF NEW.additional_fields IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'additional_fields'); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + IF NEW.created_by IS NOT NULL THEN + SET set_fields_array = JSON_ARRAY_APPEND(set_fields_array, '$', 'created_by'); + SET new_vals = JSON_SET(new_vals, '$.created_by', NEW.created_by); + END IF; + + -- Log the INSERT with only the fields that were set + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'INSERT', + NEW.id, + set_fields_array, + new_vals, + @current_user_id, + username + ); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_before_update_meta +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + SET NEW.last_modified_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.last_modified_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER prevent_lend_non_lendable_assets +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + -- Check if trying to set lending_status to any borrowed state on a non-lendable asset + IF (NEW.lendable = FALSE OR NEW.lendable IS NULL) AND + NEW.lending_status IN ('Borrowed', 'Deployed', 'Overdue') AND + (OLD.lending_status NOT IN ('Borrowed', 'Deployed', 'Overdue') OR OLD.lending_status IS NULL) THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot lend asset that is marked as non-lendable. Set lendable=TRUE first.'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_zone_plus_update +BEFORE UPDATE ON assets +FOR EACH ROW +BEGIN + IF NEW.zone_plus = 'Clarify' AND (NEW.zone_note IS NULL OR NEW.zone_note = '') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'zone_note is required when zone_plus is set to Clarify'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_update_log +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE changed_fields_array JSON; + DECLARE old_vals JSON; + DECLARE new_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with changed fields + SET changed_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + SET new_vals = JSON_OBJECT(); + + IF OLD.asset_tag <=> NEW.asset_tag IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + SET new_vals = JSON_SET(new_vals, '$.asset_tag', NEW.asset_tag); + END IF; + + IF OLD.asset_numeric_id <=> NEW.asset_numeric_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + SET new_vals = JSON_SET(new_vals, '$.asset_numeric_id', NEW.asset_numeric_id); + END IF; + + IF OLD.asset_type <=> NEW.asset_type IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + SET new_vals = JSON_SET(new_vals, '$.asset_type', NEW.asset_type); + END IF; + + IF OLD.name <=> NEW.name IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + SET new_vals = JSON_SET(new_vals, '$.name', NEW.name); + END IF; + + IF OLD.category_id <=> NEW.category_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + SET new_vals = JSON_SET(new_vals, '$.category_id', NEW.category_id); + END IF; + + IF OLD.manufacturer <=> NEW.manufacturer IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + SET new_vals = JSON_SET(new_vals, '$.manufacturer', NEW.manufacturer); + END IF; + + IF OLD.model <=> NEW.model IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + SET new_vals = JSON_SET(new_vals, '$.model', NEW.model); + END IF; + + IF OLD.serial_number <=> NEW.serial_number IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + SET new_vals = JSON_SET(new_vals, '$.serial_number', NEW.serial_number); + END IF; + + IF OLD.zone_id <=> NEW.zone_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + SET new_vals = JSON_SET(new_vals, '$.zone_id', NEW.zone_id); + END IF; + + IF OLD.zone_plus <=> NEW.zone_plus IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + SET new_vals = JSON_SET(new_vals, '$.zone_plus', NEW.zone_plus); + END IF; + + IF OLD.zone_note <=> NEW.zone_note IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + SET new_vals = JSON_SET(new_vals, '$.zone_note', NEW.zone_note); + END IF; + + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + IF OLD.last_audit <=> NEW.last_audit IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + SET new_vals = JSON_SET(new_vals, '$.last_audit', NEW.last_audit); + END IF; + + IF OLD.last_audit_status <=> NEW.last_audit_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + SET new_vals = JSON_SET(new_vals, '$.last_audit_status', NEW.last_audit_status); + END IF; + + IF OLD.price <=> NEW.price IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + SET new_vals = JSON_SET(new_vals, '$.price', NEW.price); + END IF; + + IF OLD.purchase_date <=> NEW.purchase_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + SET new_vals = JSON_SET(new_vals, '$.purchase_date', NEW.purchase_date); + END IF; + + IF OLD.warranty_until <=> NEW.warranty_until IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + SET new_vals = JSON_SET(new_vals, '$.warranty_until', NEW.warranty_until); + END IF; + + IF OLD.expiry_date <=> NEW.expiry_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + SET new_vals = JSON_SET(new_vals, '$.expiry_date', NEW.expiry_date); + END IF; + + IF OLD.quantity_available <=> NEW.quantity_available IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + SET new_vals = JSON_SET(new_vals, '$.quantity_available', NEW.quantity_available); + END IF; + + IF OLD.quantity_total <=> NEW.quantity_total IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + SET new_vals = JSON_SET(new_vals, '$.quantity_total', NEW.quantity_total); + END IF; + + IF OLD.quantity_used <=> NEW.quantity_used IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + SET new_vals = JSON_SET(new_vals, '$.quantity_used', NEW.quantity_used); + END IF; + + IF OLD.supplier_id <=> NEW.supplier_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + SET new_vals = JSON_SET(new_vals, '$.supplier_id', NEW.supplier_id); + END IF; + + IF OLD.lendable <=> NEW.lendable IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + SET new_vals = JSON_SET(new_vals, '$.lendable', NEW.lendable); + END IF; + + IF OLD.minimum_role_for_lending <=> NEW.minimum_role_for_lending IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + SET new_vals = JSON_SET(new_vals, '$.minimum_role_for_lending', NEW.minimum_role_for_lending); + END IF; + + IF OLD.lending_status <=> NEW.lending_status IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + SET new_vals = JSON_SET(new_vals, '$.lending_status', NEW.lending_status); + END IF; + + IF OLD.current_borrower_id <=> NEW.current_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.current_borrower_id', NEW.current_borrower_id); + END IF; + + IF OLD.due_date <=> NEW.due_date IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + SET new_vals = JSON_SET(new_vals, '$.due_date', NEW.due_date); + END IF; + + IF OLD.previous_borrower_id <=> NEW.previous_borrower_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + SET new_vals = JSON_SET(new_vals, '$.previous_borrower_id', NEW.previous_borrower_id); + END IF; + + IF OLD.audit_task_id <=> NEW.audit_task_id IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + SET new_vals = JSON_SET(new_vals, '$.audit_task_id', NEW.audit_task_id); + END IF; + + IF OLD.no_scan <=> NEW.no_scan IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + SET new_vals = JSON_SET(new_vals, '$.no_scan', NEW.no_scan); + END IF; + + IF OLD.notes <=> NEW.notes IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + SET new_vals = JSON_SET(new_vals, '$.notes', NEW.notes); + END IF; + + IF OLD.additional_fields <=> NEW.additional_fields IS FALSE THEN + SET changed_fields_array = JSON_ARRAY_APPEND(changed_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + SET new_vals = JSON_SET(new_vals, '$.additional_fields', NEW.additional_fields); + END IF; + + -- Only log if there were actual changes (excluding auto-updated fields) + IF JSON_LENGTH(changed_fields_array) > 0 THEN + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, new_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'UPDATE', + NEW.id, + changed_fields_array, + old_vals, + new_vals, + @current_user_id, + username + ); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_asset_issues +AFTER UPDATE ON assets +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + DECLARE issue_severity ENUM('Critical', 'High', 'Medium', 'Low'); + DECLARE detection_trigger_name VARCHAR(100); + + -- Check for lending_status changes to problematic states + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + + -- Determine issue details based on lending_status + CASE NEW.lending_status + WHEN 'Overdue' THEN + SET issue_title = CONCAT('Asset Overdue: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Overdue. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'LENDING_OVERDUE'; + + WHEN 'Illegally Handed Out' THEN + SET issue_title = CONCAT('Asset Illegally Handed Out: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Illegally Handed Out. Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_ILLEGAL'; + + WHEN 'Stolen' THEN + SET issue_title = CONCAT('Asset Stolen: ', COALESCE(NEW.name, NEW.asset_tag, CAST(NEW.asset_numeric_id AS CHAR))); + SET issue_description = CONCAT('Asset lending status changed to Stolen (14+ days overdue). Asset: ', NEW.asset_tag, + CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'LENDING_STOLEN'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Urgent', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Check for status changes to problematic states + IF OLD.status != NEW.status AND NEW.status IN ('Attention', 'Faulty', 'Missing', 'Retired', 'In Repair', 'Expired') THEN + + -- Determine issue details based on status + CASE NEW.status + WHEN 'Attention' THEN + SET issue_title = CONCAT('Asset Needs Attention: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Attention. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_ATTENTION'; + + WHEN 'Faulty' THEN + SET issue_title = CONCAT('Asset Faulty: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Faulty. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'High'; + SET detection_trigger_name = 'STATUS_FAULTY'; + + WHEN 'Missing' THEN + SET issue_title = CONCAT('Asset Missing: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Missing. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Critical'; + SET detection_trigger_name = 'STATUS_MISSING'; + + WHEN 'Retired' THEN + SET issue_title = CONCAT('Asset Retired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Retired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Low'; + SET detection_trigger_name = 'STATUS_RETIRED'; + + WHEN 'In Repair' THEN + SET issue_title = CONCAT('Asset In Repair: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to In Repair. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_IN_REPAIR'; + + WHEN 'Expired' THEN + SET issue_title = CONCAT('Asset Expired: ', COALESCE(NEW.name, NEW.asset_tag, NEW.asset_numeric_id)); + SET issue_description = CONCAT('Asset status changed to Expired. Asset: ', NEW.asset_numeric_id, CASE WHEN NEW.name IS NOT NULL THEN CONCAT(' (', NEW.name, ')') ELSE '' END); + SET issue_severity = 'Medium'; + SET detection_trigger_name = 'STATUS_EXPIRED'; + END CASE; + + -- Insert the auto-detected issue + INSERT INTO issue_tracker ( + issue_type, asset_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Asset Issue', NEW.id, issue_title, issue_description, issue_severity, 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, detection_trigger_name, NOW() + ); + END IF; + + -- Auto-resolve issues when status becomes Good again + IF OLD.status != NEW.status AND NEW.status = 'Good' AND OLD.status IN ('Faulty', 'Missing', 'In Repair', 'Expired') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Automatically Fixed', + solution_plus = CONCAT('Asset status automatically changed from ', OLD.status, ' to Good'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('STATUS_FAULTY', 'STATUS_MISSING', 'STATUS_IN_REPAIR', 'STATUS_EXPIRED'); + END IF; + + -- Auto-resolve overdue/stolen/illegal issues when item is returned (lending_status becomes Available) + IF (OLD.lending_status IS NULL OR OLD.lending_status != NEW.lending_status) + AND NEW.lending_status = 'Available' + AND OLD.lending_status IN ('Overdue', 'Illegally Handed Out', 'Stolen') THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Asset was returned - lending status changed from ', OLD.lending_status, ' to Available'), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, 1) + WHERE asset_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger IN ('LENDING_OVERDUE', 'LENDING_ILLEGAL', 'LENDING_STOLEN'); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER prevent_delete_borrowed_assets +BEFORE DELETE ON assets +FOR EACH ROW +BEGIN + IF OLD.lending_status IN ('Borrowed', 'Deployed', 'Overdue') THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot delete asset that is currently borrowed or deployed, maybe update to retired or unmanaged before'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER assets_after_delete_log +AFTER DELETE ON assets +FOR EACH ROW +BEGIN + DECLARE username VARCHAR(100); + DECLARE deleted_fields_array JSON; + DECLARE old_vals JSON; + + IF @current_user_id IS NOT NULL THEN + SELECT users.username INTO username FROM users WHERE id = @current_user_id; + END IF; + + -- Build JSON objects only with non-NULL fields (for restore capability) + SET deleted_fields_array = JSON_ARRAY(); + SET old_vals = JSON_OBJECT(); + + IF OLD.asset_tag IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_tag'); + SET old_vals = JSON_SET(old_vals, '$.asset_tag', OLD.asset_tag); + END IF; + + IF OLD.asset_numeric_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_numeric_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_numeric_id', OLD.asset_numeric_id); + END IF; + + IF OLD.asset_type IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'asset_type'); + SET old_vals = JSON_SET(old_vals, '$.asset_type', OLD.asset_type); + END IF; + + IF OLD.name IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'name'); + SET old_vals = JSON_SET(old_vals, '$.name', OLD.name); + END IF; + + IF OLD.category_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'category_id'); + SET old_vals = JSON_SET(old_vals, '$.category_id', OLD.category_id); + END IF; + + IF OLD.manufacturer IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'manufacturer'); + SET old_vals = JSON_SET(old_vals, '$.manufacturer', OLD.manufacturer); + END IF; + + IF OLD.model IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'model'); + SET old_vals = JSON_SET(old_vals, '$.model', OLD.model); + END IF; + + IF OLD.serial_number IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'serial_number'); + SET old_vals = JSON_SET(old_vals, '$.serial_number', OLD.serial_number); + END IF; + + IF OLD.zone_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_id'); + SET old_vals = JSON_SET(old_vals, '$.zone_id', OLD.zone_id); + END IF; + + IF OLD.zone_plus IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_plus'); + SET old_vals = JSON_SET(old_vals, '$.zone_plus', OLD.zone_plus); + END IF; + + IF OLD.zone_note IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'zone_note'); + SET old_vals = JSON_SET(old_vals, '$.zone_note', OLD.zone_note); + END IF; + + IF OLD.status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + + IF OLD.last_audit IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit'); + SET old_vals = JSON_SET(old_vals, '$.last_audit', OLD.last_audit); + END IF; + + IF OLD.last_audit_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_audit_status'); + SET old_vals = JSON_SET(old_vals, '$.last_audit_status', OLD.last_audit_status); + END IF; + + IF OLD.price IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'price'); + SET old_vals = JSON_SET(old_vals, '$.price', OLD.price); + END IF; + + IF OLD.purchase_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'purchase_date'); + SET old_vals = JSON_SET(old_vals, '$.purchase_date', OLD.purchase_date); + END IF; + + IF OLD.warranty_until IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'warranty_until'); + SET old_vals = JSON_SET(old_vals, '$.warranty_until', OLD.warranty_until); + END IF; + + IF OLD.expiry_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'expiry_date'); + SET old_vals = JSON_SET(old_vals, '$.expiry_date', OLD.expiry_date); + END IF; + + IF OLD.quantity_available IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_available'); + SET old_vals = JSON_SET(old_vals, '$.quantity_available', OLD.quantity_available); + END IF; + + IF OLD.quantity_total IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_total'); + SET old_vals = JSON_SET(old_vals, '$.quantity_total', OLD.quantity_total); + END IF; + + IF OLD.quantity_used IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'quantity_used'); + SET old_vals = JSON_SET(old_vals, '$.quantity_used', OLD.quantity_used); + END IF; + + IF OLD.supplier_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'supplier_id'); + SET old_vals = JSON_SET(old_vals, '$.supplier_id', OLD.supplier_id); + END IF; + + IF OLD.lendable IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lendable'); + SET old_vals = JSON_SET(old_vals, '$.lendable', OLD.lendable); + END IF; + + IF OLD.minimum_role_for_lending IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'minimum_role_for_lending'); + SET old_vals = JSON_SET(old_vals, '$.minimum_role_for_lending', OLD.minimum_role_for_lending); + END IF; + + IF OLD.lending_status IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'lending_status'); + SET old_vals = JSON_SET(old_vals, '$.lending_status', OLD.lending_status); + END IF; + + IF OLD.current_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'current_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.current_borrower_id', OLD.current_borrower_id); + END IF; + + IF OLD.due_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'due_date'); + SET old_vals = JSON_SET(old_vals, '$.due_date', OLD.due_date); + END IF; + + IF OLD.previous_borrower_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'previous_borrower_id'); + SET old_vals = JSON_SET(old_vals, '$.previous_borrower_id', OLD.previous_borrower_id); + END IF; + + IF OLD.audit_task_id IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'audit_task_id'); + SET old_vals = JSON_SET(old_vals, '$.audit_task_id', OLD.audit_task_id); + END IF; + + IF OLD.no_scan IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'no_scan'); + SET old_vals = JSON_SET(old_vals, '$.no_scan', OLD.no_scan); + END IF; + + IF OLD.notes IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'notes'); + SET old_vals = JSON_SET(old_vals, '$.notes', OLD.notes); + END IF; + + IF OLD.additional_fields IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'additional_fields'); + SET old_vals = JSON_SET(old_vals, '$.additional_fields', OLD.additional_fields); + END IF; + + -- Always capture metadata fields for restore + IF OLD.created_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_date'); + SET old_vals = JSON_SET(old_vals, '$.created_date', OLD.created_date); + END IF; + + IF OLD.created_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'created_by'); + SET old_vals = JSON_SET(old_vals, '$.created_by', OLD.created_by); + END IF; + + IF OLD.last_modified_date IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_date'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_date', OLD.last_modified_date); + END IF; + + IF OLD.last_modified_by IS NOT NULL THEN + SET deleted_fields_array = JSON_ARRAY_APPEND(deleted_fields_array, '$', 'last_modified_by'); + SET old_vals = JSON_SET(old_vals, '$.last_modified_by', OLD.last_modified_by); + END IF; + + -- Log the DELETE with only non-NULL fields + INSERT INTO asset_change_log ( + table_name, action, record_id, changed_fields, old_values, + changed_by_id, changed_by_username + ) + VALUES ( + 'assets', + 'DELETE', + OLD.id, + deleted_fields_array, + old_vals, + @current_user_id, + username + ); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `audit_tasks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_name` varchar(200) NOT NULL, + `json_sequence` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`json_sequence`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `borrowers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `phone_number` varchar(50) DEFAULT NULL, + `class_name` varchar(100) DEFAULT NULL, + `role` varchar(100) DEFAULT NULL, + `notes` text DEFAULT NULL, + `added_by` int(11) NOT NULL, + `added_date` timestamp NULL DEFAULT current_timestamp(), + `banned` tinyint(1) DEFAULT 0, + `unban_fine` decimal(10,2) DEFAULT 0.00, + `last_unban_by` int(11) DEFAULT NULL, + `last_unban_date` date DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `added_by` (`added_by`), + KEY `last_unban_by` (`last_unban_by`), + KEY `idx_name` (`name`), + KEY `idx_banned` (`banned`), + CONSTRAINT `borrowers_ibfk_1` FOREIGN KEY (`added_by`) REFERENCES `users` (`id`), + CONSTRAINT `borrowers_ibfk_2` FOREIGN KEY (`last_unban_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER borrowers_before_insert_meta +BEFORE INSERT ON borrowers +FOR EACH ROW +BEGIN + IF NEW.added_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.added_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER borrowers_before_update_meta +BEFORE UPDATE ON borrowers +FOR EACH ROW +BEGIN + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + IF NEW.last_unban_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.last_unban_by = @current_user_id; + END IF; + IF NEW.last_unban_date IS NULL THEN + SET NEW.last_unban_date = CURDATE(); + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_borrower_issues +AFTER UPDATE ON borrowers +FOR EACH ROW +BEGIN + DECLARE issue_title VARCHAR(255); + DECLARE issue_description TEXT; + + -- Auto-detect when borrower gets banned + IF OLD.banned = FALSE AND NEW.banned = TRUE THEN + SET issue_title = CONCAT('Borrower Banned: ', NEW.name); + SET issue_description = CONCAT('Borrower has been banned. Name: ', NEW.name, CASE WHEN NEW.unban_fine > 0 THEN CONCAT(', Unban Fine: $', NEW.unban_fine) ELSE '' END); + + INSERT INTO issue_tracker ( + issue_type, borrower_id, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date + ) + VALUES ( + 'Borrower Issue', NEW.id, issue_title, issue_description, 'High', 'Normal', 'Open', + COALESCE(@current_user_id, 1), TRUE, 'BORROWER_BANNED', NOW() + ); + END IF; + + -- Auto-resolve when borrower gets unbanned + IF OLD.banned = TRUE AND NEW.banned = FALSE THEN + UPDATE issue_tracker + SET status = 'Resolved', + solution = 'Items Returned', + solution_plus = CONCAT('Borrower unbanned on ', COALESCE(NEW.last_unban_date, CURDATE()), CASE WHEN NEW.last_unban_by IS NOT NULL THEN CONCAT(' by user ID ', NEW.last_unban_by) ELSE '' END), + resolved_date = NOW(), + resolved_by = COALESCE(@current_user_id, NEW.last_unban_by, 1) + WHERE borrower_id = NEW.id + AND status IN ('Open', 'In Progress') + AND auto_detected = TRUE + AND detection_trigger = 'BORROWER_BANNED'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `categories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `category_name` varchar(200) NOT NULL, + `category_description` text DEFAULT NULL, + `parent_id` int(11) DEFAULT NULL, + `category_code` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_parent` (`parent_id`), + KEY `idx_code` (`category_code`), + CONSTRAINT `categories_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `issue_tracker` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `issue_type` enum('Asset Issue','Borrower Issue','System Issue','Maintenance','Other') NOT NULL, + `asset_id` int(11) DEFAULT NULL, + `borrower_id` int(11) DEFAULT NULL, + `title` varchar(255) NOT NULL, + `description` text NOT NULL, + `severity` enum('Critical','High','Medium','Low') DEFAULT NULL, + `priority` enum('Urgent','High','Normal','Low') DEFAULT 'Normal', + `status` enum('Open','In Progress','Resolved','Closed','On Hold') DEFAULT 'Open', + `solution` enum('Fixed','Replaced','Clarify','No Action Needed','Deferred','Items Returned','Automatically Fixed') DEFAULT NULL, + `solution_plus` text DEFAULT NULL, + `replacement_asset_id` int(11) DEFAULT NULL, + `reported_by` int(11) NOT NULL, + `assigned_to` int(11) DEFAULT NULL, + `resolved_by` int(11) DEFAULT NULL, + `cost` decimal(10,2) DEFAULT NULL, + `created_date` datetime NOT NULL DEFAULT current_timestamp(), + `updated_date` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `resolved_date` datetime DEFAULT NULL, + `notes` text DEFAULT NULL, + `auto_detected` tinyint(1) DEFAULT 0, + `detection_trigger` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `replacement_asset_id` (`replacement_asset_id`), + KEY `reported_by` (`reported_by`), + KEY `assigned_to` (`assigned_to`), + KEY `resolved_by` (`resolved_by`), + KEY `idx_issue_type` (`issue_type`), + KEY `idx_asset` (`asset_id`), + KEY `idx_borrower` (`borrower_id`), + KEY `idx_severity` (`severity`), + KEY `idx_status` (`status`), + KEY `idx_created_date` (`created_date`), + KEY `idx_auto_detected` (`auto_detected`), + CONSTRAINT `issue_tracker_ibfk_1` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_ibfk_2` FOREIGN KEY (`borrower_id`) REFERENCES `borrowers` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_ibfk_3` FOREIGN KEY (`replacement_asset_id`) REFERENCES `assets` (`id`) ON DELETE SET NULL, + CONSTRAINT `issue_tracker_ibfk_4` FOREIGN KEY (`reported_by`) REFERENCES `users` (`id`), + CONSTRAINT `issue_tracker_ibfk_5` FOREIGN KEY (`assigned_to`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `issue_tracker_ibfk_6` FOREIGN KEY (`resolved_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_before_insert_meta +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + IF NEW.reported_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.reported_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_issue_tracker_insert +BEFORE INSERT ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Asset Issue requires asset_id + IF NEW.issue_type = 'Asset Issue' AND NEW.asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'asset_id is required for Asset Issue type'; + END IF; + + -- Borrower Issue requires borrower_id + IF NEW.issue_type = 'Borrower Issue' AND NEW.borrower_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'borrower_id is required for Borrower Issue type'; + END IF; + + -- Auto-set resolved_date when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_date IS NULL THEN + SET NEW.resolved_date = NOW(); + END IF; + + -- Auto-set resolved_by when status becomes Resolved or Closed + IF NEW.status IN ('Resolved', 'Closed') AND NEW.resolved_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_insert_log +AFTER INSERT ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE set_fields JSON DEFAULT JSON_ARRAY(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Build JSON of non-NULL inserted fields + IF NEW.issue_type IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'issue_type'); + SET new_vals = JSON_SET(new_vals, '$.issue_type', NEW.issue_type); + END IF; + IF NEW.asset_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'asset_id'); + SET new_vals = JSON_SET(new_vals, '$.asset_id', NEW.asset_id); + END IF; + IF NEW.borrower_id IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'borrower_id'); + SET new_vals = JSON_SET(new_vals, '$.borrower_id', NEW.borrower_id); + END IF; + IF NEW.title IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'title'); + SET new_vals = JSON_SET(new_vals, '$.title', NEW.title); + END IF; + IF NEW.severity IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'severity'); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF NEW.status IS NOT NULL THEN + SET set_fields = JSON_ARRAY_APPEND(set_fields, '$', 'status'); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, new_values, changed_by) + VALUES (NEW.id, 'INSERT', set_fields, new_vals, COALESCE(@current_user_id, NEW.reported_by)); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER validate_issue_tracker_update +BEFORE UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + -- Clarify solution requires solution_plus + IF NEW.solution = 'Clarify' AND (NEW.solution_plus IS NULL OR NEW.solution_plus = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'solution_plus is required when solution is set to Clarify'; + END IF; + + -- Replacement solution requires replacement_asset_id + IF NEW.solution = 'Replaced' AND NEW.replacement_asset_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'replacement_asset_id is required when solution is set to Replaced'; + END IF; + + -- Auto-set resolved_date when status changes to Resolved or Closed + IF OLD.status NOT IN ('Resolved', 'Closed') AND NEW.status IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NOW(); + IF @current_user_id IS NOT NULL THEN + SET NEW.resolved_by = @current_user_id; + END IF; + END IF; + + -- Clear resolved_date when status changes away from Resolved/Closed + IF OLD.status IN ('Resolved', 'Closed') AND NEW.status NOT IN ('Resolved', 'Closed') THEN + SET NEW.resolved_date = NULL; + SET NEW.resolved_by = NULL; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_update_log +AFTER UPDATE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE changed_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + DECLARE new_vals JSON DEFAULT JSON_OBJECT(); + + -- Track all changed fields + IF OLD.status <=> NEW.status IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + SET new_vals = JSON_SET(new_vals, '$.status', NEW.status); + END IF; + IF OLD.severity <=> NEW.severity IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'severity'); + SET old_vals = JSON_SET(old_vals, '$.severity', OLD.severity); + SET new_vals = JSON_SET(new_vals, '$.severity', NEW.severity); + END IF; + IF OLD.priority <=> NEW.priority IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'priority'); + SET old_vals = JSON_SET(old_vals, '$.priority', OLD.priority); + SET new_vals = JSON_SET(new_vals, '$.priority', NEW.priority); + END IF; + IF OLD.solution <=> NEW.solution IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + SET new_vals = JSON_SET(new_vals, '$.solution', NEW.solution); + END IF; + IF OLD.assigned_to <=> NEW.assigned_to IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'assigned_to'); + SET old_vals = JSON_SET(old_vals, '$.assigned_to', OLD.assigned_to); + SET new_vals = JSON_SET(new_vals, '$.assigned_to', NEW.assigned_to); + END IF; + IF OLD.resolved_by <=> NEW.resolved_by IS FALSE THEN + SET changed_fields = JSON_ARRAY_APPEND(changed_fields, '$', 'resolved_by'); + SET old_vals = JSON_SET(old_vals, '$.resolved_by', OLD.resolved_by); + SET new_vals = JSON_SET(new_vals, '$.resolved_by', NEW.resolved_by); + END IF; + + -- Only log if something actually changed + IF JSON_LENGTH(changed_fields) > 0 THEN + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, new_values, changed_by) + VALUES (NEW.id, 'UPDATE', changed_fields, old_vals, new_vals, COALESCE(@current_user_id, OLD.reported_by)); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_before_delete +BEFORE DELETE ON issue_tracker +FOR EACH ROW +BEGIN + -- If issue is not already resolved/closed, update it before deletion + IF OLD.status NOT IN ('Resolved', 'Closed') THEN + -- Can't UPDATE in a BEFORE DELETE trigger, so we just ensure it was marked resolved + -- This will prevent accidental deletion of open issues + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete open issues. Please close or resolve the issue first.'; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER issue_tracker_after_delete_log +AFTER DELETE ON issue_tracker +FOR EACH ROW +BEGIN + DECLARE deleted_fields JSON DEFAULT JSON_ARRAY(); + DECLARE old_vals JSON DEFAULT JSON_OBJECT(); + + -- Log all fields from deleted issue + IF OLD.issue_type IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'issue_type'); + SET old_vals = JSON_SET(old_vals, '$.issue_type', OLD.issue_type); + END IF; + IF OLD.asset_id IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'asset_id'); + SET old_vals = JSON_SET(old_vals, '$.asset_id', OLD.asset_id); + END IF; + IF OLD.title IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'title'); + SET old_vals = JSON_SET(old_vals, '$.title', OLD.title); + END IF; + IF OLD.status IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'status'); + SET old_vals = JSON_SET(old_vals, '$.status', OLD.status); + END IF; + IF OLD.solution IS NOT NULL THEN + SET deleted_fields = JSON_ARRAY_APPEND(deleted_fields, '$', 'solution'); + SET old_vals = JSON_SET(old_vals, '$.solution', OLD.solution); + END IF; + + INSERT INTO issue_tracker_change_log (issue_id, change_type, changed_fields, old_values, changed_by) + VALUES (OLD.id, 'DELETE', deleted_fields, old_vals, COALESCE(@current_user_id, OLD.reported_by)); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `issue_tracker_change_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `issue_id` int(11) NOT NULL, + `change_type` enum('INSERT','UPDATE','DELETE') NOT NULL, + `changed_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`changed_fields`)), + `old_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`old_values`)), + `new_values` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`new_values`)), + `changed_by` int(11) DEFAULT NULL, + `change_date` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + KEY `changed_by` (`changed_by`), + KEY `idx_issue` (`issue_id`), + KEY `idx_change_type` (`change_type`), + KEY `idx_change_date` (`change_date`), + CONSTRAINT `issue_tracker_change_log_ibfk_1` FOREIGN KEY (`issue_id`) REFERENCES `issue_tracker` (`id`) ON DELETE CASCADE, + CONSTRAINT `issue_tracker_change_log_ibfk_2` FOREIGN KEY (`changed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `label_templates` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `template_code` varchar(100) NOT NULL COMMENT 'Unique code like "CABLE"', + `template_name` varchar(200) NOT NULL COMMENT 'Human readable name', + `layout_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Universal label design: SVG graphics, auto-populated field placeholders, styling' CHECK (json_valid(`layout_json`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `template_code` (`template_code`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_template_code` (`template_code`), + KEY `idx_template_name` (`template_name`), + CONSTRAINT `label_templates_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `label_templates_ibfk_2` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `lending_history` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `asset_id` int(11) NOT NULL, + `borrower_id` int(11) NOT NULL, + `checkout_date` datetime NOT NULL DEFAULT current_timestamp(), + `due_date` date DEFAULT NULL, + `return_date` datetime DEFAULT NULL, + `checked_out_by` int(11) DEFAULT NULL, + `checked_in_by` int(11) DEFAULT NULL, + `notes` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `checked_out_by` (`checked_out_by`), + KEY `checked_in_by` (`checked_in_by`), + KEY `idx_asset` (`asset_id`), + KEY `idx_borrower` (`borrower_id`), + KEY `idx_checkout_date` (`checkout_date`), + KEY `idx_return_date` (`return_date`), + CONSTRAINT `lending_history_ibfk_1` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `lending_history_ibfk_2` FOREIGN KEY (`borrower_id`) REFERENCES `borrowers` (`id`), + CONSTRAINT `lending_history_ibfk_3` FOREIGN KEY (`checked_out_by`) REFERENCES `users` (`id`), + CONSTRAINT `lending_history_ibfk_4` FOREIGN KEY (`checked_in_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER lending_history_before_insert_meta +BEFORE INSERT ON lending_history +FOR EACH ROW +BEGIN + IF NEW.checked_out_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_out_by = @current_user_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER lending_history_before_update_meta +BEFORE UPDATE ON lending_history +FOR EACH ROW +BEGIN + IF OLD.return_date IS NULL AND NEW.return_date IS NOT NULL THEN + IF NEW.checked_in_by IS NULL AND @current_user_id IS NOT NULL THEN + SET NEW.checked_in_by = @current_user_id; + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `physical_audit_logs` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `physical_audit_id` int(11) NOT NULL COMMENT 'Reference to the audit session', + `asset_id` int(11) NOT NULL, + `audit_date` datetime NOT NULL DEFAULT current_timestamp(), + `audited_by` int(11) NOT NULL, + `status_found` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT 'Good', + `audit_task_id` int(11) DEFAULT NULL COMMENT 'Which audit task was run on this asset', + `audit_task_responses` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'User responses to the JSON sequence questions' CHECK (json_valid(`audit_task_responses`)), + `exception_type` enum('wrong-zone','unexpected-asset','damaged','missing-label','other') DEFAULT NULL, + `exception_details` text DEFAULT NULL COMMENT 'Details about the exception found', + `found_in_zone_id` int(11) DEFAULT NULL COMMENT 'Which zone the asset was actually found in (if different from expected)', + `auditor_action` enum('physical-move','virtual-update','no-action') DEFAULT NULL COMMENT 'What the auditor chose to do about wrong-zone assets', + `notes` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `found_in_zone_id` (`found_in_zone_id`), + KEY `idx_physical_audit` (`physical_audit_id`), + KEY `idx_asset` (`asset_id`), + KEY `idx_audit_date` (`audit_date`), + KEY `idx_audited_by` (`audited_by`), + KEY `idx_status_found` (`status_found`), + KEY `idx_exception_type` (`exception_type`), + CONSTRAINT `physical_audit_logs_ibfk_1` FOREIGN KEY (`physical_audit_id`) REFERENCES `physical_audits` (`id`) ON DELETE CASCADE, + CONSTRAINT `physical_audit_logs_ibfk_2` FOREIGN KEY (`asset_id`) REFERENCES `assets` (`id`) ON DELETE CASCADE, + CONSTRAINT `physical_audit_logs_ibfk_3` FOREIGN KEY (`audited_by`) REFERENCES `users` (`id`), + CONSTRAINT `physical_audit_logs_ibfk_4` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL, + CONSTRAINT `physical_audit_logs_ibfk_5` FOREIGN KEY (`found_in_zone_id`) REFERENCES `zones` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER physical_audit_logs_before_insert_meta +BEFORE INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + -- Auto-populate audited_by from session variable if not provided + IF NEW.audited_by IS NULL OR NEW.audited_by = 0 THEN + SET NEW.audited_by = COALESCE(@current_user_id, 1); + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER update_assets_found +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + UPDATE physical_audits + SET assets_found = assets_found + 1 + WHERE id = NEW.physical_audit_id; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER update_asset_from_audit +AFTER INSERT ON physical_audit_logs +FOR EACH ROW +BEGIN + DECLARE current_status VARCHAR(100); + + -- Update asset's last_audit date + UPDATE assets + SET last_audit = DATE(NEW.audit_date), + last_audit_status = NEW.status_found + WHERE id = NEW.asset_id; + + -- Compare found status with current asset status + SELECT status INTO current_status FROM assets WHERE id = NEW.asset_id LIMIT 1; + + IF NEW.status_found != current_status THEN + UPDATE assets + SET status = NEW.status_found + WHERE id = NEW.asset_id; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `physical_audits` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `audit_type` enum('full-zone','spot-check') NOT NULL, + `zone_id` int(11) DEFAULT NULL COMMENT 'Zone being audited (NULL for spot-check audits)', + `audit_name` varchar(255) DEFAULT NULL COMMENT 'Custom name for the audit session', + `started_by` int(11) NOT NULL, + `started_at` datetime NOT NULL DEFAULT current_timestamp(), + `completed_at` datetime DEFAULT NULL, + `status` enum('in-progress','all-good','timeout','attention','cancelled') DEFAULT 'in-progress', + `timeout_minutes` int(11) DEFAULT NULL COMMENT 'Timeout setting used for this audit', + `issues_found` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Array of issues: missing_assets, moved_assets, damaged_assets, etc.' CHECK (json_valid(`issues_found`)), + `assets_expected` int(11) DEFAULT NULL COMMENT 'Total assets expected to be found in zone', + `assets_found` int(11) DEFAULT 0 COMMENT 'Total assets actually found and scanned', + `notes` text DEFAULT NULL, + `cancelled_reason` text DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_audit_type` (`audit_type`), + KEY `idx_zone` (`zone_id`), + KEY `idx_status` (`status`), + KEY `idx_started_at` (`started_at`), + KEY `idx_started_by` (`started_by`), + CONSTRAINT `physical_audits_ibfk_1` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`), + CONSTRAINT `physical_audits_ibfk_2` FOREIGN KEY (`started_by`) REFERENCES `users` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER calculate_assets_expected +BEFORE INSERT ON physical_audits +FOR EACH ROW +BEGIN + DECLARE expected_count INT DEFAULT 0; + DECLARE v_timeout INT; + + -- For full-zone audits, calculate expected assets in the zone + IF NEW.audit_type = 'full-zone' AND NEW.zone_id IS NOT NULL THEN + SELECT COUNT(*) INTO expected_count + FROM assets + WHERE zone_id = NEW.zone_id + AND status NOT IN ('Missing', 'Retired'); + + SET NEW.assets_expected = expected_count; + END IF; + + -- Set timeout from zone settings if not specified + IF NEW.timeout_minutes IS NULL AND NEW.zone_id IS NOT NULL THEN + SELECT audit_timeout_minutes INTO v_timeout + FROM zones + WHERE id = NEW.zone_id + LIMIT 1; + + IF v_timeout IS NOT NULL THEN + SET NEW.timeout_minutes = v_timeout; + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER auto_detect_audit_issues +AFTER UPDATE ON physical_audits +FOR EACH ROW +BEGIN + DECLARE missing_count INT DEFAULT 0; + DECLARE zone_name VARCHAR(200); + + -- Only process when audit status changes to completed states + IF OLD.status = 'in-progress' AND NEW.status IN ('all-good', 'attention', 'timeout') THEN + + -- Get zone name for reporting + IF NEW.zone_id IS NOT NULL THEN + SELECT zone_name INTO zone_name FROM zones WHERE id = NEW.zone_id; + END IF; + + -- For full-zone audits, check for missing assets + IF NEW.audit_type = 'full-zone' AND NEW.assets_expected IS NOT NULL THEN + SET missing_count = GREATEST(0, NEW.assets_expected - NEW.assets_found); + END IF; + + -- Create issue for missing assets + IF missing_count > 0 THEN + INSERT INTO issue_tracker ( + issue_type, title, description, severity, priority, status, + reported_by, auto_detected, detection_trigger, created_date, notes + ) + VALUES ( + 'System Issue', + CONCAT('Audit: Missing Assets in ', COALESCE(zone_name, 'Unknown Zone')), + CONCAT('Full zone audit completed with ', missing_count, ' missing assets. Expected: ', NEW.assets_expected, ', Found: ', NEW.assets_found, '. Audit ID: ', NEW.id), + CASE WHEN missing_count >= 5 THEN 'Critical' WHEN missing_count >= 2 THEN 'High' ELSE 'Medium' END, + 'High', 'Open', + NEW.started_by, TRUE, 'AUDIT_MISSING_ASSETS', NOW(), + CONCAT('Physical Audit ID: ', NEW.id, ' in zone: ', COALESCE(zone_name, NEW.zone_id)) + ); + END IF; + END IF; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `print_history` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `entity_type` enum('Asset','Template','Borrower','Zone','Report','Custom') NOT NULL, + `entity_id` int(11) DEFAULT NULL COMMENT 'ID of the asset/template/borrower/zone (NULL for reports)', + `label_template_id` int(11) DEFAULT NULL, + `printer_id` int(11) DEFAULT NULL, + `quantity` int(11) DEFAULT 1, + `print_status` enum('Success','Failed','Cancelled','Queued') NOT NULL, + `error_message` text DEFAULT NULL, + `rendered_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'The actual data that was sent to printer (for debugging)' CHECK (json_valid(`rendered_data`)), + `printed_at` timestamp NULL DEFAULT current_timestamp(), + `printed_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `label_template_id` (`label_template_id`), + KEY `idx_entity` (`entity_type`,`entity_id`), + KEY `idx_printed_at` (`printed_at`), + KEY `idx_printed_by` (`printed_by`), + KEY `idx_printer` (`printer_id`), + KEY `idx_status` (`print_status`), + CONSTRAINT `print_history_ibfk_1` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL, + CONSTRAINT `print_history_ibfk_2` FOREIGN KEY (`printer_id`) REFERENCES `printer_settings` (`id`) ON DELETE SET NULL, + CONSTRAINT `print_history_ibfk_3` FOREIGN KEY (`printed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `printer_settings` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `printer_name` varchar(200) NOT NULL, + `description` text DEFAULT NULL, + `log` tinyint(1) DEFAULT 1 COMMENT 'Log all print jobs to this printer', + `can_be_used_for_reports` tinyint(1) DEFAULT 0 COMMENT 'Can this printer be used for printing reports', + `min_powerlevel_to_use` int(11) NOT NULL DEFAULT 75 COMMENT 'Minimum role power level required to use this printer', + `printer_plugin` enum('Ptouch','Brother','Zebra','System','PDF','Network','Custom') NOT NULL COMMENT 'Which printer plugin the client should send printer_settings to', + `printer_settings` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'Printer-specific settings: connection, paper size, DPI, margins, etc.' CHECK (json_valid(`printer_settings`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + `created_by` int(11) DEFAULT NULL, + `last_modified_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `last_modified_by` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `created_by` (`created_by`), + KEY `last_modified_by` (`last_modified_by`), + KEY `idx_printer_name` (`printer_name`), + KEY `idx_printer_plugin` (`printer_plugin`), + KEY `idx_min_powerlevel` (`min_powerlevel_to_use`), + KEY `idx_can_reports` (`can_be_used_for_reports`), + CONSTRAINT `printer_settings_ibfk_1` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `printer_settings_ibfk_2` FOREIGN KEY (`last_modified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL, + CONSTRAINT `CONSTRAINT_1` CHECK (`min_powerlevel_to_use` >= 1 and `min_powerlevel_to_use` <= 100) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `roles` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL, + `power` int(11) NOT NULL CHECK (`power` >= 1 and `power` <= 100), + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `suppliers` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `contact` varchar(200) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `phone` varchar(50) DEFAULT NULL, + `website` varchar(255) DEFAULT NULL, + `notes` text DEFAULT NULL, + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `templates` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `template_code` varchar(50) DEFAULT NULL, + `asset_tag_generation_string` varchar(500) DEFAULT NULL, + `description` text DEFAULT NULL, + `active` tinyint(1) DEFAULT 1, + `asset_type` enum('N','B','L','C') DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `category_id` int(11) DEFAULT NULL, + `manufacturer` varchar(200) DEFAULT NULL, + `model` varchar(200) DEFAULT NULL, + `zone_id` int(11) DEFAULT NULL, + `zone_plus` enum('Floating Local','Floating Global','Clarify') DEFAULT NULL, + `zone_note` text DEFAULT NULL, + `status` enum('Good','Attention','Faulty','Missing','Retired','In Repair','In Transit','Expired','Unmanaged') DEFAULT NULL, + `price` decimal(12,2) DEFAULT NULL CHECK (`price` is null or `price` >= 0), + `purchase_date` date DEFAULT NULL COMMENT 'Default purchase date for assets created from this template', + `purchase_date_now` tinyint(1) DEFAULT 0 COMMENT 'Auto-set purchase date to current date when creating assets', + `warranty_until` date DEFAULT NULL, + `warranty_auto` tinyint(1) DEFAULT 0 COMMENT 'Auto-calculate warranty_until from purchase_date', + `warranty_auto_amount` int(11) DEFAULT NULL COMMENT 'Number of days/years for warranty calculation', + `warranty_auto_unit` enum('days','years') DEFAULT 'years' COMMENT 'Unit for warranty auto-calculation', + `expiry_date` date DEFAULT NULL, + `expiry_auto` tinyint(1) DEFAULT 0 COMMENT 'Auto-calculate expiry_date from purchase_date', + `expiry_auto_amount` int(11) DEFAULT NULL COMMENT 'Number of days/years for expiry calculation', + `expiry_auto_unit` enum('days','years') DEFAULT 'years' COMMENT 'Unit for expiry auto-calculation', + `quantity_total` int(11) DEFAULT NULL, + `quantity_used` int(11) DEFAULT NULL, + `supplier_id` int(11) DEFAULT NULL, + `lendable` tinyint(1) DEFAULT NULL, + `lending_status` enum('Available','Borrowed','Overdue','Deployed','Illegally Handed Out','Stolen') DEFAULT 'Available' COMMENT 'Default lending status for assets created from this template', + `minimum_role_for_lending` int(11) DEFAULT NULL, + `audit_task_id` int(11) DEFAULT NULL, + `label_template_id` int(11) DEFAULT NULL, + `no_scan` enum('Yes','Ask','No') DEFAULT NULL, + `notes` text DEFAULT NULL, + `additional_fields` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`additional_fields`)), + `created_at` timestamp NULL DEFAULT current_timestamp(), + PRIMARY KEY (`id`), + UNIQUE KEY `template_code` (`template_code`), + KEY `category_id` (`category_id`), + KEY `zone_id` (`zone_id`), + KEY `supplier_id` (`supplier_id`), + KEY `audit_task_id` (`audit_task_id`), + KEY `idx_template_code` (`template_code`), + KEY `idx_label_template` (`label_template_id`), + KEY `idx_asset_tag_generation` (`asset_tag_generation_string`), + CONSTRAINT `fk_template_label_template` FOREIGN KEY (`label_template_id`) REFERENCES `label_templates` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_2` FOREIGN KEY (`zone_id`) REFERENCES `zones` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_3` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`) ON DELETE SET NULL, + CONSTRAINT `templates_ibfk_4` FOREIGN KEY (`audit_task_id`) REFERENCES `audit_tasks` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `username` varchar(100) NOT NULL, + `password` varchar(255) NOT NULL, + `pin_code` varchar(8) DEFAULT NULL, + `login_string` varchar(255) DEFAULT NULL, + `role_id` int(11) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `phone` varchar(50) DEFAULT NULL, + `notes` text DEFAULT NULL, + `active` tinyint(1) DEFAULT 1, + `last_login_date` datetime DEFAULT NULL, + `created_date` timestamp NULL DEFAULT current_timestamp(), + `password_reset_token` varchar(255) DEFAULT NULL, + `password_reset_expiry` datetime DEFAULT NULL, + `preferences` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'User personalization settings: common (all clients) + client-specific (web, mobile, desktop)' CHECK (json_valid(`preferences`)), + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + KEY `role_id` (`role_id`), + KEY `idx_username` (`username`), + KEY `idx_login_string` (`login_string`), + CONSTRAINT `users_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `zones` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `zone_name` varchar(200) NOT NULL, + `zone_notes` text DEFAULT NULL, + `zone_type` enum('Building','Floor','Room','Storage Area') NOT NULL, + `zone_code` varchar(50) DEFAULT NULL, + `mini_code` varchar(50) DEFAULT NULL, + `parent_id` int(11) DEFAULT NULL, + `include_in_parent` tinyint(1) DEFAULT 1, + `audit_timeout_minutes` int(11) DEFAULT 60 COMMENT 'Audit timeout in minutes for this zone', + PRIMARY KEY (`id`), + KEY `idx_parent` (`parent_id`), + KEY `idx_type` (`zone_type`), + CONSTRAINT `zones_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `zones` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +-- +-- WARNING: can't read the INFORMATION_SCHEMA.libraries table. It's most probably an old server 12.0.2-MariaDB-ubu2404. +-- + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/backend/database/dev/export-clean-schema.sh b/backend/database/dev/export-clean-schema.sh new file mode 100755 index 0000000..3548183 --- /dev/null +++ b/backend/database/dev/export-clean-schema.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Export clean schema (DDL only, no data) from current dev database + +set -euo pipefail + +# Add mysql-client to PATH (keg-only on macOS) +export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH" + +DB_HOST="127.0.0.1" +DB_PORT="3306" +DB_USER="beepzone_user" +DB_PASS="beepzone" +DB_NAME="beepzone" + +echo "Exporting clean schema (DDL only, no data)..." +mysqldump \ + --host="$DB_HOST" \ + --port="$DB_PORT" \ + --user="$DB_USER" \ + --password="$DB_PASS" \ + --no-data \ + --skip-comments \ + --skip-dump-date \ + --skip-add-locks \ + --skip-add-drop-table \ + --skip-set-charset \ + --skip-tz-utc \ + --routines \ + --triggers \ + "$DB_NAME" | sed 's/ AUTO_INCREMENT=[0-9]*//g' > beepzone-schema-clean.sql + +echo "✓ Exported to: beepzone-schema-clean.sql" diff --git a/backend/database/dev/export-full-dump.sh b/backend/database/dev/export-full-dump.sh new file mode 100755 index 0000000..782e4be --- /dev/null +++ b/backend/database/dev/export-full-dump.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Export full database dump (schema + data + triggers) from current dev database +# This creates a single file that can be imported directly + +set -euo pipefail + +# Add mysql-client to PATH (keg-only on macOS) +export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH" + +DB_HOST="127.0.0.1" +DB_PORT="3306" +DB_USER="beepzone_user" +DB_PASS="BeepZONE77" +DB_NAME="beepzone" + +echo "Exporting full database dump (schema + data + triggers)..." +mysqldump \ + --host="$DB_HOST" \ + --port="$DB_PORT" \ + --user="$DB_USER" \ + --password="$DB_PASS" \ + --single-transaction \ + --routines \ + --triggers \ + --events \ + --skip-comments \ + --skip-dump-date \ + --skip-tz-utc \ + "$DB_NAME" > beepzone-full-dump.sql + +echo "Exported to: beepzone-full-dump.sql" +echo "" +echo "This file contains:" +echo " - Complete schema (CREATE TABLE statements)" +echo " - All data (INSERT statements)" +echo " - Triggers and routines" +echo " - Ready for single-file import" |
