-- Rewrite identifying values in fpsms_demo. Does not delete rows. -- Ids, foreign keys, statuses, quantities stay. -- Item codes, lot numbers, warehouse codes, and floors (1/F–4/F) stay. -- Shop codes, delivery-order codes, and purchase-order codes are rewritten -- and the same old string is updated everywhere it was copied. -- The three most recent delivery and purchase arrival dates move to -- today, tomorrow, and the day after. Today is the real calendar day -- in UTC+8. Lot expiry dates move forward by the same number of days -- as the newest delivery arrival day, so usable stock stays usable. -- settings DEMO.dateAnchor stores that day. Line prices become 1. -- Opening the backend does not move dates. On a later demo day, run -- scripts/refresh_demo_dates.sql. -- -- Run only against a disposable full copy of production: -- mysql -h127.0.0.1 -P3306 -uroot -p fpsms_demo < scripts/anonymize_demo_copy.sql -- -- Refuses to run unless the current database is fpsms_demo. -- Refuses to run again once settings.name = DEMO.anonymized.v1 exists. -- Password for every user, including demo / demo.stock / demo.prod, is Demo2026!. -- -- MySQL Workbench stops a query after 30 seconds and reports error 2013. -- Before running: Preferences, SQL Editor, DBMS connection read timeout = 3600. -- Reconnect, select fpsms_demo, then run this whole file. SET NAMES utf8mb4; SET SESSION time_zone = '+08:00'; SET SESSION sql_safe_updates = 0; SET SESSION group_concat_max_len = 1000000; SET SESSION net_read_timeout = 7200; SET SESSION net_write_timeout = 7200; SET SESSION wait_timeout = 28800; DROP PROCEDURE IF EXISTS demo_exec_if_table; DROP PROCEDURE IF EXISTS demo_apply_map; DROP PROCEDURE IF EXISTS demo_apply_pair; DROP PROCEDURE IF EXISTS demo_add_missing; DROP PROCEDURE IF EXISTS demo_blank_columns; DROP PROCEDURE IF EXISTS demo_replace_floors; DROP PROCEDURE IF EXISTS demo_remap_shop_csv; DROP PROCEDURE IF EXISTS demo_drop_maps; DROP PROCEDURE IF EXISTS anonymize_demo_copy; DELIMITER $$ CREATE PROCEDURE demo_drop_maps() BEGIN DROP TABLE IF EXISTS _demo_map_shop; DROP TABLE IF EXISTS _demo_map_do; DROP TABLE IF EXISTS _demo_map_po; DROP TABLE IF EXISTS _demo_map_user; DROP TABLE IF EXISTS _demo_map_username; DROP TABLE IF EXISTS _demo_map_person; END$$ CREATE PROCEDURE demo_exec_if_table(IN p_table VARCHAR(64), IN p_sql TEXT) BEGIN DECLARE n INT DEFAULT 0; SELECT COUNT(*) INTO n FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = p_table; IF n > 0 THEN SET @demo_sql = p_sql; PREPARE demo_stmt FROM @demo_sql; EXECUTE demo_stmt; DEALLOCATE PREPARE demo_stmt; END IF; END$$ CREATE PROCEDURE demo_apply_map(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_map VARCHAR(64)) BEGIN DECLARE n INT DEFAULT 0; SELECT COUNT(*) INTO n FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table AND column_name = p_column; IF n > 0 THEN SET @demo_sql = CONCAT( 'UPDATE `', p_table, '` t INNER JOIN `', p_map, '` m ', 'ON t.`', p_column, '` COLLATE utf8mb4_unicode_ci = m.old_value ', 'SET t.`', p_column, '` = m.new_value' ); PREPARE demo_stmt FROM @demo_sql; EXECUTE demo_stmt; DEALLOCATE PREPARE demo_stmt; END IF; END$$ CREATE PROCEDURE demo_apply_pair( IN p_table VARCHAR(64), IN p_code VARCHAR(64), IN p_name VARCHAR(64), IN p_map VARCHAR(64) ) BEGIN DECLARE ncode INT DEFAULT 0; DECLARE nname INT DEFAULT 0; SELECT COUNT(*) INTO ncode FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table AND column_name = p_code; SELECT COUNT(*) INTO nname FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = p_table AND column_name = p_name; IF ncode > 0 AND nname > 0 THEN SET @demo_sql = CONCAT( 'UPDATE `', p_table, '` t INNER JOIN `', p_map, '` m ', 'ON t.`', p_code, '` COLLATE utf8mb4_unicode_ci = m.old_value ', 'SET t.`', p_code, '` = m.new_value, t.`', p_name, '` = m.new_name' ); PREPARE demo_stmt FROM @demo_sql; EXECUTE demo_stmt; DEALLOCATE PREPARE demo_stmt; ELSEIF ncode > 0 THEN CALL demo_apply_map(p_table, p_code, p_map); END IF; END$$ CREATE PROCEDURE anonymize_demo_copy() BEGIN DECLARE marker_count INT DEFAULT 0; DECLARE demo_id BIGINT DEFAULT NULL; DECLARE stock_id BIGINT DEFAULT NULL; DECLARE prod_id BIGINT DEFAULT NULL; DECLARE pwd_hash VARCHAR(80) DEFAULT '$2a$10$9eqxnNGtxFADAW9AbbjRIevr8YWgBqkLr//23U6qYOWs5U9figE2u'; DECLARE expiry_delta INT DEFAULT NULL; DECLARE CONTINUE HANDLER FOR NOT FOUND SET demo_id = demo_id; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; CALL demo_drop_maps(); RESIGNAL; END; IF DATABASE() IS NULL OR DATABASE() <> 'fpsms_demo' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Refusing to anonymize: current database is not fpsms_demo'; END IF; SELECT COUNT(*) INTO marker_count FROM settings WHERE name = 'DEMO.anonymized.v1'; IF marker_count > 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Refusing to anonymize: DEMO.anonymized.v1 already exists'; END IF; CALL demo_drop_maps(); CREATE TABLE _demo_map_shop ( old_value VARCHAR(255) NOT NULL, new_value VARCHAR(255) NOT NULL, new_name VARCHAR(255) NOT NULL, PRIMARY KEY (old_value) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE _demo_map_do ( old_value VARCHAR(255) NOT NULL, new_value VARCHAR(255) NOT NULL, PRIMARY KEY (old_value) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE _demo_map_po ( old_value VARCHAR(255) NOT NULL, new_value VARCHAR(255) NOT NULL, PRIMARY KEY (old_value) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE _demo_map_user ( user_id BIGINT NOT NULL, old_username VARCHAR(255) NULL, new_username VARCHAR(255) NOT NULL, old_name VARCHAR(255) NULL, new_name VARCHAR(255) NOT NULL, PRIMARY KEY (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE _demo_map_username ( old_value VARCHAR(255) NOT NULL, new_value VARCHAR(255) NOT NULL, PRIMARY KEY (old_value) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE _demo_map_person ( old_value VARCHAR(255) NOT NULL, new_value VARCHAR(255) NOT NULL, PRIMARY KEY (old_value) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; SELECT u.id INTO demo_id FROM `user` u JOIN user_authority ua ON ua.userId = u.id JOIN authority a ON a.id = ua.authId WHERE a.authority = 'ADMIN' ORDER BY u.deleted, u.id LIMIT 1; IF demo_id IS NULL THEN SELECT id INTO demo_id FROM `user` ORDER BY deleted, id LIMIT 1; END IF; SELECT u.id INTO stock_id FROM `user` u WHERE u.id <> IFNULL(demo_id, -1) AND EXISTS ( SELECT 1 FROM user_authority ua JOIN authority a ON a.id = ua.authId WHERE ua.userId = u.id AND a.authority = 'STOCK' ) AND NOT EXISTS ( SELECT 1 FROM user_authority ua JOIN authority a ON a.id = ua.authId WHERE ua.userId = u.id AND a.authority = 'ADMIN' ) ORDER BY u.deleted, u.id LIMIT 1; SELECT u.id INTO prod_id FROM `user` u WHERE u.id <> IFNULL(demo_id, -1) AND u.id <> IFNULL(stock_id, -1) AND EXISTS ( SELECT 1 FROM user_authority ua JOIN authority a ON a.id = ua.authId WHERE ua.userId = u.id AND a.authority = 'PROD' ) AND NOT EXISTS ( SELECT 1 FROM user_authority ua JOIN authority a ON a.id = ua.authId WHERE ua.userId = u.id AND a.authority = 'ADMIN' ) ORDER BY u.deleted, u.id LIMIT 1; INSERT INTO _demo_map_user (user_id, old_username, new_username, old_name, new_name) SELECT id, NULLIF(username, ''), CASE WHEN id = demo_id THEN 'demo' WHEN id = stock_id THEN 'demo.stock' WHEN id = prod_id THEN 'demo.prod' ELSE CONCAT('USER-', id) END, NULLIF(name, ''), CASE WHEN id = demo_id THEN 'Demo' WHEN id = stock_id THEN 'Demo Stock' WHEN id = prod_id THEN 'Demo Prod' ELSE CONCAT('User ', id) END FROM `user`; INSERT INTO _demo_map_username (old_value, new_value) SELECT old_username, MIN(new_username) FROM _demo_map_user WHERE old_username IS NOT NULL GROUP BY old_username; INSERT INTO _demo_map_person (old_value, new_value) SELECT old_name, MIN(new_name) FROM _demo_map_user WHERE old_name IS NOT NULL GROUP BY old_name; INSERT INTO _demo_map_shop (old_value, new_value, new_name) SELECT code, CONCAT('S', MIN(id)), CONCAT('Shop ', MIN(id)) FROM shop WHERE code IS NOT NULL AND code <> '' GROUP BY code; INSERT INTO _demo_map_do (old_value, new_value) SELECT code, CASE WHEN stripped = '' THEN CONCAT('DO-', min_id) WHEN dup_count > 1 THEN CONCAT(stripped, '-', min_id) ELSE stripped END FROM ( SELECT g.code, g.stripped, g.min_id, COUNT(*) OVER (PARTITION BY g.stripped) AS dup_count FROM ( SELECT code, REPLACE(code, 'TO', '') AS stripped, MIN(id) AS min_id FROM delivery_order WHERE code IS NOT NULL AND code <> '' GROUP BY code ) g ) numbered; INSERT INTO _demo_map_po (old_value, new_value) SELECT code, CASE WHEN stripped = '' THEN CONCAT('PO-', min_id) WHEN dup_count > 1 THEN CONCAT(stripped, '-', min_id) ELSE stripped END FROM ( SELECT g.code, g.stripped, g.min_id, COUNT(*) OVER (PARTITION BY g.stripped) AS dup_count FROM ( SELECT code, REPLACE(REPLACE(code, 'PF', ''), 'PP', '') AS stripped, MIN(id) AS min_id FROM purchase_order WHERE code IS NOT NULL AND code <> '' GROUP BY code ) g ) numbered; START TRANSACTION; UPDATE `user` u JOIN _demo_map_user m ON m.user_id = u.id SET u.username = CONCAT('~u', u.id); UPDATE `user` u JOIN _demo_map_user m ON m.user_id = u.id SET u.username = m.new_username, u.password = pwd_hash, u.name = m.new_name, u.fullname = m.new_name, u.firstname = 'User', u.lastname = CAST(u.id AS CHAR), u.email = CASE m.new_username WHEN 'demo' THEN 'demo@example.com' WHEN 'demo.stock' THEN 'demo.stock@example.com' WHEN 'demo.prod' THEN 'demo.prod@example.com' ELSE CONCAT('user', u.id, '@example.com') END, u.phone1 = '', u.phone2 = '', u.department = '', u.title = '', u.remarks = '', u.staffNo = ''; CALL demo_apply_map('item_lot_trace_log', 'username', '_demo_map_username'); CALL demo_apply_map('stocktakerecord', 'createdBy', '_demo_map_username'); CALL demo_apply_map('pick_execution_issue', 'createdBy', '_demo_map_username'); CALL demo_apply_map('pick_execution_issue', 'modifiedBy', '_demo_map_username'); CALL demo_apply_map('pick_execution_issue', 'handled_by', '_demo_map_username'); CALL demo_apply_map('do_pick_order', 'createdBy', '_demo_map_username'); CALL demo_apply_map('do_pick_order', 'modifiedBy', '_demo_map_username'); CALL demo_apply_map('do_pick_order_record', 'createdBy', '_demo_map_username'); CALL demo_apply_map('do_pick_order_record', 'modifiedBy', '_demo_map_username'); CALL demo_apply_map('delivery_order_pick_order', 'createdBy', '_demo_map_username'); CALL demo_apply_map('delivery_order_pick_order', 'modifiedBy', '_demo_map_username'); CALL demo_apply_map('stocktakerecord', 'approverName', '_demo_map_person'); CALL demo_apply_map('stocktakerecord', 'stockTakerName', '_demo_map_person'); CALL demo_apply_map('do_pick_order', 'handler_name', '_demo_map_person'); CALL demo_apply_map('do_pick_order_record', 'handler_name', '_demo_map_person'); CALL demo_apply_map('delivery_order_pick_order', 'handlerName', '_demo_map_person'); CALL demo_apply_map('pick_execution_issue', 'picker_name', '_demo_map_person'); UPDATE items SET name = REPLACE(REPLACE(IFNULL(name, ''), 'TOA', ''), '平記', ''), description = REPLACE(REPLACE(IFNULL(description, ''), 'TOA', ''), '平記', ''); UPDATE shop SET code = CONCAT('S', id), name = CONCAT('Shop ', id), contactNo = '88888888', addr1 = CONCAT('Addr row 1 ', id), addr2 = CONCAT('Addr row 2 ', id), addr3 = '香港'; CALL demo_apply_pair('truck', 'ShopCode', 'ShopName', '_demo_map_shop'); CALL demo_apply_pair('truck_lane_schedule_line', 'shopCode', 'shopName', '_demo_map_shop'); CALL demo_apply_pair('delivery_order_pick_order', 'shopCode', 'shopName', '_demo_map_shop'); CALL demo_apply_pair('do_pick_order', 'ShopCode', 'ShopName', '_demo_map_shop'); CALL demo_apply_pair('do_pick_order_record', 'ShopCode', 'ShopName', '_demo_map_shop'); CALL demo_apply_pair('do_replenishment', 'shopCode', 'shopName', '_demo_map_shop'); CALL demo_apply_pair('truck_lane_version_line', 'shopCode', 'branchName', '_demo_map_shop'); CALL demo_apply_map('truck', 'ShopCode', '_demo_map_shop'); CALL demo_apply_map('do_replenishment', 'shopCode', '_demo_map_shop'); CALL demo_apply_map('delivery_order', 'code', '_demo_map_do'); CALL demo_apply_map('do_pick_order', 'delivery_order_code', '_demo_map_do'); CALL demo_apply_map('do_pick_order_record', 'delivery_order_code', '_demo_map_do'); CALL demo_apply_map('do_pick_order_line', 'delivery_order_code', '_demo_map_do'); CALL demo_apply_map('do_pick_order_line_record', 'delivery_order_code', '_demo_map_do'); CALL demo_apply_map('stock_out', 'deliveryOrderCode', '_demo_map_do'); CALL demo_apply_map('do_replenishment', 'sourceDoCode', '_demo_map_do'); CALL demo_apply_map('do_replenishment', 'targetDoCode', '_demo_map_do'); CALL demo_apply_map('purchase_order', 'code', '_demo_map_po'); UPDATE truck SET TruckLanceCode = REPLACE(TruckLanceCode, 'P06B', 'AA'), remark = REPLACE(remark, 'P06B', 'AA'); CALL demo_exec_if_table('truck_lane_version', 'UPDATE truck_lane_version SET truckLanceCode = REPLACE(truckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('truck_lane_version_line', 'UPDATE truck_lane_version_line SET truckLanceCode = REPLACE(truckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('truck_lane_schedule_line', 'UPDATE truck_lane_schedule_line SET fromTruckLanceCode = REPLACE(fromTruckLanceCode, ''P06B'', ''AA''), toTruckLanceCode = REPLACE(toTruckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('delivery_order_pick_order', 'UPDATE delivery_order_pick_order SET truckLanceCode = REPLACE(truckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('do_pick_order', 'UPDATE do_pick_order SET TruckLanceCode = REPLACE(TruckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('do_pick_order_record', 'UPDATE do_pick_order_record SET TruckLanceCode = REPLACE(TruckLanceCode, ''P06B'', ''AA'')'); CALL demo_exec_if_table('do_replenishment', 'UPDATE do_replenishment SET truckLaneCode = REPLACE(truckLaneCode, ''P06B'', ''AA'')'); UPDATE purchase_order_line SET price = 1, up = 1; UPDATE delivery_order_line SET price = 1, up = 1; SELECT DATEDIFF(CURDATE(), MAX(DATE(estimatedArrivalDate))) INTO expiry_delta FROM delivery_order WHERE deleted = 0 AND estimatedArrivalDate IS NOT NULL; IF expiry_delta IS NOT NULL AND expiry_delta > 0 THEN -- Clamp at DATE max so sentinel 9999-12-31 lots do not Error 1441. UPDATE inventory_lot SET expiryDate = IF( expiryDate > DATE_SUB('9999-12-31', INTERVAL expiry_delta DAY), '9999-12-31', DATE_ADD(expiryDate, INTERVAL expiry_delta DAY) ) WHERE expiryDate IS NOT NULL; END IF; UPDATE delivery_order d JOIN ( SELECT eta_day, rn FROM ( SELECT DATE(estimatedArrivalDate) AS eta_day, ROW_NUMBER() OVER (ORDER BY DATE(estimatedArrivalDate) DESC) AS rn FROM delivery_order WHERE deleted = 0 AND estimatedArrivalDate IS NOT NULL GROUP BY DATE(estimatedArrivalDate) ) t WHERE rn <= 3 ) m ON DATE(d.estimatedArrivalDate) = m.eta_day SET d.estimatedArrivalDate = TIMESTAMP( DATE_ADD(CURDATE(), INTERVAL (m.rn - 1) DAY), TIME(d.estimatedArrivalDate) ) WHERE d.deleted = 0; UPDATE purchase_order p JOIN ( SELECT eta_day, rn FROM ( SELECT DATE(estimatedArrivalDate) AS eta_day, ROW_NUMBER() OVER (ORDER BY DATE(estimatedArrivalDate) DESC) AS rn FROM purchase_order WHERE deleted = 0 AND estimatedArrivalDate IS NOT NULL GROUP BY DATE(estimatedArrivalDate) ) t WHERE rn <= 3 ) m ON DATE(p.estimatedArrivalDate) = m.eta_day SET p.estimatedArrivalDate = TIMESTAMP( DATE_ADD(CURDATE(), INTERVAL (m.rn - 1) DAY), TIME(p.estimatedArrivalDate) ) WHERE p.deleted = 0; UPDATE settings SET value = ( SELECT GROUP_CONCAT(DISTINCT s.code ORDER BY s.code SEPARATOR ',') FROM delivery_order d JOIN shop s ON s.id = d.supplierId AND s.deleted = 0 WHERE d.deleted = 0 AND d.estimatedArrivalDate IS NOT NULL AND DATE(d.estimatedArrivalDate) BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 2 DAY) ) WHERE name IN ('DO.floor.suppliers.2F', 'DO.floor.suppliers.4F'); INSERT INTO settings (name, value, category, type) VALUES ('DEMO.anonymized.v1', '1', 'DEMO', 'string'); INSERT INTO settings (name, value, category, type) VALUES ('DEMO.dateAnchor', DATE_FORMAT(CURDATE(), '%Y-%m-%d'), 'DEMO', 'string'); COMMIT; CALL demo_drop_maps(); END$$ DELIMITER ; CALL anonymize_demo_copy(); DROP PROCEDURE IF EXISTS demo_exec_if_table; DROP PROCEDURE IF EXISTS demo_apply_map; DROP PROCEDURE IF EXISTS demo_apply_pair; DROP PROCEDURE IF EXISTS demo_drop_maps; DROP PROCEDURE IF EXISTS anonymize_demo_copy; SELECT name, value FROM settings WHERE name IN ('DEMO.anonymized.v1', 'DEMO.dateAnchor');