| Autor | SHA1 | Mensaje | Fecha |
|---|---|---|---|
|
|
e51e48969a | Delete obsolete demo data seeder | hace 3 semanas |
|
|
e28df6afee |
Anonymise full prod db copy instead of seeding demo data
Will not run unless in the anonymised format, to ensure full protection of Pings data |
hace 3 semanas |
|
|
4867f4d70e | EN support for scheduling page | hace 3 semanas |
| @@ -1,6 +1,6 @@ | |||
| # Demo branch setup (`debrand`) | |||
| This branch is a white-label demo. It uses a separate database, `fpsms_demo`, filled with fictional data. It must not read or write the live company database `fpsmsdb`, and it must not call the live M18 system. | |||
| This branch is a white-label demo. `fpsms_demo` is a full copy of the live database `fpsmsdb`, then rewritten in place so names, codes, and other identifying values become placeholders. Row counts, ids, statuses, quantities, and dates stay. It must not read or write `fpsmsdb`, and it must not call the live M18 system. | |||
| Check out `debrand` in both repositories: | |||
| @@ -21,36 +21,36 @@ Do not merge this branch. Do not start it with `db-local`, `prod`, or `db-toa`. | |||
| | Also | `demo.stock` / `Demo2026!` (倉務), `demo.prod` / `Demo2026!` (出品) | | |||
| `demo` has ADMIN, STOCK, PROD, PACK, and REPORT_MGMT. The UI language switch still works. Item names and other master data are stored in Chinese. | |||
| On the report page, leave the dates empty for most downloads. For 「庫存盤點報告」 choose round 「示範盤點輪次」. For 「送貨訂單與倉存單位不符報告」 use today’s date. | |||
| `demo` is an existing admin user, renamed. Every copied password is reset to `Demo2026!`. `demo.stock` and `demo.prod` are renamed only when a stock-only user and a production-only user already exist. Floors are `A/F` (was 2/F) and `B/F` (was 4/F). Item codes look like `ITEM-12`, shops like `SHOP-3`, lots like `LOT-9`. | |||
| ## 1. Stop anything already on port 8090 | |||
| The frontend on this branch calls `http://localhost:8090/api`. If a normal backend is already listening there, it is the live database. Stop that process before starting the demo. | |||
| ## 2. Copy the table structure into `fpsms_demo` | |||
| ## 2. Copy production into `fpsms_demo`, then replace identifying values | |||
| Liquibase changesets in this repo name the schema `fpsmsdb`. Running them against an empty database, as `root`, would change the live database. Do not do that. | |||
| Liquibase changesets in this repo name the schema `fpsmsdb`. Do not run them against the live database. | |||
| Use the local `root` account from `application-db-local.yml` only for this one-time copy. Create an empty schema, load the structure (no rows), then copy the Liquibase history so startup treats the schema as already up to date. | |||
| Use the local `root` account from `application-db-local.yml` only for this copy. Load structure and rows, including Liquibase history, then run the anonymize script. The script updates existing rows. It does not delete them. It refuses to run unless the current database is `fpsms_demo`, and it refuses to run again after `settings.name = DEMO.anonymized.v1` is set. | |||
| ```bash | |||
| mysql -h127.0.0.1 -P3306 -uroot -p -e \ | |||
| "CREATE DATABASE IF NOT EXISTS fpsms_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" | |||
| mysqldump --no-data --routines --triggers -h127.0.0.1 -P3306 -uroot -p fpsmsdb \ | |||
| > /tmp/fpsms_demo_schema.sql | |||
| "DROP DATABASE IF EXISTS fpsms_demo; CREATE DATABASE fpsms_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" | |||
| mysql -h127.0.0.1 -P3306 -uroot -p fpsms_demo < /tmp/fpsms_demo_schema.sql | |||
| mysqldump --routines --triggers -h127.0.0.1 -P3306 -uroot -p fpsmsdb \ | |||
| > /tmp/fpsms_demo_copy.sql | |||
| mysqldump -h127.0.0.1 -P3306 -uroot -p fpsmsdb DATABASECHANGELOG DATABASECHANGELOGLOCK \ | |||
| > /tmp/fpsms_demo_changelog.sql | |||
| mysql -h127.0.0.1 -P3306 -uroot -p fpsms_demo < /tmp/fpsms_demo_copy.sql | |||
| mysql -h127.0.0.1 -P3306 -uroot -p fpsms_demo < /tmp/fpsms_demo_changelog.sql | |||
| mysql -h127.0.0.1 -P3306 -uroot -p fpsms_demo \ | |||
| < scripts/anonymize_demo_copy.sql | |||
| ``` | |||
| `fpsms_demo` should now have tables and Liquibase history, and no company rows. | |||
| In MySQL Workbench the default query timeout is 30 seconds. A full copy takes longer, and Workbench then reports error 2013 even though the server may still be working. Before running the script: **MySQL Workbench → Settings → SQL Editor → DBMS connection read timeout interval** = `3600`. Reconnect, select `fpsms_demo`, and run the whole file. | |||
| Dropping `fpsms_demo` removes a previous demo copy only. Do not point that `DROP` at `fpsmsdb`. | |||
| After the script, a sample of `items.code`, `shop.name`, `inventory_lot.lotNo`, `user.username`, and floor `store_id` values should be placeholders, and `SELECT COUNT(*)` on those tables should match the production copy. Every user password is `Demo2026!`. | |||
| ## 3. Create a login that cannot see `fpsmsdb` | |||
| @@ -91,11 +91,7 @@ JDK 17. From `FPSMS-backend`, on branch `debrand`: | |||
| ./gradlew bootRun --args='--spring.profiles.active=db-demo' | |||
| ``` | |||
| The first boot inserts the fictional sandbox (`DemoSandboxSeeder`) and the extra report rows (`src/main/resources/demo/demo-coverage.sql`). Later boots skip both when user `demo` and setting `DEMO.coverage.v1` already exist. | |||
| `DemoDateKeeper` then keeps those sample dates current. It runs at startup, every day at 00:10, and when the truck board is opened. It moves `DEMO-` expiry, delivery, order, pick, job, and stock-take dates forward by the number of days since the last run (stored as setting `DEMO.dates.anchor`). Lots stay unexpired, and screens that default to today still show the sample orders. You do not need to re-seed or set the date filter back to the original seed day. Quarterly stock-ledger samples stay in January, April, July, and September, and roll into the new year. | |||
| On the first run it also fills 「送貨訂單」 so a search for today returns a row on each tab: 2/F and 4/F (pending, receiving, completed), Truck X, 加單, and 補貨. Those rows use `DEMO-` codes, so the same daily shift keeps their dates on today. The marker is setting `DEMO.do.showcase.v1`. | |||
| Startup does not insert sample rows. It refuses to start unless `DEMO.anonymized.v1` is present, so a raw production copy cannot be served. Dates stay on the days they had in the copy. | |||
| In the log, expect: | |||
| @@ -126,6 +122,7 @@ Sign in as `demo` / `Demo2026!`. | |||
| | Can read live schema `fpsmsdb` | The datasource user is too powerful. Recreate `fpsms_demo` as in step 3. Do not bypass the check. | | |||
| | Access denied for `fpsms_demo` | The user or password does not match `application-db-demo.yml`, or the grants were not flushed. | | |||
| | Liquibase tries to alter `fpsmsdb` | The changelog tables were not copied into `fpsms_demo`. Repeat step 2. With the restricted login this should fail closed, not change live data. | | |||
| | has not been anonymized | Run `scripts/anonymize_demo_copy.sql` against `fpsms_demo`. Do not start the app on the raw copy. | | |||
| | Port 8090 already in use | Stop the other backend. Do not point the demo frontend at it. | | |||
| @@ -0,0 +1,947 @@ | |||
| -- Rewrite identifying values in fpsms_demo. Does not delete rows. | |||
| -- Ids, foreign keys, statuses, quantities, and dates stay. | |||
| -- | |||
| -- 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 sql_safe_updates = 0; | |||
| SET SESSION group_concat_max_len = 1000000; | |||
| 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_item; | |||
| DROP TABLE IF EXISTS _demo_map_shop; | |||
| DROP TABLE IF EXISTS _demo_map_lot; | |||
| DROP TABLE IF EXISTS _demo_map_loc; | |||
| DROP TABLE IF EXISTS _demo_map_truck; | |||
| DROP TABLE IF EXISTS _demo_map_do; | |||
| DROP TABLE IF EXISTS _demo_map_po; | |||
| DROP TABLE IF EXISTS _demo_map_jo; | |||
| DROP TABLE IF EXISTS _demo_map_pk; | |||
| DROP TABLE IF EXISTS _demo_map_si; | |||
| DROP TABLE IF EXISTS _demo_map_st; | |||
| DROP TABLE IF EXISTS _demo_map_pp; | |||
| DROP TABLE IF EXISTS _demo_map_user; | |||
| DROP TABLE IF EXISTS _demo_map_username; | |||
| DROP TABLE IF EXISTS _demo_map_person; | |||
| DROP TABLE IF EXISTS _demo_map_equip; | |||
| DROP TABLE IF EXISTS _demo_map_stage; | |||
| 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 demo_add_missing( | |||
| IN p_map VARCHAR(64), | |||
| IN p_table VARCHAR(64), | |||
| IN p_column VARCHAR(64), | |||
| IN p_prefix VARCHAR(24) | |||
| ) | |||
| demo_add_missing_body: 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 | |||
| LEAVE demo_add_missing_body; | |||
| END IF; | |||
| DELETE FROM _demo_map_stage; | |||
| SET @demo_sql = CONCAT( | |||
| 'INSERT IGNORE INTO _demo_map_stage (old_value) ', | |||
| 'SELECT DISTINCT `', p_column, '` FROM `', p_table, '` ', | |||
| 'WHERE `', p_column, '` IS NOT NULL AND `', p_column, '` <> ''''' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| SET @demo_sql = CONCAT( | |||
| 'DELETE s FROM _demo_map_stage s INNER JOIN `', p_map, '` m ', | |||
| 'ON m.old_value = s.old_value' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| SET @demo_sql = CONCAT( | |||
| 'INSERT INTO `', p_map, '` (old_value, new_value) ', | |||
| 'SELECT old_value, CONCAT(''', p_prefix, ''', rn) FROM (', | |||
| ' SELECT old_value, ROW_NUMBER() OVER (ORDER BY old_value) AS rn FROM _demo_map_stage', | |||
| ') numbered' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| END$$ | |||
| CREATE PROCEDURE demo_blank_columns() | |||
| BEGIN | |||
| DECLARE done INT DEFAULT 0; | |||
| DECLARE tname VARCHAR(64); | |||
| DECLARE cname VARCHAR(64); | |||
| DECLARE cur CURSOR FOR | |||
| SELECT table_name, column_name | |||
| FROM information_schema.columns | |||
| WHERE table_schema = DATABASE() | |||
| AND data_type IN ('varchar', 'char', 'text', 'tinytext', 'mediumtext', 'longtext') | |||
| AND table_name NOT LIKE '\_demo\_map\_%' | |||
| AND table_name NOT IN ('settings', 'DATABASECHANGELOG', 'DATABASECHANGELOGLOCK', 'authority', 'uom_conversion', 'currency') | |||
| AND LOWER(column_name) IN ( | |||
| 'remarks', 'remark', 'reason', 'issue_remark', 'errormessage', | |||
| 'addr1', 'addr2', 'addr3', 'addr4', | |||
| 'contactno', 'contactemail', 'contactname', 'brno', 'district', | |||
| 'phone1', 'phone2', 'staffno', 'department', 'title', | |||
| 'sendermail', 'tomail', 'subject', 'content', | |||
| 'districtreference', 'countryoforigin', | |||
| 'dnno', 'invoiceno', | |||
| 'subjectcht', 'subjecteng', 'contentcht', 'contenteng' | |||
| ); | |||
| DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; | |||
| OPEN cur; | |||
| blank_loop: LOOP | |||
| FETCH cur INTO tname, cname; | |||
| IF done = 1 THEN | |||
| LEAVE blank_loop; | |||
| END IF; | |||
| SET @demo_sql = CONCAT( | |||
| 'UPDATE `', tname, '` SET `', cname, '` = '''' WHERE `', cname, '` IS NOT NULL AND `', cname, '` <> ''''' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| END LOOP; | |||
| CLOSE cur; | |||
| END$$ | |||
| CREATE PROCEDURE demo_replace_floors() | |||
| BEGIN | |||
| DECLARE done INT DEFAULT 0; | |||
| DECLARE tname VARCHAR(64); | |||
| DECLARE cname VARCHAR(64); | |||
| DECLARE cset VARCHAR(64); | |||
| DECLARE bincol VARCHAR(64); | |||
| DECLARE cur CURSOR FOR | |||
| SELECT table_name, column_name, character_set_name | |||
| FROM information_schema.columns | |||
| WHERE table_schema = DATABASE() | |||
| AND data_type IN ('varchar', 'char', 'text', 'tinytext', 'mediumtext', 'longtext') | |||
| AND table_name NOT LIKE '\_demo\_map\_%' | |||
| AND table_name NOT IN ('DATABASECHANGELOG', 'DATABASECHANGELOGLOCK', 'authority'); | |||
| DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; | |||
| OPEN cur; | |||
| floor_loop: LOOP | |||
| FETCH cur INTO tname, cname, cset; | |||
| IF done = 1 THEN | |||
| LEAVE floor_loop; | |||
| END IF; | |||
| SET bincol = IF(cset = 'utf8mb4', 'utf8mb4_bin', 'utf8mb3_bin'); | |||
| SET @demo_sql = CONCAT( | |||
| 'UPDATE `', tname, '` SET `', cname, '` = CASE ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''2/F'' THEN ''A/F'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''4/F'' THEN ''B/F'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''1/F'' THEN ''C/F'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''3/F'' THEN ''D/F'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''2F'' THEN ''AF'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''4F'' THEN ''BF'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''1F'' THEN ''CF'' ', | |||
| 'WHEN UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' = ''3F'' THEN ''DF'' ', | |||
| 'ELSE `', cname, '` END ', | |||
| 'WHERE UPPER(TRIM(`', cname, '`)) COLLATE ', bincol, ' IN (''2/F'',''4/F'',''1/F'',''3/F'',''2F'',''4F'',''1F'',''3F'')' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| SET @demo_sql = CONCAT( | |||
| 'UPDATE `', tname, '` SET `', cname, '` = ', | |||
| 'REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(`', cname, '`, ', | |||
| '''-2F-'', ''-AF-''), ''-4F-'', ''-BF-''), ''-1F-'', ''-CF-''), ''-3F-'', ''-DF-''), ', | |||
| '''-2/F-'', ''-A/F-''), ''-4/F-'', ''-B/F-''), ''-1/F-'', ''-C/F-''), ''-3/F-'', ''-D/F-'') ', | |||
| 'WHERE `', cname, '` COLLATE ', bincol, ' LIKE ''%-2F-%'' OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-4F-%'' ', | |||
| 'OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-1F-%'' OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-3F-%'' ', | |||
| 'OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-2/F-%'' OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-4/F-%'' ', | |||
| 'OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-1/F-%'' OR `', cname, '` COLLATE ', bincol, ' LIKE ''%-3/F-%''' | |||
| ); | |||
| PREPARE demo_stmt FROM @demo_sql; | |||
| EXECUTE demo_stmt; | |||
| DEALLOCATE PREPARE demo_stmt; | |||
| END LOOP; | |||
| CLOSE cur; | |||
| END$$ | |||
| CREATE PROCEDURE demo_remap_shop_csv(IN p_setting VARCHAR(255)) | |||
| demo_remap_shop_csv_body: BEGIN | |||
| DECLARE done INT DEFAULT 0; | |||
| DECLARE v LONGTEXT; | |||
| DECLARE oldv VARCHAR(255); | |||
| DECLARE newv VARCHAR(255); | |||
| DECLARE setting_rows INT DEFAULT 0; | |||
| DECLARE cur CURSOR FOR | |||
| SELECT old_value, new_value | |||
| FROM _demo_map_shop | |||
| ORDER BY CHAR_LENGTH(old_value) DESC, old_value; | |||
| DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; | |||
| SELECT COUNT(*) INTO setting_rows | |||
| FROM settings | |||
| WHERE name = p_setting COLLATE utf8mb4_0900_ai_ci; | |||
| IF setting_rows = 0 THEN | |||
| LEAVE demo_remap_shop_csv_body; | |||
| END IF; | |||
| SELECT IFNULL(value, '') INTO v | |||
| FROM settings | |||
| WHERE name = p_setting COLLATE utf8mb4_0900_ai_ci | |||
| LIMIT 1; | |||
| IF v IS NULL OR v COLLATE utf8mb4_0900_ai_ci = '' THEN | |||
| LEAVE demo_remap_shop_csv_body; | |||
| END IF; | |||
| SET v = CONCAT(',', REPLACE(v, ' ', ''), ','); | |||
| OPEN cur; | |||
| shop_csv_loop: LOOP | |||
| FETCH cur INTO oldv, newv; | |||
| IF done = 1 THEN | |||
| LEAVE shop_csv_loop; | |||
| END IF; | |||
| SET v = REPLACE( | |||
| v COLLATE utf8mb4_bin, | |||
| CONCAT(',', oldv COLLATE utf8mb4_bin, ','), | |||
| CONCAT(',', newv COLLATE utf8mb4_bin, ',') | |||
| ); | |||
| END LOOP; | |||
| CLOSE cur; | |||
| SET v = TRIM(BOTH ',' FROM v); | |||
| UPDATE settings | |||
| SET value = IFNULL(v, '') | |||
| WHERE name = p_setting COLLATE utf8mb4_0900_ai_ci; | |||
| 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 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_stage ( | |||
| old_value VARCHAR(512) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_item ( | |||
| 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_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_lot ( | |||
| old_value VARCHAR(512) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_loc ( | |||
| old_value VARCHAR(512) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_truck ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) 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(64) 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(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_jo ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_pk ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_si ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_st ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_pp ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) NOT NULL, | |||
| PRIMARY KEY (old_value) | |||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | |||
| CREATE TABLE _demo_map_equip ( | |||
| old_value VARCHAR(255) NOT NULL, | |||
| new_value VARCHAR(64) 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_item (old_value, new_value, new_name) | |||
| SELECT code, CONCAT('ITEM-', MIN(id)), CONCAT('Item ', MIN(id)) | |||
| FROM items | |||
| WHERE code IS NOT NULL AND code <> '' | |||
| GROUP BY code; | |||
| INSERT INTO _demo_map_shop (old_value, new_value, new_name) | |||
| SELECT code, CONCAT('SHOP-', MIN(id)), CONCAT('Shop ', MIN(id)) | |||
| FROM shop | |||
| WHERE code IS NOT NULL AND code <> '' | |||
| GROUP BY code; | |||
| INSERT INTO _demo_map_lot (old_value, new_value) | |||
| SELECT lotNo, CONCAT('LOT-', MIN(id)) | |||
| FROM inventory_lot | |||
| WHERE lotNo IS NOT NULL AND lotNo <> '' | |||
| GROUP BY lotNo; | |||
| CALL demo_add_missing('_demo_map_lot', 'stock_in_line', 'lotNo', 'LOT-S'); | |||
| CALL demo_add_missing('_demo_map_lot', 'stock_in_line', 'productLotNo', 'LOT-P'); | |||
| CALL demo_add_missing('_demo_map_lot', 'delivery_order_line', 'm18Lot', 'LOT-M'); | |||
| CALL demo_add_missing('_demo_map_lot', 'stocktakerecord', 'lotNo', 'LOT-T'); | |||
| CALL demo_add_missing('_demo_map_lot', 'stock_adjustment_record', 'lotNo', 'LOT-A'); | |||
| CALL demo_add_missing('_demo_map_lot', 'stock_transfer_record', 'lotNo', 'LOT-R'); | |||
| CALL demo_add_missing('_demo_map_lot', 'item_lot_trace_log', 'lotNo', 'LOT-L'); | |||
| CALL demo_add_missing('_demo_map_lot', 'bag_lot_line', 'lotNo', 'LOT-B'); | |||
| CALL demo_add_missing('_demo_map_lot', 'pick_execution_issue', 'lot_no', 'LOT-I'); | |||
| INSERT INTO _demo_map_loc (old_value, new_value) | |||
| SELECT code, | |||
| CASE code | |||
| WHEN '2F-W202-01-00' THEN 'LOC-HOLD' | |||
| WHEN '2F-W200-#A-00' THEN 'LOC-WIP' | |||
| ELSE CONCAT('LOC-', MIN(id)) | |||
| END | |||
| FROM warehouse | |||
| WHERE code IS NOT NULL AND code <> '' | |||
| AND UPPER(REPLACE(code, ' ', '')) NOT IN ('2/F', '2F', '4/F', '4F', '1/F', '1F', '3/F', '3F') | |||
| GROUP BY code; | |||
| CALL demo_add_missing('_demo_map_loc', 'items', 'LocationCode', 'LOC-I'); | |||
| CALL demo_add_missing('_demo_map_loc', 'stock_transfer_record', 'startLocation', 'LOC-S'); | |||
| CALL demo_add_missing('_demo_map_loc', 'stock_transfer_record', 'targetLocation', 'LOC-T'); | |||
| CALL demo_add_missing('_demo_map_loc', 'pick_execution_issue', 'store_location', 'LOC-P'); | |||
| DELETE FROM _demo_map_loc | |||
| WHERE UPPER(REPLACE(old_value, ' ', '')) IN ('2/F', '2F', '4/F', '4F', '1/F', '1F', '3/F', '3F'); | |||
| INSERT INTO _demo_map_loc (old_value, new_value) VALUES | |||
| ('2F-W202-01-00', 'LOC-HOLD'), | |||
| ('2F-W200-#A-00', 'LOC-WIP'), | |||
| ('4F-W402-01-00', 'LOC-X01'), | |||
| ('4F-W402-02-00', 'LOC-X02'), | |||
| ('4F-W402-03-00', 'LOC-X03'), | |||
| ('4F-W402-04-00', 'LOC-X04'), | |||
| ('4F-W402-05-00', 'LOC-X05'), | |||
| ('4F-W402-#A-00', 'LOC-X06'), | |||
| ('4F-W402-#B-00', 'LOC-X07'), | |||
| ('4F-W402-#C-00', 'LOC-X08'), | |||
| ('4F-W402-#D-00', 'LOC-X09'), | |||
| ('4F-W402-#E-00', 'LOC-X10'), | |||
| ('4F-W402-#F-00', 'LOC-X11'), | |||
| ('4F-W402-#G-00', 'LOC-X12'), | |||
| ('4F-W402-#H-00', 'LOC-X13'), | |||
| ('4F-W402-#I-00', 'LOC-X14'), | |||
| ('4F-W402-#J-00', 'LOC-X15'), | |||
| ('4F-W402-#K-00', 'LOC-X16'), | |||
| ('4F-W402-#L-00', 'LOC-X17'), | |||
| ('4F-W402-#M-00', 'LOC-X18'), | |||
| ('4F-W402-#N-00', 'LOC-X19'), | |||
| ('4F-W402-#O-00', 'LOC-X20'), | |||
| ('4F-W402-#P-00', 'LOC-X21'), | |||
| ('4F-W402-#Q-00', 'LOC-X22'), | |||
| ('4F-W402-#R-00', 'LOC-X23'), | |||
| ('4F-W402-#S-00', 'LOC-X24') | |||
| ON DUPLICATE KEY UPDATE new_value = VALUES(new_value); | |||
| INSERT INTO _demo_map_truck (old_value, new_value) | |||
| SELECT TruckLanceCode, CONCAT('TRUCK-', MIN(id)) | |||
| FROM truck | |||
| WHERE TruckLanceCode IS NOT NULL AND TruckLanceCode <> '' | |||
| GROUP BY TruckLanceCode; | |||
| CALL demo_add_missing('_demo_map_truck', 'truck_lane_version', 'truckLanceCode', 'TRUCK-V'); | |||
| CALL demo_add_missing('_demo_map_truck', 'truck_lane_version_line', 'truckLanceCode', 'TRUCK-L'); | |||
| CALL demo_add_missing('_demo_map_truck', 'truck_lane_schedule_line', 'fromTruckLanceCode', 'TRUCK-F'); | |||
| CALL demo_add_missing('_demo_map_truck', 'truck_lane_schedule_line', 'toTruckLanceCode', 'TRUCK-T'); | |||
| CALL demo_add_missing('_demo_map_truck', 'delivery_order_pick_order', 'truckLanceCode', 'TRUCK-D'); | |||
| CALL demo_add_missing('_demo_map_truck', 'do_pick_order', 'TruckLanceCode', 'TRUCK-O'); | |||
| CALL demo_add_missing('_demo_map_truck', 'do_pick_order_record', 'TruckLanceCode', 'TRUCK-R'); | |||
| CALL demo_add_missing('_demo_map_truck', 'do_replenishment', 'truckLaneCode', 'TRUCK-P'); | |||
| INSERT INTO _demo_map_do (old_value, new_value) | |||
| SELECT code, CONCAT('DO-', MIN(id)) FROM delivery_order | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_po (old_value, new_value) | |||
| SELECT code, CONCAT('PO-', MIN(id)) FROM purchase_order | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_jo (old_value, new_value) | |||
| SELECT code, CONCAT('JO-', MIN(id)) FROM job_order | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_pk (old_value, new_value) | |||
| SELECT code, CONCAT('PK-', MIN(id)) FROM pick_order | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_si (old_value, new_value) | |||
| SELECT code, CONCAT('SI-', MIN(id)) FROM stock_in | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_st (old_value, new_value) | |||
| SELECT code, CONCAT('ST-', MIN(id)) FROM stock_take | |||
| WHERE code IS NOT NULL AND code <> '' GROUP BY code; | |||
| INSERT INTO _demo_map_pp (old_value, new_value) | |||
| SELECT productprocesscode, CONCAT('PP-', MIN(id)) FROM productprocess | |||
| WHERE productprocesscode IS NOT NULL AND productprocesscode <> '' | |||
| GROUP BY productprocesscode; | |||
| INSERT INTO _demo_map_equip (old_value, new_value) | |||
| SELECT code, CONCAT('EQ-', MIN(id)) FROM equipment | |||
| WHERE code IS NOT NULL AND code <> '' | |||
| GROUP BY code; | |||
| START TRANSACTION; | |||
| UPDATE items i | |||
| LEFT JOIN _demo_map_item m ON i.code COLLATE utf8mb4_unicode_ci = m.old_value | |||
| SET i.code = COALESCE(m.new_value, CONCAT('ITEM-', i.id)), | |||
| i.name = COALESCE(m.new_name, CONCAT('Item ', i.id)), | |||
| i.description = COALESCE(m.new_name, CONCAT('Item ', i.id)), | |||
| i.remarks = '', | |||
| i.countryOfOrigin = ''; | |||
| UPDATE shop s | |||
| JOIN _demo_map_shop m ON s.code COLLATE utf8mb4_unicode_ci = m.old_value | |||
| SET s.code = m.new_value, | |||
| s.name = m.new_name, | |||
| s.brNo = '', | |||
| s.contactNo = '', | |||
| s.contactEmail = '', | |||
| s.contactName = '', | |||
| s.addr1 = '', | |||
| s.addr2 = '', | |||
| s.addr3 = '', | |||
| s.addr4 = '', | |||
| s.district = ''; | |||
| UPDATE shop | |||
| SET code = CONCAT('SHOP-', id), | |||
| name = CONCAT('Shop ', id), | |||
| brNo = '', | |||
| contactNo = '', | |||
| contactEmail = '', | |||
| contactName = '', | |||
| addr1 = '', | |||
| addr2 = '', | |||
| addr3 = '', | |||
| addr4 = '', | |||
| district = '' | |||
| WHERE code IS NULL OR code = ''; | |||
| 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_pair('do_replenishment', 'itemNo', 'itemName', '_demo_map_item'); | |||
| CALL demo_apply_pair('stock_transfer_record', 'itemCode', 'itemName', '_demo_map_item'); | |||
| CALL demo_apply_pair('stocktakerecord', 'itemCode', 'itemName', '_demo_map_item'); | |||
| CALL demo_apply_pair('bag', 'itemCode', 'itemName', '_demo_map_item'); | |||
| CALL demo_apply_map('purchase_order_line', 'itemNo', '_demo_map_item'); | |||
| CALL demo_apply_map('delivery_order_line', 'itemNo', '_demo_map_item'); | |||
| CALL demo_apply_map('stock_in_line', 'itemNo', '_demo_map_item'); | |||
| CALL demo_apply_map('do_replenishment', 'itemNo', '_demo_map_item'); | |||
| CALL demo_apply_map('stock_ledger', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('stocktakerecord', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('stock_adjustment_record', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('stock_transfer_record', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('item_lot_trace_log', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('item_default_shelf_life', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('coffee_or_tea', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('onpack_template_file', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('onpack_expiry_item_code', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('bag', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('jo_pick_order', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('jo_pick_order_record', 'itemCode', '_demo_map_item'); | |||
| CALL demo_apply_map('pick_execution_issue', 'item_code', '_demo_map_item'); | |||
| CALL demo_apply_map('m18_cunit', 'itemNo', '_demo_map_item'); | |||
| CALL demo_apply_map('inventory_lot', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('stock_in_line', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('stock_in_line', 'productLotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('delivery_order_line', 'm18Lot', '_demo_map_lot'); | |||
| CALL demo_apply_map('stocktakerecord', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('stock_adjustment_record', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('stock_transfer_record', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('item_lot_trace_log', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('bag_lot_line', 'lotNo', '_demo_map_lot'); | |||
| CALL demo_apply_map('pick_execution_issue', 'lot_no', '_demo_map_lot'); | |||
| UPDATE warehouse w | |||
| LEFT JOIN _demo_map_loc m ON w.code COLLATE utf8mb4_unicode_ci = m.old_value | |||
| SET w.code = COALESCE(m.new_value, CONCAT('LOC-', w.id)), | |||
| w.name = LEFT(COALESCE(m.new_value, CONCAT('LOC-', w.id)), 30), | |||
| w.description = LEFT(COALESCE(m.new_value, CONCAT('LOC-', w.id)), 30), | |||
| w.warehouse = NULL, | |||
| w.area = NULL, | |||
| w.slot = NULL, | |||
| w.stockTakeSection = NULL, | |||
| w.stockTakeSectionDescription = NULL; | |||
| CALL demo_apply_map('items', 'LocationCode', '_demo_map_loc'); | |||
| CALL demo_apply_map('stock_transfer_record', 'startLocation', '_demo_map_loc'); | |||
| CALL demo_apply_map('stock_transfer_record', 'targetLocation', '_demo_map_loc'); | |||
| CALL demo_apply_map('pick_execution_issue', 'store_location', '_demo_map_loc'); | |||
| UPDATE items | |||
| SET warehouse = NULL, area = NULL, slot = NULL; | |||
| UPDATE warehouse | |||
| SET `order` = CASE | |||
| WHEN `order` REGEXP '^2F-[0-9]+$' THEN CONCAT('AF-', SUBSTRING(`order`, 4)) | |||
| WHEN `order` REGEXP '^4F-[0-9]+$' THEN CONCAT('BF-', SUBSTRING(`order`, 4)) | |||
| WHEN `order` REGEXP '^1F-[0-9]+$' THEN CONCAT('CF-', SUBSTRING(`order`, 4)) | |||
| WHEN `order` REGEXP '^3F-[0-9]+$' THEN CONCAT('DF-', SUBSTRING(`order`, 4)) | |||
| ELSE `order` | |||
| END; | |||
| CALL demo_apply_map('truck', 'TruckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('truck_lane_version', 'truckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('truck_lane_version_line', 'truckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('truck_lane_schedule_line', 'fromTruckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('truck_lane_schedule_line', 'toTruckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('delivery_order_pick_order', 'truckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('do_pick_order', 'TruckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('do_pick_order_record', 'TruckLanceCode', '_demo_map_truck'); | |||
| CALL demo_apply_map('do_replenishment', 'truckLaneCode', '_demo_map_truck'); | |||
| 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'); | |||
| CALL demo_apply_map('job_order', 'code', '_demo_map_jo'); | |||
| CALL demo_apply_map('jo_pick_order', 'jobOrderCode', '_demo_map_jo'); | |||
| CALL demo_apply_map('jo_pick_order_record', 'jobOrderCode', '_demo_map_jo'); | |||
| CALL demo_apply_map('jo_bag_consumption', 'jobOrderCode', '_demo_map_jo'); | |||
| CALL demo_apply_map('pick_order', 'code', '_demo_map_pk'); | |||
| CALL demo_apply_map('do_pick_order', 'pick_order_code', '_demo_map_pk'); | |||
| CALL demo_apply_map('do_pick_order_record', 'pick_order_code', '_demo_map_pk'); | |||
| CALL demo_apply_map('do_pick_order_line', 'pick_order_code', '_demo_map_pk'); | |||
| CALL demo_apply_map('do_pick_order_line_record', 'pick_order_code', '_demo_map_pk'); | |||
| CALL demo_apply_map('jo_pick_order', 'pickOrderCode', '_demo_map_pk'); | |||
| CALL demo_apply_map('jo_pick_order_record', 'pickOrderCode', '_demo_map_pk'); | |||
| CALL demo_apply_map('pick_execution_issue', 'pick_order_code', '_demo_map_pk'); | |||
| CALL demo_apply_map('stock_out', 'consoPickOrderCode', '_demo_map_pk'); | |||
| CALL demo_apply_map('stock_in', 'code', '_demo_map_si'); | |||
| CALL demo_apply_map('stock_take', 'code', '_demo_map_st'); | |||
| CALL demo_apply_map('productprocess', 'productprocesscode', '_demo_map_pp'); | |||
| UPDATE pick_order | |||
| SET consoCode = CONCAT('CS-', id) | |||
| WHERE consoCode IS NOT NULL AND consoCode <> ''; | |||
| UPDATE do_replenishment | |||
| SET code = CONCAT('DR-', id) | |||
| WHERE code IS NOT NULL AND code <> ''; | |||
| UPDATE do_pick_order_record | |||
| SET deliveryNoteCode = CONCAT('DN-', id) | |||
| WHERE deliveryNoteCode IS NOT NULL AND deliveryNoteCode <> ''; | |||
| UPDATE delivery_order_pick_order | |||
| SET deliveryNoteCode = CONCAT('DN-P', id) | |||
| WHERE deliveryNoteCode IS NOT NULL AND deliveryNoteCode <> ''; | |||
| CALL demo_exec_if_table('purchase_quotation', | |||
| 'UPDATE purchase_quotation SET code = CONCAT(''PQ-'', id) WHERE code IS NOT NULL'); | |||
| CALL demo_exec_if_table('purchase_quotation_line', | |||
| 'UPDATE purchase_quotation_line SET code = CONCAT(''PQL-'', id) WHERE code IS NOT NULL'); | |||
| UPDATE bom | |||
| SET code = CONCAT('BOM-', id), | |||
| name = CONCAT('Bom ', id), | |||
| description = CONCAT('Bom ', id); | |||
| CALL demo_exec_if_table('company', | |||
| 'UPDATE company SET code = CONCAT(''CO-'', id), name = CONCAT(''Company '', id), brNo = '''', contactNo = '''', email = '''', addr1 = '''', addr2 = '''', addr3 = '''', addr4 = '''''); | |||
| CALL demo_exec_if_table('process', | |||
| 'UPDATE `process` SET code = CONCAT(''PROC-'', id), name = LEFT(CONCAT(''Process '', id), 30), description = LEFT(CONCAT(''Process '', id), 30), remarks = '''''); | |||
| CALL demo_exec_if_table('equipment', | |||
| 'UPDATE equipment SET code = CONCAT(''EQ-'', id), name = LEFT(CONCAT(''Equip '', id), 30), description = CONCAT(''Equip '', id)'); | |||
| CALL demo_apply_map('equipment_detail', 'equipmentCode', '_demo_map_equip'); | |||
| CALL demo_exec_if_table('equipment_detail', | |||
| 'UPDATE equipment_detail SET code = CONCAT(''EQD-'', id), name = CONCAT(''Equip '', id), repairAndMaintenanceRemarks = '''''); | |||
| CALL demo_exec_if_table('item_category', | |||
| 'UPDATE item_category SET code = CONCAT(''CAT-'', id), parent = '''', sub = '''''); | |||
| CALL demo_exec_if_table('qc_item', | |||
| 'UPDATE qc_item SET code = CONCAT(''QC-'', id), name = CONCAT(''Qc '', id), description = '''''); | |||
| CALL demo_exec_if_table('qc_category', | |||
| 'UPDATE qc_category SET code = CONCAT(''QCC-'', id), name = CONCAT(''Qc '', id), description = CONCAT(''Qc '', id)'); | |||
| CALL demo_exec_if_table('pick_order_group', | |||
| 'UPDATE pick_order_group SET name = CONCAT(''POG-'', id)'); | |||
| CALL demo_exec_if_table('printer', | |||
| 'UPDATE printer SET code = CONCAT(''PRINTER-'', id), name = CASE name WHEN ''Label機 2F A+B'' THEN ''LABEL-A'' WHEN ''Label機 4F 乾貨 C, D'' THEN ''LABEL-B'' ELSE CONCAT(''Printer '', id) END, description = '''', brand = '''', ip = '''''); | |||
| CALL demo_exec_if_table('logistic', | |||
| 'UPDATE logistic SET logisticName = CONCAT(''Logistic '', id), carPlate = CONCAT(''PLATE-'', id), driverName = CONCAT(''Driver '', id), driverNumber = id'); | |||
| CALL demo_exec_if_table('group', | |||
| 'UPDATE `group` SET name = CONCAT(''~g'', id)'); | |||
| CALL demo_exec_if_table('group', | |||
| 'UPDATE `group` SET name = CONCAT(''GROUP-'', id), description = '''''); | |||
| CALL demo_exec_if_table('onpack_template_file', | |||
| 'UPDATE onpack_template_file SET fileName = CONCAT(''file-'', id)'); | |||
| CALL demo_exec_if_table('onpack_expiry_item_code', | |||
| 'UPDATE onpack_expiry_item_code SET printName = itemCode'); | |||
| CALL demo_exec_if_table('m18_data_log', | |||
| 'UPDATE m18_data_log SET dataLog = JSON_OBJECT()'); | |||
| CALL demo_exec_if_table('grn_send_log', | |||
| 'UPDATE grn_send_log SET data = JSON_OBJECT()'); | |||
| CALL demo_exec_if_table('m18_goods_receipt_note_log', | |||
| 'UPDATE m18_goods_receipt_note_log SET grn_code = CONCAT(''GRN-'', id)'); | |||
| CALL demo_exec_if_table('mail_template', | |||
| 'UPDATE mail_template SET `to` = '''', cc = '''', description = CONCAT(''Template '', id), subjectCht = '''', subjectEng = '''', contentCht = '''', contentEng = '''''); | |||
| CALL demo_exec_if_table('mail_send_log', | |||
| 'UPDATE mail_send_log SET senderMail = '''', subject = '''', content = '''', tempDesc = ''Mail'', errorMessage = '''''); | |||
| CALL demo_exec_if_table('mail_send_log_line', | |||
| 'UPDATE mail_send_log_line SET toMail = '''', errorMessage = '''''); | |||
| 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'); | |||
| CALL demo_exec_if_table('pick_execution_issue', | |||
| 'UPDATE pick_execution_issue SET item_description = '''''); | |||
| 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 '[email protected]' | |||
| WHEN 'demo.stock' THEN '[email protected]' | |||
| WHEN 'demo.prod' THEN '[email protected]' | |||
| ELSE CONCAT('user', u.id, '@example.com') | |||
| END, | |||
| u.phone1 = '', | |||
| u.phone2 = '', | |||
| u.department = '', | |||
| u.title = '', | |||
| u.remarks = '', | |||
| u.staffNo = ''; | |||
| CALL demo_blank_columns(); | |||
| UPDATE settings SET name = 'DO.floor.suppliers.AF' WHERE name = 'DO.floor.suppliers.2F'; | |||
| UPDATE settings SET name = 'DO.floor.suppliers.BF' WHERE name = 'DO.floor.suppliers.4F'; | |||
| CALL demo_remap_shop_csv('DO.floor.suppliers.AF'); | |||
| CALL demo_remap_shop_csv('DO.floor.suppliers.BF'); | |||
| UPDATE settings | |||
| SET value = '' | |||
| WHERE name IN ( | |||
| 'LASER_PRINT.host', | |||
| 'LASER_PRINT.itemCodes', | |||
| 'LASER_PRINT.lastReceiveSuccess' | |||
| ) | |||
| OR name LIKE 'MAIL.smtp.%' | |||
| OR name LIKE 'REPORT.dailyMaint.recipients%'; | |||
| CALL demo_replace_floors(); | |||
| INSERT INTO settings (name, value, category, type) | |||
| VALUES ('DEMO.anonymized.v1', '1', '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_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; | |||
| SELECT name, value FROM settings WHERE name = 'DEMO.anonymized.v1'; | |||
| @@ -1,94 +0,0 @@ | |||
| package com.ffii.fpsms.modules.common.demo | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import org.springframework.boot.ApplicationArguments | |||
| import org.springframework.boot.ApplicationRunner | |||
| import org.springframework.context.annotation.Profile | |||
| import org.springframework.core.annotation.Order | |||
| import org.springframework.core.io.ClassPathResource | |||
| import org.springframework.jdbc.core.JdbcTemplate | |||
| import org.springframework.stereotype.Component | |||
| import java.nio.charset.StandardCharsets | |||
| import java.sql.Connection | |||
| import javax.sql.DataSource | |||
| /** | |||
| * Extra fictional rows so demo screens and report downloads have something to show. | |||
| * Runs only after the base sandbox exists, and only against fpsms_demo. | |||
| */ | |||
| @Component | |||
| @Profile("db-demo") | |||
| @Order(2) | |||
| class DemoCoverageSeeder( | |||
| @Value("\${spring.datasource.jdbc-url}") private val jdbcUrl: String, | |||
| private val dataSource: DataSource, | |||
| private val jdbcTemplate: JdbcTemplate, | |||
| ) : ApplicationRunner { | |||
| private val log = LoggerFactory.getLogger(DemoCoverageSeeder::class.java) | |||
| override fun run(args: ApplicationArguments) { | |||
| val configuredName = databaseName(jdbcUrl) | |||
| val connectedName = dataSource.connection.use { it.catalog } | |||
| if (configuredName != DEMO_DATABASE || !DEMO_DATABASE.equals(connectedName, ignoreCase = true)) { | |||
| throw IllegalStateException( | |||
| "Demo coverage refused to start. Configured database is '$configuredName', connected database is '$connectedName'.", | |||
| ) | |||
| } | |||
| val already = jdbcTemplate.queryForObject( | |||
| "SELECT COUNT(*) FROM settings WHERE name = ?", | |||
| Int::class.java, | |||
| MARKER, | |||
| ) ?: 0 | |||
| if (already > 0) { | |||
| log.info("Demo coverage data already present.") | |||
| return | |||
| } | |||
| val script = ClassPathResource(SCRIPT).inputStream.use { it.readBytes().toString(StandardCharsets.UTF_8) } | |||
| dataSource.connection.use { connection -> | |||
| connection.autoCommit = false | |||
| try { | |||
| executeScript(connection, script) | |||
| connection.commit() | |||
| } catch (ex: Exception) { | |||
| connection.rollback() | |||
| throw IllegalStateException("Demo coverage data failed to load.", ex) | |||
| } | |||
| } | |||
| log.info("Demo coverage data loaded.") | |||
| } | |||
| private fun executeScript(connection: Connection, script: String) { | |||
| val statement = StringBuilder() | |||
| connection.createStatement().use { jdbc -> | |||
| for (rawLine in script.lineSequence()) { | |||
| val line = rawLine.trim() | |||
| if (line.isEmpty() || line.startsWith("--")) continue | |||
| statement.append(rawLine).append('\n') | |||
| if (line.endsWith(";")) { | |||
| val sql = statement.toString().trim().removeSuffix(";").trim() | |||
| statement.clear() | |||
| if (sql.isNotEmpty()) jdbc.execute(sql) | |||
| } | |||
| } | |||
| } | |||
| val tail = statement.toString().trim().removeSuffix(";").trim() | |||
| if (tail.isNotEmpty()) { | |||
| connection.createStatement().use { it.execute(tail) } | |||
| } | |||
| } | |||
| private fun databaseName(jdbcUrl: String): String? { | |||
| val path = jdbcUrl.substringBefore('?').substringAfter("://") | |||
| val slash = path.indexOf('/') | |||
| if (slash < 0 || slash == path.lastIndex) return null | |||
| return path.substring(slash + 1).trim().ifEmpty { null } | |||
| } | |||
| companion object { | |||
| private const val DEMO_DATABASE = "fpsms_demo" | |||
| private const val MARKER = "DEMO.coverage.v1" | |||
| private const val SCRIPT = "demo/demo-coverage.sql" | |||
| } | |||
| } | |||
| @@ -1,314 +1,12 @@ | |||
| package com.ffii.fpsms.modules.common.demo | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.beans.factory.ObjectProvider | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import org.springframework.boot.ApplicationArguments | |||
| import org.springframework.boot.ApplicationRunner | |||
| import org.springframework.context.annotation.Profile | |||
| import org.springframework.core.annotation.Order | |||
| import org.springframework.jdbc.core.JdbcTemplate | |||
| import org.springframework.scheduling.annotation.Scheduled | |||
| import org.springframework.stereotype.Component | |||
| import org.springframework.transaction.support.TransactionTemplate | |||
| import java.time.LocalDate | |||
| import java.time.temporal.ChronoUnit | |||
| import javax.sql.DataSource | |||
| /** | |||
| * Keeps fictional sandbox dates lined up with today. | |||
| * | |||
| * Seed data is inserted once, with expiry and delivery dates relative to that day. | |||
| * Without a later shift, lots expire and list screens that default to today stop showing the samples. | |||
| * This only runs on the db-demo profile, and only against fpsms_demo, and only rows coded DEMO-. | |||
| * Date shifting is retired. Demo dates come from the production copy and stay as copied. | |||
| * The shift helper remains for the existing unit test. | |||
| */ | |||
| @Component | |||
| @Profile("db-demo") | |||
| @Order(20) | |||
| class DemoDateKeeper( | |||
| @Value("\${spring.datasource.jdbc-url}") private val jdbcUrl: String, | |||
| private val dataSource: DataSource, | |||
| private val jdbcTemplate: JdbcTemplate, | |||
| private val transactionTemplate: TransactionTemplate, | |||
| private val sandboxSeeder: ObjectProvider<DemoSandboxSeeder>, | |||
| ) : ApplicationRunner { | |||
| private val log = LoggerFactory.getLogger(DemoDateKeeper::class.java) | |||
| override fun run(args: ApplicationArguments) { | |||
| refresh() | |||
| } | |||
| /** Catch a process that stays up past midnight. Startup and the truck board also call [refresh]. */ | |||
| @Scheduled(cron = "\${fpsms.demo.date-refresh-cron:0 10 0 * * *}") | |||
| fun scheduledRefresh() { | |||
| try { | |||
| refresh() | |||
| } catch (ex: Exception) { | |||
| log.error("Demo date refresh failed", ex) | |||
| } | |||
| } | |||
| @Synchronized | |||
| fun refresh() { | |||
| assertDemoDatabase() | |||
| transactionTemplate.executeWithoutResult { | |||
| val stored = readAnchor() | |||
| val anchor = stored ?: inferAnchorFromPurchaseOrder() | |||
| val today = LocalDate.now() | |||
| if (anchor == null) { | |||
| saveAnchor(today) | |||
| log.warn("Demo date keeper found no DEMO-PO-001 order date. Marked {} without shifting.", today) | |||
| return@executeWithoutResult | |||
| } | |||
| when (val shift = demoDateShiftDays(anchor, today)) { | |||
| is DemoDateShift.TooLarge -> { | |||
| log.error( | |||
| "Demo date anchor {} is {} days before today, over the {} day limit. Dates were not moved.", | |||
| anchor, | |||
| shift.days, | |||
| DEMO_DATE_SHIFT_MAX_DAYS, | |||
| ) | |||
| return@executeWithoutResult | |||
| } | |||
| is DemoDateShift.Forward -> { | |||
| val updated = shiftDemoDates(shift.days.toInt()) | |||
| // Truck tickets are also pinned to today/tomorrow by the seeder. Re-pin after the | |||
| // shift so a board that was already on today is not pushed further ahead. | |||
| sandboxSeeder.getIfAvailable()?.realignDashboardTrucks() | |||
| log.info( | |||
| "Demo dates moved forward {} day(s), {} row update(s). Anchor {} -> {}.", | |||
| shift.days, | |||
| updated, | |||
| anchor, | |||
| today, | |||
| ) | |||
| } | |||
| DemoDateShift.None -> Unit | |||
| } | |||
| val rolled = rollQuarterlyLedgerIntoCurrentYear() | |||
| if (rolled > 0) { | |||
| log.info("Rolled {} demo stock-ledger sample row(s) into the current year.", rolled) | |||
| } | |||
| if (stored != today) { | |||
| saveAnchor(today) | |||
| } | |||
| // After the shift, so a multi-day catch-up does not push brand-new "today" rows into the future. | |||
| sandboxSeeder.getIfAvailable()?.ensureDeliveryShowcase() | |||
| } | |||
| } | |||
| private fun assertDemoDatabase() { | |||
| val configuredName = databaseName(jdbcUrl) | |||
| val connectedName = dataSource.connection.use { it.catalog } | |||
| if (configuredName != DEMO_DATABASE || !DEMO_DATABASE.equals(connectedName, ignoreCase = true)) { | |||
| throw IllegalStateException( | |||
| "Demo date keeper refused to run. Configured database is '$configuredName', connected database is '$connectedName'.", | |||
| ) | |||
| } | |||
| } | |||
| private fun readAnchor(): LocalDate? { | |||
| val value = jdbcTemplate.query( | |||
| "SELECT value FROM settings WHERE name = ? ORDER BY id DESC LIMIT 1", | |||
| { rs, _ -> rs.getString("value") }, | |||
| ANCHOR_SETTING, | |||
| ).firstOrNull() ?: return null | |||
| return runCatching { LocalDate.parse(value) }.getOrNull() | |||
| } | |||
| /** DEMO-PO-001 is inserted with orderDate = the seed day, so an existing sandbox can catch up once. */ | |||
| private fun inferAnchorFromPurchaseOrder(): LocalDate? { | |||
| return jdbcTemplate.query( | |||
| "SELECT DATE(orderDate) AS anchorDate FROM purchase_order WHERE code = 'DEMO-PO-001' LIMIT 1", | |||
| { rs, _ -> rs.getDate("anchorDate")?.toLocalDate() }, | |||
| ).firstOrNull() | |||
| } | |||
| private fun saveAnchor(today: LocalDate) { | |||
| val value = today.toString() | |||
| val updated = jdbcTemplate.update( | |||
| "UPDATE settings SET value = ? WHERE name = ?", | |||
| value, | |||
| ANCHOR_SETTING, | |||
| ) | |||
| if (updated == 0) { | |||
| jdbcTemplate.update( | |||
| """ | |||
| INSERT INTO settings (name, value, category, type) | |||
| SELECT ?, ?, 'DEMO', 'date' | |||
| WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = ?) | |||
| """.trimIndent(), | |||
| ANCHOR_SETTING, | |||
| value, | |||
| ANCHOR_SETTING, | |||
| ) | |||
| } | |||
| } | |||
| private fun shiftDemoDates(days: Int): Int { | |||
| val statements = listOf( | |||
| """ | |||
| UPDATE purchase_order | |||
| SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY), | |||
| estimatedArrivalDate = DATE_ADD(estimatedArrivalDate, INTERVAL $days DAY), | |||
| completeDate = DATE_ADD(completeDate, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE delivery_order | |||
| SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY), | |||
| estimatedArrivalDate = DATE_ADD(estimatedArrivalDate, INTERVAL $days DAY), | |||
| completeDate = DATE_ADD(completeDate, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE delivery_order_pick_order | |||
| SET requiredDeliveryDate = DATE_ADD(requiredDeliveryDate, INTERVAL $days DAY), | |||
| ticketReleaseTime = DATE_ADD(ticketReleaseTime, INTERVAL $days DAY), | |||
| ticketCompleteDateTime = DATE_ADD(ticketCompleteDateTime, INTERVAL $days DAY) | |||
| WHERE ticketNo LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE do_pick_order_record | |||
| SET RequiredDeliveryDate = DATE_ADD(RequiredDeliveryDate, INTERVAL $days DAY), | |||
| ticket_release_time = DATE_ADD(ticket_release_time, INTERVAL $days DAY), | |||
| ticketCompleteDateTime = DATE_ADD(ticketCompleteDateTime, INTERVAL $days DAY) | |||
| WHERE ticket_no LIKE 'DEMO-%' | |||
| OR delivery_order_code LIKE 'DEMO-%' | |||
| OR pick_order_code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE do_replenishment | |||
| SET deliveryDate = DATE_ADD(deliveryDate, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE job_order | |||
| SET planStart = DATE_ADD(planStart, INTERVAL $days DAY), | |||
| actualStart = DATE_ADD(actualStart, INTERVAL $days DAY), | |||
| planEnd = DATE_ADD(planEnd, INTERVAL $days DAY), | |||
| actualEnd = DATE_ADD(actualEnd, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE productprocess | |||
| SET startTime = DATE_ADD(startTime, INTERVAL $days DAY), | |||
| endTime = DATE_ADD(endTime, INTERVAL $days DAY), | |||
| date = DATE_ADD(date, INTERVAL $days DAY) | |||
| WHERE productprocesscode LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE pick_order | |||
| SET targetDate = DATE_ADD(targetDate, INTERVAL $days DAY), | |||
| completeDate = DATE_ADD(completeDate, INTERVAL $days DAY), | |||
| releasedDate = DATE_ADD(releasedDate, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_in | |||
| SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY), | |||
| estimatedCompleteDate = DATE_ADD(estimatedCompleteDate, INTERVAL $days DAY), | |||
| completeDate = DATE_ADD(completeDate, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_in_line sil | |||
| JOIN stock_in si ON si.id = sil.stockInId | |||
| SET sil.expiryDate = DATE_ADD(sil.expiryDate, INTERVAL $days DAY), | |||
| sil.receiptDate = DATE_ADD(sil.receiptDate, INTERVAL $days DAY), | |||
| sil.productionDate = DATE_ADD(sil.productionDate, INTERVAL $days DAY), | |||
| sil.dnDate = DATE_ADD(sil.dnDate, INTERVAL $days DAY) | |||
| WHERE si.code LIKE 'DEMO-%' | |||
| OR sil.lotNo LIKE 'DEMO-LOT-%' | |||
| OR sil.itemNo LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE inventory_lot | |||
| SET expiryDate = DATE_ADD(expiryDate, INTERVAL $days DAY), | |||
| stockInDate = DATE_ADD(stockInDate, INTERVAL $days DAY), | |||
| productionDate = DATE_ADD(productionDate, INTERVAL $days DAY) | |||
| WHERE lotNo LIKE 'DEMO-LOT-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_out | |||
| SET completeDate = DATE_ADD(completeDate, INTERVAL $days DAY) | |||
| WHERE deliveryOrderCode LIKE 'DEMO-%' | |||
| OR consoPickOrderCode LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_out_line sol | |||
| JOIN stock_out so ON so.id = sol.stockOutId | |||
| SET sol.pickTime = DATE_ADD(sol.pickTime, INTERVAL $days DAY), | |||
| sol.startTime = DATE_ADD(sol.startTime, INTERVAL $days DAY), | |||
| sol.endTime = DATE_ADD(sol.endTime, INTERVAL $days DAY) | |||
| WHERE so.deliveryOrderCode LIKE 'DEMO-%' | |||
| OR so.consoPickOrderCode LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_take | |||
| SET planStart = DATE_ADD(planStart, INTERVAL $days DAY), | |||
| planEnd = DATE_ADD(planEnd, INTERVAL $days DAY), | |||
| actualStart = DATE_ADD(actualStart, INTERVAL $days DAY), | |||
| actualEnd = DATE_ADD(actualEnd, INTERVAL $days DAY) | |||
| WHERE code LIKE 'DEMO-%' | |||
| """, | |||
| """ | |||
| UPDATE stocktakerecord r | |||
| JOIN stock_take st ON st.id = r.stockTakeId | |||
| SET r.date = DATE_ADD(r.date, INTERVAL $days DAY), | |||
| r.approverTime = DATE_ADD(r.approverTime, INTERVAL $days DAY), | |||
| r.stockTakeStartTime = DATE_ADD(r.stockTakeStartTime, INTERVAL $days DAY), | |||
| r.stockTakeEndTime = DATE_ADD(r.stockTakeEndTime, INTERVAL $days DAY), | |||
| r.expiredDate = DATE_ADD(r.expiredDate, INTERVAL $days DAY) | |||
| WHERE st.code LIKE 'DEMO-%' | |||
| OR r.itemCode LIKE 'DEMO-%' | |||
| OR r.lotNo LIKE 'DEMO-LOT-%' | |||
| """, | |||
| """ | |||
| UPDATE stock_ledger | |||
| SET date = DATE_ADD(date, INTERVAL $days DAY), | |||
| modified = DATE_ADD(modified, INTERVAL $days DAY) | |||
| WHERE itemCode LIKE 'DEMO-%' | |||
| AND (type IS NULL OR type <> 'OUT') | |||
| """, | |||
| ) | |||
| return statements.sumOf { jdbcTemplate.update(it.trimIndent()) } | |||
| } | |||
| /** | |||
| * January / April / July / September ledger samples are fixed calendar days, not offsets from the seed day. | |||
| * Leave them in those months, and move them into the new year when the calendar rolls over. | |||
| */ | |||
| private fun rollQuarterlyLedgerIntoCurrentYear(): Int { | |||
| return jdbcTemplate.update( | |||
| """ | |||
| UPDATE stock_ledger | |||
| SET date = DATE_ADD(date, INTERVAL (YEAR(CURDATE()) - YEAR(date)) YEAR), | |||
| modified = CASE | |||
| WHEN modified IS NULL THEN NULL | |||
| ELSE DATE_ADD(modified, INTERVAL (YEAR(CURDATE()) - YEAR(modified)) YEAR) | |||
| END | |||
| WHERE itemCode LIKE 'DEMO-%' | |||
| AND type = 'OUT' | |||
| AND date IS NOT NULL | |||
| AND YEAR(date) < YEAR(CURDATE()) | |||
| """.trimIndent(), | |||
| ) | |||
| } | |||
| private fun databaseName(jdbcUrl: String): String? { | |||
| val path = jdbcUrl.substringBefore('?').substringAfter("://") | |||
| val slash = path.indexOf('/') | |||
| if (slash < 0 || slash == path.lastIndex) return null | |||
| return path.substring(slash + 1).trim().ifEmpty { null } | |||
| } | |||
| companion object { | |||
| private const val DEMO_DATABASE = "fpsms_demo" | |||
| const val ANCHOR_SETTING = "DEMO.dates.anchor" | |||
| } | |||
| } | |||
| internal const val DEMO_DATE_SHIFT_MAX_DAYS = 2000L | |||
| @@ -0,0 +1,26 @@ | |||
| package com.ffii.fpsms.modules.common.demo | |||
| /** | |||
| * Floor and bin labels stored in the anonymized demo database. | |||
| * A/F was 2/F, B/F was 4/F, C/F was 1/F, D/F was 3/F. | |||
| */ | |||
| object DemoFloors { | |||
| const val FLOOR_A = "A/F" | |||
| const val FLOOR_A_KEY = "AF" | |||
| const val FLOOR_B = "B/F" | |||
| const val FLOOR_B_KEY = "BF" | |||
| const val FLOOR_C = "C/F" | |||
| const val FLOOR_C_KEY = "CF" | |||
| const val FLOOR_D = "D/F" | |||
| const val FLOOR_D_KEY = "DF" | |||
| const val LOC_HOLD = "LOC-HOLD" | |||
| const val LOC_WIP = "LOC-WIP" | |||
| const val LOC_PREFIX = "LOC-" | |||
| const val LABEL_A = "LABEL-A" | |||
| const val LABEL_B = "LABEL-B" | |||
| const val SETTING_SUPPLIERS_A = "DO.floor.suppliers.AF" | |||
| const val SETTING_SUPPLIERS_B = "DO.floor.suppliers.BF" | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.common.internalSetup | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import org.springframework.http.HttpStatus | |||
| import org.springframework.http.ResponseEntity | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| @@ -283,7 +284,7 @@ fun printLotStockInLabelsByStoreIds( | |||
| val printQty = (request["printQty"] as? Number)?.toInt() ?: 1 | |||
| val fromIndex = (request["fromIndex"] as? Number)?.toInt() | |||
| val toIndex = (request["toIndex"] as? Number)?.toInt() | |||
| // storeIds: ["2F","4F"] | |||
| // storeIds: [DemoFloors.FLOOR_A_KEY,DemoFloors.FLOOR_B_KEY] | |||
| val storeIds = (request["storeIds"] as? List<*>)?.mapNotNull { it?.toString() } ?: emptyList() | |||
| if (printerId == null) { | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.utils.PdfUtils | |||
| import org.springframework.context.annotation.Lazy | |||
| import com.ffii.core.utils.ZebraPrinterUtil | |||
| @@ -847,7 +848,7 @@ open class DeliveryOrderService( | |||
| val result = jdbcDao.queryForList(sql, mapOf("lotId" to lotId)) | |||
| if (result.isNotEmpty()) { | |||
| val orderString = result.first()["warehouseOrder"] as? String | |||
| // Extract the last 3 digits from format like "2F-001" | |||
| // Extract the last 3 digits from format like "${DemoFloors.FLOOR_A_KEY}-001" | |||
| orderString?.let { order -> | |||
| // Try to extract numeric part after the last "-" | |||
| val parts = order.split("-") | |||
| @@ -1107,10 +1108,10 @@ open class DeliveryOrderService( | |||
| println(" DEBUG: Found ${trucks.size} trucks for shop $shopId") | |||
| val preferredStoreId = when (preferredFloor) { | |||
| "2F" -> "2F" | |||
| "4F" -> "4F" | |||
| "3F" -> "3F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A_KEY | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| // 优先选择匹配的 truck | |||
| val selectedTruck = if (trucks.size > 1) { | |||
| @@ -1127,10 +1128,10 @@ open class DeliveryOrderService( | |||
| // 检查 truck 和 preferredFloor 是否匹配 | |||
| val truckStoreId = truck?.storeId | |||
| val expectedStoreId = when (preferredFloor) { | |||
| "2F" -> "2F" | |||
| "4F" -> "4F" | |||
| "3F" -> "3F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A_KEY | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val defaultTruckId = 5577L | |||
| @@ -1453,13 +1454,13 @@ open class DeliveryOrderService( | |||
| } | |||
| val sortedLines = when (doStoreFloorKey) { | |||
| "2F" -> exportLines.sortedWith( | |||
| DemoFloors.FLOOR_A_KEY -> exportLines.sortedWith( | |||
| compareBy( | |||
| { row -> itemsById[row.line.itemId]?.item_Order ?: Int.MAX_VALUE }, | |||
| { row -> row.line.itemNo }, | |||
| ), | |||
| ) | |||
| "4F" -> exportLines.sortedBy { row -> | |||
| DemoFloors.FLOOR_B_KEY -> exportLines.sortedBy { row -> | |||
| row.line.itemId?.let { getWarehouseOrderByItemId(it) } ?: Int.MAX_VALUE | |||
| } | |||
| else -> exportLines.sortedBy { row -> | |||
| @@ -2233,10 +2234,10 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| val preferredStoreId = when (preferredFloor) { | |||
| "2F" -> "2F" | |||
| "4F" -> "4F" | |||
| "3F" -> "3F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A_KEY | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | |||
| @@ -2244,7 +2245,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| if (matchedTrucks.isEmpty()) { | |||
| null | |||
| } else { | |||
| if (preferredStoreId == "4F" && matchedTrucks.size > 1) { | |||
| if (preferredStoreId == DemoFloors.FLOOR_B_KEY && matchedTrucks.size > 1) { | |||
| deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> | |||
| val targetDate = estimatedArrivalDate.toLocalDate() | |||
| val dayOfWeek = targetDate.dayOfWeek | |||
| @@ -2260,7 +2261,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| } | |||
| println(" DEBUG: DO ${deliveryOrder.id} - Target date: $targetDate ($dayAbbr), Shop: $shopId") | |||
| println(" DEBUG: Found ${matchedTrucks.size} matched 4F trucks") | |||
| println(" DEBUG: Found ${matchedTrucks.size} matched ${DemoFloors.FLOOR_B_KEY} trucks") | |||
| val dayMatchedTrucks = matchedTrucks.filter { | |||
| it.truckLanceCode?.contains(dayAbbr, ignoreCase = true) == true | |||
| @@ -2398,17 +2399,17 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| val preferredStoreId = when (preferredFloor) { | |||
| "2F" -> "2F" | |||
| "4F" -> "4F" | |||
| "3F" -> "3F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A_KEY | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val matchedTrucks = trucks.filter { it.storeId == preferredStoreId } | |||
| if (matchedTrucks.isEmpty()) { | |||
| null | |||
| } else { | |||
| if (preferredStoreId == "4F" && matchedTrucks.size > 1) { | |||
| if (preferredStoreId == DemoFloors.FLOOR_B_KEY && matchedTrucks.size > 1) { | |||
| deliveryOrder.estimatedArrivalDate?.let { estimatedArrivalDate -> | |||
| val targetDate2 = estimatedArrivalDate.toLocalDate() | |||
| val dayAbbr = when (targetDate2.dayOfWeek) { | |||
| @@ -2570,7 +2571,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| preferredFloor: String, | |||
| dayAbbr: String, | |||
| ): Truck? { | |||
| if (preferredFloor == "2F") { | |||
| if (preferredFloor == DemoFloors.FLOOR_A_KEY) { | |||
| return truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| .filter { it.storeId == preferredFloor } | |||
| .minByOrNull { it.departureTime ?: LocalTime.of(23, 59, 59) } | |||
| @@ -2607,8 +2608,8 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| // 1) Check if this DO is a 4F order (your “all items on 4F, no 2F” rule) | |||
| val sql = """ | |||
| SELECT | |||
| COUNT(DISTINCT CASE WHEN i.store_id = '4F' THEN dol.itemId END) AS count_4f, | |||
| COUNT(DISTINCT CASE WHEN i.store_id = '2F' THEN dol.itemId END) AS count_2f, | |||
| COUNT(DISTINCT CASE WHEN i.store_id = '${DemoFloors.FLOOR_B_KEY}' THEN dol.itemId END) AS count_4f, | |||
| COUNT(DISTINCT CASE WHEN i.store_id = '${DemoFloors.FLOOR_A_KEY}' THEN dol.itemId END) AS count_2f, | |||
| COUNT(DISTINCT dol.itemId) AS total_items | |||
| FROM delivery_order_line dol | |||
| INNER JOIN items i ON i.id = dol.itemId AND i.deleted = 0 | |||
| @@ -2628,12 +2629,12 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||
| // 2) Find 4F trucks for that shop on that weekday | |||
| val dayAbbr = getDayOfWeekAbbr(eta) | |||
| val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, "4F", dayAbbr) | |||
| val trucks = truckRepository.findByShopIdAndStoreIdAndDayOfWeek(shopId, DemoFloors.FLOOR_B_KEY, dayAbbr) | |||
| if (trucks.isEmpty()) { | |||
| // Problem DO: 4F order but no matching 4F truck that day | |||
| val availableTrucksAll4F = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| .filter { it.storeId == "4F" } | |||
| .filter { it.storeId == DemoFloors.FLOOR_B_KEY } | |||
| val truckDtos = availableTrucksAll4F.map { t -> | |||
| TruckInfoDto( | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.settings.entity.SettingsRepository | |||
| import org.springframework.stereotype.Service | |||
| import java.util.Locale | |||
| @@ -10,11 +11,11 @@ open class DoFloorSupplierSettingsService( | |||
| private val settingsRepository: SettingsRepository, | |||
| ) { | |||
| companion object { | |||
| private const val SETTING_DO_FLOOR_SUPPLIERS_2F = "DO.floor.suppliers.2F" | |||
| private const val SETTING_DO_FLOOR_SUPPLIERS_4F = "DO.floor.suppliers.4F" | |||
| private const val SETTING_DO_FLOOR_SUPPLIERS_2F = DemoFloors.SETTING_SUPPLIERS_A | |||
| private const val SETTING_DO_FLOOR_SUPPLIERS_4F = DemoFloors.SETTING_SUPPLIERS_B | |||
| private val DEFAULT_SUPPLIERS_2F = listOf("DEMO-SUP") | |||
| private val DEFAULT_SUPPLIERS_4F = listOf("DEMO-SUP") | |||
| private val DEFAULT_SUPPLIERS_2F = emptyList<String>() | |||
| private val DEFAULT_SUPPLIERS_4F = emptyList<String>() | |||
| } | |||
| open fun supplierCodesFromSetting(settingName: String, defaultList: List<String>): List<String> { | |||
| @@ -37,8 +38,8 @@ open class DoFloorSupplierSettingsService( | |||
| val (codes2F, codes4F) = loadDoFloorSupplierLists() | |||
| return when { | |||
| f.isEmpty() || f == "ALL" || f == "All" -> (codes2F + codes4F).distinct() | |||
| f == "2F" -> codes2F | |||
| f == "4F" -> codes4F | |||
| f == DemoFloors.FLOOR_A_KEY -> codes2F | |||
| f == DemoFloors.FLOOR_B_KEY -> codes4F | |||
| else -> (codes2F + codes4F).distinct() | |||
| } | |||
| } | |||
| @@ -50,10 +51,10 @@ open class DoFloorSupplierSettingsService( | |||
| suppliers4F: List<String>, | |||
| ): String { | |||
| val code = supplierCode?.trim().orEmpty() | |||
| if (code.isEmpty()) return "2F" | |||
| if (suppliers4F.contains(code)) return "4F" | |||
| if (suppliers2F.contains(code)) return "2F" | |||
| return "2F" | |||
| if (code.isEmpty()) return DemoFloors.FLOOR_A_KEY | |||
| if (suppliers4F.contains(code)) return DemoFloors.FLOOR_B_KEY | |||
| if (suppliers2F.contains(code)) return DemoFloors.FLOOR_A_KEY | |||
| return DemoFloors.FLOOR_A_KEY | |||
| } | |||
| /** DO 揀貨建議:名單外供應商不限制 2F/4F。 */ | |||
| @@ -64,8 +65,8 @@ open class DoFloorSupplierSettingsService( | |||
| ): String? { | |||
| val code = supplierCode?.trim().orEmpty() | |||
| if (code.isEmpty()) return null | |||
| if (suppliers4F.contains(code)) return "4F" | |||
| if (suppliers2F.contains(code)) return "2F" | |||
| if (suppliers4F.contains(code)) return DemoFloors.FLOOR_B_KEY | |||
| if (suppliers2F.contains(code)) return DemoFloors.FLOOR_A_KEY | |||
| return null | |||
| } | |||
| @@ -79,16 +80,16 @@ open class DoFloorSupplierSettingsService( | |||
| val raw = dbStoreId?.trim()?.takeIf { it.isNotEmpty() } | |||
| if (raw != null) { | |||
| return when (raw.replace("/", "").uppercase(Locale.ROOT)) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> raw | |||
| } | |||
| } | |||
| val (s2, s4) = loadDoFloorSupplierLists() | |||
| val any4F = supplierCodes.any { code -> | |||
| preferredStoreFloorForSupplier(code, s2, s4) == "4F" | |||
| preferredStoreFloorForSupplier(code, s2, s4) == DemoFloors.FLOOR_B_KEY | |||
| } | |||
| return if (any4F) "4/F" else "2/F" | |||
| return if (any4F) DemoFloors.FLOOR_B else DemoFloors.FLOOR_A | |||
| } | |||
| data class SqlPreferredFloorCases( | |||
| @@ -106,12 +107,13 @@ open class DoFloorSupplierSettingsService( | |||
| val in4 = joinSqlInList(s4f) | |||
| val in2 = joinSqlInList(s2f) | |||
| val floor = | |||
| "CASE WHEN $codeExpr IN ($in4) THEN '4F' WHEN $codeExpr IN ($in2) THEN '2F' ELSE NULL END" | |||
| "CASE WHEN $codeExpr IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' WHEN $codeExpr IN ($in2) THEN '${DemoFloors.FLOOR_A_KEY}' ELSE NULL END" | |||
| val storeId = | |||
| "CASE WHEN $codeExpr IN ($in4) THEN 4 WHEN $codeExpr IN ($in2) THEN 2 ELSE NULL END" | |||
| "CASE WHEN $codeExpr IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' WHEN $codeExpr IN ($in2) THEN '${DemoFloors.FLOOR_A_KEY}' ELSE NULL END" | |||
| return SqlPreferredFloorCases(floorStringCase = floor, storeIdNumericCase = storeId) | |||
| } | |||
| private fun joinSqlInList(codes: List<String>): String = | |||
| codes.joinToString(", ") { "'" + it.replace("'", "''") + "'" } | |||
| if (codes.isEmpty()) "NULL" | |||
| else codes.joinToString(", ") { "'" + it.replace("'", "''") + "'" } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository | |||
| @@ -33,8 +34,8 @@ class DoPickOrderAssignmentService( | |||
| // 转换 storeId 格式 | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> request.storeId | |||
| } | |||
| println(" DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus | |||
| @@ -24,8 +25,8 @@ class DoPickOrderQueryService( | |||
| val targetDate = requiredDate ?: LocalDate.now() | |||
| val actualStoreId = when (storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> storeId | |||
| } | |||
| @@ -79,7 +80,7 @@ class DoPickOrderQueryService( | |||
| DoPickOrderSummaryItem( | |||
| truckDepartureTime = it.truckDepartureTime, | |||
| truckLanceCode = it.truckLanceCode, | |||
| loadingSequence = if (actualStoreId == "4/F") it.loadingSequence else null, | |||
| loadingSequence = if (actualStoreId == DemoFloors.FLOOR_B) it.loadingSequence else null, | |||
| handledBy = it.handledBy, | |||
| handlerName = it.handlerName | |||
| ) | |||
| @@ -87,7 +88,7 @@ class DoPickOrderQueryService( | |||
| DoPickOrderSummaryItem( | |||
| truckDepartureTime = it.truckDepartureTime, | |||
| truckLanceCode = it.truckLanceCode, | |||
| loadingSequence = if (actualStoreId == "4/F") it.loadingSequence else null, | |||
| loadingSequence = if (actualStoreId == DemoFloors.FLOOR_B) it.loadingSequence else null, | |||
| handledBy = it.handledBy, | |||
| handlerName = null | |||
| ) | |||
| @@ -97,7 +98,7 @@ class DoPickOrderQueryService( | |||
| val defaultTruck = truckRepository.findById(defaultTruckId).orElse(null) | |||
| val defaultTruckLaneCode = defaultTruck?.truckLanceCode ?: "" | |||
| val grouped = if (actualStoreId == "4/F") { | |||
| val grouped = if (actualStoreId == DemoFloors.FLOOR_B) { | |||
| allRecords.groupBy { Triple(it.truckDepartureTime, it.truckLanceCode, it.loadingSequence) } | |||
| } else { | |||
| allRecords.groupBy { Pair(it.truckDepartureTime, it.truckLanceCode) } | |||
| @@ -110,7 +111,7 @@ class DoPickOrderQueryService( | |||
| LaneBtn( | |||
| truckLanceCode = list.first().truckLanceCode ?: "", | |||
| loadingSequence = if (actualStoreId == "4/F") list.first().loadingSequence else null, | |||
| loadingSequence = if (actualStoreId == DemoFloors.FLOOR_B) list.first().loadingSequence else null, | |||
| unassigned = list.count { it.handledBy == null }, | |||
| total = list.size, | |||
| handlerName = mergedHandlerNames.ifBlank { null } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.m18.entity.M18DataLogRepository | |||
| import com.ffii.fpsms.m18.service.M18DataLogService | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrder | |||
| @@ -45,10 +46,8 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord | |||
| import org.springframework.beans.factory.ObjectProvider | |||
| import org.springframework.context.annotation.Lazy | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||
| import com.ffii.fpsms.modules.common.demo.DemoSandboxSeeder | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.TicketReleaseTableResponse | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.TruckScheduleDashboardResponse | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.SearchDeliveryOrderInfoRequest | |||
| @@ -67,7 +66,6 @@ open class DoPickOrderService( | |||
| @Lazy private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, | |||
| private val pickOrderLineRepository: PickOrderLineRepository, | |||
| private val demoSandboxSeeder: ObjectProvider<DemoSandboxSeeder>, | |||
| ) { | |||
| open fun findReleasedDoPickOrders(): List<DoPickOrder> { | |||
| return doPickOrderRepository.findByTicketStatusIn( | |||
| @@ -385,8 +383,8 @@ open class DoPickOrderService( | |||
| //println(" DEBUG: Getting summary for store=$storeId, date=$targetDate") | |||
| val actualStoreId = when (storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> storeId | |||
| } | |||
| @@ -525,8 +523,8 @@ open class DoPickOrderService( | |||
| // 转换 storeId 格式:'2/F' -> '2F/F', '4/F' -> '4F/F' | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" // 保持原格式 | |||
| "4/F" -> "4/F" // 保持原格式 | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A // 保持原格式 | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B // 保持原格式 | |||
| else -> request.storeId | |||
| } | |||
| println(" DEBUG: assignByLane - Converting storeId from '${request.storeId}' to '$actualStoreId'") | |||
| @@ -624,15 +622,15 @@ open class DoPickOrderService( | |||
| val results = jdbcDao.queryForList(sql, mapOf("doOrderId" to doOrderId)) | |||
| return if (results.isNotEmpty()) { | |||
| val storeId = results.first()["store_id"] as? String ?: "2F" | |||
| val storeId = results.first()["store_id"] as? String ?: DemoFloors.FLOOR_A_KEY | |||
| when (storeId) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| "3F" -> "3/F" | |||
| else -> "2/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D | |||
| else -> DemoFloors.FLOOR_A | |||
| } | |||
| } else { | |||
| "2/F" // 默认值 | |||
| DemoFloors.FLOOR_A // 默认值 | |||
| } | |||
| } | |||
| @@ -640,11 +638,11 @@ open class DoPickOrderService( | |||
| if (shopId == null) return null | |||
| val storeId= when (storeId) { | |||
| "2F" -> "2F" | |||
| "4F" -> "4F" | |||
| "3F" -> "3F" | |||
| "3/F" -> "3F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A_KEY | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| DemoFloors.FLOOR_D_KEY -> DemoFloors.FLOOR_D_KEY | |||
| DemoFloors.FLOOR_D -> DemoFloors.FLOOR_D_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val defaultTruckId = 5577L | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| @@ -850,7 +848,6 @@ open class DoPickOrderService( | |||
| * Groups DoPickOrder and DoPickOrderRecord data to provide summary statistics. | |||
| */ | |||
| open fun getTruckScheduleDashboard(targetDate: LocalDate): List<TruckScheduleDashboardResponse> { | |||
| demoSandboxSeeder.getIfAvailable()?.alignDashboardTrucksToToday() | |||
| // Source of truth: delivery_order_pick_order (+ linked pick_order / pick_order_line) | |||
| // | |||
| // NOTE: delivery_order_pick_order 沒有 truckId 欄位;dashboard 的 truckId 目前僅作為展示/鍵值用途, | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import org.springframework.stereotype.Service | |||
| @@ -163,18 +164,18 @@ class DoReleaseCoordinatorService( | |||
| pf.preferred_floor, | |||
| $dayOfWeekSql AS day_of_week_abbr, | |||
| CASE | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = 4 | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = '${DemoFloors.FLOOR_B_KEY}' | |||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||
| COALESCE( | |||
| (SELECT t.DepartureTime FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.DepartureTime FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.DepartureTime FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| @@ -185,7 +186,7 @@ class DoReleaseCoordinatorService( | |||
| COALESCE( | |||
| (SELECT t.DepartureTime FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.DepartureTime FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| @@ -201,18 +202,18 @@ class DoReleaseCoordinatorService( | |||
| ) | |||
| END AS selected_departure_time, | |||
| CASE | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = 4 | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = '${DemoFloors.FLOOR_B_KEY}' | |||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||
| COALESCE( | |||
| (SELECT t.TruckLanceCode FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.TruckLanceCode FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.TruckLanceCode FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| @@ -223,7 +224,7 @@ class DoReleaseCoordinatorService( | |||
| COALESCE( | |||
| (SELECT t.TruckLanceCode FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.TruckLanceCode FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| @@ -241,13 +242,13 @@ class DoReleaseCoordinatorService( | |||
| COALESCE( | |||
| (SELECT t.LoadingSequence FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND (pf.preferred_store_id != 4 OR do.estimatedArrivalDate IS NULL OR | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND (pf.preferred_store_id != '${DemoFloors.FLOOR_B_KEY}' OR do.estimatedArrivalDate IS NULL OR | |||
| t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%')) | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.LoadingSequence FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.LoadingSequence FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| @@ -276,7 +277,7 @@ class DoReleaseCoordinatorService( | |||
| LPAD( | |||
| ROW_NUMBER() OVER ( | |||
| PARTITION BY DATE(dpo2.RequiredDeliveryDate), | |||
| IF(dpo2.store_id IS NULL OR TRIM(dpo2.store_id) = '', 'XF', COALESCE(ts.preferred_floor, '2F')) | |||
| IF(dpo2.store_id IS NULL OR TRIM(dpo2.store_id) = '', 'XF', COALESCE(ts.preferred_floor, '${DemoFloors.FLOOR_A_KEY}')) | |||
| ORDER BY COALESCE(ts.selected_departure_time, '23:59:59'), | |||
| COALESCE(ts.selected_truck_lance, 'ZZ'), | |||
| COALESCE(ts.loading_sequence, 999), | |||
| @@ -324,22 +325,22 @@ class DoReleaseCoordinatorService( | |||
| pf.preferred_store_id, | |||
| $dayOfWeekSql AS day_of_week_abbr, | |||
| CASE | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = 4 | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = '${DemoFloors.FLOOR_B_KEY}' | |||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||
| COALESCE( | |||
| (SELECT t.DepartureTime | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId | |||
| AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.DepartureTime | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.DepartureTime | |||
| @@ -354,7 +355,7 @@ class DoReleaseCoordinatorService( | |||
| (SELECT t.DepartureTime | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.DepartureTime | |||
| @@ -375,22 +376,22 @@ class DoReleaseCoordinatorService( | |||
| ) | |||
| END AS selected_departure_time, | |||
| CASE | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND t.Store_id = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = 4 | |||
| WHEN (SELECT COUNT(*) FROM truck t WHERE t.shopId = do.shopId AND t.deleted = 0 AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id) > 1 | |||
| AND pf.preferred_store_id = '${DemoFloors.FLOOR_B_KEY}' | |||
| AND do.estimatedArrivalDate IS NOT NULL THEN | |||
| COALESCE( | |||
| (SELECT t.TruckLanceCode | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId | |||
| AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%') | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.TruckLanceCode | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.TruckLanceCode | |||
| @@ -405,7 +406,7 @@ class DoReleaseCoordinatorService( | |||
| (SELECT t.TruckLanceCode | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC | |||
| LIMIT 1), | |||
| (SELECT t.TruckLanceCode | |||
| @@ -429,14 +430,14 @@ class DoReleaseCoordinatorService( | |||
| (SELECT t.LoadingSequence | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND (pf.preferred_store_id != 4 OR do.estimatedArrivalDate IS NULL OR | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| AND (pf.preferred_store_id != '${DemoFloors.FLOOR_B_KEY}' OR do.estimatedArrivalDate IS NULL OR | |||
| t.TruckLanceCode LIKE CONCAT('%', $dayOfWeekSql, '%')) | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.LoadingSequence | |||
| FROM truck t | |||
| WHERE t.shopId = do.shopId AND t.deleted = 0 | |||
| AND t.Store_id = pf.preferred_store_id | |||
| AND REPLACE(IFNULL(t.Store_id, ''), '/', '') = pf.preferred_store_id | |||
| ORDER BY t.DepartureTime ASC LIMIT 1), | |||
| (SELECT t.LoadingSequence | |||
| FROM truck t | |||
| @@ -698,9 +699,9 @@ class DoReleaseCoordinatorService( | |||
| val storeId: String? = when { | |||
| first.usedDefaultTruck != false -> null | |||
| else -> when (first.preferredFloor) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| else -> "2/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> DemoFloors.FLOOR_A | |||
| } | |||
| } | |||
| @@ -776,15 +777,15 @@ class DoReleaseCoordinatorService( | |||
| preferredFloor: String?, | |||
| ): Boolean { | |||
| val target = when (preferredFloor?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "")) { | |||
| "4F" -> "4F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val id = dpo.id ?: return true | |||
| val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(id) | |||
| if (lines.isEmpty()) return true | |||
| val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists() | |||
| fun floorOf(supplierCode: String?): String = | |||
| if (!supplierCode.isNullOrBlank() && s4f.contains(supplierCode.trim())) "4F" else "2F" | |||
| if (!supplierCode.isNullOrBlank() && s4f.contains(supplierCode.trim())) DemoFloors.FLOOR_B_KEY else DemoFloors.FLOOR_A_KEY | |||
| for (line in lines) { | |||
| val doId = line.doOrderId ?: continue | |||
| val dorder = deliveryOrderRepository.findById(doId).orElse(null) ?: continue | |||
| @@ -884,9 +885,9 @@ class DoReleaseCoordinatorService( | |||
| val storeId: String? = when { | |||
| result.usedDefaultTruck!=false -> null | |||
| else -> when (result.preferredFloor) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| else -> "2/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> DemoFloors.FLOOR_A | |||
| } | |||
| } | |||
| @@ -1011,7 +1012,7 @@ class DoReleaseCoordinatorService( | |||
| // 2. 按日期和 storeId 分组 | |||
| val grouped = tempTickets.groupBy { ticket -> | |||
| val date = ticket.requiredDeliveryDate | |||
| val storeId = ticket.storeId?.replace("/", "") ?: "2F" | |||
| val storeId = ticket.storeId?.replace("/", "") ?: DemoFloors.FLOOR_A_KEY | |||
| Pair(date, storeId) | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByDeliveryOrderPickOrderIdRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByLaneRequest | |||
| @@ -108,8 +109,8 @@ open class DoWorkbenchDopoAssignmentService( | |||
| userBusyResponse(request.userId)?.let { return it } | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> request.storeId | |||
| } | |||
| println(" DEBUG: assignByLaneForWorkbench storeId=$actualStoreId date=${request.requiredDate} lane=${request.truckLanceCode} dep=${request.truckDepartureTime} seq=${request.loadingSequence}") | |||
| @@ -156,7 +157,7 @@ open class DoWorkbenchDopoAssignmentService( | |||
| val candidateLimit = 50 | |||
| val maxRounds = 3 | |||
| val shouldOrderBySequence = actualStoreId == "2/F" && request.loadingSequence == null | |||
| val shouldOrderBySequence = actualStoreId == DemoFloors.FLOOR_A && request.loadingSequence == null | |||
| if (shouldOrderBySequence) { | |||
| sql.append(" ORDER BY dop.requiredDeliveryDate ASC, dop.truckDepartureTime ASC, dop.loadingSequence ASC, dop.id ASC LIMIT $candidateLimit ") | |||
| } else { | |||
| @@ -220,8 +221,8 @@ open class DoWorkbenchDopoAssignmentService( | |||
| userBusyResponse(request.userId)?.let { return it } | |||
| val actualStoreId = when (request.storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> request.storeId | |||
| } | |||
| println(" DEBUG: assignByLaneForWorkbenchV1 storeId=$actualStoreId date=${request.requiredDate} lane=${request.truckLanceCode} dep=${request.truckDepartureTime} seq=${request.loadingSequence}") | |||
| @@ -262,7 +263,7 @@ open class DoWorkbenchDopoAssignmentService( | |||
| } else { | |||
| sql.append(WorkbenchReleaseTypeSupport.assignFilterSql(request.releaseType)) | |||
| } | |||
| val shouldOrderBySequenceV1 = actualStoreId == "2/F" && request.loadingSequence == null | |||
| val shouldOrderBySequenceV1 = actualStoreId == DemoFloors.FLOOR_A && request.loadingSequence == null | |||
| if (shouldOrderBySequenceV1) { | |||
| sql.append(" ORDER BY dop.requiredDeliveryDate ASC, dop.truckDepartureTime ASC, dop.loadingSequence ASC, dop.id ASC LIMIT 1 ") | |||
| } else { | |||
| @@ -1,6 +1,7 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.bag.entity.BagRepository | |||
| import com.ffii.fpsms.modules.bag.service.BagService | |||
| @@ -699,8 +700,8 @@ return MessageResponse( | |||
| open fun getDeliveryOrderPickOrderSummaryByStore(storeId: String, requiredDate: LocalDate?, releaseType: String): StoreLaneSummary { | |||
| val targetDate = requiredDate ?: LocalDate.now() | |||
| val actualStoreId = when (storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> storeId | |||
| } | |||
| val rt = releaseType.lowercase() | |||
| @@ -805,7 +806,7 @@ return MessageResponse( | |||
| // 2/F:與 [DoPickOrderQueryService.getSummaryByStore] 一致,只按「出發時間 + 車線」合併,不按 loadingSequence 拆開。 | |||
| // 否則同一車線會出現多顆 (1/1) 按鈕,與 normal FG 畫面不一致。 | |||
| val laneAggsForUi = if (actualStoreId == "2/F") { | |||
| val laneAggsForUi = if (actualStoreId == DemoFloors.FLOOR_A) { | |||
| laneAggs | |||
| .groupBy { it.sortTime to it.lance } | |||
| .values | |||
| @@ -841,7 +842,7 @@ return MessageResponse( | |||
| .map { | |||
| LaneBtn( | |||
| truckLanceCode = it.lance, | |||
| loadingSequence = if (actualStoreId == "4/F") it.loadingSequence else null, | |||
| loadingSequence = if (actualStoreId == DemoFloors.FLOOR_B) it.loadingSequence else null, | |||
| unassigned = it.unassigned, | |||
| total = it.total, | |||
| handlerName = it.handlerName, | |||
| @@ -904,8 +905,8 @@ return MessageResponse( | |||
| val defaultTruckLaneCode = (defaultTruck?.truckLanceCode ?: "車線-X").trim() | |||
| val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists() | |||
| val in4 = s4f.joinToString(", ") { "'" + it.replace("'", "''") + "'" } | |||
| .ifEmpty { "'__NO_4F_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '4F' ELSE '2F' END" | |||
| .ifEmpty { "'__NO_${DemoFloors.FLOOR_B_KEY}_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END" | |||
| val etraReleaseFilter = WorkbenchReleaseTypeSupport.etraFamilySql("dop.releaseType") | |||
| val sql = """ | |||
| @@ -943,7 +944,7 @@ return MessageResponse( | |||
| INNER JOIN delivery_order d ON d.id = po.doId AND d.deleted = 0 | |||
| LEFT JOIN shop s ON s.id = d.supplierId AND s.deleted = 0 | |||
| WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0 | |||
| AND ($floorExpr) = '4F' | |||
| AND ($floorExpr) = '${DemoFloors.FLOOR_B_KEY}' | |||
| ) | |||
| AND NOT EXISTS ( | |||
| SELECT 1 | |||
| @@ -951,16 +952,16 @@ return MessageResponse( | |||
| INNER JOIN delivery_order d2 ON d2.id = po2.doId AND d2.deleted = 0 | |||
| LEFT JOIN shop s2 ON s2.id = d2.supplierId AND s2.deleted = 0 | |||
| WHERE po2.deliveryOrderPickOrderId = dop.id AND po2.deleted = 0 | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '4F' ELSE '2F' END) <> '4F' | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END) <> '${DemoFloors.FLOOR_B_KEY}' | |||
| ) | |||
| THEN '4/F' | |||
| THEN '${DemoFloors.FLOOR_B}' | |||
| WHEN EXISTS ( | |||
| SELECT 1 | |||
| FROM pick_order po | |||
| INNER JOIN delivery_order d ON d.id = po.doId AND d.deleted = 0 | |||
| LEFT JOIN shop s ON s.id = d.supplierId AND s.deleted = 0 | |||
| WHERE po.deliveryOrderPickOrderId = dop.id AND po.deleted = 0 | |||
| AND ($floorExpr) = '2F' | |||
| AND ($floorExpr) = '${DemoFloors.FLOOR_A_KEY}' | |||
| ) | |||
| AND NOT EXISTS ( | |||
| SELECT 1 | |||
| @@ -968,9 +969,9 @@ return MessageResponse( | |||
| INNER JOIN delivery_order d2 ON d2.id = po2.doId AND d2.deleted = 0 | |||
| LEFT JOIN shop s2 ON s2.id = d2.supplierId AND s2.deleted = 0 | |||
| WHERE po2.deliveryOrderPickOrderId = dop.id AND po2.deleted = 0 | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '4F' ELSE '2F' END) <> '2F' | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END) <> '${DemoFloors.FLOOR_A_KEY}' | |||
| ) | |||
| THEN '2/F' | |||
| THEN '${DemoFloors.FLOOR_A}' | |||
| ELSE NULL | |||
| END | |||
| ELSE dop.storeId | |||
| @@ -1090,7 +1091,7 @@ return MessageResponse( | |||
| .thenBy { it.storeId ?: "" } | |||
| ) | |||
| .map { | |||
| val is4F = it.storeId?.replace("/", "")?.trim()?.equals("4F", ignoreCase = true) == true | |||
| val is4F = it.storeId?.replace("/", "")?.trim()?.equals(DemoFloors.FLOOR_B_KEY, ignoreCase = true) == true | |||
| LaneBtn( | |||
| truckLanceCode = it.lance, | |||
| loadingSequence = if (is4F) it.loadingSequence else null, | |||
| @@ -1846,11 +1847,11 @@ return MessageResponse( | |||
| */ | |||
| private fun truckXPreferredFloorExistsSql(floorKey: String?): String { | |||
| val key = floorKey?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "").orEmpty() | |||
| if (key != "2F" && key != "4F") return "" | |||
| if (key != DemoFloors.FLOOR_A_KEY && key != DemoFloors.FLOOR_B_KEY) return "" | |||
| val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists() | |||
| val in4 = s4f.joinToString(", ") { "'" + it.replace("'", "''") + "'" } | |||
| .ifEmpty { "'__NO_4F_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '4F' ELSE '2F' END" | |||
| .ifEmpty { "'__NO_${DemoFloors.FLOOR_B_KEY}_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END" | |||
| return """ | |||
| AND EXISTS ( | |||
| SELECT 1 | |||
| @@ -1866,7 +1867,7 @@ return MessageResponse( | |||
| INNER JOIN delivery_order d2 ON d2.id = po2.doId AND d2.deleted = 0 | |||
| LEFT JOIN shop s2 ON s2.id = d2.supplierId AND s2.deleted = 0 | |||
| WHERE po2.deliveryOrderPickOrderId = dop.id AND po2.deleted = 0 | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '4F' ELSE '2F' END) <> '$key' | |||
| AND (CASE WHEN s2.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END) <> '$key' | |||
| ) | |||
| """.trimIndent() | |||
| } | |||
| @@ -1915,8 +1916,8 @@ return MessageResponse( | |||
| ) | |||
| if (!storeId.isNullOrBlank()) { | |||
| val sid = when (storeId) { | |||
| "2/F" -> "2/F" | |||
| "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> storeId.trim() | |||
| } | |||
| sqlBuilder.append(" AND dop.storeId = :storeId ") | |||
| @@ -2389,7 +2390,7 @@ return MessageResponse( | |||
| try { | |||
| if (pickOrderId != null) { | |||
| // JO / Consumable prime with storeId=null; re-suggest must match or non-2F lots | |||
| // are filtered out after resolveWorkbenchSuggestionStoreId defaults to "2/F". | |||
| // are filtered out after resolveWorkbenchSuggestionStoreId defaults to DemoFloors.FLOOR_A. | |||
| val resolvedPoType = pickOrderRepository.findById(pickOrderId).orElse(null)?.type | |||
| val suggestionStoreId = | |||
| if (resolvedPoType == PickOrderType.JOB_ORDER || | |||
| @@ -3066,7 +3067,7 @@ return MessageResponse( | |||
| emptyMap() | |||
| } | |||
| val sortedLines = when (doStoreFloorKey) { | |||
| "2F" -> exportLines.sortedWith( | |||
| DemoFloors.FLOOR_A_KEY -> exportLines.sortedWith( | |||
| compareBy( | |||
| { row -> itemsById[row.line.itemId]?.item_Order ?: Int.MAX_VALUE }, | |||
| { row -> row.line.itemNo }, | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.ReleaseDoRequest | |||
| @@ -512,9 +513,9 @@ open class DoWorkbenchReleaseService( | |||
| first.truckLanceCode?.trim() == WORKBENCH_DEFAULT_TRUCK_LANCE_CODE | |||
| if (isDefaultTruckLane) return null | |||
| return when (first.preferredFloor) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| else -> "2/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> DemoFloors.FLOOR_A | |||
| } | |||
| } | |||
| @@ -553,7 +554,7 @@ open class DoWorkbenchReleaseService( | |||
| if (isDefaultTruckLane) { | |||
| WORKBENCH_TICKET_FLOOR_SEGMENT_DEFAULT_TRUCK | |||
| } else { | |||
| (storeId ?: "2/F").replace("/", "").trim() | |||
| (storeId ?: DemoFloors.FLOOR_A).replace("/", "").trim() | |||
| } | |||
| private data class MergeableWorkbenchTicket( | |||
| @@ -610,13 +611,13 @@ open class DoWorkbenchReleaseService( | |||
| preferredFloor: String?, | |||
| ) { | |||
| val floor = when (preferredFloor?.trim()?.uppercase(java.util.Locale.ROOT)?.replace("/", "")) { | |||
| "4F" -> "4F" | |||
| else -> "2F" | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B_KEY | |||
| else -> DemoFloors.FLOOR_A_KEY | |||
| } | |||
| val (_, s4f) = doFloorSupplierSettingsService.loadDoFloorSupplierLists() | |||
| val in4 = s4f.joinToString(", ") { "'" + it.replace("'", "''") + "'" } | |||
| .ifEmpty { "'__NO_4F_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '4F' ELSE '2F' END" | |||
| .ifEmpty { "'__NO_${DemoFloors.FLOOR_B_KEY}_SUPPLIER__'" } | |||
| val floorExpr = "CASE WHEN s.code IN ($in4) THEN '${DemoFloors.FLOOR_B_KEY}' ELSE '${DemoFloors.FLOOR_A_KEY}' END" | |||
| // Reject tickets that already have any DO on a different floor. | |||
| sql.append( | |||
| """ | |||
| @@ -1462,7 +1463,7 @@ open class DoWorkbenchReleaseService( | |||
| loadingSequence: Int?, | |||
| ): String { | |||
| val storeNorm = normalizeStoreIdForLaneKey(storeId) | |||
| val is4F = storeNorm.replace("/", "").equals("4F", ignoreCase = true) | |||
| val is4F = storeNorm.replace("/", "").equals(DemoFloors.FLOOR_B_KEY, ignoreCase = true) | |||
| val seqPart = if (is4F) loadingSequence?.toString() ?: "" else "" | |||
| return listOf( | |||
| shopId?.toString() ?: "", | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus | |||
| import org.springframework.stereotype.Service | |||
| @@ -21,8 +22,8 @@ class TruckRoutingSummaryService( | |||
| return jdbcDao.queryForList(sql, emptyMap<String, Any>()).map { row -> | |||
| val rawValue = (row["value"] ?: "").toString().trim() | |||
| val label = when (rawValue.uppercase()) { | |||
| "2F", "2/F" -> "2/F" | |||
| "4F", "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY, DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> rawValue | |||
| } | |||
| mapOf("label" to label, "value" to rawValue) | |||
| @@ -205,8 +206,8 @@ class TruckRoutingSummaryService( | |||
| if (raw.isNullOrBlank()) return "" | |||
| val normalized = raw.trim().replace("/", "") | |||
| return when (normalized.uppercase()) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> raw.trim() | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus | |||
| import org.springframework.stereotype.Service | |||
| @@ -20,8 +21,8 @@ class WorkbenchTruckRoutingSummaryService( | |||
| return jdbcDao.queryForList(sql, emptyMap<String, Any>()).map { row -> | |||
| val rawValue = (row["value"] ?: "").toString().trim() | |||
| val label = when (rawValue.uppercase()) { | |||
| "2F", "2/F" -> "2/F" | |||
| "4F", "4/F" -> "4/F" | |||
| DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_A -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY, DemoFloors.FLOOR_B -> DemoFloors.FLOOR_B | |||
| else -> rawValue | |||
| } | |||
| mapOf("label" to label, "value" to rawValue) | |||
| @@ -191,8 +192,8 @@ class WorkbenchTruckRoutingSummaryService( | |||
| if (raw.isNullOrBlank()) return "" | |||
| val normalized = raw.trim().replace("/", "") | |||
| return when (normalized.uppercase()) { | |||
| "2F" -> "2/F" | |||
| "4F" -> "4/F" | |||
| DemoFloors.FLOOR_A_KEY -> DemoFloors.FLOOR_A | |||
| DemoFloors.FLOOR_B_KEY -> DemoFloors.FLOOR_B | |||
| else -> raw.trim() | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.web.models | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| data class AssignByStoreRequest( | |||
| val storeId: String, // "2/F" or "4/F" | |||
| val storeId: String, // DemoFloors.FLOOR_A or DemoFloors.FLOOR_B | |||
| val assignTo: Long | |||
| ) | |||
| @@ -6,32 +6,10 @@ package com.ffii.fpsms.modules.jobOrder.service | |||
| * [DEFAULT_EXCLUDE_WAREHOUSE_CODES] applies on **assign / first prime** ([JoWorkbenchMainService]) | |||
| * and on **scan-pick re-suggest** ([com.ffii.fpsms.modules.deliveryOrder.service.DoWorkbenchMainService]). | |||
| * JO re-suggest keeps [storeId] null (same as assign); it does not use DO floor store resolution. | |||
| * | |||
| * These bins are rewritten to LOC-X01..LOC-X24 by scripts/anonymize_demo_copy.sql. | |||
| */ | |||
| object JoWorkbenchPickConstants { | |||
| val DEFAULT_EXCLUDE_WAREHOUSE_CODES: Set<String> = setOf( | |||
| "4F-W402-01-00", | |||
| "4F-W402-02-00", | |||
| "4F-W402-03-00", | |||
| "4F-W402-04-00", | |||
| "4F-W402-05-00", | |||
| "4F-W402-#A-00", | |||
| "4F-W402-#B-00", | |||
| "4F-W402-#C-00", | |||
| "4F-W402-#D-00", | |||
| "4F-W402-#E-00", | |||
| "4F-W402-#F-00", | |||
| "4F-W402-#G-00", | |||
| "4F-W402-#H-00", | |||
| "4F-W402-#I-00", | |||
| "4F-W402-#J-00", | |||
| "4F-W402-#K-00", | |||
| "4F-W402-#L-00", | |||
| "4F-W402-#M-00", | |||
| "4F-W402-#N-00", | |||
| "4F-W402-#O-00", | |||
| "4F-W402-#P-00", | |||
| "4F-W402-#Q-00", | |||
| "4F-W402-#R-00", | |||
| "4F-W402-#S-00", | |||
| ) | |||
| val DEFAULT_EXCLUDE_WAREHOUSE_CODES: Set<String> = | |||
| (1..24).map { "LOC-X%02d".format(it) }.toSet() | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.exception.BadRequestException | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.core.utils.CanonPrinterUtil | |||
| @@ -740,8 +741,8 @@ open class JobOrderService( | |||
| private fun validatePickRecordFloor(floor: String?): String { | |||
| val normalizedFloor = floor?.trim()?.uppercase() | |||
| ?: throw BadRequestException("floor is required for pick record print") | |||
| if (normalizedFloor !in setOf("2F", "3F", "4F", "ALL")) { | |||
| throw BadRequestException("floor must be one of 2F, 3F, 4F, ALL") | |||
| if (normalizedFloor !in setOf(DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_D_KEY, DemoFloors.FLOOR_B_KEY, "ALL")) { | |||
| throw BadRequestException("floor must be one of ${DemoFloors.FLOOR_A_KEY}, ${DemoFloors.FLOOR_D_KEY}, ${DemoFloors.FLOOR_B_KEY}, ALL") | |||
| } | |||
| return normalizedFloor | |||
| } | |||
| @@ -783,17 +784,17 @@ open class JobOrderService( | |||
| private fun updatePickOrderPlasticBoxCartonQty(pickOrderId: Long, floor: String, qty: Int) { | |||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return | |||
| when (floor) { | |||
| "2F" -> pickOrder.plasticBoxCartonQty2f = qty | |||
| "3F" -> pickOrder.plasticBoxCartonQty3f = qty | |||
| "4F" -> pickOrder.plasticBoxCartonQty4f = qty | |||
| DemoFloors.FLOOR_A_KEY -> pickOrder.plasticBoxCartonQty2f = qty | |||
| DemoFloors.FLOOR_D_KEY -> pickOrder.plasticBoxCartonQty3f = qty | |||
| DemoFloors.FLOOR_B_KEY -> pickOrder.plasticBoxCartonQty4f = qty | |||
| } | |||
| pickOrderRepository.save(pickOrder) | |||
| } | |||
| private fun persistAllFloorsPlasticBoxCartonQty(pickOrderId: Long, all: AllFloorsPlasticBoxCartonQty) { | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "2F", all.qty2f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "3F", all.qty3f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "4F", all.qty4f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, DemoFloors.FLOOR_A_KEY, all.qty2f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, DemoFloors.FLOOR_D_KEY, all.qty3f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, DemoFloors.FLOOR_B_KEY, all.qty4f) | |||
| } | |||
| open fun getPickRecordPlasticBoxCartonQty(pickOrderId: Long): PickRecordPlasticBoxCartonQtyResponse { | |||
| @@ -839,10 +840,10 @@ open class JobOrderService( | |||
| val fields = mutableListOf<MutableMap<String, Any>>() | |||
| val params = mutableMapOf<String, Any>() | |||
| val normalizedFloor = request.floor?.trim()?.uppercase() | |||
| val selectedFloor = normalizedFloor?.takeIf { it in setOf("2F", "3F", "4F") } | |||
| val selectedFloor = normalizedFloor?.takeIf { it in setOf(DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_D_KEY, DemoFloors.FLOOR_B_KEY) } | |||
| val floorLabel = when (normalizedFloor) { | |||
| "2F", "3F", "4F" -> normalizedFloor.replace("F", "/F") | |||
| "ALL" -> "2/F+3/F+4/F+N/A" | |||
| DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_D_KEY, DemoFloors.FLOOR_B_KEY -> normalizedFloor.replace("F", "/F") | |||
| "ALL" -> "${DemoFloors.FLOOR_A}+${DemoFloors.FLOOR_D}+${DemoFloors.FLOOR_B}+N/A" | |||
| else -> "" | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | |||
| @@ -318,7 +319,7 @@ fun getJobOrderPickOrderLotDetails( | |||
| @GetMapping("/AllJoPickOrder") | |||
| fun getAllJoPickOrder( | |||
| @RequestParam(name = "type", required = false) bomType: String?, | |||
| // Single floor, e.g. "2F"/"3F"/"4F". When provided, backend returns job pick orders | |||
| // Single floor, e.g. DemoFloors.FLOOR_A_KEY/DemoFloors.FLOOR_D_KEY/DemoFloors.FLOOR_B_KEY. When provided, backend returns job pick orders | |||
| // that still have unpicked lines on that floor OR any "no lot" remaining lines. | |||
| @RequestParam(required = false) floor: String?, | |||
| @RequestParam(name = "jobOrderCode", required = false) jobOrderCode: String?, | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderBomMaterialStatus | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDateTime | |||
| @@ -35,7 +36,7 @@ data class CreateJobOrderProcessRequest ( | |||
| ) | |||
| data class FloorPickCountDto( | |||
| val floor: String, // e.g. "2F", "4F" | |||
| val floor: String, // e.g. DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_B_KEY | |||
| val finishedCount: Int, | |||
| val totalCount: Int | |||
| ) | |||
| @@ -482,16 +482,14 @@ open class ProductionScheduleService( | |||
| return MessageResponse( | |||
| id = request.id.toLong(), | |||
| //message = "Successfully created Job Orders for all production schedule lines (Parent ID: ${request.id}).", | |||
| message = if (jobOrderCount == 0) | |||
| "沒有建立新工單(可能因為成品已有生產中的工單)." | |||
| else | |||
| "成功建立 $jobOrderCount 張工單!", | |||
| name = "", | |||
| code = "", | |||
| type = "", | |||
| code = jobOrderCount.toString(), | |||
| type = "psJobOrders", | |||
| errorPosition = null | |||
| // entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines) | |||
| ) | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import java.util.Locale | |||
| import kotlin.system.measureTimeMillis | |||
| @@ -290,7 +291,7 @@ ORDER BY | |||
| ?: "" | |||
| val sortMs = measureTimeMillis { | |||
| when (doStoreFloorKey) { | |||
| "2F" -> { | |||
| DemoFloors.FLOOR_A_KEY -> { | |||
| allPickOrderLines.sortWith( | |||
| compareBy( | |||
| { line -> | |||
| @@ -304,7 +305,7 @@ ORDER BY | |||
| ), | |||
| ) | |||
| } | |||
| "4F" -> { | |||
| DemoFloors.FLOOR_B_KEY -> { | |||
| } | |||
| else -> { | |||
| allPickOrderLines.sortWith( | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| @@ -3552,10 +3553,10 @@ AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL) | |||
| ORDER BY | |||
| CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END, | |||
| COALESCE(w.`order`, 999999) ASC, -- 改用 warehouse.order | |||
| -- Add frontpart (floor) sorting: 1F > 2F/4F | |||
| -- Add frontpart (floor) sorting: ${DemoFloors.FLOOR_C_KEY} > ${DemoFloors.FLOOR_A_KEY}/${DemoFloors.FLOOR_B_KEY} | |||
| CASE | |||
| WHEN w.store_id = '1F' THEN 1 | |||
| WHEN w.store_id IN ('2F', '4F') THEN 2 | |||
| WHEN REPLACE(IFNULL(w.store_id, ''), '/', '') = '${DemoFloors.FLOOR_C_KEY}' THEN 1 | |||
| WHEN REPLACE(IFNULL(w.store_id, ''), '/', '') IN ('${DemoFloors.FLOOR_A_KEY}', '${DemoFloors.FLOOR_B_KEY}') THEN 2 | |||
| ELSE 3 | |||
| END ASC, | |||
| po.code ASC, | |||
| @@ -3937,13 +3938,13 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto | |||
| allPickOrderLines.addAll(pickOrderLines) | |||
| } | |||
| // 合并到总列表 | |||
| // DB 里 store_id 可能是 "2/F"、"4/F",与字面 "2F" 不一致会导致分支走错 | |||
| // DB 里 store_id 可能是 DemoFloors.FLOOR_A、DemoFloors.FLOOR_B,与字面 DemoFloors.FLOOR_A_KEY 不一致会导致分支走错 | |||
| val doStoreFloorKey = doPickOrderInfo["store_id"]?.toString()?.trim()?.uppercase(Locale.ROOT) | |||
| ?.replace("/", "") | |||
| ?.replace(" ", "") | |||
| ?: "" | |||
| when (doStoreFloorKey) { | |||
| "2F" -> { | |||
| DemoFloors.FLOOR_A_KEY -> { | |||
| // 2F:按 items.item_Order 排序(INT,NULL 置后) | |||
| allPickOrderLines.sortWith( | |||
| compareBy( | |||
| @@ -3958,7 +3959,7 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto | |||
| ) | |||
| ) | |||
| } | |||
| "4F" -> { | |||
| DemoFloors.FLOOR_B_KEY -> { | |||
| // 4F:旧逻辑 — 保持 SQL 顺序(COALESCE(w.`order`, 999999), pol.id, lotNo),不二次排序 | |||
| } | |||
| else -> { | |||
| @@ -4290,7 +4291,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| ?.replace(" ", "") | |||
| ?: "" | |||
| when (doStoreFloorKey) { | |||
| "2F" -> { | |||
| DemoFloors.FLOOR_A_KEY -> { | |||
| allPickOrderLines.sortWith( | |||
| compareBy( | |||
| { line -> | |||
| @@ -4304,7 +4305,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| ) | |||
| ) | |||
| } | |||
| "4F" -> { | |||
| DemoFloors.FLOOR_B_KEY -> { | |||
| // 4F:保留 SQL 順序,不做二次排序 | |||
| } | |||
| else -> { | |||
| @@ -5149,10 +5150,10 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| is String -> { | |||
| val parts = routerIndex.split("-") | |||
| if (parts.isNotEmpty()) { | |||
| val floorPart = parts[0].uppercase() // "1F", "2F", "4F" | |||
| val floorPart = parts[0].uppercase() // DemoFloors.FLOOR_C_KEY, DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_B_KEY | |||
| when (floorPart) { | |||
| "1F" -> 1 | |||
| "2F", "4F" -> 2 | |||
| DemoFloors.FLOOR_C_KEY -> 1 | |||
| DemoFloors.FLOOR_A_KEY, DemoFloors.FLOOR_B_KEY -> 2 | |||
| else -> 3 | |||
| } | |||
| } else { | |||
| @@ -5164,7 +5165,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| floorSortValue | |||
| }, | |||
| { line -> | |||
| // 提取数字部分:格式为 "store_id-number" (如 "2F-004") | |||
| // 提取数字部分:格式为 "store_id-number" (如 "${DemoFloors.FLOOR_A_KEY}-004") | |||
| val firstLot = line.lots.firstOrNull() | |||
| val routerIndex = firstLot?.router?.index | |||
| when (routerIndex) { | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.pickOrder.entity.Truck | |||
| import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.TruckLaneScheduleExcelPreviewRow | |||
| @@ -80,7 +81,7 @@ class TruckLaneScheduleExcelSupport( | |||
| val storeRaw = colIndex["targetstoreid"]?.let { cellString(row, it) }.orEmpty() | |||
| val storeId = truckService.normalizeRouteStoreId( | |||
| if (storeRaw.isBlank()) "2F" else storeRaw, | |||
| if (storeRaw.isBlank()) DemoFloors.FLOOR_A_KEY else storeRaw, | |||
| ) | |||
| val remark = colIndex["targetremark"]?.let { cellString(row, it) } | |||
| ?.trim()?.takeIf { it.isNotEmpty() } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.core.utils.ExcelUtils | |||
| @@ -146,7 +147,7 @@ open class TruckService( | |||
| storeId: String, | |||
| remark: String?, | |||
| ): List<Truck> { | |||
| val bucketRemark = if (storeId == "4F") remark?.trim()?.takeIf { it.isNotEmpty() } else null | |||
| val bucketRemark = if (storeId == DemoFloors.FLOOR_B_KEY) remark?.trim()?.takeIf { it.isNotEmpty() } else null | |||
| val trimmed = bucketRemark?.trim().orEmpty() | |||
| val blankRemark = trimmed.isEmpty() | |||
| return truckRepository.findAllByTruckLanceCodeAndRemarkAndDeletedFalse( | |||
| @@ -384,9 +385,9 @@ open class TruckService( | |||
| val remark = ExcelUtils.getStringValue(row.getCell(COLUMN_REMARK_INDEX)).trim() | |||
| val storeIdInt = when (store_id.uppercase()) { | |||
| "2F" -> 2 | |||
| "4F" -> 4 | |||
| "3F" -> 3 | |||
| DemoFloors.FLOOR_A_KEY -> 2 | |||
| DemoFloors.FLOOR_B_KEY -> 4 | |||
| DemoFloors.FLOOR_D_KEY -> 3 | |||
| else -> { | |||
| logger.warn("Invalid store_id '${store_id}', defaulting to 2") | |||
| 2 | |||
| @@ -482,11 +483,11 @@ open class TruckService( | |||
| open fun normalizeRouteStoreId(storeId: String?): String { | |||
| val s = storeId?.trim()?.uppercase().orEmpty() | |||
| return if (s == "4F") "4F" else "2F" | |||
| return if (s == DemoFloors.FLOOR_B_KEY) DemoFloors.FLOOR_B_KEY else DemoFloors.FLOOR_A_KEY | |||
| } | |||
| open fun bucketRemarkForStore(storeId: String, remark: String?): String? { | |||
| return if (normalizeRouteStoreId(storeId) == "4F") { | |||
| return if (normalizeRouteStoreId(storeId) == DemoFloors.FLOOR_B_KEY) { | |||
| remark?.trim()?.takeIf { it.isNotEmpty() } | |||
| } else { | |||
| null | |||
| @@ -630,7 +631,7 @@ open class TruckService( | |||
| ?: targetBucket.first() | |||
| val storeNorm = normalizeRouteStoreId(to.storeId) | |||
| val toRemark = | |||
| if (storeNorm == "4F") to.remark?.trim()?.takeIf { it.isNotEmpty() } else null | |||
| if (storeNorm == DemoFloors.FLOOR_B_KEY) to.remark?.trim()?.takeIf { it.isNotEmpty() } else null | |||
| val resolvedDeparture = | |||
| departureTime ?: template.departureTime ?: truck.departureTime ?: LocalTime.of(0, 0) | |||
| @@ -710,8 +711,8 @@ open class TruckService( | |||
| updateTruckLance.districtReference = request.districtReference | |||
| updateTruckLance.departureTime = request.departureTime | |||
| updateTruckLance.storeId = request.storeId | |||
| // Only set remark if storeId is "4F", otherwise set to null | |||
| updateTruckLance.remark = if (request.storeId == "4F") request.remark else null | |||
| // Only set remark if storeId is DemoFloors.FLOOR_B_KEY, otherwise set to null | |||
| updateTruckLance.remark = if (request.storeId == DemoFloors.FLOOR_B_KEY) request.remark else null | |||
| if (request.updateLogistic) { | |||
| updateTruckLance.logistic = logisticRefOrNull(request.logisticId) | |||
| } | |||
| @@ -766,8 +767,8 @@ open class TruckService( | |||
| this.shopCode = request.shopCode | |||
| this.loadingSequence = request.loadingSequence | |||
| this.districtReference = request.districtReference | |||
| // Only set remark if store_id is "4F", otherwise set to null | |||
| this.remark = if (request.store_id == "4F") request.remark else null | |||
| // Only set remark if store_id is DemoFloors.FLOOR_B_KEY, otherwise set to null | |||
| this.remark = if (request.store_id == DemoFloors.FLOOR_B_KEY) request.remark else null | |||
| this.logistic = logisticRefOrNull(request.logisticId) | |||
| } | |||
| @@ -850,9 +851,9 @@ open class TruckService( | |||
| // Update loadingSequence | |||
| truck.loadingSequence = request.loadingSequence | |||
| // Update remark - only set if storeId is "4F", otherwise set to null | |||
| // Update remark - only set if storeId is DemoFloors.FLOOR_B_KEY, otherwise set to null | |||
| val storeIdStr = truck.storeId | |||
| if (storeIdStr == "4F") { | |||
| if (storeIdStr == DemoFloors.FLOOR_B_KEY) { | |||
| truck.remark = request.remark | |||
| } else { | |||
| truck.remark = null | |||
| @@ -909,8 +910,8 @@ open class TruckService( | |||
| this.shopCode = null | |||
| this.loadingSequence = request.loadingSequence | |||
| this.districtReference = request.districtReference | |||
| // Only set remark if store_id is "4F", otherwise set to null | |||
| this.remark = if (request.store_id == "4F") request.remark else null | |||
| // Only set remark if store_id is DemoFloors.FLOOR_B_KEY, otherwise set to null | |||
| this.remark = if (request.store_id == DemoFloors.FLOOR_B_KEY) request.remark else null | |||
| this.logistic = logisticRefOrNull(request.logisticId) | |||
| } | |||
| @@ -1860,7 +1861,7 @@ open class TruckService( | |||
| } | |||
| val effectiveRemark = | |||
| if (storeId == "4F") { | |||
| if (storeId == DemoFloors.FLOOR_B_KEY) { | |||
| when { | |||
| scheduleRemark.isNotEmpty() -> scheduleRemark | |||
| laneRemark != null && laneRemark.isNotEmpty() -> laneRemark | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.report.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import org.springframework.stereotype.Service | |||
| import net.sf.jasperreports.engine.* | |||
| import net.sf.jasperreports.engine.data.JRMapCollectionDataSource | |||
| @@ -166,7 +167,7 @@ FORMAT(ROUND(IFNULL(IFNULL(sol.qty, dol.qty), 0), 0), 0) AS qty, | |||
| (${supplierFloorSqlCases.floorStringCase} | |||
| AND (SELECT COUNT(*) FROM truck t3 | |||
| WHERE t3.shopId = do.shopId AND t3.deleted = 0 | |||
| AND t3.Store_id = '4F') > 1 | |||
| AND t3.Store_id = '${DemoFloors.FLOOR_B_KEY}') > 1 | |||
| AND IFNULL(dopo.requiredDeliveryDate, do.estimatedArrivalDate) IS NOT NULL | |||
| AND t2.TruckLanceCode LIKE CONCAT('%', | |||
| CASE DAYNAME(IFNULL(dopo.requiredDeliveryDate, do.estimatedArrivalDate)) | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.report.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.core.support.JdbcDao | |||
| import org.springframework.stereotype.Service | |||
| import java.time.LocalDate | |||
| @@ -440,7 +441,7 @@ ORDER BY | |||
| "" | |||
| } else { | |||
| args["storeId"] = normalized | |||
| // DB 里 store_id 可能是 "2/F" 或 "2F";用 REPLACE 去斜線做匹配 | |||
| // DB 里 store_id 可能是 DemoFloors.FLOOR_A 或 DemoFloors.FLOOR_A_KEY;用 REPLACE 去斜線做匹配 | |||
| """ | |||
| AND REPLACE(COALESCE(wh.store_id, ''), '/', '') = REPLACE(:storeId, '/', '') | |||
| """.trimIndent() | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.master.service.ItemUomService | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrder | |||
| @@ -107,8 +108,8 @@ open class SuggestedPickLotService( | |||
| val suggestedList: MutableList<SuggestedPickLot> = mutableListOf() | |||
| val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap | |||
| val excludedWarehouseCodes = setOf<String>( | |||
| "2F-W202-01-00", | |||
| "2F-W200-#A-00", | |||
| DemoFloors.LOC_HOLD, | |||
| DemoFloors.LOC_WIP, | |||
| ) | |||
| val availableInventoryLotLines = inventoryLotLineService | |||
| .allInventoryLotLinesByItemIdIn(itemIds) | |||
| @@ -180,11 +181,11 @@ open class SuggestedPickLotService( | |||
| ) { | |||
| return@forEachIndexed | |||
| } | |||
| if (warehouseStoreId == "3F" && isDeliveryOrderPick) { | |||
| if (warehouseStoreId == DemoFloors.FLOOR_D_KEY && isDeliveryOrderPick) { | |||
| return@forEachIndexed | |||
| } | |||
| if (doPreferredFloor != null && | |||
| warehouseStoreId != "1F" && | |||
| warehouseStoreId != DemoFloors.FLOOR_C_KEY && | |||
| warehouseStoreId != doPreferredFloor | |||
| ) { | |||
| // 例:首选 2F,但是 lot 在 4F -> 跳过 | |||
| @@ -290,8 +291,8 @@ open class SuggestedPickLotService( | |||
| val suggestedList: MutableList<SuggestedPickLot> = mutableListOf() | |||
| val holdQtyMap: MutableMap<Long?, BigDecimal?> = request.holdQtyMap | |||
| val excludedWarehouseCodes = setOf<String>( | |||
| "2F-W202-01-00", | |||
| // "4F-W401-#C-00", | |||
| DemoFloors.LOC_HOLD, | |||
| // "${DemoFloors.FLOOR_B_KEY}-W401-#C-00", | |||
| ) | |||
| // get current inventory lot line qty & grouped by item id | |||
| val availableInventoryLotLines = inventoryLotLineService | |||
| @@ -316,7 +317,7 @@ open class SuggestedPickLotService( | |||
| val lotEntityById = lotEntities.associateBy { it.id } | |||
| // 计算“默认优先仓位”(短期固定,后续可从 Items/配置取) | |||
| val preferredWarehouseCode = if (isWipItem) "2F-W200-#A-00" else null | |||
| val preferredWarehouseCode = if (isWipItem) DemoFloors.LOC_WIP else null | |||
| // 分组:先默认仓位,再其他;组内保持 FEFO 顺序(lotInfos 已按 expiry 排过序) | |||
| val (preferred, others) = if (!preferredWarehouseCode.isNullOrBlank()) { | |||
| @@ -343,7 +344,7 @@ open class SuggestedPickLotService( | |||
| // 既有规则 | |||
| if (warehouseCode != null && excludedWarehouseCodes.contains(warehouseCode)) continue | |||
| val isJobOrder = line.pickOrder?.type?.value == "jo" | |||
| if (storeId == "3F" && !isJobOrder) continue | |||
| if (storeId == DemoFloors.FLOOR_D_KEY && !isJobOrder) continue | |||
| val availableQtyInBase = (lot.inQty ?: BigDecimal.ZERO) | |||
| .minus(lot.outQty ?: BigDecimal.ZERO) | |||
| @@ -1285,7 +1286,7 @@ private fun generateOptimalSuggestionsForAllPickOrders( | |||
| lotEntities.forEach { lot -> | |||
| if (remainingPickOrderLines.isEmpty()) return@forEach | |||
| val warehouseStoreId=lot.warehouse?.store_id | |||
| if (warehouseStoreId == "3F" && isDeliveryOrderContext) { | |||
| if (warehouseStoreId == DemoFloors.FLOOR_D_KEY && isDeliveryOrderContext) { | |||
| return@forEach | |||
| } | |||
| val totalQty = lot.inQty ?: zero | |||
| @@ -1574,7 +1575,7 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L | |||
| val warehouseStoreId=lot.warehouse?.store_id | |||
| // 3F filter only for delivery order | |||
| if (warehouseStoreId == "3F" && pickOrder.type?.value == "do") { | |||
| if (warehouseStoreId == DemoFloors.FLOOR_D_KEY && pickOrder.type?.value == "do") { | |||
| return@forEach | |||
| } | |||
| val totalQty = lot.inQty ?: zero | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.fpsms.modules.common.demo.DemoFloors | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLine | |||
| @@ -133,8 +134,8 @@ open class SuggestedPickLotWorkbenchService( | |||
| * Keep it empty if you don't need any default excludes. | |||
| */ | |||
| private val defaultExcludeWarehouseCodes: Set<String> = setOf( | |||
| "2F-W202-01-00", | |||
| "2F-W200-#A-00", | |||
| DemoFloors.LOC_HOLD, | |||
| DemoFloors.LOC_WIP, | |||
| ) | |||
| private fun normalizeStoreId(raw: String?): String? { | |||
| val s = raw?.trim()?.uppercase() ?: return null | |||
| @@ -5,10 +5,10 @@ spring: | |||
| username: fpsms_demo | |||
| password: FpsmsDemoOnly2026 | |||
| # Demo sandbox only. Do not point this profile at fpsmsdb. | |||
| # Demo copy only. Do not point this profile at fpsmsdb. | |||
| fpsms: | |||
| demo-mode: true | |||
| # Slide DEMO- expiry and delivery dates forward so sample rows stay on today's screens. | |||
| # Dates stay as copied from production. This cron is unused. | |||
| demo: | |||
| date-refresh-cron: "0 10 0 * * *" | |||
| monitoring: | |||
| @@ -1,484 +1,2 @@ | |||
| SET @user := (SELECT id FROM user WHERE username = 'demo' LIMIT 1); | |||
| SET @kg := (SELECT id FROM uom_conversion WHERE code = 'KG' LIMIT 1); | |||
| SET @pcs := (SELECT id FROM uom_conversion WHERE code = 'PCS' LIMIT 1); | |||
| SET @cur := (SELECT id FROM currency WHERE code = 'HKD' LIMIT 1); | |||
| SET @sup := (SELECT id FROM shop WHERE code = 'DEMO-SUP' LIMIT 1); | |||
| SET @shp := (SELECT id FROM shop WHERE code = 'DEMO-SHP' LIMIT 1); | |||
| SET @log := (SELECT id FROM m18_data_log WHERE refType = 'DEMO' LIMIT 1); | |||
| SET @whA := (SELECT id FROM warehouse WHERE code = 'DEMO-A' LIMIT 1); | |||
| SET @whB := (SELECT id FROM warehouse WHERE code = 'DEMO-B' LIMIT 1); | |||
| SET @flour := (SELECT id FROM items WHERE code = 'DEMO-RM-01' LIMIT 1); | |||
| SET @milk := (SELECT id FROM items WHERE code = 'DEMO-RM-02' LIMIT 1); | |||
| SET @loaf := (SELECT id FROM items WHERE code = 'DEMO-FG-01' LIMIT 1); | |||
| SET @bun := (SELECT id FROM items WHERE code = 'DEMO-FG-02' LIMIT 1); | |||
| SET @po3 := (SELECT id FROM purchase_order WHERE code = 'DEMO-PO-003' LIMIT 1); | |||
| SET @do3 := (SELECT id FROM delivery_order WHERE code = 'DEMO-DO-003' LIMIT 1); | |||
| SET @pkDo3 := (SELECT id FROM pick_order WHERE code = 'DEMO-PK-DO3' LIMIT 1); | |||
| SET @pkJo2 := (SELECT id FROM pick_order WHERE code = 'DEMO-PK-JO2' LIMIT 1); | |||
| SET @bom := (SELECT id FROM bom WHERE code = 'DEMO-BOM-01' LIMIT 1); | |||
| UPDATE warehouse | |||
| SET store_id = '2F', warehouse = 'W201', area = '#A', slot = '01', stockTakeSection = 'A', stockTakeSectionDescription = '甲倉區' | |||
| WHERE code = 'DEMO-A'; | |||
| UPDATE warehouse | |||
| SET store_id = '4F', warehouse = 'W202', area = '#B', slot = '02', stockTakeSection = 'B', stockTakeSectionDescription = '乙倉區' | |||
| WHERE code = 'DEMO-B'; | |||
| INSERT INTO settings (name, value, category, type) | |||
| SELECT 'DO.floor.suppliers.2F', 'DEMO-SUP', 'DO', 'csv' | |||
| WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = 'DO.floor.suppliers.2F'); | |||
| INSERT INTO settings (name, value, category, type) | |||
| SELECT 'DO.floor.suppliers.4F', 'DEMO-SUP', 'DO', 'csv' | |||
| WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = 'DO.floor.suppliers.4F'); | |||
| INSERT INTO item_category (code, parent, sub, temperature) | |||
| SELECT 'DEMO-CAT-BAKE', '食品', '烘焙', '常溫' | |||
| WHERE NOT EXISTS (SELECT 1 FROM item_category WHERE code = 'DEMO-CAT-BAKE'); | |||
| INSERT INTO item_category (code, parent, sub, temperature) | |||
| SELECT 'DEMO-CAT-DAIRY', '食品', '乳品', '冷藏' | |||
| WHERE NOT EXISTS (SELECT 1 FROM item_category WHERE code = 'DEMO-CAT-DAIRY'); | |||
| SET @catBake := (SELECT id FROM item_category WHERE code = 'DEMO-CAT-BAKE' LIMIT 1); | |||
| SET @catDairy := (SELECT id FROM item_category WHERE code = 'DEMO-CAT-DAIRY' LIMIT 1); | |||
| UPDATE items SET categoryId = @catBake WHERE code IN ('DEMO-RM-01', 'DEMO-SF-01', 'DEMO-FG-01', 'DEMO-FG-02') AND categoryId IS NULL; | |||
| UPDATE items SET categoryId = @catDairy WHERE code = 'DEMO-RM-02' AND categoryId IS NULL; | |||
| INSERT INTO shop (code, name, type, contactName, contactNo, addr1, district, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-SUP2', '東岸糖廠', 'supplier', '陳小姐', '21110002', '示範大道 8 號', '九龍', 910031, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM shop WHERE code = 'DEMO-SUP2'); | |||
| INSERT INTO shop (code, name, type, contactName, contactNo, addr1, district, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-SHP2', '南風麵包店', 'shop', '林先生', '51110002', '海旁街 18 號', '港島', 910032, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM shop WHERE code = 'DEMO-SHP2'); | |||
| INSERT INTO warehouse (code, name, description, capacity, `order`, warehouse, area, slot, store_id, stockTakeSection, stockTakeSectionDescription) | |||
| SELECT 'DEMO-C', '丙倉', '示範貨架丙', 800, '3', 'W203', '#C', '03', '2F', 'C', '丙倉區' | |||
| WHERE NOT EXISTS (SELECT 1 FROM warehouse WHERE code = 'DEMO-C'); | |||
| INSERT INTO user (username, password, locked, name, title, staffNo, lotusNotesUser) | |||
| SELECT 'demo.stock', u.password, 0, '示範倉務', '倉務', 'demo.stock', 0 | |||
| FROM user u | |||
| WHERE u.username = 'demo' | |||
| AND NOT EXISTS (SELECT 1 FROM user x WHERE x.username = 'demo.stock'); | |||
| INSERT INTO user (username, password, locked, name, title, staffNo, lotusNotesUser) | |||
| SELECT 'demo.prod', u.password, 0, '示範出品', '出品', 'demo.prod', 0 | |||
| FROM user u | |||
| WHERE u.username = 'demo' | |||
| AND NOT EXISTS (SELECT 1 FROM user x WHERE x.username = 'demo.prod'); | |||
| INSERT INTO user_authority (userId, authId) | |||
| SELECT u.id, a.id | |||
| FROM user u | |||
| JOIN authority a ON a.authority = 'STOCK' | |||
| WHERE u.username = 'demo.stock' | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM user_authority ua WHERE ua.userId = u.id AND ua.authId = a.id | |||
| ); | |||
| INSERT INTO user_authority (userId, authId) | |||
| SELECT u.id, a.id | |||
| FROM user u | |||
| JOIN authority a ON a.authority IN ('PROD', 'PACK') | |||
| WHERE u.username = 'demo.prod' | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM user_authority ua WHERE ua.userId = u.id AND ua.authId = a.id | |||
| ); | |||
| INSERT INTO equipment (code, name, description) | |||
| SELECT 'DEMO-EQ-01', '示範烤箱', '示範用熱風烤箱' | |||
| WHERE NOT EXISTS (SELECT 1 FROM equipment WHERE code = 'DEMO-EQ-01'); | |||
| INSERT INTO equipment_detail (code, name, description, equipmentCode) | |||
| SELECT 'DEMO-EQD-01', '烤箱一號', '二樓示範烤箱', 'DEMO-EQ-01' | |||
| WHERE NOT EXISTS (SELECT 1 FROM equipment_detail WHERE code = 'DEMO-EQD-01'); | |||
| INSERT INTO truck (TruckLanceCode, DepartureTime, shopId, ShopName, ShopCode, LoadingSequence, Store_id) | |||
| SELECT 'DEMO-AM', '08:00:00', @shp, '海港咖啡店', 'DEMO-SHP', 1, '2F' | |||
| WHERE NOT EXISTS (SELECT 1 FROM truck WHERE TruckLanceCode = 'DEMO-AM'); | |||
| INSERT INTO truck (TruckLanceCode, DepartureTime, shopId, ShopName, ShopCode, LoadingSequence, Store_id) | |||
| SELECT 'DEMO-PM', '14:30:00', @shp, '海港咖啡店', 'DEMO-SHP', 2, '4F' | |||
| WHERE NOT EXISTS (SELECT 1 FROM truck WHERE TruckLanceCode = 'DEMO-PM'); | |||
| INSERT INTO qc_category (code, name, isDefault) | |||
| SELECT 'DEMO-QC', '示範品質檢查', 1 | |||
| WHERE NOT EXISTS (SELECT 1 FROM qc_category WHERE code = 'DEMO-QC'); | |||
| SET @qcCat := (SELECT id FROM qc_category WHERE code = 'DEMO-QC' LIMIT 1); | |||
| INSERT INTO qc_item (code, name, description) | |||
| SELECT 'DEMO-QC-TEMP', '溫度', '入倉溫度' | |||
| WHERE NOT EXISTS (SELECT 1 FROM qc_item WHERE code = 'DEMO-QC-TEMP'); | |||
| INSERT INTO qc_item (code, name, description) | |||
| SELECT 'DEMO-QC-LOOK', '外觀', '包裝與外觀' | |||
| WHERE NOT EXISTS (SELECT 1 FROM qc_item WHERE code = 'DEMO-QC-LOOK'); | |||
| SET @qcTemp := (SELECT id FROM qc_item WHERE code = 'DEMO-QC-TEMP' LIMIT 1); | |||
| SET @qcLook := (SELECT id FROM qc_item WHERE code = 'DEMO-QC-LOOK' LIMIT 1); | |||
| INSERT INTO qc_item_category (qcItemId, qcCategoryId, `order`, description) | |||
| SELECT @qcTemp, @qcCat, 1, '溫度須低於 8 度' | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM qc_item_category WHERE qcItemId = @qcTemp AND qcCategoryId = @qcCat | |||
| ); | |||
| INSERT INTO qc_item_category (qcItemId, qcCategoryId, `order`, description) | |||
| SELECT @qcLook, @qcCat, 2, '包裝完整' | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM qc_item_category WHERE qcItemId = @qcLook AND qcCategoryId = @qcCat | |||
| ); | |||
| INSERT INTO items_qc_category_mapping (itemId, qcCategoryId, type) | |||
| SELECT @flour, @qcCat, 'IQC' | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM items_qc_category_mapping WHERE itemId = @flour AND type = 'IQC' | |||
| ); | |||
| INSERT INTO items_qc_category_mapping (itemId, qcCategoryId, type) | |||
| SELECT @loaf, @qcCat, 'EPQC' | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM items_qc_category_mapping WHERE itemId = @loaf AND type = 'EPQC' | |||
| ); | |||
| INSERT INTO items (code, name, type, description, categoryId, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-MAT-01', '高筋麵粉', 'MAT', '報告用材料', @catBake, 910301, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM items WHERE code = 'DEMO-MAT-01'); | |||
| INSERT INTO items (code, name, type, description, categoryId, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-WIP-01', '發酵麵團', 'WIP', '報告用半成品', @catBake, 910302, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM items WHERE code = 'DEMO-WIP-01'); | |||
| INSERT INTO items (code, name, type, description, categoryId, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-FG-03', '牛油包', 'FG', '報告用成品', @catBake, 910303, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM items WHERE code = 'DEMO-FG-03'); | |||
| INSERT INTO items (code, name, type, description, categoryId, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-NM-01', '包裝袋', 'NM', '報告用非耗材', @catBake, 910304, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM items WHERE code = 'DEMO-NM-01'); | |||
| INSERT INTO items (code, name, type, description, categoryId, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-CMB-01', '早餐套餐', 'CMB', '報告用組合', @catBake, 910305, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM items WHERE code = 'DEMO-CMB-01'); | |||
| SET @mat := (SELECT id FROM items WHERE code = 'DEMO-MAT-01' LIMIT 1); | |||
| SET @wip := (SELECT id FROM items WHERE code = 'DEMO-WIP-01' LIMIT 1); | |||
| SET @fg3 := (SELECT id FROM items WHERE code = 'DEMO-FG-03' LIMIT 1); | |||
| SET @nm := (SELECT id FROM items WHERE code = 'DEMO-NM-01' LIMIT 1); | |||
| SET @cmb := (SELECT id FROM items WHERE code = 'DEMO-CMB-01' LIMIT 1); | |||
| INSERT INTO item_uom (uomId, itemId, baseUnit, stockUnit, pickingUnit, salesUnit, purchaseUnit, ratioN, ratioD, price, currencyId, m18Id, m18LastModifyDate) | |||
| SELECT @kg, i.id, 1, 1, 1, 1, 1, 1, 1, 6.50, @cur, 910400 + i.id, NOW() | |||
| FROM items i | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-NM-01') | |||
| AND NOT EXISTS (SELECT 1 FROM item_uom iu WHERE iu.itemId = i.id AND iu.stockUnit = 1 AND iu.deleted = 0); | |||
| INSERT INTO item_uom (uomId, itemId, baseUnit, stockUnit, pickingUnit, salesUnit, purchaseUnit, ratioN, ratioD, price, currencyId, m18Id, m18LastModifyDate) | |||
| SELECT @pcs, i.id, 1, 1, 1, 1, 1, 1, 1, 12.00, @cur, 910500 + i.id, NOW() | |||
| FROM items i | |||
| WHERE i.code IN ('DEMO-FG-03', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM item_uom iu WHERE iu.itemId = i.id AND iu.stockUnit = 1 AND iu.deleted = 0); | |||
| INSERT INTO bom (code, name, description, itemId, outputQty, outputQtyUom, type, status, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-FG-01', '鄉村麵包', 'FG', @loaf, 10, 'PCS', 'FG', 'active', 910211, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM bom WHERE code = 'DEMO-FG-01'); | |||
| INSERT INTO bom (code, name, description, itemId, outputQty, outputQtyUom, type, status, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-FG-03', '牛油包', 'FG', @fg3, 12, 'PCS', 'FG', 'active', 910212, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM bom WHERE code = 'DEMO-FG-03'); | |||
| INSERT INTO bom (code, name, description, itemId, outputQty, outputQtyUom, type, status, m18Id, m18LastModifyDate) | |||
| SELECT 'DEMO-WIP-01', '發酵麵團', 'WIP', @wip, 8, 'KG', 'WIP', 'active', 910213, NOW() | |||
| WHERE NOT EXISTS (SELECT 1 FROM bom WHERE code = 'DEMO-WIP-01'); | |||
| UPDATE stock_in_line | |||
| SET dnNo = 'DEMO-DN-FL1', productionDate = DATE_SUB(NOW(), INTERVAL 3 DAY), productLotNo = 'PLOT-FL1' | |||
| WHERE lotNo = 'DEMO-LOT-FL1'; | |||
| UPDATE stock_in_line | |||
| SET dnNo = 'DEMO-DN-MK1', productionDate = DATE_SUB(NOW(), INTERVAL 3 DAY), productLotNo = 'PLOT-MK1' | |||
| WHERE lotNo = 'DEMO-LOT-MK1'; | |||
| UPDATE stock_in_line | |||
| SET dnNo = 'DEMO-DN-LF1', productionDate = DATE_SUB(NOW(), INTERVAL 2 DAY), productLotNo = 'PLOT-LF1' | |||
| WHERE lotNo = 'DEMO-LOT-LF1'; | |||
| UPDATE stock_in_line | |||
| SET dnNo = 'DEMO-DN-LF2', productionDate = DATE_SUB(NOW(), INTERVAL 1 DAY) | |||
| WHERE lotNo = 'DEMO-LOT-LF2'; | |||
| UPDATE purchase_order_line pol | |||
| JOIN items i ON i.id = pol.itemId | |||
| SET pol.up = CASE i.code WHEN 'DEMO-RM-01' THEN 8.50 WHEN 'DEMO-RM-02' THEN 15.00 ELSE pol.up END | |||
| WHERE pol.purchaseOrderId = @po3; | |||
| INSERT INTO m18_goods_receipt_note_log (m18_record_id, grn_code, stock_in_line_id, purchase_order_line_id, purchase_order_id, po_code, status, message) | |||
| SELECT 910501, 'DEMO-GRN-001', sil.id, sil.purchaseOrderLineId, sil.purchaseOrderId, 'DEMO-PO-003', 1, '示範入倉' | |||
| FROM stock_in_line sil | |||
| WHERE sil.lotNo = 'DEMO-LOT-FL1' | |||
| AND NOT EXISTS (SELECT 1 FROM m18_goods_receipt_note_log WHERE grn_code = 'DEMO-GRN-001'); | |||
| INSERT INTO m18_goods_receipt_note_log (m18_record_id, grn_code, stock_in_line_id, purchase_order_line_id, purchase_order_id, po_code, status, message) | |||
| SELECT 910502, 'DEMO-GRN-002', sil.id, sil.purchaseOrderLineId, sil.purchaseOrderId, 'DEMO-PO-003', 1, '示範入倉' | |||
| FROM stock_in_line sil | |||
| WHERE sil.lotNo = 'DEMO-LOT-MK1' | |||
| AND NOT EXISTS (SELECT 1 FROM m18_goods_receipt_note_log WHERE grn_code = 'DEMO-GRN-002'); | |||
| INSERT INTO qc_result (qcItemId, itemId, stockInLineId, failQty, type, remarks, qcPassed) | |||
| SELECT @qcLook, @flour, sil.id, 1.00, 'IQC', '包裝略有壓損', 0 | |||
| FROM stock_in_line sil | |||
| WHERE sil.lotNo = 'DEMO-LOT-FL1' | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM qc_result qr WHERE qr.stockInLineId = sil.id AND qr.qcItemId = @qcLook | |||
| ); | |||
| INSERT INTO qc_result (qcItemId, itemId, stockInLineId, failQty, type, remarks, qcPassed) | |||
| SELECT @qcTemp, @loaf, sil.id, 0.00, 'EPQC', '溫度正常', 1 | |||
| FROM stock_in_line sil | |||
| WHERE sil.lotNo = 'DEMO-LOT-LF1' | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM qc_result qr WHERE qr.stockInLineId = sil.id AND qr.qcItemId = @qcTemp | |||
| ); | |||
| INSERT INTO qc_result_measurement (qcResultId, qcType, measureType, value, unit) | |||
| SELECT qr.id, 'EPQC', '溫度', 6.50, 'C' | |||
| FROM qc_result qr | |||
| JOIN stock_in_line sil ON sil.id = qr.stockInLineId | |||
| WHERE sil.lotNo = 'DEMO-LOT-LF1' AND qr.qcItemId = @qcTemp | |||
| AND NOT EXISTS (SELECT 1 FROM qc_result_measurement m WHERE m.qcResultId = qr.id); | |||
| INSERT INTO delivery_order_pick_order ( | |||
| shopId, storeId, requiredDeliveryDate, truckDepartureTime, truckLanceCode, | |||
| shopCode, shopName, loadingSequence, ticketNo, ticketReleaseTime, ticketCompleteDateTime, | |||
| ticketStatus, handledBy, handlerName, deliveryNoteCode | |||
| ) | |||
| SELECT @shp, '2F', DATE_SUB(CURDATE(), INTERVAL 1 DAY), '08:00:00', 'DEMO-AM', | |||
| 'DEMO-SHP', '海港咖啡店', 1, 'DEMO-TK-003', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_SUB(NOW(), INTERVAL 20 HOUR), | |||
| 'completed', @user, '示範倉務', 'DEMO-DN-DO3' | |||
| WHERE NOT EXISTS (SELECT 1 FROM delivery_order_pick_order WHERE ticketNo = 'DEMO-TK-003'); | |||
| SET @dopo := (SELECT id FROM delivery_order_pick_order WHERE ticketNo = 'DEMO-TK-003' LIMIT 1); | |||
| UPDATE pick_order | |||
| SET deliveryOrderPickOrderId = @dopo | |||
| WHERE code = 'DEMO-PK-DO3' AND deliveryOrderPickOrderId IS NULL; | |||
| INSERT INTO stock_out (type, deliveryOrderCode, completeDate, status, handlerId) | |||
| SELECT 'do', 'DEMO-DO-003', DATE_SUB(NOW(), INTERVAL 1 DAY), 'completed', @user | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_out WHERE deliveryOrderCode = 'DEMO-DO-003' AND type = 'do'); | |||
| INSERT INTO stock_out (type, consoPickOrderCode, completeDate, status, handlerId) | |||
| SELECT 'job', 'DEMO-PK-JO2', DATE_SUB(NOW(), INTERVAL 2 HOUR), 'completed', @user | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_out WHERE consoPickOrderCode = 'DEMO-PK-JO2' AND type = 'job'); | |||
| SET @soDo := (SELECT id FROM stock_out WHERE deliveryOrderCode = 'DEMO-DO-003' AND type = 'do' LIMIT 1); | |||
| SET @soJo := (SELECT id FROM stock_out WHERE consoPickOrderCode = 'DEMO-PK-JO2' AND type = 'job' LIMIT 1); | |||
| SET @polDo := (SELECT pol.id FROM pick_order_line pol WHERE pol.poId = @pkDo3 LIMIT 1); | |||
| SET @polJo := (SELECT pol.id FROM pick_order_line pol JOIN items i ON i.id = pol.itemId WHERE pol.poId = @pkJo2 AND i.code = 'DEMO-RM-01' LIMIT 1); | |||
| SET @illLoaf := ( | |||
| SELECT ill.id FROM inventory_lot_line ill | |||
| JOIN inventory_lot il ON il.id = ill.inventoryLotId | |||
| WHERE il.lotNo = 'DEMO-LOT-LF1' LIMIT 1 | |||
| ); | |||
| SET @illFlour := ( | |||
| SELECT ill.id FROM inventory_lot_line ill | |||
| JOIN inventory_lot il ON il.id = ill.inventoryLotId | |||
| WHERE il.lotNo = 'DEMO-LOT-FL1' LIMIT 1 | |||
| ); | |||
| INSERT INTO stock_out_line (itemId, qty, stockOutId, pickOrderLineId, inventoryLotLineId, status, pickTime, handled_by, endTime) | |||
| SELECT @loaf, 6, @soDo, @polDo, @illLoaf, 'completed', DATE_SUB(NOW(), INTERVAL 1 DAY), @user, DATE_SUB(NOW(), INTERVAL 1 DAY) | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_out_line WHERE stockOutId = @soDo AND pickOrderLineId = @polDo); | |||
| INSERT INTO stock_out_line (itemId, qty, stockOutId, pickOrderLineId, inventoryLotLineId, status, pickTime, handled_by, endTime) | |||
| SELECT @flour, 5, @soJo, @polJo, @illFlour, 'completed', DATE_SUB(NOW(), INTERVAL 2 HOUR), @user, DATE_SUB(NOW(), INTERVAL 2 HOUR) | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_out_line WHERE stockOutId = @soJo AND pickOrderLineId = @polJo); | |||
| INSERT INTO do_pick_order_record ( | |||
| record_id, pick_order_id, do_order_id, shop_id, store_id, RequiredDeliveryDate, | |||
| TruckLanceCode, ShopCode, ShopName, delivery_order_code, deliveryNoteCode, pick_order_code, | |||
| ticket_no, ticket_release_time, ticketCompleteDateTime, ticket_status, handled_by, handler_name, deleted | |||
| ) | |||
| SELECT 0, @pkDo3, @do3, @shp, '2F', DATE_SUB(CURDATE(), INTERVAL 1 DAY), | |||
| 'DEMO-AM', 'DEMO-SHP', '海港咖啡店', 'DEMO-DO-003', 'DEMO-DN-DO3', 'DEMO-PK-DO3', | |||
| 'DEMO-TK-003', DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_SUB(NOW(), INTERVAL 20 HOUR), 'completed', @user, '示範倉務', 0 | |||
| WHERE NOT EXISTS (SELECT 1 FROM do_pick_order_record WHERE ticket_no = 'DEMO-TK-003'); | |||
| UPDATE do_pick_order_record SET record_id = id WHERE ticket_no = 'DEMO-TK-003' AND record_id = 0; | |||
| SET @dpor := (SELECT id FROM do_pick_order_record WHERE ticket_no = 'DEMO-TK-003' LIMIT 1); | |||
| INSERT INTO do_pick_order_line_record ( | |||
| record_id, do_pick_order_id, pick_order_id, do_order_id, pick_order_code, delivery_order_code, status, deleted | |||
| ) | |||
| SELECT @dpor, @dopo, @pkDo3, @do3, 'DEMO-PK-DO3', 'DEMO-DO-003', 'correct', 0 | |||
| WHERE @dpor IS NOT NULL | |||
| AND NOT EXISTS (SELECT 1 FROM do_pick_order_line_record WHERE record_id = @dpor AND do_order_id = @do3); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT DATE_SUB(CURDATE(), INTERVAL 20 DAY), inv.id, inv.itemId, i.code, inv.onHandQty, 0, inv.onHandQty, 'IN', DATE_SUB(NOW(), INTERVAL 20 DAY) | |||
| FROM inventory inv | |||
| JOIN items i ON i.id = inv.itemId | |||
| WHERE i.code IN ('DEMO-RM-01', 'DEMO-RM-02', 'DEMO-FG-01') | |||
| AND NOT EXISTS ( | |||
| SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'IN' AND sl.deleted = 0 | |||
| ); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, stockOutLineId, modified) | |||
| SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY), inv.id, @loaf, 'DEMO-FG-01', 0, 6, inv.onHandQty, 'DO', sol.id, DATE_SUB(NOW(), INTERVAL 1 DAY) | |||
| FROM inventory inv | |||
| JOIN stock_out_line sol ON sol.stockOutId = @soDo | |||
| WHERE inv.itemId = @loaf | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = 'DEMO-FG-01' AND sl.type = 'DO' AND sl.deleted = 0); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, stockOutLineId, modified) | |||
| SELECT CURDATE(), inv.id, @flour, 'DEMO-RM-01', 0, 5, inv.onHandQty, 'JO', sol.id, DATE_SUB(NOW(), INTERVAL 2 HOUR) | |||
| FROM inventory inv | |||
| JOIN stock_out_line sol ON sol.stockOutId = @soJo | |||
| WHERE inv.itemId = @flour | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = 'DEMO-RM-01' AND sl.type = 'JO' AND sl.deleted = 0); | |||
| INSERT INTO stock_in (code, supplierId, shopId, m18DataLogId, orderDate, completeDate, status) | |||
| SELECT 'DEMO-SI-RPT', @sup, @shp, @log, DATE_SUB(NOW(), INTERVAL 40 DAY), DATE_SUB(NOW(), INTERVAL 39 DAY), 'completed' | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_in WHERE code = 'DEMO-SI-RPT'); | |||
| SET @siRpt := (SELECT id FROM stock_in WHERE code = 'DEMO-SI-RPT' LIMIT 1); | |||
| INSERT INTO stock_in_line (itemId, itemNo, stockInId, demandQty, acceptedQty, status, lotNo, expiryDate, receiptDate, productionDate, productLotNo, dnNo) | |||
| SELECT i.id, i.code, @siRpt, 20, 20, 'completed', CONCAT('DEMO-LOT-', i.code), DATE_ADD(CURDATE(), INTERVAL 90 DAY), DATE_SUB(NOW(), INTERVAL 39 DAY), DATE_SUB(NOW(), INTERVAL 40 DAY), CONCAT('PLOT-', i.code), CONCAT('DN-', i.code) | |||
| FROM items i | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_in_line sil WHERE sil.itemId = i.id AND sil.stockInId = @siRpt); | |||
| INSERT INTO inventory_lot (itemId, stockInLineId, expiryDate, lotNo, stockInDate, productionDate) | |||
| SELECT sil.itemId, sil.id, sil.expiryDate, sil.lotNo, sil.receiptDate, sil.productionDate | |||
| FROM stock_in_line sil | |||
| WHERE sil.stockInId = @siRpt | |||
| AND NOT EXISTS (SELECT 1 FROM inventory_lot il WHERE il.stockInLineId = sil.id); | |||
| UPDATE stock_in_line sil | |||
| JOIN inventory_lot il ON il.stockInLineId = sil.id | |||
| SET sil.inventoryLotId = il.id | |||
| WHERE sil.stockInId = @siRpt AND sil.inventoryLotId IS NULL; | |||
| INSERT INTO inventory_lot_line (inventoryLotId, warehouseId, stockItemUomId, inQty, outQty, holdQty, status) | |||
| SELECT il.id, @whA, iu.id, 20, 0, 0, 'available' | |||
| FROM inventory_lot il | |||
| JOIN stock_in_line sil ON sil.id = il.stockInLineId | |||
| JOIN item_uom iu ON iu.itemId = il.itemId AND iu.stockUnit = 1 AND iu.deleted = 0 | |||
| WHERE sil.stockInId = @siRpt | |||
| AND NOT EXISTS (SELECT 1 FROM inventory_lot_line ill WHERE ill.inventoryLotId = il.id); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT DATE_SUB(CURDATE(), INTERVAL 30 DAY), inv.id, inv.itemId, i.code, 20, 0, 20, 'IN', DATE_SUB(NOW(), INTERVAL 30 DAY) | |||
| FROM inventory inv | |||
| JOIN items i ON i.id = inv.itemId | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'IN' AND sl.deleted = 0); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-15'), '%Y-%m-%d'), inv.id, i.id, i.code, 0, 4, 16, 'OUT', STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-15 10:00:00'), '%Y-%m-%d %H:%i:%s') | |||
| FROM items i | |||
| JOIN inventory inv ON inv.itemId = i.id | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'OUT' AND MONTH(sl.modified) = 1); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-04-15'), '%Y-%m-%d'), inv.id, i.id, i.code, 0, 6, 10, 'OUT', STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-04-15 10:00:00'), '%Y-%m-%d %H:%i:%s') | |||
| FROM items i | |||
| JOIN inventory inv ON inv.itemId = i.id | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'OUT' AND MONTH(sl.modified) = 4); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-07-15'), '%Y-%m-%d'), inv.id, i.id, i.code, 0, 3, 7, 'OUT', STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-07-15 10:00:00'), '%Y-%m-%d %H:%i:%s') | |||
| FROM items i | |||
| JOIN inventory inv ON inv.itemId = i.id | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'OUT' AND MONTH(sl.modified) = 7); | |||
| INSERT INTO stock_ledger (date, inventoryId, itemId, itemCode, inQty, outQty, balance, type, modified) | |||
| SELECT STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-09-10'), '%Y-%m-%d'), inv.id, i.id, i.code, 0, 5, 2, 'OUT', STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-09-10 10:00:00'), '%Y-%m-%d %H:%i:%s') | |||
| FROM items i | |||
| JOIN inventory inv ON inv.itemId = i.id | |||
| WHERE i.code IN ('DEMO-MAT-01', 'DEMO-WIP-01', 'DEMO-FG-03', 'DEMO-NM-01', 'DEMO-CMB-01') | |||
| AND NOT EXISTS (SELECT 1 FROM stock_ledger sl WHERE sl.itemCode = i.code AND sl.type = 'OUT' AND MONTH(sl.modified) = 9); | |||
| INSERT INTO stock_take (code, planStart, planEnd, actualStart, actualEnd, status, stockTakeRoundId, stockTakeRoundName, stockTakeSection) | |||
| SELECT 'DEMO-ST-001', DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), 'completed', 9001, '示範盤點輪次', 'A' | |||
| WHERE NOT EXISTS (SELECT 1 FROM stock_take WHERE code = 'DEMO-ST-001'); | |||
| SET @st := (SELECT id FROM stock_take WHERE code = 'DEMO-ST-001' LIMIT 1); | |||
| SET @lotFl := (SELECT id FROM inventory_lot WHERE lotNo = 'DEMO-LOT-FL1' LIMIT 1); | |||
| SET @lotMk := (SELECT id FROM inventory_lot WHERE lotNo = 'DEMO-LOT-MK1' LIMIT 1); | |||
| SET @lotLf := (SELECT id FROM inventory_lot WHERE lotNo = 'DEMO-LOT-LF1' LIMIT 1); | |||
| INSERT INTO stocktakerecord ( | |||
| itemId, lotId, warehouseId, stockTakeId, stockTakeRoundId, stockTakerId, stockTakerName, | |||
| approverId, approverName, bookQty, approverStockTakeQty, varianceQty, uom, date, status, | |||
| approverTime, itemCode, itemName, lotNo, expiredDate | |||
| ) | |||
| SELECT @flour, @lotFl, @whA, @st, 9001, @user, '示範倉務', @user, '示範管理員', 40, 38, -2, '公斤', DATE_SUB(CURDATE(), INTERVAL 1 DAY), 'completed', DATE_SUB(NOW(), INTERVAL 1 DAY), 'DEMO-RM-01', '普通麵粉', 'DEMO-LOT-FL1', DATE_ADD(CURDATE(), INTERVAL 180 DAY) | |||
| WHERE NOT EXISTS (SELECT 1 FROM stocktakerecord WHERE stockTakeId = @st AND lotId = @lotFl); | |||
| INSERT INTO stocktakerecord ( | |||
| itemId, lotId, warehouseId, stockTakeId, stockTakeRoundId, stockTakerId, stockTakerName, | |||
| bookQty, approverStockTakeQty, varianceQty, uom, date, status, itemCode, itemName, lotNo | |||
| ) | |||
| SELECT @milk, @lotMk, @whA, @st, 9001, @user, '示範倉務', 20, 20, 0, '公斤', DATE_SUB(CURDATE(), INTERVAL 1 DAY), 'completed', 'DEMO-RM-02', '鮮奶', 'DEMO-LOT-MK1' | |||
| WHERE NOT EXISTS (SELECT 1 FROM stocktakerecord WHERE stockTakeId = @st AND lotId = @lotMk); | |||
| INSERT INTO stocktakerecord ( | |||
| itemId, lotId, warehouseId, stockTakeId, stockTakeRoundId, stockTakerId, stockTakerName, | |||
| bookQty, uom, date, status, itemCode, itemName, lotNo | |||
| ) | |||
| SELECT @loaf, @lotLf, @whB, @st, 9001, @user, '示範倉務', 30, '件', CURDATE(), 'pending', 'DEMO-FG-01', '鄉村麵包', 'DEMO-LOT-LF1' | |||
| WHERE NOT EXISTS (SELECT 1 FROM stocktakerecord WHERE stockTakeId = @st AND lotId = @lotLf); | |||
| INSERT INTO delivery_order (code, shopId, supplierId, currencyId, orderDate, estimatedArrivalDate, status, m18DataLogId, m18BeId, isExtra) | |||
| SELECT 'DEMO-DO-004', @shp, @sup, currencyId, NOW(), CURDATE(), 'pending', m18DataLogId, m18BeId, 0 | |||
| FROM delivery_order | |||
| WHERE code = 'DEMO-DO-001' | |||
| AND NOT EXISTS (SELECT 1 FROM delivery_order WHERE code = 'DEMO-DO-004'); | |||
| SET @do4 := (SELECT id FROM delivery_order WHERE code = 'DEMO-DO-004' LIMIT 1); | |||
| INSERT INTO delivery_order_line (deliveryOrderId, itemId, itemNo, uomId, uomIdM18, qty, qtyM18, status, m18DataLogId) | |||
| SELECT @do4, @loaf, 'DEMO-FG-01', @kg, @kg, 3, 3, 'pending', @log | |||
| WHERE @do4 IS NOT NULL | |||
| AND NOT EXISTS (SELECT 1 FROM delivery_order_line WHERE deliveryOrderId = @do4); | |||
| SET @doLine3 := ( | |||
| SELECT dol.id FROM delivery_order_line dol WHERE dol.deliveryOrderId = @do3 LIMIT 1 | |||
| ); | |||
| INSERT INTO do_replenishment ( | |||
| code, deliveryDate, sourceDoId, sourceDoCode, sourceDoLineId, sourceM18DataLogId, sourceM18Id, | |||
| itemId, itemNo, itemName, replenishQty, uomId, shopId, shopCode, shopName, truckLaneCode, | |||
| status, reason, deliveryOrderPickOrderId, pickOrderLineId | |||
| ) | |||
| SELECT 'DEMO-REPL-001', DATE_SUB(CURDATE(), INTERVAL 1 DAY), @do3, 'DEMO-DO-003', @doLine3, @log, 910601, | |||
| @loaf, 'DEMO-FG-01', '鄉村麵包', 2, @pcs, @shp, 'DEMO-SHP', '海港咖啡店', 'DEMO-AM', | |||
| 'completed', '示範補貨', @dopo, @polDo | |||
| WHERE NOT EXISTS (SELECT 1 FROM do_replenishment WHERE code = 'DEMO-REPL-001'); | |||
| INSERT INTO m18_bom_shop_sync_log (bom_id, finished_item_code, m18_header_code, m18_api_status, synced, message) | |||
| SELECT @bom, 'DEMO-FG-01', 'DEMO-BOM-HDR', 1, 1, '示範同步成功' | |||
| WHERE NOT EXISTS (SELECT 1 FROM m18_bom_shop_sync_log WHERE finished_item_code = 'DEMO-FG-01' AND synced = 1); | |||
| INSERT INTO m18_bom_shop_sync_log (bom_id, finished_item_code, m18_header_code, m18_api_status, synced, message) | |||
| SELECT b.id, 'DEMO-FG-03', 'DEMO-BOM-HDR-2', 0, 0, '示範同步失敗' | |||
| FROM bom b | |||
| WHERE b.code = 'DEMO-FG-03' | |||
| AND NOT EXISTS (SELECT 1 FROM m18_bom_shop_sync_log WHERE finished_item_code = 'DEMO-FG-03' AND synced = 0); | |||
| INSERT INTO settings (name, value, category, type) | |||
| SELECT 'DEMO.coverage.v1', 'loaded', 'DEMO', 'marker' | |||
| WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = 'DEMO.coverage.v1'); | |||
| -- Retired. Demo data is an anonymized copy of production. | |||
| -- Do not run this script. It inserts fictional DEMO- rows. | |||