From 8b050ac9d61e5ed6b63bc7a5f7c9dc42deaffb7d Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Tue, 11 Aug 2026 19:16:07 +0800 Subject: [PATCH] feat: add shadow-only SQL Server package --- .../db/sqlserver/diagnostics-shadow.sql | 77 + .../resources/db/sqlserver/diagnostics.sql | 224 -- .../resources/db/sqlserver/install-shadow.sql | 654 ++++++ .../main/resources/db/sqlserver/install.sql | 1936 ----------------- .../db/sqlserver/uninstall-shadow.sql | 51 + .../main/resources/db/sqlserver/uninstall.sql | 53 - .../ShadowSqlServerMigrationScriptTest.java | 143 ++ .../SqlServerMigrationScriptTest.java | 200 -- .../shadow/ShadowPlanningEngine.java | 1 + .../shadow/ShadowPlanningEngineTest.java | 2 + 10 files changed, 928 insertions(+), 2413 deletions(-) create mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql delete mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql create mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql delete mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/install.sql create mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql delete mode 100644 fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql create mode 100644 fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/ShadowSqlServerMigrationScriptTest.java delete mode 100644 fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql new file mode 100644 index 0000000..377ecdd --- /dev/null +++ b/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics-shadow.sql @@ -0,0 +1,77 @@ +/* Read-only field diagnostics and export queries for the FT546 shadow package. */ +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar(30)) AS product_version, + DB_NAME() AS database_name, + SYSUTCDATETIME() AS checked_at_utc; + +SELECT schema_name(object.schema_id) AS schema_name, + object.name AS object_name, + object.type_desc +FROM sys.objects object +WHERE schema_name(object.schema_id) = N'alg' +ORDER BY schema_name(object.schema_id), object.type_desc, object.name; + +SELECT warehouse_id, + COUNT(*) AS lane_state_count, + SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, + SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count, + MAX(rebuilt_at) AS latest_rebuild_at +FROM alg.lane_state +GROUP BY warehouse_id +ORDER BY warehouse_id; + +SELECT warehouse_id, + COUNT(*) AS inventory_item_count, + COUNT(DISTINCT space_id) AS inventory_space_count, + MAX(synchronized_at) AS latest_inventory_sync_at +FROM alg.inventory_item_snapshot +GROUP BY warehouse_id +ORDER BY warehouse_id; + +SELECT warehouse_id, + COUNT(*) AS space_state_count, + SUM(CASE WHEN inbound_available = 1 THEN 1 ELSE 0 END) AS inbound_available_count, + SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_outbound_count, + SUM(CASE WHEN tray_conflict = 1 OR project_conflict = 1 OR product_conflict = 1 + THEN 1 ELSE 0 END) AS conflict_count +FROM alg.v_space_state +GROUP BY warehouse_id +ORDER BY warehouse_id; + +/* Legacy checksums should remain stable apart from normal live traffic. */ +SELECT 'bas_cargo_space' AS table_name, + COUNT_BIG(*) AS row_count, + CHECKSUM_AGG(BINARY_CHECKSUM(cargo_space_id, warehouse_id, group_coding_no, + group_coding, status, tray_code)) AS row_checksum +FROM dbo.bas_cargo_space +UNION ALL +SELECT 'bas_stock', COUNT_BIG(*), + CHECKSUM_AGG(BINARY_CHECKSUM(stock_id, cargo_space_id, tray_code, spare4, + product_id, lot_no, in_date)) +FROM dbo.bas_stock +UNION ALL +SELECT 'mydo_job', COUNT_BIG(*), + CHECKSUM_AGG(BINARY_CHECKSUM(job_id, status, task_no, tray_code, + cargo_space_id, target_cargo_space_id, + in_or_out, warehouse_id)) +FROM dbo.mydo_job; + +/* Locks held on the three observed Legacy tables. */ +SELECT request_session_id, resource_type, request_mode, request_status, + object_name(partition.object_id, lock.resource_database_id) AS object_name +FROM sys.dm_tran_locks lock +LEFT JOIN sys.partitions partition + ON partition.hobt_id = lock.resource_associated_entity_id +WHERE lock.resource_database_id = DB_ID() + AND object_name(partition.object_id, lock.resource_database_id) + IN (N'bas_cargo_space', N'bas_stock', N'mydo_job') +ORDER BY request_session_id, object_name, request_mode; + +/* Export this result before uninstalling. */ +SELECT decision_id, warehouse_id, decision_type, input_version, + result_code, reason, created_at +FROM alg.decision_log +WHERE decision_type IN ('INBOUND', 'OUTBOUND', 'INBOUND_COMPARE', 'OUTBOUND_COMPARE') +ORDER BY created_at, decision_id; diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql deleted file mode 100644 index 14a3530..0000000 --- a/fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql +++ /dev/null @@ -1,224 +0,0 @@ -/* Read-only validation queries for a SQL Server 2016 installation. */ -SET NOCOUNT ON; - -SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar(30)) AS product_version, - DB_NAME() AS database_name; - -SELECT o.type_desc, COUNT(*) AS object_count -FROM sys.objects o -JOIN sys.schemas s ON s.schema_id = o.schema_id -WHERE s.name = N'alg' AND o.type IN ('U', 'V', 'P') -GROUP BY o.type_desc -ORDER BY o.type_desc; - -SELECT installed.object_kind, installed.object_name -FROM ( - SELECT o.type_desc AS object_kind, o.name AS object_name - FROM sys.objects o - JOIN sys.schemas s ON s.schema_id = o.schema_id - WHERE s.name = N'alg' AND o.type IN ('U', 'V', 'P') - UNION ALL - SELECT 'INDEX' AS object_kind, i.name AS object_name - FROM sys.indexes i - JOIN sys.objects o ON o.object_id = i.object_id - JOIN sys.schemas s ON s.schema_id = o.schema_id - WHERE s.name = N'alg' AND i.name IS NOT NULL -) installed -ORDER BY installed.object_kind, installed.object_name; - -SELECT warehouse_id, - COUNT_BIG(*) AS excluded_from_algorithm_topology_count, - SUM(CASE WHEN group_coding_no IS NULL OR LTRIM(RTRIM(group_coding_no)) = '' - THEN 1 ELSE 0 END) AS missing_lane_code_count, - SUM(CASE WHEN TRY_CONVERT(int, group_coding) IS NULL - THEN 1 ELSE 0 END) AS invalid_depth_count -FROM dbo.bas_cargo_space -WHERE group_coding_no IS NULL OR LTRIM(RTRIM(group_coding_no)) = '' - OR TRY_CONVERT(int, group_coding) IS NULL -GROUP BY warehouse_id -ORDER BY warehouse_id; - -SELECT warehouse_id, - COUNT(*) AS space_state_count, - SUM(CASE WHEN space_status = 2 THEN 1 ELSE 0 END) AS full_space_count, - SUM(CASE WHEN inbound_available = 1 THEN 1 ELSE 0 END) AS inbound_available_count, - SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_outbound_space_count, - SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_space_count, - SUM(CASE WHEN tray_conflict = 1 OR project_conflict = 1 OR product_conflict = 1 THEN 1 ELSE 0 END) AS conflict_space_count -FROM alg.v_space_state -GROUP BY warehouse_id -ORDER BY warehouse_id; - -SELECT warehouse_id, COUNT(*) AS topology_count -FROM alg.space_topology -GROUP BY warehouse_id -ORDER BY warehouse_id; - -SELECT warehouse_id, - COUNT(*) AS inventory_detail_snapshot_count, - COUNT(DISTINCT space_id) AS inventory_space_snapshot_count, - MAX(synchronized_at) AS last_inventory_synchronized_at -FROM alg.inventory_item_snapshot -GROUP BY warehouse_id -ORDER BY warehouse_id; - -;WITH warehouse_floors AS ( - SELECT DISTINCT warehouse_id, the_floor AS floor_no - FROM dbo.bas_cargo_space -), role_counts AS ( - SELECT cargo.warehouse_id, cargo.the_floor AS floor_no, - SUM(CASE WHEN role.role = 'RELOCATION_BUFFER' THEN 1 ELSE 0 END) AS relocation_buffer_count, - SUM(CASE WHEN role.role = 'OUTBOUND_BUFFER' THEN 1 ELSE 0 END) AS outbound_buffer_count, - SUM(CASE WHEN role.role = 'PROTECTED_CORE' THEN 1 ELSE 0 END) AS protected_core_count - FROM dbo.bas_cargo_space cargo - LEFT JOIN alg.space_role role ON role.space_id = cargo.cargo_space_id - GROUP BY cargo.warehouse_id, cargo.the_floor -) -SELECT floors.warehouse_id, floors.floor_no, - ISNULL(counts.relocation_buffer_count, 0) AS relocation_buffer_count, - CAST(4 AS int) AS relocation_buffer_target, - CASE WHEN ISNULL(counts.relocation_buffer_count, 0) >= 4 THEN 0 - ELSE 4 - ISNULL(counts.relocation_buffer_count, 0) END AS relocation_buffer_shortage, - ISNULL(counts.outbound_buffer_count, 0) AS outbound_buffer_count, - ISNULL(counts.protected_core_count, 0) AS protected_core_count -FROM warehouse_floors floors -LEFT JOIN role_counts counts ON counts.warehouse_id = floors.warehouse_id - AND counts.floor_no = floors.floor_no -ORDER BY floors.warehouse_id, floors.floor_no; - -SELECT cargo.warehouse_id, cargo.the_floor AS floor_no, role.space_id, role.role, role.updated_at -FROM alg.space_role role -JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = role.space_id -WHERE role.role IN ('RELOCATION_BUFFER', 'OUTBOUND_BUFFER', 'PROTECTED_CORE') -ORDER BY cargo.warehouse_id, cargo.the_floor, role.role, role.space_id; - -SELECT warehouse_id, COUNT(*) AS lane_state_count, - SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, - SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count -FROM alg.lane_state -GROUP BY warehouse_id -ORDER BY warehouse_id; - -SELECT 'bas_cargo_space' AS table_name, COUNT_BIG(*) AS row_count, - CHECKSUM_AGG(BINARY_CHECKSUM(cargo_space_id, warehouse_id, group_coding_no, group_coding, status, tray_code)) AS row_checksum -FROM dbo.bas_cargo_space -UNION ALL -SELECT 'bas_stock', COUNT_BIG(*), - CHECKSUM_AGG(BINARY_CHECKSUM(stock_id, cargo_space_id, tray_code, spare4, product_id, lot_no, in_date)) -FROM dbo.bas_stock -UNION ALL -SELECT 'mydo_job', COUNT_BIG(*), - CHECKSUM_AGG(BINARY_CHECKSUM(job_id, status, task_no, tray_code, cargo_space_id, target_cargo_space_id, in_or_out, warehouse_id)) -FROM dbo.mydo_job; - -;WITH pallet_details AS ( - SELECT cargo_space_id, - MIN(NULLIF(LTRIM(RTRIM(tray_code)), '')) AS tray_code, - MIN(NULLIF(LTRIM(RTRIM(spare4)), '')) AS project_no, - MIN(NULLIF(LTRIM(RTRIM(product_id)), '')) AS first_product_no, - MAX(NULLIF(LTRIM(RTRIM(product_id)), '')) AS last_product_no, - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(product_id)), '')) AS product_count - FROM dbo.bas_stock - GROUP BY cargo_space_id -) -SELECT TOP 5 state.space_id, details.tray_code, details.project_no, - details.first_product_no, details.last_product_no, details.product_count -FROM pallet_details details -JOIN alg.v_space_state state ON state.space_id = details.cargo_space_id -WHERE details.product_count > 1 - AND state.space_status = '2' - AND state.stock_tray_count = 1 - AND state.stock_project_count = 1 - AND state.tray_conflict = 0 - AND state.project_conflict = 0 - AND state.pending_outbound = 0 - AND state.reserved = 0 - AND state.has_gap = 0 -ORDER BY state.space_id; - -SELECT space_id, warehouse_id, lane_code, depth, space_status, role, - tray_conflict, project_conflict, product_conflict, pending_outbound, reserved, has_gap, - inbound_available -FROM alg.v_space_state -WHERE tray_conflict = 1 OR project_conflict = 1 OR product_conflict = 1 - OR pending_outbound = 1 OR has_gap = 1 -ORDER BY warehouse_id, lane_code, depth; - -SELECT reservation_id, warehouse_id, lane_code, space_id, project_no, status, - created_at, released_at -FROM alg.reservation -ORDER BY created_at DESC; - -SELECT lock_id, request_id, warehouse_id, floor_no, status, created_at, released_at -FROM alg.deep_workflow_lock -ORDER BY created_at DESC; - -SELECT warehouse_id, lane_code, topology_dirty, reason_mask, change_count, - enqueued_at, last_changed_at -FROM alg.dirty_lane_queue -ORDER BY last_changed_at, warehouse_id, lane_code; - -SELECT workflow_id, request_id, stockout_detail_id, warehouse_id, source_stock_id, - selected_tray_code, source_space_id, lane_code, floor_no, exit_id, - status, current_step, total_steps, created_at, updated_at, completed_at -FROM alg.outbound_workflow -WHERE status <> 'COMPLETED' -ORDER BY created_at, workflow_id; - -SELECT step.workflow_id, step.assignment_id, step.phase, step.source_space_id, - step.target_space_id, step.workflow_step, step.workflow_total_steps, - step.depends_on_assignment_id, step.stock_id, step.tray_code, step.status, - step.wms_job_id, step.created_at, step.started_at, step.at_exit_at, step.completed_at -FROM alg.outbound_workflow_step step -JOIN alg.outbound_workflow workflow ON workflow.workflow_id = step.workflow_id -WHERE workflow.status <> 'COMPLETED' OR step.status <> 'COMPLETED' -ORDER BY step.workflow_id, step.workflow_step; - -SELECT mode.warehouse_id, mode.requested_enabled, mode.effective_state, - mode.status_reason, mode.mode_version, mode.updated_by, mode.updated_at -FROM alg.tidy_mode mode -ORDER BY mode.warehouse_id; - -SELECT queue.warehouse_id, queue.lane_code, queue.dirty_version, queue.queue_status, - queue.reason_mask, queue.change_count, queue.last_dirty_at, queue.claimed_version, - queue.claimed_by, queue.claimed_at, queue.lease_expires_at, queue.last_error -FROM alg.tidy_lane_queue queue -ORDER BY queue.last_dirty_at, queue.warehouse_id, queue.lane_code; - -SELECT workflow.workflow_id, workflow.warehouse_id, workflow.lane_code, workflow.origin, - workflow.candidate_type, workflow.source_space_id, workflow.target_space_id, - workflow.stock_id, workflow.tray_code, workflow.status, workflow.claimed_dirty_version, - workflow.vehicle_id, workflow.wcs_task_id, workflow.status_reason, - workflow.created_at, workflow.updated_at, workflow.dispatched_at, workflow.completed_at -FROM alg.tidy_workflow workflow -WHERE workflow.completed_at IS NULL - OR workflow.status IN ('PAUSED_RECOVERY', 'CANCELLED_RECONCILED') -ORDER BY workflow.created_at, workflow.workflow_id; - -SELECT step.workflow_id, step.step_id, step.step_no, step.source_space_id, - step.target_space_id, step.stock_id, step.tray_code, step.status, - step.wms_job_id, step.wcs_task_id, step.expected_event_stage, - step.created_at, step.started_at, step.completed_at -FROM alg.tidy_step step -JOIN alg.tidy_workflow workflow ON workflow.workflow_id = step.workflow_id -WHERE workflow.completed_at IS NULL OR step.completed_at IS NULL -ORDER BY step.workflow_id, step.step_no; - -SELECT lock.workflow_id, lock.warehouse_id, lock.resource_type, lock.resource_id, - lock.status, lock.locked_at, lock.released_at -FROM alg.tidy_space_lock lock -WHERE lock.status = 'ACTIVE' -ORDER BY lock.warehouse_id, lock.resource_type, lock.resource_id; - -SELECT event.workflow_id, event.step_id, event.wcs_task_id, event.event_stage, - COUNT(*) AS callback_count, MIN(event.received_at) AS first_received_at, - MAX(event.received_at) AS last_received_at -FROM alg.tidy_callback_event event -GROUP BY event.workflow_id, event.step_id, event.wcs_task_id, event.event_stage -HAVING COUNT(*) > 1 -ORDER BY event.workflow_id, event.step_id, event.event_stage; - -SELECT workflow.origin, workflow.status, COUNT(*) AS workflow_count -FROM alg.tidy_workflow workflow -GROUP BY workflow.origin, workflow.status -ORDER BY workflow.origin, workflow.status; diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql new file mode 100644 index 0000000..4d2246b --- /dev/null +++ b/fortucky-algorithm/src/main/resources/db/sqlserver/install-shadow.sql @@ -0,0 +1,654 @@ +/* + FT546 shadow-only read model for SQL Server 2016 RTM (13.0). + This package creates no Legacy-table triggers and never inserts, updates, + or deletes Legacy WMS business rows. +*/ +SET NOCOUNT ON; +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; + +IF SCHEMA_ID(N'alg') IS NULL EXEC(N'CREATE SCHEMA alg'); +GO + +IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NULL +CREATE TABLE alg.warehouse_policy ( + warehouse_id varchar(80) NOT NULL PRIMARY KEY, + target_fill_rate decimal(9,4) NULL, + fifo_tolerance_hours int NULL, + updated_at datetime2 NOT NULL CONSTRAINT DF_alg_warehouse_policy_updated_at DEFAULT SYSUTCDATETIME() +); +GO + +IF OBJECT_ID(N'alg.project_policy', N'U') IS NULL +CREATE TABLE alg.project_policy ( + project_no varchar(255) NOT NULL PRIMARY KEY, + is_isolated bit NOT NULL CONSTRAINT DF_alg_project_policy_isolated DEFAULT 0, + is_common bit NOT NULL CONSTRAINT DF_alg_project_policy_common DEFAULT 0, + allow_mixed bit NOT NULL CONSTRAINT DF_alg_project_policy_mixed DEFAULT 0, + updated_at datetime2 NOT NULL CONSTRAINT DF_alg_project_policy_updated_at DEFAULT SYSUTCDATETIME() +); +GO + +IF OBJECT_ID(N'alg.space_role', N'U') IS NULL +CREATE TABLE alg.space_role ( + space_id varchar(80) NOT NULL PRIMARY KEY, + role varchar(40) NOT NULL, + updated_at datetime2 NOT NULL CONSTRAINT DF_alg_space_role_updated_at DEFAULT SYSUTCDATETIME(), + CONSTRAINT CK_alg_space_role_role CHECK (role IN ('STORAGE', 'RELOCATION_BUFFER', 'OUTBOUND_BUFFER', 'PROTECTED_CORE')) +); +GO + +IF OBJECT_ID(N'alg.space_topology', N'U') IS NULL +CREATE TABLE alg.space_topology ( + warehouse_id varchar(80) NOT NULL, + space_id varchar(80) NOT NULL CONSTRAINT PK_alg_space_topology PRIMARY KEY, + lane_code varchar(80) NOT NULL, + lane_depth int NOT NULL, + base_zone varchar(20) NOT NULL, + rebuilt_at datetime2 NOT NULL CONSTRAINT DF_alg_space_topology_rebuilt_at DEFAULT SYSUTCDATETIME(), + CONSTRAINT CK_alg_space_topology_base_zone CHECK (base_zone IN ('CACHE', 'RESERVE', 'MAIN', 'FLEX')) +); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.space_topology') AND name = N'IX_alg_space_topology_warehouse_lane') +CREATE INDEX IX_alg_space_topology_warehouse_lane ON alg.space_topology(warehouse_id, lane_code); +GO + +IF OBJECT_ID(N'alg.inventory_item_snapshot', N'U') IS NULL +CREATE TABLE alg.inventory_item_snapshot ( + warehouse_id varchar(80) NOT NULL, + stock_id varchar(80) NOT NULL, + space_id varchar(80) NOT NULL, + tray_code varchar(255) NULL, + project_no varchar(255) NULL, + product_no varchar(255) NULL, + lot_no varchar(80) NULL, + inbound_time_raw varchar(80) NULL, + inbound_at datetime2 NULL, + quantity decimal(38,6) NULL, + out_quantity decimal(38,6) NULL, + synchronized_at datetime2 NOT NULL CONSTRAINT DF_alg_inventory_item_snapshot_sync DEFAULT SYSUTCDATETIME(), + CONSTRAINT PK_alg_inventory_item_snapshot PRIMARY KEY (warehouse_id, stock_id) +); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_space') +CREATE INDEX IX_alg_inventory_item_snapshot_space ON alg.inventory_item_snapshot(warehouse_id, space_id); +GO +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_tray') +CREATE INDEX IX_alg_inventory_item_snapshot_tray ON alg.inventory_item_snapshot(warehouse_id, tray_code) INCLUDE (space_id, project_no, product_no); +GO +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_project_product') +CREATE INDEX IX_alg_inventory_item_snapshot_project_product ON alg.inventory_item_snapshot(warehouse_id, project_no, product_no) INCLUDE (space_id, tray_code, inbound_at); +GO + +IF OBJECT_ID(N'alg.space_inventory_summary', N'U') IS NULL +CREATE TABLE alg.space_inventory_summary ( + warehouse_id varchar(80) NOT NULL, + space_id varchar(80) NOT NULL, + stock_detail_count int NOT NULL, + stock_tray_count int NOT NULL, + stock_project_count int NOT NULL, + stock_product_count int NOT NULL, + synchronized_at datetime2 NOT NULL CONSTRAINT DF_alg_space_inventory_summary_sync DEFAULT SYSUTCDATETIME(), + CONSTRAINT PK_alg_space_inventory_summary PRIMARY KEY (warehouse_id, space_id) +); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.space_inventory_summary') AND name = N'IX_alg_space_inventory_summary_space') +CREATE INDEX IX_alg_space_inventory_summary_space ON alg.space_inventory_summary(space_id, warehouse_id); +GO + +IF OBJECT_ID(N'alg.lane_state', N'U') IS NULL +CREATE TABLE alg.lane_state ( + warehouse_id varchar(80) NOT NULL, + lane_code varchar(80) NOT NULL, + space_count int NOT NULL CONSTRAINT DF_alg_lane_state_space_count DEFAULT 0, + occupied_count int NOT NULL CONSTRAINT DF_alg_lane_state_occupied_count DEFAULT 0, + available_count int NOT NULL CONSTRAINT DF_alg_lane_state_available_count DEFAULT 0, + pending_outbound bit NOT NULL CONSTRAINT DF_alg_lane_state_pending_outbound DEFAULT 0, + has_gap bit NOT NULL CONSTRAINT DF_alg_lane_state_has_gap DEFAULT 0, + semantic_fingerprint varbinary(32) NULL, + state_version rowversion NOT NULL, + rebuilt_at datetime2 NOT NULL CONSTRAINT DF_alg_lane_state_rebuilt_at DEFAULT SYSUTCDATETIME(), + CONSTRAINT PK_alg_lane_state PRIMARY KEY (warehouse_id, lane_code) +); +GO + +IF COL_LENGTH(N'alg.lane_state', N'semantic_fingerprint') IS NULL +ALTER TABLE alg.lane_state ADD semantic_fingerprint varbinary(32) NULL; +GO + +IF OBJECT_ID(N'alg.reservation', N'U') IS NULL +CREATE TABLE alg.reservation ( + reservation_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_reservation PRIMARY KEY, + warehouse_id varchar(80) NOT NULL, + lane_code varchar(80) NOT NULL, + space_id varchar(80) NOT NULL, + project_no varchar(255) NULL, + status varchar(20) NOT NULL, + created_at datetime2 NOT NULL CONSTRAINT DF_alg_reservation_created_at DEFAULT SYSUTCDATETIME(), + released_at datetime2 NULL, + CONSTRAINT CK_alg_reservation_status CHECK (status IN ('ACTIVE', 'RELEASED', 'EXPIRED')) +); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.reservation') AND name = N'UX_alg_reservation_active_space') +CREATE UNIQUE INDEX UX_alg_reservation_active_space ON alg.reservation(space_id) WHERE status = 'ACTIVE'; +GO + +IF OBJECT_ID(N'alg.deep_workflow_lock', N'U') IS NULL +CREATE TABLE alg.deep_workflow_lock ( + lock_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_deep_workflow_lock PRIMARY KEY, + request_id varchar(80) NOT NULL, + warehouse_id varchar(80) NOT NULL, + floor_no int NOT NULL, + status varchar(20) NOT NULL, + created_at datetime2 NOT NULL CONSTRAINT DF_alg_deep_workflow_lock_created_at DEFAULT SYSUTCDATETIME(), + released_at datetime2 NULL, + CONSTRAINT CK_alg_deep_workflow_lock_status CHECK (status IN ('ACTIVE', 'RELEASED')) +); +GO + +IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.deep_workflow_lock') AND name = N'UX_alg_deep_workflow_lock_active_floor') +CREATE UNIQUE INDEX UX_alg_deep_workflow_lock_active_floor +ON alg.deep_workflow_lock(warehouse_id, floor_no) WHERE status = 'ACTIVE'; +GO + +IF OBJECT_ID(N'alg.decision_log', N'U') IS NULL +CREATE TABLE alg.decision_log ( + decision_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_decision_log PRIMARY KEY, + warehouse_id varchar(80) NULL, + decision_type varchar(40) NOT NULL, + input_version binary(8) NULL, + result_code varchar(80) NULL, + reason varchar(2000) NULL, + created_at datetime2 NOT NULL CONSTRAINT DF_alg_decision_log_created_at DEFAULT SYSUTCDATETIME() +); +GO + +IF OBJECT_ID(N'alg.usp_rebuild_space_topology', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_space_topology; +GO +EXEC(N' +CREATE PROCEDURE alg.usp_rebuild_space_topology + @warehouse_id varchar(80) = NULL +AS +BEGIN + SET NOCOUNT ON; + ;WITH lane_topology AS ( + SELECT warehouse_id, group_coding_no AS lane_code, + COUNT(*) AS lane_depth, + MAX(the_column) AS column_no, + MIN(the_platoon) AS row_no, + MIN(the_floor) AS floor_no, + MIN(cargo_space_id) AS stable_id + FROM dbo.bas_cargo_space + WHERE NULLIF(LTRIM(RTRIM(group_coding_no)), '''') IS NOT NULL + AND TRY_CONVERT(int, group_coding) IS NOT NULL + AND (@warehouse_id IS NULL OR warehouse_id = @warehouse_id) + GROUP BY warehouse_id, group_coding_no + ), ranked_lanes AS ( + SELECT lane_topology.*, + CASE WHEN lane_depth = 1 THEN ROW_NUMBER() OVER ( + PARTITION BY warehouse_id + ORDER BY CASE WHEN lane_depth = 1 THEN 0 ELSE 1 END, + column_no DESC, row_no ASC, floor_no ASC, stable_id ASC + ) END AS single_depth_rank + FROM lane_topology + ), source AS ( + SELECT c.warehouse_id, c.cargo_space_id AS space_id, c.group_coding_no AS lane_code, + ranked_lanes.lane_depth, + CASE WHEN ranked_lanes.lane_depth = 1 AND ranked_lanes.single_depth_rank <= 80 THEN ''CACHE'' + WHEN ranked_lanes.lane_depth = 1 AND ranked_lanes.single_depth_rank <= 133 THEN ''RESERVE'' + WHEN ranked_lanes.lane_depth = 5 THEN ''MAIN'' + ELSE ''FLEX'' END AS base_zone + FROM dbo.bas_cargo_space c + JOIN ranked_lanes ON ranked_lanes.warehouse_id = c.warehouse_id + AND ranked_lanes.lane_code = c.group_coding_no + WHERE NULLIF(LTRIM(RTRIM(c.group_coding_no)), '''') IS NOT NULL + AND TRY_CONVERT(int, c.group_coding) IS NOT NULL + AND (@warehouse_id IS NULL OR c.warehouse_id = @warehouse_id) + ) + MERGE alg.space_topology AS target + USING source ON source.space_id = target.space_id + WHEN MATCHED AND (target.warehouse_id <> source.warehouse_id OR target.lane_code <> source.lane_code + OR target.lane_depth <> source.lane_depth OR target.base_zone <> source.base_zone) + THEN UPDATE SET warehouse_id = source.warehouse_id, lane_code = source.lane_code, + lane_depth = source.lane_depth, base_zone = source.base_zone, + rebuilt_at = SYSUTCDATETIME() + WHEN NOT MATCHED BY TARGET THEN + INSERT (warehouse_id, space_id, lane_code, lane_depth, base_zone) + VALUES (source.warehouse_id, source.space_id, source.lane_code, source.lane_depth, source.base_zone) + WHEN NOT MATCHED BY SOURCE AND (@warehouse_id IS NULL OR target.warehouse_id = @warehouse_id) + THEN DELETE; +END;'); +GO + +IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot', N'P') IS NOT NULL DROP PROCEDURE alg.usp_sync_inventory_snapshot; +GO +EXEC(N' +CREATE PROCEDURE alg.usp_sync_inventory_snapshot + @warehouse_id varchar(80) = NULL +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + BEGIN TRANSACTION; + + DELETE summary + FROM alg.space_inventory_summary summary + WHERE @warehouse_id IS NULL OR summary.warehouse_id = @warehouse_id; + + DELETE item + FROM alg.inventory_item_snapshot item + WHERE @warehouse_id IS NULL OR item.warehouse_id = @warehouse_id; + + INSERT INTO alg.inventory_item_snapshot ( + warehouse_id, stock_id, space_id, tray_code, project_no, product_no, lot_no, + inbound_time_raw, inbound_at, quantity, out_quantity, synchronized_at) + SELECT space.warehouse_id, stock.stock_id, stock.cargo_space_id, stock.tray_code, + stock.spare4, stock.product_id, stock.lot_no, CONVERT(varchar(80), stock.in_date), + TRY_CONVERT(datetime2, stock.in_date), stock.quantity, stock.out_quantity, SYSUTCDATETIME() + FROM dbo.bas_stock stock + JOIN dbo.bas_cargo_space space ON space.cargo_space_id = stock.cargo_space_id + WHERE @warehouse_id IS NULL OR space.warehouse_id = @warehouse_id; + + INSERT INTO alg.space_inventory_summary ( + warehouse_id, space_id, stock_detail_count, stock_tray_count, + stock_project_count, stock_product_count, synchronized_at) + SELECT item.warehouse_id, item.space_id, COUNT(*), + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.tray_code)), '''')), + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.project_no)), '''')), + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.product_no)), '''')), + SYSUTCDATETIME() + FROM alg.inventory_item_snapshot item + WHERE @warehouse_id IS NULL OR item.warehouse_id = @warehouse_id + GROUP BY item.warehouse_id, item.space_id; + + COMMIT TRANSACTION; +END;'); +GO + +IF OBJECT_ID(N'alg.v_inventory_item', N'V') IS NOT NULL DROP VIEW alg.v_inventory_item; +GO +EXEC(N' +CREATE VIEW alg.v_inventory_item AS +SELECT stock_id, space_id, tray_code, project_no, product_no, lot_no, + inbound_time_raw, inbound_at, quantity, out_quantity +FROM alg.inventory_item_snapshot;'); +GO + +IF OBJECT_ID(N'alg.v_active_job', N'V') IS NOT NULL DROP VIEW alg.v_active_job; +GO +EXEC(N' +CREATE VIEW alg.v_active_job AS +WITH tray_candidates AS ( + SELECT j.job_id, + COUNT(DISTINCT s.space_id) AS space_count, + MIN(s.space_id) AS cargo_space_id, + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(s.project_no)), '''')) AS project_count, + MIN(NULLIF(LTRIM(RTRIM(s.project_no)), '''')) AS project_no, + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(s.product_no)), '''')) AS product_count, + MIN(NULLIF(LTRIM(RTRIM(s.product_no)), '''')) AS product_no + FROM dbo.mydo_job j + JOIN alg.inventory_item_snapshot s ON s.warehouse_id = j.warehouse_id + AND s.tray_code = NULLIF(LTRIM(RTRIM(j.tray_code)), '''') + GROUP BY j.job_id +), resolved_jobs AS ( + SELECT j.job_id, j.task_no, j.tray_code, j.target_cargo_space_id, j.warehouse_id, + j.status, j.in_or_out, j.create_time AS create_time_raw, + COALESCE(source_space.cargo_space_id, + CASE WHEN tray_candidates.space_count = 1 THEN tray_candidates.cargo_space_id END) AS source_space_id, + CASE WHEN source_space.cargo_space_id IS NOT NULL THEN ''EXPLICIT'' + WHEN tray_candidates.space_count = 1 THEN ''TRAY'' + WHEN tray_candidates.space_count > 1 THEN ''AMBIGUOUS_TRAY'' + ELSE ''UNRESOLVED'' END AS source_resolution + FROM dbo.mydo_job j + LEFT JOIN dbo.bas_cargo_space source_space + ON source_space.cargo_space_id = NULLIF(LTRIM(RTRIM(j.cargo_space_id)), '''') + AND source_space.warehouse_id = j.warehouse_id + LEFT JOIN tray_candidates ON tray_candidates.job_id = j.job_id + WHERE j.finish_time IS NULL OR LTRIM(RTRIM(j.finish_time)) = '''' +), source_scope AS ( + SELECT resolved_jobs.job_id, + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(stock.project_no)), '''')) AS project_count, + MIN(NULLIF(LTRIM(RTRIM(stock.project_no)), '''')) AS project_no, + COUNT(DISTINCT NULLIF(LTRIM(RTRIM(stock.product_no)), '''')) AS product_count, + MIN(NULLIF(LTRIM(RTRIM(stock.product_no)), '''')) AS product_no + FROM resolved_jobs + JOIN alg.inventory_item_snapshot stock ON stock.warehouse_id = resolved_jobs.warehouse_id + AND stock.space_id = resolved_jobs.source_space_id + GROUP BY resolved_jobs.job_id +) +SELECT resolved_jobs.job_id, resolved_jobs.task_no, resolved_jobs.tray_code, + CASE WHEN resolved_jobs.source_space_id IS NOT NULL + THEN CASE WHEN source_scope.project_count = 1 THEN source_scope.project_no END + ELSE CASE WHEN tray_candidates.project_count = 1 THEN tray_candidates.project_no END END AS project_no, + CASE WHEN resolved_jobs.source_space_id IS NOT NULL + THEN CASE WHEN source_scope.product_count = 1 THEN source_scope.product_no END + ELSE CASE WHEN tray_candidates.product_count = 1 THEN tray_candidates.product_no END END AS product_no, + resolved_jobs.source_space_id, resolved_jobs.target_cargo_space_id, resolved_jobs.warehouse_id, + resolved_jobs.status, resolved_jobs.in_or_out, resolved_jobs.create_time_raw, + resolved_space.group_coding_no AS source_lane_code, resolved_jobs.source_resolution +FROM resolved_jobs +LEFT JOIN tray_candidates ON tray_candidates.job_id = resolved_jobs.job_id +LEFT JOIN source_scope ON source_scope.job_id = resolved_jobs.job_id +LEFT JOIN dbo.bas_cargo_space resolved_space ON resolved_space.cargo_space_id = resolved_jobs.source_space_id;'); +GO + +IF OBJECT_ID(N'alg.v_space_state', N'V') IS NOT NULL DROP VIEW alg.v_space_state; +GO +EXEC(N' +CREATE VIEW alg.v_space_state AS +WITH outbound_lanes AS ( + SELECT DISTINCT warehouse_id, source_lane_code AS lane_code + FROM alg.v_active_job + WHERE in_or_out >= 20 AND source_lane_code IS NOT NULL +), lane_gaps AS ( + SELECT DISTINCT inner_space.warehouse_id, inner_space.group_coding_no AS lane_code + FROM dbo.bas_cargo_space inner_space + JOIN dbo.bas_cargo_space outer_space + ON outer_space.warehouse_id = inner_space.warehouse_id + AND outer_space.group_coding_no = inner_space.group_coding_no + AND TRY_CONVERT(int, outer_space.group_coding) > TRY_CONVERT(int, inner_space.group_coding) + WHERE TRY_CONVERT(int, inner_space.group_coding) IS NOT NULL + AND TRY_CONVERT(int, outer_space.group_coding) IS NOT NULL + AND NULLIF(LTRIM(RTRIM(inner_space.tray_code)), '''') IS NULL + AND NOT EXISTS ( + SELECT 1 FROM alg.inventory_item_snapshot inner_stock + WHERE inner_stock.warehouse_id = inner_space.warehouse_id + AND inner_stock.space_id = inner_space.cargo_space_id + ) + AND ( + NULLIF(LTRIM(RTRIM(outer_space.tray_code)), '''') IS NOT NULL + OR EXISTS ( + SELECT 1 FROM alg.inventory_item_snapshot outer_stock + WHERE outer_stock.warehouse_id = outer_space.warehouse_id + AND outer_stock.space_id = outer_space.cargo_space_id + ) + ) +) +SELECT c.cargo_space_id AS space_id, + c.warehouse_id, + c.group_coding_no AS lane_code, + c.cargo_area_id AS area_id, + CASE + WHEN ISNULL(role.role, ''STORAGE'') IN (''RELOCATION_BUFFER'', ''OUTBOUND_BUFFER'') THEN ''CACHE'' + WHEN ISNULL(role.role, ''STORAGE'') = ''PROTECTED_CORE'' THEN ''RESERVE'' + ELSE topology.base_zone + END AS zone_code, + c.roadway_id, + c.cargo_space_name AS display_name, + c.the_platoon AS platoon, + c.the_column AS column_no, + c.the_floor AS floor_no, + c.group_coding AS depth_raw, + TRY_CONVERT(int, c.group_coding) AS depth, + c.status AS space_status, + c.locked, + c.tray_code AS space_tray_code, + ISNULL(role.role, ''STORAGE'') AS role, + ISNULL(stock.stock_detail_count, 0) AS stock_detail_count, + ISNULL(stock.stock_tray_count, 0) AS stock_tray_count, + ISNULL(stock.stock_project_count, 0) AS stock_project_count, + ISNULL(stock.stock_product_count, 0) AS stock_product_count, + CONVERT(bit, CASE WHEN ISNULL(stock.stock_tray_count, 0) > 1 THEN 1 ELSE 0 END) AS tray_conflict, + CONVERT(bit, CASE WHEN ISNULL(stock.stock_project_count, 0) > 1 THEN 1 ELSE 0 END) AS project_conflict, + CONVERT(bit, 0) AS product_conflict, + CONVERT(bit, CASE WHEN outbound_lanes.lane_code IS NULL THEN 0 ELSE 1 END) AS pending_outbound, + active_reservations.reservation_id, + CONVERT(bit, CASE WHEN active_reservations.reservation_id IS NULL THEN 0 ELSE 1 END) AS reserved, + CONVERT(bit, CASE WHEN lane_gaps.lane_code IS NULL THEN 0 ELSE 1 END) AS has_gap, + CONVERT(bit, CASE + WHEN c.status IS NULL OR c.status <> ''1'' THEN 0 + WHEN NULLIF(LTRIM(RTRIM(c.locked)), '''') IS NOT NULL AND LTRIM(RTRIM(c.locked)) NOT IN (''0'', ''N'') THEN 0 + WHEN NULLIF(LTRIM(RTRIM(c.tray_code)), '''') IS NOT NULL THEN 0 + WHEN ISNULL(stock.stock_detail_count, 0) <> 0 THEN 0 + WHEN TRY_CONVERT(int, c.group_coding) IS NULL THEN 0 + WHEN ISNULL(role.role, ''STORAGE'') <> ''STORAGE'' THEN 0 + WHEN outbound_lanes.lane_code IS NOT NULL THEN 0 + WHEN active_reservations.reservation_id IS NOT NULL THEN 0 + WHEN lane_gaps.lane_code IS NOT NULL THEN 0 + WHEN EXISTS ( + SELECT 1 + FROM dbo.bas_cargo_space inner_candidate + LEFT JOIN alg.space_inventory_summary inner_stock + ON inner_stock.warehouse_id = inner_candidate.warehouse_id + AND inner_stock.space_id = inner_candidate.cargo_space_id + WHERE inner_candidate.warehouse_id = c.warehouse_id + AND inner_candidate.group_coding_no = c.group_coding_no + AND TRY_CONVERT(int, inner_candidate.group_coding) < TRY_CONVERT(int, c.group_coding) + AND inner_candidate.status = ''1'' + AND NULLIF(LTRIM(RTRIM(inner_candidate.tray_code)), '''') IS NULL + AND inner_stock.space_id IS NULL + ) THEN 0 + ELSE 1 END) AS inbound_available +FROM dbo.bas_cargo_space c +JOIN alg.space_topology topology ON topology.space_id = c.cargo_space_id +LEFT JOIN alg.space_inventory_summary stock ON stock.warehouse_id = c.warehouse_id + AND stock.space_id = c.cargo_space_id +LEFT JOIN alg.space_role role ON role.space_id = c.cargo_space_id +LEFT JOIN outbound_lanes ON outbound_lanes.warehouse_id = c.warehouse_id AND outbound_lanes.lane_code = c.group_coding_no +LEFT JOIN alg.reservation active_reservations ON active_reservations.space_id = c.cargo_space_id AND active_reservations.status = ''ACTIVE'' +LEFT JOIN lane_gaps ON lane_gaps.warehouse_id = c.warehouse_id AND lane_gaps.lane_code = c.group_coding_no;'); +GO + +IF OBJECT_ID(N'alg.usp_compute_lane_semantic_fingerprint', N'P') IS NOT NULL DROP PROCEDURE alg.usp_compute_lane_semantic_fingerprint; +GO +EXEC(N' +CREATE PROCEDURE alg.usp_compute_lane_semantic_fingerprint + @warehouse_id varchar(80), + @lane_code varchar(80), + @semantic_fingerprint varbinary(32) OUTPUT, + @inventory_source varchar(20) = ''LIVE'' +AS +BEGIN + SET NOCOUNT ON; + DECLARE @payload nvarchar(max); + + CREATE TABLE #fingerprint_inventory ( + stock_id varchar(80) NOT NULL, + space_id varchar(80) NOT NULL, + tray_code varchar(255) NULL, + project_no varchar(255) NULL, + product_no varchar(255) NULL, + lot_no varchar(80) NULL, + inbound_time varchar(80) NULL, + quantity decimal(38,6) NULL, + out_quantity decimal(38,6) NULL + ); + + IF @inventory_source = ''SNAPSHOT'' + BEGIN + INSERT INTO #fingerprint_inventory ( + stock_id, space_id, tray_code, project_no, product_no, lot_no, + inbound_time, quantity, out_quantity) + SELECT stock.stock_id, stock.space_id, stock.tray_code, stock.project_no, + stock.product_no, stock.lot_no, stock.inbound_time_raw, + CONVERT(decimal(38,6), stock.quantity), CONVERT(decimal(38,6), stock.out_quantity) + FROM alg.inventory_item_snapshot stock + JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = stock.space_id + AND cargo.warehouse_id = stock.warehouse_id + WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code; + END + ELSE IF @inventory_source = ''LIVE'' + BEGIN + INSERT INTO #fingerprint_inventory ( + stock_id, space_id, tray_code, project_no, product_no, lot_no, + inbound_time, quantity, out_quantity) + SELECT stock.stock_id, stock.cargo_space_id, stock.tray_code, stock.spare4, + stock.product_id, stock.lot_no, CONVERT(varchar(80), stock.in_date), + CONVERT(decimal(38,6), stock.quantity), CONVERT(decimal(38,6), stock.out_quantity) + FROM dbo.bas_stock stock + JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = stock.cargo_space_id + WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code; + END + ELSE + BEGIN + THROW 50001, ''Unsupported inventory fingerprint source'', 1; + END; + + SELECT @payload = ISNULL(CONVERT(nvarchar(max), ( + SELECT cargo.cargo_space_id AS [@space_id], cargo.status AS [@status], + cargo.locked AS [@locked], cargo.tray_code AS [@tray_code], + cargo.group_coding AS [@depth], cargo.the_platoon AS [@platoon], + cargo.the_column AS [@column], cargo.the_floor AS [@floor], + topology.lane_depth AS [@lane_depth], topology.base_zone AS [@base_zone], + ISNULL(role.role, ''STORAGE'') AS [@role] + FROM dbo.bas_cargo_space cargo + LEFT JOIN alg.space_topology topology ON topology.space_id = cargo.cargo_space_id + LEFT JOIN alg.space_role role ON role.space_id = cargo.cargo_space_id + WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code + ORDER BY TRY_CONVERT(int, cargo.group_coding), cargo.cargo_space_id + FOR XML PATH(''space''), ROOT(''spaces''), TYPE + )), N''''); + + SET @payload = @payload + N''|INVENTORY|'' + ISNULL(CONVERT(nvarchar(max), ( + SELECT stock.stock_id AS [@stock_id], stock.space_id AS [@space_id], + stock.tray_code AS [@tray_code], stock.project_no AS [@project_no], + stock.product_no AS [@product_no], stock.lot_no AS [@lot_no], + stock.inbound_time AS [@inbound_time], + stock.quantity AS [@quantity], stock.out_quantity AS [@out_quantity] + FROM #fingerprint_inventory stock + ORDER BY stock.space_id, stock.stock_id + FOR XML PATH(''item''), ROOT(''inventory''), TYPE + )), N''''); + + SET @payload = @payload + N''|ACTIVE_JOBS|'' + ISNULL(CONVERT(nvarchar(max), ( + SELECT job.job_id AS [@job_id], job.task_no AS [@task_no], job.tray_code AS [@tray_code], + job.source_space_id AS [@source_space_id], job.target_cargo_space_id AS [@target_space_id], + job.status AS [@status], job.in_or_out AS [@in_or_out], + job.create_time_raw AS [@create_time] + FROM alg.v_active_job job + WHERE job.warehouse_id = @warehouse_id AND job.source_lane_code = @lane_code + ORDER BY job.job_id + FOR XML PATH(''job''), ROOT(''jobs''), TYPE + )), N''''); + + SET @payload = @payload + N''|PROJECT_POLICY|'' + ISNULL(CONVERT(nvarchar(max), ( + SELECT policy.project_no AS [@project_no], policy.is_isolated AS [@is_isolated], + policy.is_common AS [@is_common], policy.allow_mixed AS [@allow_mixed] + FROM alg.project_policy policy + ORDER BY policy.project_no + FOR XML PATH(''project''), ROOT(''project_policy''), TYPE + )), N''''); + + SET @payload = @payload + N''|WAREHOUSE_POLICY|'' + ISNULL(CONVERT(nvarchar(max), ( + SELECT policy.warehouse_id AS [@warehouse_id], policy.target_fill_rate AS [@target_fill_rate], + policy.fifo_tolerance_hours AS [@fifo_tolerance_hours] + FROM alg.warehouse_policy policy + WHERE policy.warehouse_id = @warehouse_id + FOR XML PATH(''warehouse''), ROOT(''warehouse_policy''), TYPE + )), N''''); + + SET @payload = @payload + N''|RESERVATIONS|'' + ISNULL(CONVERT(nvarchar(max), ( + SELECT reservation.reservation_id AS [@reservation_id], reservation.space_id AS [@space_id], + reservation.project_no AS [@project_no], reservation.status AS [@status] + FROM alg.reservation reservation + WHERE reservation.warehouse_id = @warehouse_id AND reservation.lane_code = @lane_code + AND reservation.status = ''ACTIVE'' + ORDER BY reservation.space_id, reservation.reservation_id + FOR XML PATH(''reservation''), ROOT(''reservations''), TYPE + )), N''''); + + SET @semantic_fingerprint = HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), @payload)); +END;'); +GO + +IF OBJECT_ID(N'alg.usp_rebuild_lane_state', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_lane_state; +GO +EXEC(N' +CREATE PROCEDURE alg.usp_rebuild_lane_state + @warehouse_id varchar(80) = NULL, + @lane_code varchar(80) = NULL +AS +BEGIN + SET NOCOUNT ON; + SELECT warehouse_id, lane_code, + COUNT(*) AS space_count, + SUM(CASE WHEN space_status = ''2'' OR stock_detail_count > 0 THEN 1 ELSE 0 END) AS occupied_count, + SUM(CASE WHEN inbound_available = 1 THEN 1 ELSE 0 END) AS available_count, + MAX(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_outbound, + MAX(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS has_gap, + CAST(NULL AS varbinary(32)) AS semantic_fingerprint + INTO #source_state + FROM alg.v_space_state + WHERE (@warehouse_id IS NULL OR warehouse_id = @warehouse_id) + AND (@lane_code IS NULL OR lane_code = @lane_code) + GROUP BY warehouse_id, lane_code; + + DECLARE @fingerprint_warehouse varchar(80), @fingerprint_lane varchar(80), + @fingerprint varbinary(32); + DECLARE fingerprint_cursor CURSOR LOCAL FAST_FORWARD FOR + SELECT warehouse_id, lane_code FROM #source_state ORDER BY warehouse_id, lane_code; + OPEN fingerprint_cursor; + FETCH NEXT FROM fingerprint_cursor INTO @fingerprint_warehouse, @fingerprint_lane; + WHILE @@FETCH_STATUS = 0 + BEGIN + EXEC alg.usp_compute_lane_semantic_fingerprint + @warehouse_id = @fingerprint_warehouse, + @lane_code = @fingerprint_lane, + @semantic_fingerprint = @fingerprint OUTPUT, + @inventory_source = ''SNAPSHOT''; + UPDATE #source_state SET semantic_fingerprint = @fingerprint + WHERE warehouse_id = @fingerprint_warehouse AND lane_code = @fingerprint_lane; + FETCH NEXT FROM fingerprint_cursor INTO @fingerprint_warehouse, @fingerprint_lane; + END; + CLOSE fingerprint_cursor; + DEALLOCATE fingerprint_cursor; + + MERGE alg.lane_state WITH (HOLDLOCK) AS target + USING #source_state AS source + ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code + WHEN MATCHED AND (target.space_count <> source.space_count OR + target.occupied_count <> source.occupied_count OR + target.available_count <> source.available_count OR + target.pending_outbound <> source.pending_outbound OR + target.has_gap <> source.has_gap OR + target.semantic_fingerprint IS NULL OR + target.semantic_fingerprint <> source.semantic_fingerprint) + THEN UPDATE SET space_count = source.space_count, + occupied_count = source.occupied_count, + available_count = source.available_count, + pending_outbound = source.pending_outbound, + has_gap = source.has_gap, + semantic_fingerprint = source.semantic_fingerprint, + rebuilt_at = SYSUTCDATETIME() + WHEN NOT MATCHED BY TARGET THEN + INSERT (warehouse_id, lane_code, space_count, occupied_count, available_count, + pending_outbound, has_gap, semantic_fingerprint) + VALUES (source.warehouse_id, source.lane_code, source.space_count, source.occupied_count, + source.available_count, source.pending_outbound, source.has_gap, source.semantic_fingerprint) + WHEN NOT MATCHED BY SOURCE + AND (@warehouse_id IS NULL OR target.warehouse_id = @warehouse_id) + AND (@lane_code IS NULL OR target.lane_code = @lane_code) + THEN DELETE; +END;'); +GO + +IF OBJECT_ID(N'alg.usp_refresh_dirty_lanes', N'P') IS NOT NULL DROP PROCEDURE alg.usp_refresh_dirty_lanes; +GO +EXEC(N' +CREATE PROCEDURE alg.usp_refresh_dirty_lanes + @warehouse_id varchar(80) +AS +BEGIN + SET NOCOUNT ON; + IF NULLIF(LTRIM(RTRIM(@warehouse_id)), '''') IS NULL + THROW 50002, ''warehouse_id is required'', 1; + + EXEC alg.usp_rebuild_space_topology @warehouse_id = @warehouse_id; + EXEC alg.usp_sync_inventory_snapshot @warehouse_id = @warehouse_id; + EXEC alg.usp_rebuild_lane_state @warehouse_id = @warehouse_id; +END;'); +GO + + +IF NOT EXISTS (SELECT 1 FROM alg.space_topology) + AND NOT EXISTS (SELECT 1 FROM alg.inventory_item_snapshot) + AND NOT EXISTS (SELECT 1 FROM alg.lane_state) +BEGIN + EXEC alg.usp_rebuild_space_topology; + EXEC alg.usp_sync_inventory_snapshot; + EXEC alg.usp_rebuild_lane_state; +END; +GO diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/install.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/install.sql deleted file mode 100644 index 1164263..0000000 --- a/fortucky-algorithm/src/main/resources/db/sqlserver/install.sql +++ /dev/null @@ -1,1936 +0,0 @@ -/* - FT546 algorithm read model for SQL Server 2016 RTM (13.0). - This script owns only schema alg. It never changes dbo.bas_cargo_space, - dbo.bas_stock, or dbo.mydo_job. -*/ -SET NOCOUNT ON; - -IF SCHEMA_ID(N'alg') IS NULL EXEC(N'CREATE SCHEMA alg'); -GO - -IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NULL -CREATE TABLE alg.warehouse_policy ( - warehouse_id varchar(80) NOT NULL PRIMARY KEY, - target_fill_rate decimal(9,4) NULL, - fifo_tolerance_hours int NULL, - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_warehouse_policy_updated_at DEFAULT SYSUTCDATETIME() -); -GO - -IF OBJECT_ID(N'alg.project_policy', N'U') IS NULL -CREATE TABLE alg.project_policy ( - project_no varchar(255) NOT NULL PRIMARY KEY, - is_isolated bit NOT NULL CONSTRAINT DF_alg_project_policy_isolated DEFAULT 0, - is_common bit NOT NULL CONSTRAINT DF_alg_project_policy_common DEFAULT 0, - allow_mixed bit NOT NULL CONSTRAINT DF_alg_project_policy_mixed DEFAULT 0, - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_project_policy_updated_at DEFAULT SYSUTCDATETIME() -); -GO - -IF OBJECT_ID(N'alg.space_role', N'U') IS NULL -CREATE TABLE alg.space_role ( - space_id varchar(80) NOT NULL PRIMARY KEY, - role varchar(40) NOT NULL, - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_space_role_updated_at DEFAULT SYSUTCDATETIME(), - CONSTRAINT CK_alg_space_role_role CHECK (role IN ('STORAGE', 'RELOCATION_BUFFER', 'OUTBOUND_BUFFER', 'PROTECTED_CORE')) -); -GO - -IF OBJECT_ID(N'alg.space_topology', N'U') IS NULL -CREATE TABLE alg.space_topology ( - warehouse_id varchar(80) NOT NULL, - space_id varchar(80) NOT NULL CONSTRAINT PK_alg_space_topology PRIMARY KEY, - lane_code varchar(80) NOT NULL, - lane_depth int NOT NULL, - base_zone varchar(20) NOT NULL, - rebuilt_at datetime2 NOT NULL CONSTRAINT DF_alg_space_topology_rebuilt_at DEFAULT SYSUTCDATETIME(), - CONSTRAINT CK_alg_space_topology_base_zone CHECK (base_zone IN ('CACHE', 'RESERVE', 'MAIN', 'FLEX')) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.space_topology') AND name = N'IX_alg_space_topology_warehouse_lane') -CREATE INDEX IX_alg_space_topology_warehouse_lane ON alg.space_topology(warehouse_id, lane_code); -GO - -IF OBJECT_ID(N'alg.inventory_item_snapshot', N'U') IS NULL -CREATE TABLE alg.inventory_item_snapshot ( - warehouse_id varchar(80) NOT NULL, - stock_id varchar(80) NOT NULL, - space_id varchar(80) NOT NULL, - tray_code varchar(255) NULL, - project_no varchar(255) NULL, - product_no varchar(255) NULL, - lot_no varchar(80) NULL, - inbound_time_raw varchar(80) NULL, - inbound_at datetime2 NULL, - quantity decimal(38,6) NULL, - out_quantity decimal(38,6) NULL, - synchronized_at datetime2 NOT NULL CONSTRAINT DF_alg_inventory_item_snapshot_sync DEFAULT SYSUTCDATETIME(), - CONSTRAINT PK_alg_inventory_item_snapshot PRIMARY KEY (warehouse_id, stock_id) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_space') -CREATE INDEX IX_alg_inventory_item_snapshot_space ON alg.inventory_item_snapshot(warehouse_id, space_id); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_tray') -CREATE INDEX IX_alg_inventory_item_snapshot_tray ON alg.inventory_item_snapshot(warehouse_id, tray_code) INCLUDE (space_id, project_no, product_no); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.inventory_item_snapshot') AND name = N'IX_alg_inventory_item_snapshot_project_product') -CREATE INDEX IX_alg_inventory_item_snapshot_project_product ON alg.inventory_item_snapshot(warehouse_id, project_no, product_no) INCLUDE (space_id, tray_code, inbound_at); -GO - -IF OBJECT_ID(N'alg.space_inventory_summary', N'U') IS NULL -CREATE TABLE alg.space_inventory_summary ( - warehouse_id varchar(80) NOT NULL, - space_id varchar(80) NOT NULL, - stock_detail_count int NOT NULL, - stock_tray_count int NOT NULL, - stock_project_count int NOT NULL, - stock_product_count int NOT NULL, - synchronized_at datetime2 NOT NULL CONSTRAINT DF_alg_space_inventory_summary_sync DEFAULT SYSUTCDATETIME(), - CONSTRAINT PK_alg_space_inventory_summary PRIMARY KEY (warehouse_id, space_id) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.space_inventory_summary') AND name = N'IX_alg_space_inventory_summary_space') -CREATE INDEX IX_alg_space_inventory_summary_space ON alg.space_inventory_summary(space_id, warehouse_id); -GO - -IF OBJECT_ID(N'alg.lane_state', N'U') IS NULL -CREATE TABLE alg.lane_state ( - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - space_count int NOT NULL CONSTRAINT DF_alg_lane_state_space_count DEFAULT 0, - occupied_count int NOT NULL CONSTRAINT DF_alg_lane_state_occupied_count DEFAULT 0, - available_count int NOT NULL CONSTRAINT DF_alg_lane_state_available_count DEFAULT 0, - pending_outbound bit NOT NULL CONSTRAINT DF_alg_lane_state_pending_outbound DEFAULT 0, - has_gap bit NOT NULL CONSTRAINT DF_alg_lane_state_has_gap DEFAULT 0, - semantic_fingerprint varbinary(32) NULL, - state_version rowversion NOT NULL, - rebuilt_at datetime2 NOT NULL CONSTRAINT DF_alg_lane_state_rebuilt_at DEFAULT SYSUTCDATETIME(), - CONSTRAINT PK_alg_lane_state PRIMARY KEY (warehouse_id, lane_code) -); -GO - -IF COL_LENGTH(N'alg.lane_state', N'semantic_fingerprint') IS NULL -ALTER TABLE alg.lane_state ADD semantic_fingerprint varbinary(32) NULL; -GO - -IF OBJECT_ID(N'alg.reservation', N'U') IS NULL -CREATE TABLE alg.reservation ( - reservation_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_reservation PRIMARY KEY, - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - space_id varchar(80) NOT NULL, - project_no varchar(255) NULL, - status varchar(20) NOT NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_reservation_created_at DEFAULT SYSUTCDATETIME(), - released_at datetime2 NULL, - CONSTRAINT CK_alg_reservation_status CHECK (status IN ('ACTIVE', 'RELEASED', 'EXPIRED')) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.reservation') AND name = N'UX_alg_reservation_active_space') -CREATE UNIQUE INDEX UX_alg_reservation_active_space ON alg.reservation(space_id) WHERE status = 'ACTIVE'; -GO - -IF OBJECT_ID(N'alg.deep_workflow_lock', N'U') IS NULL -CREATE TABLE alg.deep_workflow_lock ( - lock_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_deep_workflow_lock PRIMARY KEY, - request_id varchar(80) NOT NULL, - warehouse_id varchar(80) NOT NULL, - floor_no int NOT NULL, - status varchar(20) NOT NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_deep_workflow_lock_created_at DEFAULT SYSUTCDATETIME(), - released_at datetime2 NULL, - CONSTRAINT CK_alg_deep_workflow_lock_status CHECK (status IN ('ACTIVE', 'RELEASED')) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.deep_workflow_lock') AND name = N'UX_alg_deep_workflow_lock_active_floor') -CREATE UNIQUE INDEX UX_alg_deep_workflow_lock_active_floor -ON alg.deep_workflow_lock(warehouse_id, floor_no) WHERE status = 'ACTIVE'; -GO - -IF OBJECT_ID(N'alg.outbound_workflow', N'U') IS NULL -CREATE TABLE alg.outbound_workflow ( - workflow_id varchar(120) NOT NULL CONSTRAINT PK_alg_outbound_workflow PRIMARY KEY, - request_id varchar(80) NOT NULL, - stockout_detail_id varchar(80) NOT NULL, - warehouse_id varchar(80) NOT NULL, - source_stock_id varchar(80) NOT NULL, - selected_tray_code varchar(255) NOT NULL, - source_space_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - floor_no int NOT NULL, - exit_id varchar(80) NOT NULL, - status varchar(20) NOT NULL, - current_step int NOT NULL CONSTRAINT DF_alg_outbound_workflow_current_step DEFAULT 0, - total_steps int NOT NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_outbound_workflow_created_at DEFAULT SYSUTCDATETIME(), - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_outbound_workflow_updated_at DEFAULT SYSUTCDATETIME(), - completed_at datetime2 NULL, - CONSTRAINT CK_alg_outbound_workflow_status CHECK (status IN ('ACTIVE', 'COMPLETED', 'FAILED', 'CANCELLED')), - CONSTRAINT CK_alg_outbound_workflow_steps CHECK (total_steps > 0 AND current_step >= 0 AND current_step <= total_steps) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.outbound_workflow') AND name = N'UX_alg_outbound_workflow_active_request') -CREATE UNIQUE INDEX UX_alg_outbound_workflow_active_request -ON alg.outbound_workflow(request_id, warehouse_id, floor_no) WHERE status = 'ACTIVE'; -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.outbound_workflow') AND name = N'IX_alg_outbound_workflow_stockout_detail') -CREATE INDEX IX_alg_outbound_workflow_stockout_detail -ON alg.outbound_workflow(stockout_detail_id, status) INCLUDE (workflow_id, warehouse_id, floor_no); -GO - -IF COL_LENGTH(N'alg.outbound_workflow', N'origin') IS NULL -ALTER TABLE alg.outbound_workflow ADD origin varchar(24) NOT NULL - CONSTRAINT DF_alg_outbound_workflow_origin DEFAULT 'OUTBOUND_RELOCATION'; -GO -IF NOT EXISTS ( - SELECT 1 FROM sys.check_constraints - WHERE parent_object_id = OBJECT_ID(N'alg.outbound_workflow') - AND name = N'CK_alg_outbound_workflow_origin') -ALTER TABLE alg.outbound_workflow WITH CHECK ADD CONSTRAINT CK_alg_outbound_workflow_origin - CHECK (origin = 'OUTBOUND_RELOCATION'); -GO - -IF OBJECT_ID(N'alg.outbound_workflow_step', N'U') IS NULL -CREATE TABLE alg.outbound_workflow_step ( - assignment_id varchar(120) NOT NULL CONSTRAINT PK_alg_outbound_workflow_step PRIMARY KEY, - workflow_id varchar(120) NOT NULL, - phase varchar(20) NOT NULL, - source_space_id varchar(80) NOT NULL, - target_space_id varchar(80) NULL, - workflow_step int NOT NULL, - workflow_total_steps int NOT NULL, - depends_on_assignment_id varchar(120) NULL, - stock_id varchar(80) NOT NULL, - tray_code varchar(255) NOT NULL, - status varchar(20) NOT NULL, - wms_job_id varchar(120) NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_outbound_workflow_step_created_at DEFAULT SYSUTCDATETIME(), - started_at datetime2 NULL, - at_exit_at datetime2 NULL, - completed_at datetime2 NULL, - CONSTRAINT FK_alg_outbound_workflow_step_workflow FOREIGN KEY (workflow_id) - REFERENCES alg.outbound_workflow(workflow_id), - CONSTRAINT FK_alg_outbound_workflow_step_dependency FOREIGN KEY (depends_on_assignment_id) - REFERENCES alg.outbound_workflow_step(assignment_id), - CONSTRAINT CK_alg_outbound_workflow_step_phase CHECK (phase IN ('MOVE_OUT', 'OUTBOUND', 'RESTORE')), - CONSTRAINT CK_alg_outbound_workflow_step_status CHECK (status IN ('PENDING', 'RUNNING', 'AT_EXIT', 'COMPLETED', 'FAILED')), - CONSTRAINT CK_alg_outbound_workflow_step_order CHECK ( - workflow_step > 0 AND workflow_total_steps > 0 AND workflow_step <= workflow_total_steps) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.outbound_workflow_step') AND name = N'UX_alg_outbound_workflow_step_order') -CREATE UNIQUE INDEX UX_alg_outbound_workflow_step_order -ON alg.outbound_workflow_step(workflow_id, workflow_step); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.outbound_workflow_step') AND name = N'UX_alg_outbound_workflow_step_wms_job') -CREATE UNIQUE INDEX UX_alg_outbound_workflow_step_wms_job -ON alg.outbound_workflow_step(wms_job_id) WHERE wms_job_id IS NOT NULL; -GO - -IF OBJECT_ID(N'alg.outbound_workflow_space', N'U') IS NULL -CREATE TABLE alg.outbound_workflow_space ( - workflow_id varchar(120) NOT NULL, - space_id varchar(80) NOT NULL, - status varchar(20) NOT NULL, - locked_at datetime2 NOT NULL CONSTRAINT DF_alg_outbound_workflow_space_locked_at DEFAULT SYSUTCDATETIME(), - released_at datetime2 NULL, - CONSTRAINT PK_alg_outbound_workflow_space PRIMARY KEY (workflow_id, space_id), - CONSTRAINT FK_alg_outbound_workflow_space_workflow FOREIGN KEY (workflow_id) - REFERENCES alg.outbound_workflow(workflow_id), - CONSTRAINT CK_alg_outbound_workflow_space_status CHECK (status IN ('ACTIVE', 'RELEASED')) -); -GO - -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.outbound_workflow_space') AND name = N'UX_alg_outbound_workflow_space_active') -CREATE UNIQUE INDEX UX_alg_outbound_workflow_space_active -ON alg.outbound_workflow_space(space_id) WHERE status = 'ACTIVE'; -GO - -IF OBJECT_ID(N'alg.dirty_lane_queue', N'U') IS NULL -CREATE TABLE alg.dirty_lane_queue ( - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - topology_dirty bit NOT NULL CONSTRAINT DF_alg_dirty_lane_queue_topology DEFAULT 0, - reason_mask int NOT NULL CONSTRAINT DF_alg_dirty_lane_queue_reason DEFAULT 0, - change_count bigint NOT NULL CONSTRAINT DF_alg_dirty_lane_queue_count DEFAULT 1, - enqueued_at datetime2 NOT NULL CONSTRAINT DF_alg_dirty_lane_queue_enqueued DEFAULT SYSUTCDATETIME(), - last_changed_at datetime2 NOT NULL CONSTRAINT DF_alg_dirty_lane_queue_changed DEFAULT SYSUTCDATETIME(), - CONSTRAINT PK_alg_dirty_lane_queue PRIMARY KEY (warehouse_id, lane_code) -); -GO - -IF OBJECT_ID(N'alg.tidy_mode', N'U') IS NULL -CREATE TABLE alg.tidy_mode ( - warehouse_id varchar(80) NOT NULL CONSTRAINT PK_alg_tidy_mode PRIMARY KEY, - requested_enabled bit NOT NULL CONSTRAINT DF_alg_tidy_mode_requested DEFAULT 0, - effective_state varchar(24) NOT NULL CONSTRAINT DF_alg_tidy_mode_state DEFAULT 'NORMAL', - status_reason varchar(255) NULL, - mode_version bigint NOT NULL CONSTRAINT DF_alg_tidy_mode_version DEFAULT 0, - updated_by varchar(80) NULL, - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_mode_updated DEFAULT SYSUTCDATETIME(), - CONSTRAINT CK_alg_tidy_mode_effective_state CHECK ( - effective_state IN ('NORMAL', 'ENTERING_TIDY', 'TIDY_ACTIVE', 'EXITING_TIDY')) -); -GO - -IF OBJECT_ID(N'alg.tidy_lane_queue', N'U') IS NULL -CREATE TABLE alg.tidy_lane_queue ( - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - dirty_version bigint NOT NULL CONSTRAINT DF_alg_tidy_lane_queue_version DEFAULT 1, - queue_status varchar(20) NOT NULL CONSTRAINT DF_alg_tidy_lane_queue_status DEFAULT 'PENDING', - reason_mask int NOT NULL CONSTRAINT DF_alg_tidy_lane_queue_reason DEFAULT 0, - change_count bigint NOT NULL CONSTRAINT DF_alg_tidy_lane_queue_count DEFAULT 1, - last_dirty_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_lane_queue_dirty DEFAULT SYSUTCDATETIME(), - claim_token uniqueidentifier NULL, - claimed_version bigint NULL, - claimed_by varchar(120) NULL, - claimed_at datetime2 NULL, - lease_expires_at datetime2 NULL, - last_error varchar(2000) NULL, - CONSTRAINT PK_alg_tidy_lane_queue PRIMARY KEY (warehouse_id, lane_code), - CONSTRAINT CK_alg_tidy_lane_queue_status CHECK (queue_status IN ('PENDING', 'CLAIMED', 'COMPLETED')), - CONSTRAINT CK_alg_tidy_lane_queue_claim CHECK ( - (queue_status = 'CLAIMED' AND claim_token IS NOT NULL AND claimed_version IS NOT NULL - AND claimed_at IS NOT NULL AND lease_expires_at IS NOT NULL) - OR (queue_status <> 'CLAIMED' AND claim_token IS NULL AND claimed_version IS NULL - AND claimed_by IS NULL AND claimed_at IS NULL AND lease_expires_at IS NULL)) -); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_lane_queue') AND name = N'IX_alg_tidy_lane_queue_claim') -CREATE INDEX IX_alg_tidy_lane_queue_claim -ON alg.tidy_lane_queue(queue_status, last_dirty_at, warehouse_id, lane_code) -INCLUDE (dirty_version, lease_expires_at); -GO - -IF OBJECT_ID(N'alg.tidy_workflow', N'U') IS NULL -CREATE TABLE alg.tidy_workflow ( - workflow_id varchar(120) NOT NULL CONSTRAINT PK_alg_tidy_workflow PRIMARY KEY, - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - origin varchar(24) NOT NULL, - candidate_type varchar(40) NOT NULL, - source_space_id varchar(80) NOT NULL, - target_space_id varchar(80) NOT NULL, - stock_id varchar(80) NOT NULL, - tray_code varchar(255) NOT NULL, - status varchar(24) NOT NULL, - claimed_dirty_version bigint NOT NULL, - lane_claim_token uniqueidentifier NOT NULL, - mode_version bigint NOT NULL, - vehicle_id varchar(80) NULL, - wcs_task_id varchar(120) NULL, - status_reason varchar(2000) NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_workflow_created DEFAULT SYSUTCDATETIME(), - updated_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_workflow_updated DEFAULT SYSUTCDATETIME(), - dispatched_at datetime2 NULL, - completed_at datetime2 NULL, - CONSTRAINT CK_alg_tidy_workflow_origin CHECK (origin IN ('AUTO_TIDY', 'MANUAL_TIDY')), - CONSTRAINT CK_alg_tidy_workflow_status CHECK (status IN ( - 'CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY', - 'COMPLETED', 'CANCELLED_RECONCILED')), - CONSTRAINT CK_alg_tidy_workflow_spaces CHECK (source_space_id <> target_space_id) -); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_workflow') AND name = N'UX_alg_tidy_workflow_active_auto') -/* Automatic tidy is intentionally a global singleton across all warehouses. */ -CREATE UNIQUE INDEX UX_alg_tidy_workflow_active_auto -ON alg.tidy_workflow(origin) -WHERE origin = 'AUTO_TIDY' AND completed_at IS NULL; -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_workflow') AND name = N'UX_alg_tidy_workflow_wcs_task') -CREATE UNIQUE INDEX UX_alg_tidy_workflow_wcs_task -ON alg.tidy_workflow(wcs_task_id) WHERE wcs_task_id IS NOT NULL; -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_workflow') AND name = N'IX_alg_tidy_workflow_active') -CREATE INDEX IX_alg_tidy_workflow_active -ON alg.tidy_workflow(status, origin, warehouse_id, created_at) -INCLUDE (workflow_id, lane_code, vehicle_id, wcs_task_id); -GO - -IF OBJECT_ID(N'alg.tidy_step', N'U') IS NULL -CREATE TABLE alg.tidy_step ( - step_id varchar(120) NOT NULL CONSTRAINT PK_alg_tidy_step PRIMARY KEY, - workflow_id varchar(120) NOT NULL, - step_no int NOT NULL, - source_space_id varchar(80) NOT NULL, - target_space_id varchar(80) NOT NULL, - stock_id varchar(80) NOT NULL, - tray_code varchar(255) NOT NULL, - status varchar(24) NOT NULL, - wms_job_id varchar(120) NULL, - wcs_task_id varchar(120) NULL, - expected_event_stage varchar(40) NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_step_created DEFAULT SYSUTCDATETIME(), - started_at datetime2 NULL, - completed_at datetime2 NULL, - CONSTRAINT FK_alg_tidy_step_workflow FOREIGN KEY (workflow_id) - REFERENCES alg.tidy_workflow(workflow_id), - CONSTRAINT CK_alg_tidy_step_no CHECK (step_no > 0), - CONSTRAINT CK_alg_tidy_step_status CHECK (status IN ( - 'PENDING', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY', 'COMPLETED', 'CANCELLED_RECONCILED')) -); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_step') AND name = N'UX_alg_tidy_step_order') -CREATE UNIQUE INDEX UX_alg_tidy_step_order ON alg.tidy_step(workflow_id, step_no); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_step') AND name = N'UX_alg_tidy_step_workflow_key') -CREATE UNIQUE INDEX UX_alg_tidy_step_workflow_key ON alg.tidy_step(step_id, workflow_id); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_step') AND name = N'UX_alg_tidy_step_wms_job') -CREATE UNIQUE INDEX UX_alg_tidy_step_wms_job ON alg.tidy_step(wms_job_id) WHERE wms_job_id IS NOT NULL; -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_step') AND name = N'UX_alg_tidy_step_wcs_task') -CREATE UNIQUE INDEX UX_alg_tidy_step_wcs_task ON alg.tidy_step(wcs_task_id) WHERE wcs_task_id IS NOT NULL; -GO - -IF OBJECT_ID(N'alg.tidy_space_lock', N'U') IS NULL -CREATE TABLE alg.tidy_space_lock ( - lock_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_tidy_space_lock PRIMARY KEY, - workflow_id varchar(120) NOT NULL, - warehouse_id varchar(80) NOT NULL, - resource_type varchar(8) NOT NULL, - resource_id varchar(120) NOT NULL, - status varchar(20) NOT NULL CONSTRAINT DF_alg_tidy_space_lock_status DEFAULT 'ACTIVE', - locked_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_space_lock_locked DEFAULT SYSUTCDATETIME(), - released_at datetime2 NULL, - CONSTRAINT FK_alg_tidy_space_lock_workflow FOREIGN KEY (workflow_id) - REFERENCES alg.tidy_workflow(workflow_id), - CONSTRAINT CK_alg_tidy_space_lock_resource_type CHECK (resource_type IN ('SPACE', 'LANE', 'FLOOR')), - CONSTRAINT CK_alg_tidy_space_lock_status CHECK (status IN ('ACTIVE', 'RELEASED')) -); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_space_lock') AND name = N'UX_alg_tidy_space_lock_active') -CREATE UNIQUE INDEX UX_alg_tidy_space_lock_active -ON alg.tidy_space_lock(warehouse_id, resource_type, resource_id) WHERE status = 'ACTIVE'; -GO - -IF OBJECT_ID(N'alg.tidy_callback_event', N'U') IS NULL -CREATE TABLE alg.tidy_callback_event ( - callback_event_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_tidy_callback_event PRIMARY KEY, - workflow_id varchar(120) NOT NULL, - step_id varchar(120) NOT NULL, - wcs_task_id varchar(120) NOT NULL, - event_stage varchar(40) NOT NULL, - result_code varchar(40) NOT NULL, - result_message varchar(2000) NULL, - received_at datetime2 NOT NULL CONSTRAINT DF_alg_tidy_callback_event_received DEFAULT SYSUTCDATETIME(), - CONSTRAINT FK_alg_tidy_callback_event_workflow FOREIGN KEY (workflow_id) - REFERENCES alg.tidy_workflow(workflow_id), - CONSTRAINT FK_alg_tidy_callback_event_step FOREIGN KEY (step_id) - REFERENCES alg.tidy_step(step_id), - CONSTRAINT FK_alg_tidy_callback_event_step_workflow FOREIGN KEY (step_id, workflow_id) - REFERENCES alg.tidy_step(step_id, workflow_id) -); -GO -IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(N'alg.tidy_callback_event') AND name = N'UX_alg_tidy_callback_event_key') -CREATE UNIQUE INDEX UX_alg_tidy_callback_event_key -ON alg.tidy_callback_event(workflow_id, step_id, wcs_task_id, event_stage); -GO - -IF OBJECT_ID(N'alg.decision_log', N'U') IS NULL -CREATE TABLE alg.decision_log ( - decision_id uniqueidentifier NOT NULL CONSTRAINT PK_alg_decision_log PRIMARY KEY, - warehouse_id varchar(80) NULL, - decision_type varchar(40) NOT NULL, - input_version binary(8) NULL, - result_code varchar(80) NULL, - reason varchar(2000) NULL, - created_at datetime2 NOT NULL CONSTRAINT DF_alg_decision_log_created_at DEFAULT SYSUTCDATETIME() -); -GO - -IF OBJECT_ID(N'alg.usp_rebuild_space_topology', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_space_topology; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_rebuild_space_topology - @warehouse_id varchar(80) = NULL -AS -BEGIN - SET NOCOUNT ON; - ;WITH lane_topology AS ( - SELECT warehouse_id, group_coding_no AS lane_code, - COUNT(*) AS lane_depth, - MAX(the_column) AS column_no, - MIN(the_platoon) AS row_no, - MIN(the_floor) AS floor_no, - MIN(cargo_space_id) AS stable_id - FROM dbo.bas_cargo_space - WHERE NULLIF(LTRIM(RTRIM(group_coding_no)), '''') IS NOT NULL - AND TRY_CONVERT(int, group_coding) IS NOT NULL - AND (@warehouse_id IS NULL OR warehouse_id = @warehouse_id) - GROUP BY warehouse_id, group_coding_no - ), ranked_lanes AS ( - SELECT lane_topology.*, - CASE WHEN lane_depth = 1 THEN ROW_NUMBER() OVER ( - PARTITION BY warehouse_id - ORDER BY CASE WHEN lane_depth = 1 THEN 0 ELSE 1 END, - column_no DESC, row_no ASC, floor_no ASC, stable_id ASC - ) END AS single_depth_rank - FROM lane_topology - ), source AS ( - SELECT c.warehouse_id, c.cargo_space_id AS space_id, c.group_coding_no AS lane_code, - ranked_lanes.lane_depth, - CASE WHEN ranked_lanes.lane_depth = 1 AND ranked_lanes.single_depth_rank <= 80 THEN ''CACHE'' - WHEN ranked_lanes.lane_depth = 1 AND ranked_lanes.single_depth_rank <= 133 THEN ''RESERVE'' - WHEN ranked_lanes.lane_depth = 5 THEN ''MAIN'' - ELSE ''FLEX'' END AS base_zone - FROM dbo.bas_cargo_space c - JOIN ranked_lanes ON ranked_lanes.warehouse_id = c.warehouse_id - AND ranked_lanes.lane_code = c.group_coding_no - WHERE NULLIF(LTRIM(RTRIM(c.group_coding_no)), '''') IS NOT NULL - AND TRY_CONVERT(int, c.group_coding) IS NOT NULL - AND (@warehouse_id IS NULL OR c.warehouse_id = @warehouse_id) - ) - MERGE alg.space_topology AS target - USING source ON source.space_id = target.space_id - WHEN MATCHED AND (target.warehouse_id <> source.warehouse_id OR target.lane_code <> source.lane_code - OR target.lane_depth <> source.lane_depth OR target.base_zone <> source.base_zone) - THEN UPDATE SET warehouse_id = source.warehouse_id, lane_code = source.lane_code, - lane_depth = source.lane_depth, base_zone = source.base_zone, - rebuilt_at = SYSUTCDATETIME() - WHEN NOT MATCHED BY TARGET THEN - INSERT (warehouse_id, space_id, lane_code, lane_depth, base_zone) - VALUES (source.warehouse_id, source.space_id, source.lane_code, source.lane_depth, source.base_zone) - WHEN NOT MATCHED BY SOURCE AND (@warehouse_id IS NULL OR target.warehouse_id = @warehouse_id) - THEN DELETE; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot', N'P') IS NOT NULL DROP PROCEDURE alg.usp_sync_inventory_snapshot; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_sync_inventory_snapshot - @warehouse_id varchar(80) = NULL -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - BEGIN TRANSACTION; - - DELETE summary - FROM alg.space_inventory_summary summary - WHERE @warehouse_id IS NULL OR summary.warehouse_id = @warehouse_id; - - DELETE item - FROM alg.inventory_item_snapshot item - WHERE @warehouse_id IS NULL OR item.warehouse_id = @warehouse_id; - - INSERT INTO alg.inventory_item_snapshot ( - warehouse_id, stock_id, space_id, tray_code, project_no, product_no, lot_no, - inbound_time_raw, inbound_at, quantity, out_quantity, synchronized_at) - SELECT space.warehouse_id, stock.stock_id, stock.cargo_space_id, stock.tray_code, - stock.spare4, stock.product_id, stock.lot_no, CONVERT(varchar(80), stock.in_date), - TRY_CONVERT(datetime2, stock.in_date), stock.quantity, stock.out_quantity, SYSUTCDATETIME() - FROM dbo.bas_stock stock - JOIN dbo.bas_cargo_space space ON space.cargo_space_id = stock.cargo_space_id - WHERE @warehouse_id IS NULL OR space.warehouse_id = @warehouse_id; - - INSERT INTO alg.space_inventory_summary ( - warehouse_id, space_id, stock_detail_count, stock_tray_count, - stock_project_count, stock_product_count, synchronized_at) - SELECT item.warehouse_id, item.space_id, COUNT(*), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.tray_code)), '''')), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.project_no)), '''')), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.product_no)), '''')), - SYSUTCDATETIME() - FROM alg.inventory_item_snapshot item - WHERE @warehouse_id IS NULL OR item.warehouse_id = @warehouse_id - GROUP BY item.warehouse_id, item.space_id; - - COMMIT TRANSACTION; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_apply_space_role', N'P') IS NOT NULL DROP PROCEDURE alg.usp_apply_space_role; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_apply_space_role - @warehouse_id varchar(80), - @space_id varchar(80), - @role varchar(40) -AS -BEGIN - SET NOCOUNT ON; - IF @role NOT IN (''STORAGE'', ''RELOCATION_BUFFER'', ''OUTBOUND_BUFFER'', ''PROTECTED_CORE'') - BEGIN - SELECT CONVERT(bit, 0) AS applied, CAST(''INVALID_ROLE'' AS varchar(80)) AS reason; - RETURN; - END; - - DECLARE @warehouse_count int, @space_status varchar(5), @locked varchar(5), - @tray_code varchar(255), @stock_count int, @reservation_count int; - SELECT @warehouse_count = COUNT(*) - FROM dbo.bas_cargo_space WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id; - IF ISNULL(@warehouse_count, 0) = 0 - BEGIN - SELECT CONVERT(bit, 0) AS applied, CAST(''WAREHOUSE_NOT_FOUND'' AS varchar(80)) AS reason; - RETURN; - END; - - SELECT @space_status = status, @locked = locked, @tray_code = tray_code - FROM dbo.bas_cargo_space WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND cargo_space_id = @space_id; - IF @space_status IS NULL - BEGIN - SELECT CONVERT(bit, 0) AS applied, CAST(''SPACE_NOT_FOUND'' AS varchar(80)) AS reason; - RETURN; - END; - - SELECT @stock_count = COUNT(*) FROM dbo.bas_stock WITH (UPDLOCK, HOLDLOCK) - WHERE cargo_space_id = @space_id; - SELECT @reservation_count = COUNT(*) FROM alg.reservation WITH (UPDLOCK, HOLDLOCK) - WHERE space_id = @space_id AND status = ''ACTIVE''; - - IF @role <> ''STORAGE'' AND ( - @space_status <> ''1'' OR - (NULLIF(LTRIM(RTRIM(@locked)), '''') IS NOT NULL AND LTRIM(RTRIM(@locked)) NOT IN (''0'', ''N'')) OR - NULLIF(LTRIM(RTRIM(@tray_code)), '''') IS NOT NULL OR - ISNULL(@stock_count, 0) <> 0 OR ISNULL(@reservation_count, 0) <> 0) - BEGIN - SELECT CONVERT(bit, 0) AS applied, CAST(''TARGET_NOT_EMPTY_OR_RESERVED'' AS varchar(80)) AS reason; - RETURN; - END; - - MERGE alg.space_role WITH (HOLDLOCK) AS target - USING (SELECT @space_id AS space_id, @role AS role) AS source - ON source.space_id = target.space_id - WHEN MATCHED THEN UPDATE SET role = source.role, updated_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT (space_id, role) VALUES (source.space_id, source.role); - - SELECT CONVERT(bit, 1) AS applied, CAST(NULL AS varchar(80)) AS reason; -END;'); -GO - -IF OBJECT_ID(N'alg.v_inventory_item', N'V') IS NOT NULL DROP VIEW alg.v_inventory_item; -GO -EXEC(N' -CREATE VIEW alg.v_inventory_item AS -SELECT stock_id, space_id, tray_code, project_no, product_no, lot_no, - inbound_time_raw, inbound_at, quantity, out_quantity -FROM alg.inventory_item_snapshot;'); -GO - -IF OBJECT_ID(N'alg.v_active_job', N'V') IS NOT NULL DROP VIEW alg.v_active_job; -GO -EXEC(N' -CREATE VIEW alg.v_active_job AS -WITH tray_candidates AS ( - SELECT j.job_id, - COUNT(DISTINCT s.space_id) AS space_count, - MIN(s.space_id) AS cargo_space_id, - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(s.project_no)), '''')) AS project_count, - MIN(NULLIF(LTRIM(RTRIM(s.project_no)), '''')) AS project_no, - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(s.product_no)), '''')) AS product_count, - MIN(NULLIF(LTRIM(RTRIM(s.product_no)), '''')) AS product_no - FROM dbo.mydo_job j - JOIN alg.inventory_item_snapshot s ON s.warehouse_id = j.warehouse_id - AND s.tray_code = NULLIF(LTRIM(RTRIM(j.tray_code)), '''') - GROUP BY j.job_id -), resolved_jobs AS ( - SELECT j.job_id, j.task_no, j.tray_code, j.target_cargo_space_id, j.warehouse_id, - j.status, j.in_or_out, j.create_time AS create_time_raw, - COALESCE(source_space.cargo_space_id, - CASE WHEN tray_candidates.space_count = 1 THEN tray_candidates.cargo_space_id END) AS source_space_id, - CASE WHEN source_space.cargo_space_id IS NOT NULL THEN ''EXPLICIT'' - WHEN tray_candidates.space_count = 1 THEN ''TRAY'' - WHEN tray_candidates.space_count > 1 THEN ''AMBIGUOUS_TRAY'' - ELSE ''UNRESOLVED'' END AS source_resolution - FROM dbo.mydo_job j - LEFT JOIN dbo.bas_cargo_space source_space - ON source_space.cargo_space_id = NULLIF(LTRIM(RTRIM(j.cargo_space_id)), '''') - AND source_space.warehouse_id = j.warehouse_id - LEFT JOIN tray_candidates ON tray_candidates.job_id = j.job_id - WHERE j.finish_time IS NULL OR LTRIM(RTRIM(j.finish_time)) = '''' -), source_scope AS ( - SELECT resolved_jobs.job_id, - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(stock.project_no)), '''')) AS project_count, - MIN(NULLIF(LTRIM(RTRIM(stock.project_no)), '''')) AS project_no, - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(stock.product_no)), '''')) AS product_count, - MIN(NULLIF(LTRIM(RTRIM(stock.product_no)), '''')) AS product_no - FROM resolved_jobs - JOIN alg.inventory_item_snapshot stock ON stock.warehouse_id = resolved_jobs.warehouse_id - AND stock.space_id = resolved_jobs.source_space_id - GROUP BY resolved_jobs.job_id -) -SELECT resolved_jobs.job_id, resolved_jobs.task_no, resolved_jobs.tray_code, - CASE WHEN resolved_jobs.source_space_id IS NOT NULL - THEN CASE WHEN source_scope.project_count = 1 THEN source_scope.project_no END - ELSE CASE WHEN tray_candidates.project_count = 1 THEN tray_candidates.project_no END END AS project_no, - CASE WHEN resolved_jobs.source_space_id IS NOT NULL - THEN CASE WHEN source_scope.product_count = 1 THEN source_scope.product_no END - ELSE CASE WHEN tray_candidates.product_count = 1 THEN tray_candidates.product_no END END AS product_no, - resolved_jobs.source_space_id, resolved_jobs.target_cargo_space_id, resolved_jobs.warehouse_id, - resolved_jobs.status, resolved_jobs.in_or_out, resolved_jobs.create_time_raw, - resolved_space.group_coding_no AS source_lane_code, resolved_jobs.source_resolution -FROM resolved_jobs -LEFT JOIN tray_candidates ON tray_candidates.job_id = resolved_jobs.job_id -LEFT JOIN source_scope ON source_scope.job_id = resolved_jobs.job_id -LEFT JOIN dbo.bas_cargo_space resolved_space ON resolved_space.cargo_space_id = resolved_jobs.source_space_id;'); -GO - -IF OBJECT_ID(N'alg.v_space_state', N'V') IS NOT NULL DROP VIEW alg.v_space_state; -GO -EXEC(N' -CREATE VIEW alg.v_space_state AS -WITH outbound_lanes AS ( - SELECT DISTINCT warehouse_id, source_lane_code AS lane_code - FROM alg.v_active_job - WHERE in_or_out >= 20 AND source_lane_code IS NOT NULL -), lane_gaps AS ( - SELECT DISTINCT inner_space.warehouse_id, inner_space.group_coding_no AS lane_code - FROM dbo.bas_cargo_space inner_space - JOIN dbo.bas_cargo_space outer_space - ON outer_space.warehouse_id = inner_space.warehouse_id - AND outer_space.group_coding_no = inner_space.group_coding_no - AND TRY_CONVERT(int, outer_space.group_coding) > TRY_CONVERT(int, inner_space.group_coding) - WHERE TRY_CONVERT(int, inner_space.group_coding) IS NOT NULL - AND TRY_CONVERT(int, outer_space.group_coding) IS NOT NULL - AND NULLIF(LTRIM(RTRIM(inner_space.tray_code)), '''') IS NULL - AND NOT EXISTS ( - SELECT 1 FROM alg.inventory_item_snapshot inner_stock - WHERE inner_stock.warehouse_id = inner_space.warehouse_id - AND inner_stock.space_id = inner_space.cargo_space_id - ) - AND ( - NULLIF(LTRIM(RTRIM(outer_space.tray_code)), '''') IS NOT NULL - OR EXISTS ( - SELECT 1 FROM alg.inventory_item_snapshot outer_stock - WHERE outer_stock.warehouse_id = outer_space.warehouse_id - AND outer_stock.space_id = outer_space.cargo_space_id - ) - ) -) -SELECT c.cargo_space_id AS space_id, - c.warehouse_id, - c.group_coding_no AS lane_code, - c.cargo_area_id AS area_id, - CASE - WHEN ISNULL(role.role, ''STORAGE'') IN (''RELOCATION_BUFFER'', ''OUTBOUND_BUFFER'') THEN ''CACHE'' - WHEN ISNULL(role.role, ''STORAGE'') = ''PROTECTED_CORE'' THEN ''RESERVE'' - ELSE topology.base_zone - END AS zone_code, - c.roadway_id, - c.cargo_space_name AS display_name, - c.the_platoon AS platoon, - c.the_column AS column_no, - c.the_floor AS floor_no, - c.group_coding AS depth_raw, - TRY_CONVERT(int, c.group_coding) AS depth, - c.status AS space_status, - c.locked, - c.tray_code AS space_tray_code, - ISNULL(role.role, ''STORAGE'') AS role, - ISNULL(stock.stock_detail_count, 0) AS stock_detail_count, - ISNULL(stock.stock_tray_count, 0) AS stock_tray_count, - ISNULL(stock.stock_project_count, 0) AS stock_project_count, - ISNULL(stock.stock_product_count, 0) AS stock_product_count, - CONVERT(bit, CASE WHEN ISNULL(stock.stock_tray_count, 0) > 1 THEN 1 ELSE 0 END) AS tray_conflict, - CONVERT(bit, CASE WHEN ISNULL(stock.stock_project_count, 0) > 1 THEN 1 ELSE 0 END) AS project_conflict, - CONVERT(bit, 0) AS product_conflict, - CONVERT(bit, CASE WHEN outbound_lanes.lane_code IS NULL THEN 0 ELSE 1 END) AS pending_outbound, - active_reservations.reservation_id, - CONVERT(bit, CASE WHEN active_reservations.reservation_id IS NULL THEN 0 ELSE 1 END) AS reserved, - CONVERT(bit, CASE WHEN lane_gaps.lane_code IS NULL THEN 0 ELSE 1 END) AS has_gap, - CONVERT(bit, CASE - WHEN c.status IS NULL OR c.status <> ''1'' THEN 0 - WHEN NULLIF(LTRIM(RTRIM(c.locked)), '''') IS NOT NULL AND LTRIM(RTRIM(c.locked)) NOT IN (''0'', ''N'') THEN 0 - WHEN NULLIF(LTRIM(RTRIM(c.tray_code)), '''') IS NOT NULL THEN 0 - WHEN ISNULL(stock.stock_detail_count, 0) <> 0 THEN 0 - WHEN TRY_CONVERT(int, c.group_coding) IS NULL THEN 0 - WHEN ISNULL(role.role, ''STORAGE'') <> ''STORAGE'' THEN 0 - WHEN outbound_lanes.lane_code IS NOT NULL THEN 0 - WHEN active_reservations.reservation_id IS NOT NULL THEN 0 - WHEN lane_gaps.lane_code IS NOT NULL THEN 0 - WHEN EXISTS ( - SELECT 1 - FROM dbo.bas_cargo_space inner_candidate - LEFT JOIN alg.space_inventory_summary inner_stock - ON inner_stock.warehouse_id = inner_candidate.warehouse_id - AND inner_stock.space_id = inner_candidate.cargo_space_id - WHERE inner_candidate.warehouse_id = c.warehouse_id - AND inner_candidate.group_coding_no = c.group_coding_no - AND TRY_CONVERT(int, inner_candidate.group_coding) < TRY_CONVERT(int, c.group_coding) - AND inner_candidate.status = ''1'' - AND NULLIF(LTRIM(RTRIM(inner_candidate.tray_code)), '''') IS NULL - AND inner_stock.space_id IS NULL - ) THEN 0 - ELSE 1 END) AS inbound_available -FROM dbo.bas_cargo_space c -JOIN alg.space_topology topology ON topology.space_id = c.cargo_space_id -LEFT JOIN alg.space_inventory_summary stock ON stock.warehouse_id = c.warehouse_id - AND stock.space_id = c.cargo_space_id -LEFT JOIN alg.space_role role ON role.space_id = c.cargo_space_id -LEFT JOIN outbound_lanes ON outbound_lanes.warehouse_id = c.warehouse_id AND outbound_lanes.lane_code = c.group_coding_no -LEFT JOIN alg.reservation active_reservations ON active_reservations.space_id = c.cargo_space_id AND active_reservations.status = ''ACTIVE'' -LEFT JOIN lane_gaps ON lane_gaps.warehouse_id = c.warehouse_id AND lane_gaps.lane_code = c.group_coding_no;'); -GO - -IF OBJECT_ID(N'alg.usp_compute_lane_semantic_fingerprint', N'P') IS NOT NULL DROP PROCEDURE alg.usp_compute_lane_semantic_fingerprint; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_compute_lane_semantic_fingerprint - @warehouse_id varchar(80), - @lane_code varchar(80), - @semantic_fingerprint varbinary(32) OUTPUT, - @inventory_source varchar(20) = ''LIVE'' -AS -BEGIN - SET NOCOUNT ON; - DECLARE @payload nvarchar(max); - - CREATE TABLE #fingerprint_inventory ( - stock_id varchar(80) NOT NULL, - space_id varchar(80) NOT NULL, - tray_code varchar(255) NULL, - project_no varchar(255) NULL, - product_no varchar(255) NULL, - lot_no varchar(80) NULL, - inbound_time varchar(80) NULL, - quantity decimal(38,6) NULL, - out_quantity decimal(38,6) NULL - ); - - IF @inventory_source = ''SNAPSHOT'' - BEGIN - INSERT INTO #fingerprint_inventory ( - stock_id, space_id, tray_code, project_no, product_no, lot_no, - inbound_time, quantity, out_quantity) - SELECT stock.stock_id, stock.space_id, stock.tray_code, stock.project_no, - stock.product_no, stock.lot_no, stock.inbound_time_raw, - CONVERT(decimal(38,6), stock.quantity), CONVERT(decimal(38,6), stock.out_quantity) - FROM alg.inventory_item_snapshot stock - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = stock.space_id - AND cargo.warehouse_id = stock.warehouse_id - WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code; - END - ELSE IF @inventory_source = ''LIVE'' - BEGIN - INSERT INTO #fingerprint_inventory ( - stock_id, space_id, tray_code, project_no, product_no, lot_no, - inbound_time, quantity, out_quantity) - SELECT stock.stock_id, stock.cargo_space_id, stock.tray_code, stock.spare4, - stock.product_id, stock.lot_no, CONVERT(varchar(80), stock.in_date), - CONVERT(decimal(38,6), stock.quantity), CONVERT(decimal(38,6), stock.out_quantity) - FROM dbo.bas_stock stock - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = stock.cargo_space_id - WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code; - END - ELSE - BEGIN - THROW 50001, ''Unsupported inventory fingerprint source'', 1; - END; - - SELECT @payload = ISNULL(CONVERT(nvarchar(max), ( - SELECT cargo.cargo_space_id AS [@space_id], cargo.status AS [@status], - cargo.locked AS [@locked], cargo.tray_code AS [@tray_code], - cargo.group_coding AS [@depth], cargo.the_platoon AS [@platoon], - cargo.the_column AS [@column], cargo.the_floor AS [@floor], - topology.lane_depth AS [@lane_depth], topology.base_zone AS [@base_zone], - ISNULL(role.role, ''STORAGE'') AS [@role] - FROM dbo.bas_cargo_space cargo - LEFT JOIN alg.space_topology topology ON topology.space_id = cargo.cargo_space_id - LEFT JOIN alg.space_role role ON role.space_id = cargo.cargo_space_id - WHERE cargo.warehouse_id = @warehouse_id AND cargo.group_coding_no = @lane_code - ORDER BY TRY_CONVERT(int, cargo.group_coding), cargo.cargo_space_id - FOR XML PATH(''space''), ROOT(''spaces''), TYPE - )), N''''); - - SET @payload = @payload + N''|INVENTORY|'' + ISNULL(CONVERT(nvarchar(max), ( - SELECT stock.stock_id AS [@stock_id], stock.space_id AS [@space_id], - stock.tray_code AS [@tray_code], stock.project_no AS [@project_no], - stock.product_no AS [@product_no], stock.lot_no AS [@lot_no], - stock.inbound_time AS [@inbound_time], - stock.quantity AS [@quantity], stock.out_quantity AS [@out_quantity] - FROM #fingerprint_inventory stock - ORDER BY stock.space_id, stock.stock_id - FOR XML PATH(''item''), ROOT(''inventory''), TYPE - )), N''''); - - SET @payload = @payload + N''|ACTIVE_JOBS|'' + ISNULL(CONVERT(nvarchar(max), ( - SELECT job.job_id AS [@job_id], job.task_no AS [@task_no], job.tray_code AS [@tray_code], - job.source_space_id AS [@source_space_id], job.target_cargo_space_id AS [@target_space_id], - job.status AS [@status], job.in_or_out AS [@in_or_out], - job.create_time_raw AS [@create_time] - FROM alg.v_active_job job - WHERE job.warehouse_id = @warehouse_id AND job.source_lane_code = @lane_code - ORDER BY job.job_id - FOR XML PATH(''job''), ROOT(''jobs''), TYPE - )), N''''); - - SET @payload = @payload + N''|PROJECT_POLICY|'' + ISNULL(CONVERT(nvarchar(max), ( - SELECT policy.project_no AS [@project_no], policy.is_isolated AS [@is_isolated], - policy.is_common AS [@is_common], policy.allow_mixed AS [@allow_mixed] - FROM alg.project_policy policy - ORDER BY policy.project_no - FOR XML PATH(''project''), ROOT(''project_policy''), TYPE - )), N''''); - - SET @payload = @payload + N''|WAREHOUSE_POLICY|'' + ISNULL(CONVERT(nvarchar(max), ( - SELECT policy.warehouse_id AS [@warehouse_id], policy.target_fill_rate AS [@target_fill_rate], - policy.fifo_tolerance_hours AS [@fifo_tolerance_hours] - FROM alg.warehouse_policy policy - WHERE policy.warehouse_id = @warehouse_id - FOR XML PATH(''warehouse''), ROOT(''warehouse_policy''), TYPE - )), N''''); - - SET @payload = @payload + N''|RESERVATIONS|'' + ISNULL(CONVERT(nvarchar(max), ( - SELECT reservation.reservation_id AS [@reservation_id], reservation.space_id AS [@space_id], - reservation.project_no AS [@project_no], reservation.status AS [@status] - FROM alg.reservation reservation - WHERE reservation.warehouse_id = @warehouse_id AND reservation.lane_code = @lane_code - AND reservation.status = ''ACTIVE'' - ORDER BY reservation.space_id, reservation.reservation_id - FOR XML PATH(''reservation''), ROOT(''reservations''), TYPE - )), N''''); - - SET @semantic_fingerprint = HASHBYTES(''SHA2_256'', CONVERT(varbinary(max), @payload)); -END;'); -GO - -IF OBJECT_ID(N'alg.usp_rebuild_lane_state', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_lane_state; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_rebuild_lane_state - @warehouse_id varchar(80) = NULL, - @lane_code varchar(80) = NULL -AS -BEGIN - SET NOCOUNT ON; - SELECT warehouse_id, lane_code, - COUNT(*) AS space_count, - SUM(CASE WHEN space_status = ''2'' OR stock_detail_count > 0 THEN 1 ELSE 0 END) AS occupied_count, - SUM(CASE WHEN inbound_available = 1 THEN 1 ELSE 0 END) AS available_count, - MAX(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_outbound, - MAX(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS has_gap, - CAST(NULL AS varbinary(32)) AS semantic_fingerprint - INTO #source_state - FROM alg.v_space_state - WHERE (@warehouse_id IS NULL OR warehouse_id = @warehouse_id) - AND (@lane_code IS NULL OR lane_code = @lane_code) - GROUP BY warehouse_id, lane_code; - - DECLARE @fingerprint_warehouse varchar(80), @fingerprint_lane varchar(80), - @fingerprint varbinary(32); - DECLARE fingerprint_cursor CURSOR LOCAL FAST_FORWARD FOR - SELECT warehouse_id, lane_code FROM #source_state ORDER BY warehouse_id, lane_code; - OPEN fingerprint_cursor; - FETCH NEXT FROM fingerprint_cursor INTO @fingerprint_warehouse, @fingerprint_lane; - WHILE @@FETCH_STATUS = 0 - BEGIN - EXEC alg.usp_compute_lane_semantic_fingerprint - @warehouse_id = @fingerprint_warehouse, - @lane_code = @fingerprint_lane, - @semantic_fingerprint = @fingerprint OUTPUT, - @inventory_source = ''SNAPSHOT''; - UPDATE #source_state SET semantic_fingerprint = @fingerprint - WHERE warehouse_id = @fingerprint_warehouse AND lane_code = @fingerprint_lane; - FETCH NEXT FROM fingerprint_cursor INTO @fingerprint_warehouse, @fingerprint_lane; - END; - CLOSE fingerprint_cursor; - DEALLOCATE fingerprint_cursor; - - MERGE alg.lane_state WITH (HOLDLOCK) AS target - USING #source_state AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED AND (target.space_count <> source.space_count OR - target.occupied_count <> source.occupied_count OR - target.available_count <> source.available_count OR - target.pending_outbound <> source.pending_outbound OR - target.has_gap <> source.has_gap OR - target.semantic_fingerprint IS NULL OR - target.semantic_fingerprint <> source.semantic_fingerprint) - THEN UPDATE SET space_count = source.space_count, - occupied_count = source.occupied_count, - available_count = source.available_count, - pending_outbound = source.pending_outbound, - has_gap = source.has_gap, - semantic_fingerprint = source.semantic_fingerprint, - rebuilt_at = SYSUTCDATETIME() - WHEN NOT MATCHED BY TARGET THEN - INSERT (warehouse_id, lane_code, space_count, occupied_count, available_count, - pending_outbound, has_gap, semantic_fingerprint) - VALUES (source.warehouse_id, source.lane_code, source.space_count, source.occupied_count, - source.available_count, source.pending_outbound, source.has_gap, source.semantic_fingerprint) - WHEN NOT MATCHED BY SOURCE - AND (@warehouse_id IS NULL OR target.warehouse_id = @warehouse_id) - AND (@lane_code IS NULL OR target.lane_code = @lane_code) - THEN DELETE; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_mark_dirty_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_mark_dirty_lane; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_mark_dirty_lane - @warehouse_id varchar(80), - @lane_code varchar(80), - @topology_dirty bit = 0, - @reason_mask int = 0 -AS -BEGIN - SET NOCOUNT ON; - IF NULLIF(LTRIM(RTRIM(@warehouse_id)), '''') IS NULL - OR NULLIF(LTRIM(RTRIM(@lane_code)), '''') IS NULL - RETURN; - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING (SELECT @warehouse_id AS warehouse_id, @lane_code AS lane_code) AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET - topology_dirty = CASE WHEN target.topology_dirty = 1 OR @topology_dirty = 1 THEN 1 ELSE 0 END, - reason_mask = target.reason_mask | @reason_mask, - change_count = target.change_count + 1, - last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (@warehouse_id, @lane_code, @topology_dirty, @reason_mask, 1, - SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'alg.usp_refresh_dirty_lanes', N'P') IS NOT NULL DROP PROCEDURE alg.usp_refresh_dirty_lanes; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_refresh_dirty_lanes - @warehouse_id varchar(80) -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - IF NULLIF(LTRIM(RTRIM(@warehouse_id)), '''') IS NULL - THROW 50002, ''warehouse_id is required'', 1; - - CREATE TABLE #dirty ( - warehouse_id varchar(80) NOT NULL, - lane_code varchar(80) NOT NULL, - topology_dirty bit NOT NULL, - reason_mask int NOT NULL, - change_count bigint NOT NULL, - enqueued_at datetime2 NOT NULL, - last_changed_at datetime2 NOT NULL, - PRIMARY KEY (warehouse_id, lane_code) - ); - - BEGIN TRANSACTION; - DELETE queue - OUTPUT deleted.warehouse_id, deleted.lane_code, deleted.topology_dirty, - deleted.reason_mask, deleted.change_count, deleted.enqueued_at, deleted.last_changed_at - INTO #dirty (warehouse_id, lane_code, topology_dirty, reason_mask, - change_count, enqueued_at, last_changed_at) - FROM alg.dirty_lane_queue queue WITH (UPDLOCK, READPAST, ROWLOCK) - WHERE queue.warehouse_id = @warehouse_id; - COMMIT TRANSACTION; - - IF NOT EXISTS (SELECT 1 FROM #dirty) - RETURN; - - BEGIN TRY - BEGIN TRANSACTION; - IF EXISTS (SELECT 1 FROM #dirty WHERE topology_dirty = 1) - BEGIN - EXEC alg.usp_rebuild_space_topology @warehouse_id = @warehouse_id; - - MERGE #dirty AS target - USING ( - SELECT DISTINCT warehouse_id, group_coding_no AS lane_code - FROM dbo.bas_cargo_space - WHERE warehouse_id = @warehouse_id - AND NULLIF(LTRIM(RTRIM(group_coding_no)), '''') IS NOT NULL - ) AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, - change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 1, 2, 1, - SYSUTCDATETIME(), SYSUTCDATETIME()); - - DELETE snapshot - FROM alg.inventory_item_snapshot snapshot - WHERE snapshot.warehouse_id = @warehouse_id - AND NOT EXISTS ( - SELECT 1 FROM dbo.bas_cargo_space cargo - WHERE cargo.cargo_space_id = snapshot.space_id - AND cargo.warehouse_id = snapshot.warehouse_id - ); - DELETE summary - FROM alg.space_inventory_summary summary - WHERE summary.warehouse_id = @warehouse_id - AND NOT EXISTS ( - SELECT 1 FROM dbo.bas_cargo_space cargo - WHERE cargo.cargo_space_id = summary.space_id - AND cargo.warehouse_id = summary.warehouse_id - ); - END; - - DELETE summary - FROM alg.space_inventory_summary summary - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = summary.space_id - AND cargo.warehouse_id = summary.warehouse_id - JOIN #dirty dirty ON dirty.warehouse_id = cargo.warehouse_id - AND dirty.lane_code = cargo.group_coding_no; - - DELETE snapshot - FROM alg.inventory_item_snapshot snapshot - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = snapshot.space_id - AND cargo.warehouse_id = snapshot.warehouse_id - JOIN #dirty dirty ON dirty.warehouse_id = cargo.warehouse_id - AND dirty.lane_code = cargo.group_coding_no; - - INSERT INTO alg.inventory_item_snapshot ( - warehouse_id, stock_id, space_id, tray_code, project_no, product_no, lot_no, - inbound_time_raw, inbound_at, quantity, out_quantity, synchronized_at) - SELECT cargo.warehouse_id, stock.stock_id, stock.cargo_space_id, stock.tray_code, - stock.spare4, stock.product_id, stock.lot_no, CONVERT(varchar(80), stock.in_date), - TRY_CONVERT(datetime2, stock.in_date), stock.quantity, stock.out_quantity, SYSUTCDATETIME() - FROM dbo.bas_stock stock - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = stock.cargo_space_id - JOIN #dirty dirty ON dirty.warehouse_id = cargo.warehouse_id - AND dirty.lane_code = cargo.group_coding_no; - - INSERT INTO alg.space_inventory_summary ( - warehouse_id, space_id, stock_detail_count, stock_tray_count, - stock_project_count, stock_product_count, synchronized_at) - SELECT item.warehouse_id, item.space_id, COUNT(*), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.tray_code)), '''')), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.project_no)), '''')), - COUNT(DISTINCT NULLIF(LTRIM(RTRIM(item.product_no)), '''')), - SYSUTCDATETIME() - FROM alg.inventory_item_snapshot item - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = item.space_id - AND cargo.warehouse_id = item.warehouse_id - JOIN #dirty dirty ON dirty.warehouse_id = cargo.warehouse_id - AND dirty.lane_code = cargo.group_coding_no - GROUP BY item.warehouse_id, item.space_id; - - DECLARE @lane_code varchar(80); - DECLARE dirty_cursor CURSOR LOCAL FAST_FORWARD FOR - SELECT lane_code FROM #dirty ORDER BY lane_code; - OPEN dirty_cursor; - FETCH NEXT FROM dirty_cursor INTO @lane_code; - WHILE @@FETCH_STATUS = 0 - BEGIN - EXEC alg.usp_rebuild_lane_state @warehouse_id = @warehouse_id, @lane_code = @lane_code; - FETCH NEXT FROM dirty_cursor INTO @lane_code; - END; - CLOSE dirty_cursor; - DEALLOCATE dirty_cursor; - - /* A claimed row retains its lease until release; otherwise queue_status = ''PENDING''. */ - MERGE alg.tidy_lane_queue WITH (HOLDLOCK) AS target - USING #dirty AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET - dirty_version = target.dirty_version + 1, - queue_status = CASE WHEN target.queue_status = ''CLAIMED'' THEN ''CLAIMED'' ELSE ''PENDING'' END, - reason_mask = target.reason_mask | source.reason_mask, - change_count = target.change_count + source.change_count, - last_dirty_at = CASE WHEN target.last_dirty_at >= source.last_changed_at - THEN target.last_dirty_at ELSE source.last_changed_at END, - last_error = NULL - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, dirty_version, queue_status, reason_mask, - change_count, last_dirty_at) - VALUES (source.warehouse_id, source.lane_code, 1, ''PENDING'', source.reason_mask, - source.change_count, source.last_changed_at); - COMMIT TRANSACTION; - END TRY - BEGIN CATCH - IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; - BEGIN TRY - BEGIN TRANSACTION; - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING #dirty AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET - topology_dirty = CASE WHEN target.topology_dirty = 1 OR source.topology_dirty = 1 THEN 1 ELSE 0 END, - reason_mask = target.reason_mask | source.reason_mask, - change_count = target.change_count + source.change_count, - enqueued_at = CASE WHEN target.enqueued_at <= source.enqueued_at - THEN target.enqueued_at ELSE source.enqueued_at END, - last_changed_at = CASE WHEN target.last_changed_at >= source.last_changed_at - THEN target.last_changed_at ELSE source.last_changed_at END - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, - change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, source.topology_dirty, source.reason_mask, - source.change_count, source.enqueued_at, source.last_changed_at); - COMMIT TRANSACTION; - END TRY - BEGIN CATCH - IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; - THROW; - END CATCH; - THROW; - END CATCH; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_claim_tidy_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_claim_tidy_lane; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_claim_tidy_lane - @warehouse_id varchar(80) = NULL, - @eligible_lane_codes varchar(max) = NULL, - @claimed_by varchar(120), - @lease_seconds int = 120 -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - IF NULLIF(LTRIM(RTRIM(@claimed_by)), '''') IS NULL - THROW 50010, ''claimed_by is required'', 1; - IF @lease_seconds < 1 OR @lease_seconds > 3600 - THROW 50011, ''lease_seconds must be between 1 and 3600'', 1; - - DECLARE @claim_token uniqueidentifier = NEWID(), - @claimed_warehouse_id varchar(80), @claimed_lane_code varchar(80), - @claimed_dirty_version bigint, @now datetime2 = SYSUTCDATETIME(); - - BEGIN TRANSACTION; - ;WITH candidate AS ( - SELECT TOP (1) queue.* - FROM alg.tidy_lane_queue queue WITH (UPDLOCK, READPAST, ROWLOCK) - WHERE (@warehouse_id IS NULL OR queue.warehouse_id = @warehouse_id) - AND (NULLIF(LTRIM(RTRIM(@eligible_lane_codes)), '''') IS NULL - OR EXISTS ( - SELECT 1 - FROM STRING_SPLIT(@eligible_lane_codes, '','') eligible - WHERE LTRIM(RTRIM(eligible.value)) = queue.lane_code - )) - AND (queue.queue_status = ''PENDING'' - OR (queue.queue_status = ''CLAIMED'' AND queue.lease_expires_at <= @now)) - ORDER BY queue.last_dirty_at, queue.warehouse_id, queue.lane_code - ) - UPDATE candidate - SET queue_status = ''CLAIMED'', - claim_token = @claim_token, - claimed_version = dirty_version, - claimed_by = @claimed_by, - claimed_at = @now, - lease_expires_at = DATEADD(second, @lease_seconds, @now), - last_error = NULL; - - SELECT TOP (1) - @claimed_warehouse_id = warehouse_id, - @claimed_lane_code = lane_code, - @claimed_dirty_version = claimed_version - FROM alg.tidy_lane_queue - WHERE claim_token = @claim_token; - COMMIT TRANSACTION; - - SELECT @claimed_warehouse_id AS warehouse_id, - @claimed_lane_code AS lane_code, - @claimed_dirty_version AS dirty_version, - CASE WHEN @claimed_warehouse_id IS NULL THEN NULL ELSE @claim_token END AS claim_token, - CASE WHEN @claimed_warehouse_id IS NULL THEN NULL - ELSE DATEADD(second, @lease_seconds, @now) END AS lease_expires_at; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_release_tidy_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_tidy_lane; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_release_tidy_lane - @warehouse_id varchar(80), - @lane_code varchar(80), - @claim_token uniqueidentifier, - @claimed_version bigint, - @completed bit, - @last_error varchar(2000) = NULL -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - - DECLARE @released TABLE (queue_status varchar(20), dirty_version bigint); - BEGIN TRANSACTION; - UPDATE alg.tidy_lane_queue WITH (UPDLOCK, ROWLOCK) - SET queue_status = CASE WHEN dirty_version > @claimed_version OR @completed = 0 - THEN ''PENDING'' ELSE ''COMPLETED'' END, - claim_token = NULL, - claimed_version = NULL, - claimed_by = NULL, - claimed_at = NULL, - lease_expires_at = NULL, - last_error = @last_error - OUTPUT inserted.queue_status, inserted.dirty_version - INTO @released (queue_status, dirty_version) - WHERE warehouse_id = @warehouse_id - AND lane_code = @lane_code - AND queue_status = ''CLAIMED'' - AND claim_token = @claim_token - AND claimed_version = @claimed_version; - COMMIT TRANSACTION; - - SELECT CASE WHEN EXISTS (SELECT 1 FROM @released) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS released, - queue_status, dirty_version - FROM @released - UNION ALL - SELECT CAST(0 AS bit), NULL, NULL - WHERE NOT EXISTS (SELECT 1 FROM @released); -END;'); -GO - -IF OBJECT_ID(N'alg.usp_complete_move_job', N'P') IS NOT NULL DROP PROCEDURE alg.usp_complete_move_job; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_complete_move_job - @v_job_id varchar(255), - @v_result int OUTPUT -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - SET @v_result = 0; - - DECLARE @source_space_id varchar(255); - DECLARE @target_space_id varchar(255); - DECLARE @tray_code varchar(255); - DECLARE @finished_at varchar(19) = CONVERT(varchar(19), GETDATE(), 120); - - BEGIN TRANSACTION; - SELECT @source_space_id = cargo_space_id, - @target_space_id = target_cargo_space_id, - @tray_code = tray_code - FROM dbo.mydo_job WITH (UPDLOCK, HOLDLOCK) - WHERE job_id = @v_job_id - AND in_or_out = ''28''; - - IF @source_space_id IS NULL OR @target_space_id IS NULL OR @tray_code IS NULL - THROW 51400, ''Algorithm move job is missing or invalid'', 1; - - UPDATE dbo.bas_stock - SET cargo_space_id = @target_space_id, - out_quantity = 0 - WHERE cargo_space_id = @source_space_id - AND tray_code = @tray_code; - IF @@ROWCOUNT = 0 - THROW 51401, ''Algorithm move stock is not at its source space'', 1; - - UPDATE dbo.bas_cargo_space - SET status = ''1'', tray_code = '''' - WHERE cargo_space_id = @source_space_id; - UPDATE dbo.bas_cargo_space - SET status = ''2'', tray_code = @tray_code - WHERE cargo_space_id = @target_space_id; - - UPDATE dbo.mydo_job - SET status = ''4'', finish_time = @finished_at - WHERE job_id = @v_job_id - AND in_or_out = ''28''; - INSERT INTO dbo.mydo_job_finish - SELECT job.* FROM dbo.mydo_job job - WHERE job.job_id = @v_job_id - AND job.in_or_out = ''28''; - DELETE FROM dbo.mydo_job - WHERE job_id = @v_job_id - AND in_or_out = ''28''; - - INSERT INTO dbo.move_msg (id, tray_code, old_space, new_space, status) - VALUES (NEWID(), @tray_code, @source_space_id, @target_space_id, ''0''); - - COMMIT TRANSACTION; - SET @v_result = 10; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_sync_replay_sequences', N'P') IS NOT NULL DROP PROCEDURE alg.usp_sync_replay_sequences; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_sync_replay_sequences -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - - DECLARE @lock_result int; - BEGIN TRANSACTION; - EXEC @lock_result = sys.sp_getapplock - @Resource = ''alg.usp_sync_replay_sequences'', - @LockMode = ''Exclusive'', - @LockOwner = ''Transaction'', - @LockTimeout = 10000; - IF @lock_result < 0 - THROW 50012, ''Unable to acquire replay sequence lock'', 1; - - /* Regression fixture: imported history already contains CK202607210002. */ - ;WITH all_job_ids AS ( - SELECT job_id FROM dbo.mydo_job - UNION ALL - SELECT job_id FROM dbo.mydo_job_finish - ), parsed AS ( - SELECT LEFT(job_id, 2) AS job_type, - SUBSTRING(job_id, 3, 8) AS job_date, - TRY_CONVERT(int, RIGHT(job_id, 4)) AS job_sequence - FROM all_job_ids - WHERE LEN(job_id) = 14 - AND TRY_CONVERT(date, SUBSTRING(job_id, 3, 8), 112) IS NOT NULL - AND TRY_CONVERT(int, RIGHT(job_id, 4)) IS NOT NULL - ), ranked AS ( - SELECT job_type, job_date, job_sequence, - ROW_NUMBER() OVER (PARTITION BY job_type ORDER BY job_date DESC, job_sequence DESC) AS row_no - FROM parsed - ) - UPDATE generator WITH (UPDLOCK, HOLDLOCK) - SET stock_id = ranked.job_date, - job_id = RIGHT(''0000'' + CONVERT(varchar(20), ranked.job_sequence), 4) - FROM dbo.bas_job_id generator - JOIN ranked ON ranked.job_type = generator.type AND ranked.row_no = 1 - WHERE TRY_CONVERT(bigint, ranked.job_date + RIGHT(''0000'' + CONVERT(varchar(20), ranked.job_sequence), 4)) - > TRY_CONVERT(bigint, ISNULL(generator.stock_id, ''00000000'') - + RIGHT(''0000'' + ISNULL(generator.job_id, ''0''), 4)); - - ;WITH all_task_numbers AS ( - SELECT TRY_CONVERT(int, task_no) AS task_no FROM dbo.mydo_job - UNION ALL - SELECT TRY_CONVERT(int, task_no) AS task_no FROM dbo.mydo_job_finish - ), observed AS ( - SELECT generator.id, MAX(numbers.task_no) AS max_task_no - FROM dbo.bas_task_no generator - JOIN all_task_numbers numbers - ON numbers.task_no BETWEEN generator.min_no AND generator.max_no - GROUP BY generator.id - ) - UPDATE generator WITH (UPDLOCK, HOLDLOCK) - SET task_no = observed.max_task_no - FROM dbo.bas_task_no generator - JOIN observed ON observed.id = generator.id - WHERE observed.max_task_no > ISNULL(generator.task_no, generator.min_no - 1); - - COMMIT TRANSACTION; - - SELECT type, stock_id, job_id FROM dbo.bas_job_id WHERE type IN (''RK'', ''CK'', ''ZC'') ORDER BY type; - SELECT id, task_type, task_no, min_no, max_no FROM dbo.bas_task_no WHERE id IN (1, 21) ORDER BY id; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_try_reserve_inbound', N'P') IS NOT NULL DROP PROCEDURE alg.usp_try_reserve_inbound; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_try_reserve_inbound - @warehouse_id varchar(80), - @lane_code varchar(80), - @space_id varchar(80), - @project_no varchar(255), - @expected_state_version binary(8), - @reservation_id uniqueidentifier = NULL -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - IF @reservation_id IS NULL SET @reservation_id = NEWID(); - IF @expected_state_version IS NULL - BEGIN - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''LANE_VERSION_MISMATCH'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - BEGIN TRANSACTION; - - DECLARE @current_version binary(8), @stored_semantic_fingerprint varbinary(32), - @live_semantic_fingerprint varbinary(32), @target_status varchar(5), @target_locked varchar(5), - @target_tray varchar(255), @target_depth int, @role varchar(40), @target_stock_count int, - @has_inner_candidate bit, @has_gap bit, @has_active_reservation bit, @pending_outbound bit, - @lane_lock_count int, @stock_lock_count int, @job_lock_count int, - @role_lock_count int, @topology_lock_count int, @project_policy_lock_count int, - @warehouse_policy_lock_count int, @reservation_lock_count int; - - /* Hold source-row and key-range locks through eligibility recomputation. */ - SELECT @current_version = state_version, @stored_semantic_fingerprint = semantic_fingerprint - FROM alg.lane_state WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND lane_code = @lane_code; - - IF @current_version IS NULL OR @current_version <> @expected_state_version - BEGIN - ROLLBACK TRANSACTION; - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''LANE_VERSION_MISMATCH'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - - SELECT @target_status = status, @target_locked = locked, @target_tray = tray_code, - @target_depth = TRY_CONVERT(int, group_coding) - FROM dbo.bas_cargo_space WITH (UPDLOCK, HOLDLOCK) - WHERE cargo_space_id = @space_id AND warehouse_id = @warehouse_id AND group_coding_no = @lane_code; - - SELECT @lane_lock_count = COUNT(*) - FROM dbo.bas_cargo_space WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND group_coding_no = @lane_code; - - SELECT @stock_lock_count = COUNT(*) - FROM dbo.bas_stock stock WITH (UPDLOCK, HOLDLOCK) - JOIN dbo.bas_cargo_space lane_space WITH (UPDLOCK, HOLDLOCK) ON lane_space.cargo_space_id = stock.cargo_space_id - WHERE lane_space.warehouse_id = @warehouse_id AND lane_space.group_coding_no = @lane_code; - - SELECT @job_lock_count = COUNT(*) - FROM dbo.mydo_job WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id - AND (finish_time IS NULL OR LTRIM(RTRIM(finish_time)) = ''''); - - SELECT @role_lock_count = COUNT(*) - FROM alg.space_role WITH (UPDLOCK, HOLDLOCK) - WHERE space_id IN (SELECT cargo_space_id FROM dbo.bas_cargo_space - WHERE warehouse_id = @warehouse_id AND group_coding_no = @lane_code); - - SELECT @topology_lock_count = COUNT(*) - FROM alg.space_topology WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND lane_code = @lane_code; - - SELECT @project_policy_lock_count = COUNT(*) - FROM alg.project_policy WITH (UPDLOCK, HOLDLOCK); - - SELECT @warehouse_policy_lock_count = COUNT(*) - FROM alg.warehouse_policy WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id; - - SELECT @reservation_lock_count = COUNT(*) - FROM alg.reservation WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND lane_code = @lane_code AND status = ''ACTIVE''; - - EXEC alg.usp_compute_lane_semantic_fingerprint - @warehouse_id = @warehouse_id, - @lane_code = @lane_code, - @semantic_fingerprint = @live_semantic_fingerprint OUTPUT, - @inventory_source = ''LIVE''; - - IF @stored_semantic_fingerprint IS NULL OR @live_semantic_fingerprint IS NULL - OR @stored_semantic_fingerprint <> @live_semantic_fingerprint - BEGIN - ROLLBACK TRANSACTION; - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''LANE_VERSION_MISMATCH'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - - SELECT @target_stock_count = COUNT(*) - FROM dbo.bas_stock WITH (UPDLOCK, HOLDLOCK) - WHERE cargo_space_id = @space_id; - - SELECT @role = role - FROM alg.space_role WITH (UPDLOCK, HOLDLOCK) - WHERE space_id = @space_id; - - SELECT @has_active_reservation = CASE WHEN EXISTS ( - SELECT 1 FROM alg.reservation WITH (UPDLOCK, HOLDLOCK) WHERE space_id = @space_id AND status = ''ACTIVE'' - ) THEN 1 ELSE 0 END; - - SELECT @has_inner_candidate = CASE WHEN EXISTS ( - SELECT 1 - FROM dbo.bas_cargo_space inner_candidate WITH (UPDLOCK, HOLDLOCK) - LEFT JOIN dbo.bas_stock inner_stock WITH (UPDLOCK, HOLDLOCK) ON inner_stock.cargo_space_id = inner_candidate.cargo_space_id - WHERE inner_candidate.warehouse_id = @warehouse_id - AND inner_candidate.group_coding_no = @lane_code - AND TRY_CONVERT(int, inner_candidate.group_coding) < @target_depth - AND inner_candidate.status = ''1'' - AND NULLIF(LTRIM(RTRIM(inner_candidate.tray_code)), '''') IS NULL - AND inner_stock.cargo_space_id IS NULL - ) THEN 1 ELSE 0 END; - - SELECT @has_gap = CASE WHEN EXISTS ( - SELECT 1 - FROM dbo.bas_cargo_space inner_space WITH (UPDLOCK, HOLDLOCK) - JOIN dbo.bas_cargo_space outer_space WITH (UPDLOCK, HOLDLOCK) - ON outer_space.warehouse_id = inner_space.warehouse_id - AND outer_space.group_coding_no = inner_space.group_coding_no - AND TRY_CONVERT(int, outer_space.group_coding) > TRY_CONVERT(int, inner_space.group_coding) - WHERE inner_space.warehouse_id = @warehouse_id - AND inner_space.group_coding_no = @lane_code - AND NULLIF(LTRIM(RTRIM(inner_space.tray_code)), '''') IS NULL - AND NOT EXISTS ( - SELECT 1 FROM dbo.bas_stock inner_stock WITH (UPDLOCK, HOLDLOCK) - WHERE inner_stock.cargo_space_id = inner_space.cargo_space_id - ) - AND ( - NULLIF(LTRIM(RTRIM(outer_space.tray_code)), '''') IS NOT NULL - OR EXISTS ( - SELECT 1 FROM dbo.bas_stock outer_stock WITH (UPDLOCK, HOLDLOCK) - WHERE outer_stock.cargo_space_id = outer_space.cargo_space_id - ) - ) - ) THEN 1 ELSE 0 END; - - ;WITH tray_candidates AS ( - SELECT j.job_id, COUNT(DISTINCT s.cargo_space_id) AS space_count, MIN(s.cargo_space_id) AS cargo_space_id - FROM dbo.mydo_job j WITH (UPDLOCK, HOLDLOCK) - JOIN dbo.bas_stock s WITH (UPDLOCK, HOLDLOCK) ON s.tray_code = NULLIF(LTRIM(RTRIM(j.tray_code)), '''') - JOIN dbo.bas_cargo_space c WITH (UPDLOCK, HOLDLOCK) ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = j.warehouse_id - WHERE j.warehouse_id = @warehouse_id - GROUP BY j.job_id - ) - SELECT @pending_outbound = CASE WHEN EXISTS ( - SELECT 1 - FROM dbo.mydo_job j WITH (UPDLOCK, HOLDLOCK) - LEFT JOIN dbo.bas_cargo_space source_space WITH (UPDLOCK, HOLDLOCK) - ON source_space.cargo_space_id = NULLIF(LTRIM(RTRIM(j.cargo_space_id)), '''') - AND source_space.warehouse_id = j.warehouse_id - LEFT JOIN tray_candidates ON tray_candidates.job_id = j.job_id - LEFT JOIN dbo.bas_cargo_space resolved_space WITH (UPDLOCK, HOLDLOCK) - ON resolved_space.cargo_space_id = COALESCE(source_space.cargo_space_id, - CASE WHEN tray_candidates.space_count = 1 THEN tray_candidates.cargo_space_id END) - WHERE (j.finish_time IS NULL OR LTRIM(RTRIM(j.finish_time)) = '''') - AND j.in_or_out >= 20 - AND j.warehouse_id = @warehouse_id - AND resolved_space.group_coding_no = @lane_code - ) THEN 1 ELSE 0 END; - - IF ISNULL(@pending_outbound, 0) = 1 - BEGIN - ROLLBACK TRANSACTION; - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''PENDING_OUTBOUND'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - - IF @target_status IS NULL OR @target_status <> ''1'' OR - (NULLIF(LTRIM(RTRIM(@target_locked)), '''') IS NOT NULL AND LTRIM(RTRIM(@target_locked)) NOT IN (''0'', ''N'')) OR - NULLIF(LTRIM(RTRIM(@target_tray)), '''') IS NOT NULL OR - ISNULL(@target_stock_count, 0) <> 0 OR @target_depth IS NULL OR - ISNULL(@role, ''STORAGE'') <> ''STORAGE'' OR ISNULL(@has_active_reservation, 0) = 1 OR - ISNULL(@has_inner_candidate, 0) = 1 OR ISNULL(@has_gap, 0) = 1 - BEGIN - ROLLBACK TRANSACTION; - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''SPACE_NOT_ELIGIBLE'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - - BEGIN TRY - INSERT INTO alg.reservation (reservation_id, warehouse_id, lane_code, space_id, project_no, status) - VALUES (@reservation_id, @warehouse_id, @lane_code, @space_id, @project_no, ''ACTIVE''); - EXEC alg.usp_rebuild_lane_state @warehouse_id, @lane_code; - COMMIT TRANSACTION; - SELECT CAST(''RESERVED'' AS varchar(20)) AS result, CAST(NULL AS varchar(80)) AS reason, @reservation_id AS reservation_id; - END TRY - BEGIN CATCH - DECLARE @error_number int = ERROR_NUMBER(); - IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; - IF @error_number IN (2601, 2627) - BEGIN - SELECT CAST(''CONFLICT'' AS varchar(20)) AS result, CAST(''ACTIVE_RESERVATION_EXISTS'' AS varchar(80)) AS reason, @reservation_id AS reservation_id; - RETURN; - END; - THROW; - END CATCH; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_release_reservation', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_reservation; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_release_reservation - @warehouse_id varchar(80), - @reservation_id uniqueidentifier -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - DECLARE @lane_code varchar(80); - BEGIN TRANSACTION; - SELECT @lane_code = lane_code - FROM alg.reservation WITH (UPDLOCK, HOLDLOCK) - WHERE reservation_id = @reservation_id AND warehouse_id = @warehouse_id AND status = ''ACTIVE''; - - IF @lane_code IS NULL - BEGIN - ROLLBACK TRANSACTION; - SELECT CAST(''NOT_FOUND'' AS varchar(20)) AS result, @reservation_id AS reservation_id; - RETURN; - END; - - UPDATE alg.reservation - SET status = ''RELEASED'', released_at = SYSUTCDATETIME() - WHERE reservation_id = @reservation_id AND warehouse_id = @warehouse_id; - EXEC alg.usp_rebuild_lane_state @warehouse_id, @lane_code; - COMMIT TRANSACTION; - SELECT CAST(''RELEASED'' AS varchar(20)) AS result, @reservation_id AS reservation_id; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_acquire_deep_workflow_lock', N'P') IS NOT NULL DROP PROCEDURE alg.usp_acquire_deep_workflow_lock; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_acquire_deep_workflow_lock - @request_id varchar(80), - @warehouse_id varchar(80), - @floor_no int -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - DECLARE @owner varchar(80); - BEGIN TRANSACTION; - SELECT @owner = request_id - FROM alg.deep_workflow_lock WITH (UPDLOCK, HOLDLOCK) - WHERE warehouse_id = @warehouse_id AND floor_no = @floor_no AND status = ''ACTIVE''; - - IF @owner IS NOT NULL - BEGIN - COMMIT TRANSACTION; - SELECT CONVERT(bit, CASE WHEN @owner = @request_id THEN 1 ELSE 0 END) AS acquired; - RETURN; - END; - - INSERT INTO alg.deep_workflow_lock (lock_id, request_id, warehouse_id, floor_no, status) - VALUES (NEWID(), @request_id, @warehouse_id, @floor_no, ''ACTIVE''); - COMMIT TRANSACTION; - SELECT CONVERT(bit, 1) AS acquired; -END;'); -GO - -IF OBJECT_ID(N'alg.usp_release_deep_workflow_lock', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_deep_workflow_lock; -GO -EXEC(N' -CREATE PROCEDURE alg.usp_release_deep_workflow_lock - @request_id varchar(80), - @warehouse_id varchar(80), - @floor_no int -AS -BEGIN - SET NOCOUNT ON; - SET XACT_ABORT ON; - BEGIN TRANSACTION; - UPDATE alg.deep_workflow_lock WITH (UPDLOCK, HOLDLOCK) - SET status = ''RELEASED'', released_at = SYSUTCDATETIME() - WHERE request_id = @request_id AND warehouse_id = @warehouse_id - AND floor_no = @floor_no AND status = ''ACTIVE''; - DECLARE @released bit = CASE WHEN @@ROWCOUNT = 1 THEN 1 ELSE 0 END; - COMMIT TRANSACTION; - SELECT @released AS released; -END;'); -GO - -IF OBJECT_ID(N'dbo.trg_alg_dirty_bas_stock', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_bas_stock; -GO -EXEC(N' -CREATE TRIGGER dbo.trg_alg_dirty_bas_stock -ON dbo.bas_stock -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - ;WITH changed AS ( - SELECT cargo.warehouse_id, cargo.group_coding_no AS lane_code - FROM inserted row_data - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = row_data.cargo_space_id - UNION - SELECT cargo.warehouse_id, cargo.group_coding_no - FROM deleted row_data - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = row_data.cargo_space_id - ), source AS ( - SELECT warehouse_id, lane_code - FROM changed - WHERE NULLIF(LTRIM(RTRIM(warehouse_id)), '''') IS NOT NULL - AND NULLIF(LTRIM(RTRIM(lane_code)), '''') IS NOT NULL - GROUP BY warehouse_id, lane_code - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET reason_mask = target.reason_mask | 1, - change_count = target.change_count + 1, last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 0, 1, 1, SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'dbo.trg_alg_dirty_bas_cargo_space', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_bas_cargo_space; -GO -EXEC(N' -CREATE TRIGGER dbo.trg_alg_dirty_bas_cargo_space -ON dbo.bas_cargo_space -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - ;WITH paired AS ( - SELECT i.cargo_space_id AS inserted_id, d.cargo_space_id AS deleted_id, - i.warehouse_id AS inserted_warehouse, i.group_coding_no AS inserted_lane, - d.warehouse_id AS deleted_warehouse, d.group_coding_no AS deleted_lane, - CASE WHEN i.cargo_space_id IS NULL OR d.cargo_space_id IS NULL - OR ISNULL(i.warehouse_id, '''') <> ISNULL(d.warehouse_id, '''') - OR ISNULL(i.group_coding_no, '''') <> ISNULL(d.group_coding_no, '''') - OR ISNULL(i.group_coding, '''') <> ISNULL(d.group_coding, '''') - OR ISNULL(i.the_platoon, -2147483648) <> ISNULL(d.the_platoon, -2147483648) - OR ISNULL(i.the_column, -2147483648) <> ISNULL(d.the_column, -2147483648) - OR ISNULL(i.the_floor, -2147483648) <> ISNULL(d.the_floor, -2147483648) - THEN 1 ELSE 0 END AS topology_dirty - FROM inserted i - FULL OUTER JOIN deleted d ON d.cargo_space_id = i.cargo_space_id - ), changed AS ( - SELECT inserted_warehouse AS warehouse_id, inserted_lane AS lane_code, topology_dirty - FROM paired WHERE inserted_id IS NOT NULL - UNION ALL - SELECT deleted_warehouse, deleted_lane, topology_dirty - FROM paired WHERE deleted_id IS NOT NULL - ), source AS ( - SELECT warehouse_id, lane_code, CONVERT(bit, MAX(topology_dirty)) AS topology_dirty - FROM changed - WHERE NULLIF(LTRIM(RTRIM(warehouse_id)), '''') IS NOT NULL - AND NULLIF(LTRIM(RTRIM(lane_code)), '''') IS NOT NULL - GROUP BY warehouse_id, lane_code - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET - topology_dirty = CASE WHEN target.topology_dirty = 1 OR source.topology_dirty = 1 THEN 1 ELSE 0 END, - reason_mask = target.reason_mask | 2, change_count = target.change_count + 1, - last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, source.topology_dirty, 2, 1, - SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'dbo.trg_alg_dirty_mydo_job', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_mydo_job; -GO -EXEC(N' -CREATE TRIGGER dbo.trg_alg_dirty_mydo_job -ON dbo.mydo_job -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - ;WITH changed_jobs AS ( - SELECT warehouse_id, cargo_space_id, target_cargo_space_id, tray_code FROM inserted - UNION ALL - SELECT warehouse_id, cargo_space_id, target_cargo_space_id, tray_code FROM deleted - ), changed AS ( - SELECT job.warehouse_id, source_space.group_coding_no AS lane_code - FROM changed_jobs job - JOIN dbo.bas_cargo_space source_space - ON source_space.warehouse_id = job.warehouse_id - AND source_space.cargo_space_id = NULLIF(LTRIM(RTRIM(job.cargo_space_id)), '''') - UNION - SELECT job.warehouse_id, target_space.group_coding_no - FROM changed_jobs job - JOIN dbo.bas_cargo_space target_space - ON target_space.warehouse_id = job.warehouse_id - AND target_space.cargo_space_id = NULLIF(LTRIM(RTRIM(job.target_cargo_space_id)), '''') - UNION - SELECT job.warehouse_id, tray_space.group_coding_no - FROM changed_jobs job - JOIN dbo.bas_stock stock ON stock.tray_code = NULLIF(LTRIM(RTRIM(job.tray_code)), '''') - JOIN dbo.bas_cargo_space tray_space ON tray_space.cargo_space_id = stock.cargo_space_id - AND tray_space.warehouse_id = job.warehouse_id - ), source AS ( - SELECT warehouse_id, lane_code FROM changed - WHERE NULLIF(LTRIM(RTRIM(warehouse_id)), '''') IS NOT NULL - AND NULLIF(LTRIM(RTRIM(lane_code)), '''') IS NOT NULL - GROUP BY warehouse_id, lane_code - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET reason_mask = target.reason_mask | 4, - change_count = target.change_count + 1, last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 0, 4, 1, SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'alg.trg_alg_dirty_space_role', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_space_role; -GO -EXEC(N' -CREATE TRIGGER alg.trg_alg_dirty_space_role -ON alg.space_role -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - ;WITH changed_space AS ( - SELECT space_id FROM inserted UNION SELECT space_id FROM deleted - ), source AS ( - SELECT cargo.warehouse_id, cargo.group_coding_no AS lane_code - FROM changed_space changed - JOIN dbo.bas_cargo_space cargo ON cargo.cargo_space_id = changed.space_id - GROUP BY cargo.warehouse_id, cargo.group_coding_no - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET reason_mask = target.reason_mask | 8, - change_count = target.change_count + 1, last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 0, 8, 1, SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'alg.trg_alg_dirty_project_policy', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_project_policy; -GO -EXEC(N' -CREATE TRIGGER alg.trg_alg_dirty_project_policy -ON alg.project_policy -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - IF NOT EXISTS (SELECT 1 FROM inserted) AND NOT EXISTS (SELECT 1 FROM deleted) RETURN; - ;WITH source AS ( - SELECT warehouse_id, group_coding_no AS lane_code - FROM dbo.bas_cargo_space - GROUP BY warehouse_id, group_coding_no - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET reason_mask = target.reason_mask | 16, - change_count = target.change_count + 1, last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 0, 16, 1, SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF OBJECT_ID(N'alg.trg_alg_dirty_warehouse_policy', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_warehouse_policy; -GO -EXEC(N' -CREATE TRIGGER alg.trg_alg_dirty_warehouse_policy -ON alg.warehouse_policy -AFTER INSERT, UPDATE, DELETE -AS -BEGIN - SET NOCOUNT ON; - ;WITH changed_warehouse AS ( - SELECT warehouse_id FROM inserted UNION SELECT warehouse_id FROM deleted - ), source AS ( - SELECT cargo.warehouse_id, cargo.group_coding_no AS lane_code - FROM changed_warehouse changed - JOIN dbo.bas_cargo_space cargo ON cargo.warehouse_id = changed.warehouse_id - GROUP BY cargo.warehouse_id, cargo.group_coding_no - ) - MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target - USING source ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code - WHEN MATCHED THEN UPDATE SET reason_mask = target.reason_mask | 32, - change_count = target.change_count + 1, last_changed_at = SYSUTCDATETIME() - WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, topology_dirty, reason_mask, change_count, enqueued_at, last_changed_at) - VALUES (source.warehouse_id, source.lane_code, 0, 32, 1, SYSUTCDATETIME(), SYSUTCDATETIME()); -END;'); -GO - -IF NOT EXISTS (SELECT 1 FROM alg.space_topology) - AND NOT EXISTS (SELECT 1 FROM alg.inventory_item_snapshot) - AND NOT EXISTS (SELECT 1 FROM alg.lane_state) -BEGIN - EXEC alg.usp_rebuild_space_topology; - EXEC alg.usp_sync_inventory_snapshot; - EXEC alg.usp_rebuild_lane_state; - DELETE FROM alg.dirty_lane_queue; -END; -MERGE alg.tidy_mode WITH (HOLDLOCK) AS target -USING (SELECT DISTINCT warehouse_id FROM alg.space_topology) AS source - ON target.warehouse_id = source.warehouse_id -WHEN NOT MATCHED THEN INSERT ( - warehouse_id, requested_enabled, effective_state, status_reason, mode_version, updated_at) - VALUES (source.warehouse_id, 0, 'NORMAL', 'MIGRATION_INITIALIZED', 0, SYSUTCDATETIME()); -MERGE alg.tidy_lane_queue WITH (HOLDLOCK) AS target -USING (SELECT warehouse_id, lane_code FROM alg.lane_state) AS source - ON target.warehouse_id = source.warehouse_id AND target.lane_code = source.lane_code -WHEN NOT MATCHED THEN INSERT ( - warehouse_id, lane_code, dirty_version, queue_status, reason_mask, change_count, last_dirty_at) - VALUES (source.warehouse_id, source.lane_code, 1, 'PENDING', 0, 1, SYSUTCDATETIME()); -GO diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql new file mode 100644 index 0000000..a563a40 --- /dev/null +++ b/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall-shadow.sql @@ -0,0 +1,51 @@ +/* + Removes only the FT546 shadow package. Export alg.decision_log first. + No Legacy WMS business row is modified by this script. +*/ +SET NOCOUNT ON; + +IF OBJECT_ID(N'alg.usp_refresh_dirty_lanes', N'P') IS NOT NULL + DROP PROCEDURE alg.usp_refresh_dirty_lanes; +IF OBJECT_ID(N'alg.usp_rebuild_lane_state', N'P') IS NOT NULL + DROP PROCEDURE alg.usp_rebuild_lane_state; +IF OBJECT_ID(N'alg.usp_compute_lane_semantic_fingerprint', N'P') IS NOT NULL + DROP PROCEDURE alg.usp_compute_lane_semantic_fingerprint; +IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot', N'P') IS NOT NULL + DROP PROCEDURE alg.usp_sync_inventory_snapshot; +IF OBJECT_ID(N'alg.usp_rebuild_space_topology', N'P') IS NOT NULL + DROP PROCEDURE alg.usp_rebuild_space_topology; +GO + +IF OBJECT_ID(N'alg.v_space_state', N'V') IS NOT NULL + DROP VIEW alg.v_space_state; +IF OBJECT_ID(N'alg.v_active_job', N'V') IS NOT NULL + DROP VIEW alg.v_active_job; +IF OBJECT_ID(N'alg.v_inventory_item', N'V') IS NOT NULL + DROP VIEW alg.v_inventory_item; +GO + +IF OBJECT_ID(N'alg.decision_log', N'U') IS NOT NULL + DROP TABLE alg.decision_log; +IF OBJECT_ID(N'alg.deep_workflow_lock', N'U') IS NOT NULL + DROP TABLE alg.deep_workflow_lock; +IF OBJECT_ID(N'alg.reservation', N'U') IS NOT NULL + DROP TABLE alg.reservation; +IF OBJECT_ID(N'alg.lane_state', N'U') IS NOT NULL + DROP TABLE alg.lane_state; +IF OBJECT_ID(N'alg.space_inventory_summary', N'U') IS NOT NULL + DROP TABLE alg.space_inventory_summary; +IF OBJECT_ID(N'alg.inventory_item_snapshot', N'U') IS NOT NULL + DROP TABLE alg.inventory_item_snapshot; +IF OBJECT_ID(N'alg.space_topology', N'U') IS NOT NULL + DROP TABLE alg.space_topology; +IF OBJECT_ID(N'alg.space_role', N'U') IS NOT NULL + DROP TABLE alg.space_role; +IF OBJECT_ID(N'alg.project_policy', N'U') IS NOT NULL + DROP TABLE alg.project_policy; +IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NOT NULL + DROP TABLE alg.warehouse_policy; +GO + +IF SCHEMA_ID(N'alg') IS NOT NULL + EXEC(N'DROP SCHEMA alg'); +GO diff --git a/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql b/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql deleted file mode 100644 index b173938..0000000 --- a/fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql +++ /dev/null @@ -1,53 +0,0 @@ -/* Removes only objects owned by schema alg. Run independently of install.sql. */ -SET NOCOUNT ON; - -IF OBJECT_ID(N'dbo.trg_alg_dirty_bas_stock', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_bas_stock; -IF OBJECT_ID(N'dbo.trg_alg_dirty_bas_cargo_space', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_bas_cargo_space; -IF OBJECT_ID(N'dbo.trg_alg_dirty_mydo_job', N'TR') IS NOT NULL DROP TRIGGER dbo.trg_alg_dirty_mydo_job; -IF OBJECT_ID(N'alg.trg_alg_dirty_space_role', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_space_role; -IF OBJECT_ID(N'alg.trg_alg_dirty_project_policy', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_project_policy; -IF OBJECT_ID(N'alg.trg_alg_dirty_warehouse_policy', N'TR') IS NOT NULL DROP TRIGGER alg.trg_alg_dirty_warehouse_policy; -GO -IF OBJECT_ID(N'alg.usp_refresh_dirty_lanes', N'P') IS NOT NULL DROP PROCEDURE alg.usp_refresh_dirty_lanes; -IF OBJECT_ID(N'alg.usp_mark_dirty_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_mark_dirty_lane; -IF OBJECT_ID(N'alg.usp_claim_tidy_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_claim_tidy_lane; -IF OBJECT_ID(N'alg.usp_release_tidy_lane', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_tidy_lane; -IF OBJECT_ID(N'alg.usp_complete_move_job', N'P') IS NOT NULL DROP PROCEDURE alg.usp_complete_move_job; -IF OBJECT_ID(N'alg.usp_sync_replay_sequences', N'P') IS NOT NULL DROP PROCEDURE alg.usp_sync_replay_sequences; -IF OBJECT_ID(N'alg.usp_release_reservation', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_reservation; -IF OBJECT_ID(N'alg.usp_try_reserve_inbound', N'P') IS NOT NULL DROP PROCEDURE alg.usp_try_reserve_inbound; -IF OBJECT_ID(N'alg.usp_apply_space_role', N'P') IS NOT NULL DROP PROCEDURE alg.usp_apply_space_role; -IF OBJECT_ID(N'alg.usp_release_deep_workflow_lock', N'P') IS NOT NULL DROP PROCEDURE alg.usp_release_deep_workflow_lock; -IF OBJECT_ID(N'alg.usp_acquire_deep_workflow_lock', N'P') IS NOT NULL DROP PROCEDURE alg.usp_acquire_deep_workflow_lock; -IF OBJECT_ID(N'alg.usp_rebuild_lane_state', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_lane_state; -IF OBJECT_ID(N'alg.usp_compute_lane_semantic_fingerprint', N'P') IS NOT NULL DROP PROCEDURE alg.usp_compute_lane_semantic_fingerprint; -IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot', N'P') IS NOT NULL DROP PROCEDURE alg.usp_sync_inventory_snapshot; -IF OBJECT_ID(N'alg.usp_rebuild_space_topology', N'P') IS NOT NULL DROP PROCEDURE alg.usp_rebuild_space_topology; -GO -IF OBJECT_ID(N'alg.v_space_state', N'V') IS NOT NULL DROP VIEW alg.v_space_state; -IF OBJECT_ID(N'alg.v_active_job', N'V') IS NOT NULL DROP VIEW alg.v_active_job; -IF OBJECT_ID(N'alg.v_inventory_item', N'V') IS NOT NULL DROP VIEW alg.v_inventory_item; -GO -IF OBJECT_ID(N'alg.outbound_workflow_space', N'U') IS NOT NULL DROP TABLE alg.outbound_workflow_space; -IF OBJECT_ID(N'alg.outbound_workflow_step', N'U') IS NOT NULL DROP TABLE alg.outbound_workflow_step; -IF OBJECT_ID(N'alg.outbound_workflow', N'U') IS NOT NULL DROP TABLE alg.outbound_workflow; -IF OBJECT_ID(N'alg.tidy_callback_event', N'U') IS NOT NULL DROP TABLE alg.tidy_callback_event; -IF OBJECT_ID(N'alg.tidy_space_lock', N'U') IS NOT NULL DROP TABLE alg.tidy_space_lock; -IF OBJECT_ID(N'alg.tidy_step', N'U') IS NOT NULL DROP TABLE alg.tidy_step; -IF OBJECT_ID(N'alg.tidy_workflow', N'U') IS NOT NULL DROP TABLE alg.tidy_workflow; -IF OBJECT_ID(N'alg.tidy_lane_queue', N'U') IS NOT NULL DROP TABLE alg.tidy_lane_queue; -IF OBJECT_ID(N'alg.tidy_mode', N'U') IS NOT NULL DROP TABLE alg.tidy_mode; -IF OBJECT_ID(N'alg.decision_log', N'U') IS NOT NULL DROP TABLE alg.decision_log; -IF OBJECT_ID(N'alg.deep_workflow_lock', N'U') IS NOT NULL DROP TABLE alg.deep_workflow_lock; -IF OBJECT_ID(N'alg.reservation', N'U') IS NOT NULL DROP TABLE alg.reservation; -IF OBJECT_ID(N'alg.dirty_lane_queue', N'U') IS NOT NULL DROP TABLE alg.dirty_lane_queue; -IF OBJECT_ID(N'alg.lane_state', N'U') IS NOT NULL DROP TABLE alg.lane_state; -IF OBJECT_ID(N'alg.space_inventory_summary', N'U') IS NOT NULL DROP TABLE alg.space_inventory_summary; -IF OBJECT_ID(N'alg.inventory_item_snapshot', N'U') IS NOT NULL DROP TABLE alg.inventory_item_snapshot; -IF OBJECT_ID(N'alg.space_topology', N'U') IS NOT NULL DROP TABLE alg.space_topology; -IF OBJECT_ID(N'alg.space_role', N'U') IS NOT NULL DROP TABLE alg.space_role; -IF OBJECT_ID(N'alg.project_policy', N'U') IS NOT NULL DROP TABLE alg.project_policy; -IF OBJECT_ID(N'alg.warehouse_policy', N'U') IS NOT NULL DROP TABLE alg.warehouse_policy; -GO -IF SCHEMA_ID(N'alg') IS NOT NULL EXEC(N'DROP SCHEMA alg'); -GO diff --git a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/ShadowSqlServerMigrationScriptTest.java b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/ShadowSqlServerMigrationScriptTest.java new file mode 100644 index 0000000..768daf7 --- /dev/null +++ b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/ShadowSqlServerMigrationScriptTest.java @@ -0,0 +1,143 @@ +package com.ruoyi.algorithm.repository; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +class ShadowSqlServerMigrationScriptTest { + private static final Set ALLOWED_INSTALL_OBJECTS = Set.of( + "table alg.warehouse_policy", + "table alg.project_policy", + "table alg.space_role", + "table alg.space_topology", + "table alg.inventory_item_snapshot", + "table alg.space_inventory_summary", + "table alg.lane_state", + "table alg.reservation", + "table alg.deep_workflow_lock", + "table alg.decision_log", + "view alg.v_inventory_item", + "view alg.v_active_job", + "view alg.v_space_state", + "procedure alg.usp_rebuild_space_topology", + "procedure alg.usp_sync_inventory_snapshot", + "procedure alg.usp_compute_lane_semantic_fingerprint", + "procedure alg.usp_rebuild_lane_state", + "procedure alg.usp_refresh_dirty_lanes"); + + @Test + void installCreatesOnlyAllowListedShadowObjects() throws IOException { + String install = resource("db/sqlserver/install-shadow.sql"); + + assertThat(createdObjects(install)).containsExactlyInAnyOrderElementsOf(ALLOWED_INSTALL_OBJECTS); + assertThat(install.toLowerCase(Locale.ROOT)).doesNotContain( + "usp_try_reserve_inbound", + "usp_release_reservation", + "usp_acquire_deep_workflow_lock", + "usp_release_deep_workflow_lock", + "usp_complete_move_job", + "usp_sync_replay_sequences", + "tidy_", + "outbound_workflow", + "proc_yk_complete"); + } + + @Test + void installNeverMutatesLegacyBusinessTables() throws IOException { + String install = normalize(resource("db/sqlserver/install-shadow.sql")); + + assertThat(install).doesNotContain( + "insert into dbo.", + "update dbo.", + "delete from dbo.", + "merge dbo.", + "create trigger", + "on dbo.", + "dirty_lane_queue"); + } + + @Test + void installAndUninstallOwnExactlyTheSameObjects() throws IOException { + Set installed = createdObjects(resource("db/sqlserver/install-shadow.sql")); + Set removed = droppedObjects(resource("db/sqlserver/uninstall-shadow.sql")); + + assertThat(removed).containsExactlyInAnyOrderElementsOf(installed); + } + + @Test + void refreshPerformsFullSynchronizationWithoutLegacyTriggers() throws IOException { + String install = normalize(resource("db/sqlserver/install-shadow.sql")); + String refresh = install.substring(install.indexOf("create procedure alg.usp_refresh_dirty_lanes")); + + assertThat(refresh).contains( + "exec alg.usp_rebuild_space_topology", + "exec alg.usp_sync_inventory_snapshot", + "exec alg.usp_rebuild_lane_state"); + assertThat(refresh).doesNotContain("dirty_lane_queue", "tidy_lane_queue"); + assertThat(install).contains( + "exec alg.usp_rebuild_space_topology", + "exec alg.usp_sync_inventory_snapshot", + "exec alg.usp_rebuild_lane_state"); + } + + @Test + void diagnosticsSupportsSafetyChecksAndDecisionExport() throws IOException { + String diagnostics = normalize(resource("db/sqlserver/diagnostics-shadow.sql")); + + assertThat(diagnostics).contains( + "from alg.decision_log", + "inbound_compare", + "outbound_compare", + "from sys.dm_tran_locks", + "from dbo.bas_cargo_space", + "from dbo.bas_stock", + "from dbo.mydo_job"); + assertThat(diagnostics).doesNotContain("insert ", "update ", "delete ", "merge "); + } + + @Test + void installationAndDiagnosticsReadLegacyWithoutTakingSharedLocks() throws IOException { + String readUncommitted = "set transaction isolation level read uncommitted"; + + assertThat(normalize(resource("db/sqlserver/install-shadow.sql"))).contains(readUncommitted); + assertThat(normalize(resource("db/sqlserver/diagnostics-shadow.sql"))).contains(readUncommitted); + } + + private Set createdObjects(String sql) { + return objects(sql, Pattern.compile( + "(?i)create\\s+(table|view|procedure|trigger)\\s+([a-z0-9_]+\\.[a-z0-9_]+)")); + } + + private Set droppedObjects(String sql) { + return objects(sql, Pattern.compile( + "(?i)drop\\s+(table|view|procedure|trigger)\\s+([a-z0-9_]+\\.[a-z0-9_]+)")); + } + + private Set objects(String sql, Pattern pattern) { + Matcher matcher = pattern.matcher(sql); + Set objects = new LinkedHashSet<>(); + while (matcher.find()) { + objects.add((matcher.group(1) + " " + matcher.group(2)).toLowerCase(Locale.ROOT)); + } + return objects; + } + + private String normalize(String sql) { + return sql.toLowerCase(Locale.ROOT).replaceAll("\\s+", " "); + } + + private String resource(String name) throws IOException { + try (var stream = getClass().getClassLoader().getResourceAsStream(name)) { + assertThat(stream).as("resource %s", name).isNotNull(); + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } +} diff --git a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java b/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java deleted file mode 100644 index 5289b76..0000000 --- a/fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.ruoyi.algorithm.repository; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; - -class SqlServerMigrationScriptTest { - @Test - void installDefinesPersistentOutboundWorkflowSchema() throws IOException { - String install = resource("db/sqlserver/install.sql"); - - assertThat(install).contains( - "CREATE TABLE alg.outbound_workflow (", - "CREATE TABLE alg.outbound_workflow_step (", - "CREATE TABLE alg.outbound_workflow_space (", - "CK_alg_outbound_workflow_status", - "CK_alg_outbound_workflow_step_phase", - "CK_alg_outbound_workflow_step_status", - "UX_alg_outbound_workflow_active_request", - "UX_alg_outbound_workflow_step_wms_job", - "UX_alg_outbound_workflow_space_active"); - } - - @Test - void installDefinesDirtyLaneQueueRefreshAndAllMutationSources() throws IOException { - String install = resource("db/sqlserver/install.sql"); - - assertThat(install).contains( - "CREATE TABLE alg.dirty_lane_queue (", - "CREATE PROCEDURE alg.usp_mark_dirty_lane", - "CREATE PROCEDURE alg.usp_refresh_dirty_lanes", - "CREATE TRIGGER dbo.trg_alg_dirty_bas_stock", - "CREATE TRIGGER dbo.trg_alg_dirty_bas_cargo_space", - "CREATE TRIGGER dbo.trg_alg_dirty_mydo_job", - "CREATE TRIGGER alg.trg_alg_dirty_space_role", - "CREATE TRIGGER alg.trg_alg_dirty_project_policy", - "CREATE TRIGGER alg.trg_alg_dirty_warehouse_policy"); - } - - @Test - void installDefinesPersistentTidyRuntimeSchemaAndConstraints() throws IOException { - String install = resource("db/sqlserver/install.sql"); - - assertThat(install).contains( - "CREATE TABLE alg.tidy_mode (", - "CREATE TABLE alg.tidy_lane_queue (", - "CREATE TABLE alg.tidy_workflow (", - "CREATE TABLE alg.tidy_step (", - "CREATE TABLE alg.tidy_space_lock (", - "CREATE TABLE alg.tidy_callback_event (", - "CK_alg_tidy_mode_effective_state", - "CK_alg_tidy_workflow_origin", - "CK_alg_tidy_workflow_status", - "CK_alg_tidy_space_lock_resource_type", - "UX_alg_tidy_space_lock_active", - "UX_alg_tidy_callback_event_key", - "UX_alg_tidy_workflow_active_auto", - "FK_alg_tidy_callback_event_step_workflow"); - } - - @Test - void installDefinesVersionedTidyLaneClaimAndSequenceSynchronization() throws IOException { - String install = resource("db/sqlserver/install.sql"); - - assertThat(install).contains( - "CREATE PROCEDURE alg.usp_claim_tidy_lane", - "@eligible_lane_codes varchar(max) = NULL", - "STRING_SPLIT(@eligible_lane_codes, '','')", - "CREATE PROCEDURE alg.usp_release_tidy_lane", - "CREATE PROCEDURE alg.usp_sync_replay_sequences", - "sp_getapplock", - "CK202607210002"); - } - - @Test - void installCompletesAlgorithmOwnedMoveJobsWithoutChangingTheLegacyProcedure() throws IOException { - String install = resource("db/sqlserver/install.sql"); - - assertThat(install).contains( - "CREATE PROCEDURE alg.usp_complete_move_job", - "in_or_out = ''28''", - "UPDATE dbo.bas_stock", - "INSERT INTO dbo.mydo_job_finish", - "DELETE FROM dbo.mydo_job"); - assertThat(install).doesNotContain("ALTER PROCEDURE dbo.proc_yk_complete"); - } - - @Test - void dirtyLaneRefreshPublishesRebuiltLanesToPersistentTidyQueue() throws IOException { - String install = resource("db/sqlserver/install.sql"); - String procedure = install.substring( - install.indexOf("CREATE PROCEDURE alg.usp_refresh_dirty_lanes"), - install.indexOf("IF OBJECT_ID(N'alg.usp_try_reserve_inbound'")); - - int rebuildLaneState = procedure.lastIndexOf("EXEC alg.usp_rebuild_lane_state"); - int publishTidyLane = procedure.indexOf("MERGE alg.tidy_lane_queue WITH (HOLDLOCK)"); - - assertThat(rebuildLaneState).isGreaterThanOrEqualTo(0); - assertThat(publishTidyLane).isGreaterThan(rebuildLaneState); - assertThat(procedure).contains( - "dirty_version = target.dirty_version + 1", - "queue_status = ''PENDING''"); - } - - @Test - void dirtyLaneRefreshReleasesQueueLocksBeforeReadingWarehouseTablesAndRequeuesFailures() - throws IOException { - String install = resource("db/sqlserver/install.sql"); - String procedure = install.substring( - install.indexOf("CREATE PROCEDURE alg.usp_refresh_dirty_lanes"), - install.indexOf("IF OBJECT_ID(N'alg.usp_try_reserve_inbound'")); - - int claimDelete = procedure.indexOf("DELETE queue"); - int claimCommit = procedure.indexOf("COMMIT TRANSACTION;", claimDelete); - int warehouseRead = procedure.indexOf("FROM dbo.bas_cargo_space", claimDelete); - - assertThat(claimDelete).isGreaterThanOrEqualTo(0); - assertThat(claimCommit).isBetween(claimDelete + 1, warehouseRead - 1); - assertThat(procedure).contains( - "BEGIN TRY", - "BEGIN CATCH", - "MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target", - "THROW;"); - } - - @Test - void topologyRebuildExcludesUnconfiguredCargoSpacesAndDiagnosticsReportsThem() - throws IOException { - String install = resource("db/sqlserver/install.sql"); - String diagnostics = resource("db/sqlserver/diagnostics.sql"); - String procedure = install.substring( - install.indexOf("CREATE PROCEDURE alg.usp_rebuild_space_topology"), - install.indexOf("IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot'")); - - assertThat(procedure).contains( - "NULLIF(LTRIM(RTRIM(group_coding_no)), '''') IS NOT NULL", - "TRY_CONVERT(int, group_coding) IS NOT NULL"); - assertThat(diagnostics).contains( - "excluded_from_algorithm_topology_count", - "group_coding_no IS NULL OR LTRIM(RTRIM(group_coding_no)) = ''", - "TRY_CONVERT(int, group_coding) IS NULL"); - } - - @Test - void diagnosticsAndUninstallCoverNewRuntimeObjects() throws IOException { - String diagnostics = resource("db/sqlserver/diagnostics.sql"); - String uninstall = resource("db/sqlserver/uninstall.sql"); - - assertThat(diagnostics).contains( - "FROM alg.dirty_lane_queue", - "FROM alg.outbound_workflow", - "FROM alg.tidy_mode", - "FROM alg.tidy_lane_queue", - "FROM alg.tidy_workflow", - "FROM alg.tidy_space_lock", - "PAUSED_RECOVERY"); - assertThat(uninstall).contains( - "DROP TRIGGER dbo.trg_alg_dirty_bas_stock", - "DROP PROCEDURE alg.usp_refresh_dirty_lanes", - "DROP PROCEDURE alg.usp_claim_tidy_lane", - "DROP PROCEDURE alg.usp_release_tidy_lane", - "DROP PROCEDURE alg.usp_sync_replay_sequences", - "DROP TABLE alg.tidy_callback_event", - "DROP TABLE alg.tidy_space_lock", - "DROP TABLE alg.tidy_step", - "DROP TABLE alg.tidy_workflow", - "DROP TABLE alg.tidy_lane_queue", - "DROP TABLE alg.tidy_mode", - "DROP TABLE alg.outbound_workflow_space", - "DROP TABLE alg.outbound_workflow_step", - "DROP TABLE alg.outbound_workflow", - "DROP TABLE alg.dirty_lane_queue"); - } - - @Test - void repeatedInstallDoesNotDiscardPendingDirtyLanes() throws IOException { - String install = resource("db/sqlserver/install.sql"); - String initialization = install.substring(install.lastIndexOf("IF NOT EXISTS (SELECT 1 FROM alg.space_topology)")); - - assertThat(initialization).contains( - "AND NOT EXISTS (SELECT 1 FROM alg.inventory_item_snapshot)", - "AND NOT EXISTS (SELECT 1 FROM alg.lane_state)"); - assertThat(initialization.indexOf("DELETE FROM alg.dirty_lane_queue")) - .isLessThan(initialization.indexOf("END;")); - assertThat(initialization.substring(initialization.indexOf("END;") + 4)) - .doesNotContain("DELETE FROM alg.dirty_lane_queue"); - } - - private String resource(String name) throws IOException { - try (var stream = getClass().getClassLoader().getResourceAsStream(name)) { - if (stream == null) { - throw new IOException("Missing test resource " + name); - } - return new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } - } -} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngine.java b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngine.java index 5186557..8389dd9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngine.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngine.java @@ -30,6 +30,7 @@ public class ShadowPlanningEngine { this.properties = Objects.requireNonNull(properties, "properties"); this.transactions = new TransactionTemplate(Objects.requireNonNull(transactionManager, "transactionManager")); this.transactions.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.transactions.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactions.setTimeout(properties.getTimeoutSeconds()); } diff --git a/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngineTest.java b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngineTest.java index c380e0d..d64cc39 100644 --- a/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngineTest.java +++ b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowPlanningEngineTest.java @@ -49,6 +49,8 @@ class ShadowPlanningEngineTest { assertThat(transactions.definition.getPropagationBehavior()) .isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + assertThat(transactions.definition.getIsolationLevel()) + .isEqualTo(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); assertThat(transactions.definition.getTimeout()).isEqualTo(7); assertThat(observation.observed()).isTrue(); assertThat(observation.proposedSpaceId()).isEqualTo("SPACE-9");