diff --git a/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java new file mode 100644 index 0000000..80ca1bc --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java @@ -0,0 +1,163 @@ +package com.ruoyi.algorithm.wms; + +import com.ruoyi.algorithm.wms.tidy.TidyModeSnapshot; +import com.ruoyi.algorithm.wms.tidy.TidyLaneClaim; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import com.ruoyi.algorithm.wms.tidy.TidyWorkflowState; +import com.ruoyi.algorithm.wms.tidy.TidyRecoveryEvidence; + +@Mapper +public interface WmsAlgorithmMapper { + TidyLaneClaim claimTidyLane(@Param("warehouseId") String warehouseId, + @Param("claimedBy") String claimedBy, @Param("leaseSeconds") int leaseSeconds); + + TidyLaneClaim claimTidyLaneFromCandidates(@Param("warehouseId") String warehouseId, + @Param("eligibleLaneCodes") String eligibleLaneCodes, + @Param("claimedBy") String claimedBy, @Param("leaseSeconds") int leaseSeconds); + + int releaseTidyLane(@Param("warehouseId") String warehouseId, @Param("laneCode") String laneCode, + @Param("claimToken") java.util.UUID claimToken, @Param("claimedVersion") long claimedVersion, + @Param("completed") boolean completed, @Param("lastError") String lastError); + + int countActiveAutomaticTidy(); + + String selectLaneCodeForSpace(@Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId); + + String selectDatabaseName(); + + int insertTidyWorkflow(@Param("workflowId") String workflowId, @Param("warehouseId") String warehouseId, + @Param("laneCode") String laneCode, @Param("origin") String origin, + @Param("candidateType") String candidateType, @Param("sourceSpaceId") String sourceSpaceId, + @Param("targetSpaceId") String targetSpaceId, @Param("trayCode") String trayCode, + @Param("status") String status, @Param("dirtyVersion") long dirtyVersion, + @Param("claimToken") String claimToken); + + int insertTidyStep(@Param("stepId") String stepId, @Param("workflowId") String workflowId, + @Param("stepNo") int stepNo, @Param("sourceSpaceId") String sourceSpaceId, + @Param("targetSpaceId") String targetSpaceId, @Param("trayCode") String trayCode, + @Param("status") String status, @Param("dependsOnStepId") String dependsOnStepId); + + int lockTidyResource(@Param("lockId") java.util.UUID lockId, @Param("workflowId") String workflowId, + @Param("warehouseId") String warehouseId, @Param("resourceType") String resourceType, + @Param("resourceId") String resourceId); + + int insertTidyMoveJob(@Param("workflowId") String workflowId, @Param("stepId") String stepId); + String selectTidyWorkflowIdByJob(@Param("jobId") String jobId); + String selectTidyStepIdByJob(@Param("jobId") String jobId); + int insertTidyCallbackEventIfAbsent(@Param("eventId") java.util.UUID eventId, + @Param("workflowId") String workflowId, @Param("stepId") String stepId, + @Param("wcsTaskId") String wcsTaskId, @Param("eventStage") String eventStage, + @Param("resultCode") String resultCode); + int completeTidyStep(@Param("stepId") String stepId); + String selectNextTidyStepId(@Param("workflowId") String workflowId); + int completeTidyWorkflow(@Param("workflowId") String workflowId); + int releaseTidyResources(@Param("workflowId") String workflowId); + int pauseTidyWorkflow(@Param("workflowId") String workflowId, @Param("statusReason") String statusReason); + int pauseTidyStep(@Param("workflowId") String workflowId, @Param("stepNo") int stepNo, + @Param("statusReason") String statusReason); + List selectActiveTidyWorkflows(@Param("warehouseId") String warehouseId); + List selectTidyRecoveryEvidence(@Param("warehouseId") String warehouseId); + TidyModeSnapshot selectTidyMode(@Param("warehouseId") String warehouseId); + + TidyModeSnapshot selectTidyModeForUpdate(@Param("warehouseId") String warehouseId); + + int setTidyModeRequest(@Param("warehouseId") String warehouseId, + @Param("requestedEnabled") boolean requestedEnabled, + @Param("effectiveState") String effectiveState, + @Param("statusReason") String statusReason, + @Param("updatedBy") String updatedBy, + @Param("expectedVersion") long expectedVersion); + + int countActiveBusinessExecution(@Param("warehouseId") String warehouseId); + + int countActiveManualTidy(@Param("warehouseId") String warehouseId); + + List selectActiveManualTidyVehicleIds(@Param("warehouseId") String warehouseId); + + int assignTidyWorkflowVehicle(@Param("workflowId") String workflowId, + @Param("vehicleId") String vehicleId); + + List selectInboundTaskItems(@Param("jobId") String jobId); + + int claimInboundSpace(@Param("reservationId") String reservationId, + @Param("warehouseId") String warehouseId, + @Param("laneCode") String laneCode, + @Param("spaceId") String spaceId, + @Param("trayCode") String trayCode); + + int assignInboundJob(@Param("jobId") String jobId, @Param("spaceId") String spaceId); + + int releaseInboundSpace(@Param("jobId") String jobId, + @Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId, + @Param("trayCode") String trayCode); + + int clearInboundJobAssignment(@Param("jobId") String jobId, + @Param("spaceId") String spaceId); + + OutboundStockReference selectOutboundStockReference(@Param("stockId") String stockId); + + List selectOutboundStockReferences(@Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId, + @Param("trayCode") String trayCode, + @Param("projectNo") String projectNo); + + List selectOutboundStocksAtSpace(@Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId); + + OutboundStockReference selectOutboundShallowCommitStockForUpdate( + @Param("stockId") String stockId, + @Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId, + @Param("trayCode") String trayCode, + @Param("projectNo") String projectNo); + + OutboundStockReference selectOutboundWorkflowStartStockForUpdate(@Param("stockId") String stockId); + + int countActiveOutboundWorkflow(@Param("requestId") String requestId, + @Param("warehouseId") String warehouseId, + @Param("floorNo") int floorNo); + + int insertOutboundWorkflow(OutboundWorkflowHeaderState workflow); + + int insertOutboundWorkflowStep(OutboundWorkflowStepState step); + + int lockOutboundWorkflowSpace(OutboundWorkflowSpaceLock lock); + + OutboundWorkflowStepState selectNextPendingWorkflowStep(@Param("workflowId") String workflowId); + + int insertOutboundWorkflowMoveJob(OutboundWorkflowStepState step); + + int markOutboundWorkflowStepRunning(@Param("assignmentId") String assignmentId, + @Param("jobId") String jobId); + + OutboundWorkflowStepState selectOutboundWorkflowStepByJobId(@Param("jobId") String jobId); + + OutboundWorkflowStepState selectOutboundWorkflowStepForTray(@Param("trayCode") String trayCode); + + int countStockAtSpaceByTray(@Param("spaceId") String spaceId, + @Param("trayCode") String trayCode); + + int markOutboundWorkflowStepCompleted(@Param("assignmentId") String assignmentId); + + int markOutboundWorkflowStepAtExit(@Param("assignmentId") String assignmentId); + + int unlockOutboundWorkflowSpace(@Param("workflowId") String workflowId, + @Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId); + + int relockOutboundWorkflowSpace(@Param("workflowId") String workflowId, + @Param("warehouseId") String warehouseId, + @Param("spaceId") String spaceId); + + String selectActiveOutboundJobId(@Param("stockId") String stockId, + @Param("stockoutDetailId") String stockoutDetailId); + + int completeOutboundWorkflow(@Param("workflowId") String workflowId); + + int unlockOutboundWorkflowSpaces(@Param("workflowId") String workflowId); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java new file mode 100644 index 0000000..5efa800 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java @@ -0,0 +1,12 @@ +package com.ruoyi.algorithm.wms.tidy; + +public record TidyRecoveryEvidence( + String workflowId, TidyOrigin origin, String workflowStatus, int activeLockCount, + String stepId, Integer stepNo, String stepStatus, String sourceSpaceId, String targetSpaceId, + String trayCode, int activeTidyJobCount, String activeJobId, String activeJobStatus, + String activeJobInOrOut, String activeJobTrayCode, String activeJobSourceSpaceId, + String activeJobTargetSpaceId, String finishedJobId, String finishedJobStatus, + String finishedJobInOrOut, String finishedJobTrayCode, String finishedJobSourceSpaceId, + String finishedJobTargetSpaceId, int sourceStockRows, int targetStockRows, + String sourceSpaceStatus, String sourceSpaceTrayCode, String targetSpaceStatus, + String targetSpaceTrayCode) { } diff --git a/ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml b/ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml new file mode 100644 index 0000000..8dacfa8 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml @@ -0,0 +1,838 @@ + + + + + + + + + + + + + + + + + + insert into alg.tidy_workflow ( + workflow_id, warehouse_id, lane_code, origin, candidate_type, + source_space_id, target_space_id, stock_id, tray_code, status, + claimed_dirty_version, lane_claim_token, mode_version) + values (#{workflowId}, #{warehouseId}, #{laneCode}, #{origin}, #{candidateType}, + #{sourceSpaceId}, #{targetSpaceId}, #{workflowId}, #{trayCode}, #{status}, + #{dirtyVersion}, try_convert(uniqueidentifier, #{claimToken}), 0) + + + + insert into alg.tidy_step ( + step_id, workflow_id, step_no, source_space_id, target_space_id, + stock_id, tray_code, status, expected_event_stage) + values (#{stepId}, #{workflowId}, #{stepNo}, #{sourceSpaceId}, #{targetSpaceId}, + #{stepId}, #{trayCode}, #{status}, 'FINISHED') + + + + insert into alg.tidy_space_lock ( + lock_id, workflow_id, warehouse_id, resource_type, resource_id, status) + values (#{lockId}, #{workflowId}, #{warehouseId}, #{resourceType}, #{resourceId}, 'ACTIVE') + + + + insert into mydo_job ( + job_id, status, on_line_type, task_no, tray_code, cargo_space_id, + target_cargo_space_id, priority, execution_phase, in_or_out, + create_time, warehouse_id) + select concat('TDY-', #{workflowId}, '-', #{stepId}), '1', '1', + concat('TDY-', #{workflowId}, '-', #{stepId}), step.tray_code, + step.source_space_id, step.target_space_id, 10, '0', '28', + convert(varchar(19), getdate(), 120), workflow.warehouse_id + from alg.tidy_step step + join alg.tidy_workflow workflow on workflow.workflow_id = step.workflow_id + where step.workflow_id = #{workflowId} + and step.step_id = #{stepId} + and step.status = 'PENDING' + and not exists ( + select 1 from mydo_job job where job.job_id = concat('TDY-', #{workflowId}, '-', #{stepId}) + ) + + + + + + + + if not exists ( + select 1 from alg.tidy_callback_event with (updlock, holdlock) + where workflow_id = #{workflowId} and step_id = #{stepId} + and wcs_task_id = #{wcsTaskId} and event_stage = #{eventStage}) + begin + insert into alg.tidy_callback_event ( + callback_event_id, workflow_id, step_id, wcs_task_id, event_stage, result_code) + values (#{eventId}, #{workflowId}, #{stepId}, #{wcsTaskId}, #{eventStage}, #{resultCode}); + select 1; + end + else select 0; + + + + update alg.tidy_step + set status = 'COMPLETED', completed_at = SYSUTCDATETIME() + where step_id = #{stepId} and status in ('PENDING', 'DISPATCHED', 'RUNNING') + + + + + + update alg.tidy_workflow + set status = 'COMPLETED', completed_at = SYSUTCDATETIME(), updated_at = SYSUTCDATETIME() + where workflow_id = #{workflowId} + and not exists (select 1 from alg.tidy_step where workflow_id = #{workflowId} and status <> 'COMPLETED') + + + + update alg.tidy_space_lock + set status = 'RELEASED', released_at = SYSUTCDATETIME() + where workflow_id = #{workflowId} and status = 'ACTIVE' + + + + update alg.tidy_workflow + set status = 'PAUSED_RECOVERY', status_reason = #{statusReason}, updated_at = SYSUTCDATETIME() + where workflow_id = #{workflowId} and status not in ('COMPLETED', 'CANCELLED_RECONCILED') + + + + update alg.tidy_step + set status = 'PAUSED_RECOVERY' + where workflow_id = #{workflowId} + and step_no = #{stepNo} + and status in ('PENDING', 'DISPATCHED', 'RUNNING') + + + + + + + + + + + + + + + + + + + + mode.warehouse_id as warehouseId, + mode.requested_enabled as requestedEnabled, + mode.effective_state as effectiveState, + (select count(*) + from mydo_job job + where job.warehouse_id = mode.warehouse_id + and job.status in ('1', '2', '3') + and try_convert(int, job.in_or_out) is not null + and not exists ( + select 1 from alg.tidy_step tidyStep where tidyStep.wms_job_id = job.job_id + )) as drainCount, + (select count(*) + from alg.tidy_workflow tidyWorkflow + where tidyWorkflow.warehouse_id = mode.warehouse_id + and tidyWorkflow.origin = 'MANUAL_TIDY' + and tidyWorkflow.status in ( + 'CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY' + )) as activeTidyCount, + mode.status_reason as statusReason, + mode.mode_version as version, + mode.updated_by as updatedBy, + mode.updated_at as updatedAt + + + + + + + + update alg.tidy_mode with (updlock, rowlock) + set requested_enabled = #{requestedEnabled}, + effective_state = #{effectiveState}, + status_reason = #{statusReason}, + mode_version = mode_version + 1, + updated_by = #{updatedBy}, + updated_at = SYSUTCDATETIME() + where warehouse_id = #{warehouseId} + and mode_version = #{expectedVersion} + + + + + + + + + + update alg.tidy_workflow + set vehicle_id = #{vehicleId}, updated_at = SYSUTCDATETIME() + where workflow_id = #{workflowId} and vehicle_id is null + + + + + + update target with (updlock, rowlock) + set target.status = '3', + target.tray_code = #{trayCode} + from bas_cargo_space target + where target.cargo_space_id = #{spaceId} + and target.warehouse_id = #{warehouseId} + and target.group_coding_no = #{laneCode} + and target.status = '1' + and (target.locked is null or ltrim(rtrim(target.locked)) in ('', '0', 'N')) + and nullif(ltrim(rtrim(target.tray_code)), '') is null + and not exists ( + select 1 from bas_stock stock with (updlock, holdlock) + where stock.cargo_space_id = target.cargo_space_id + ) + and exists ( + select 1 from alg.reservation reservation with (updlock, holdlock) + where reservation.reservation_id = #{reservationId} + and reservation.warehouse_id = #{warehouseId} + and reservation.lane_code = #{laneCode} + and reservation.space_id = #{spaceId} + and reservation.status = 'ACTIVE' + ) + + + + update job + set job.cargo_space_id = #{spaceId}, + job.roadway_id = target.roadway_id + from mydo_job job + inner join bas_cargo_space target on target.cargo_space_id = #{spaceId} + where job.job_id = #{jobId} + and (job.cargo_space_id is null or ltrim(rtrim(job.cargo_space_id)) = '' + or job.cargo_space_id = #{spaceId}) + and target.status = '3' + + + + update target with (updlock, rowlock) + set target.status = '1', + target.tray_code = null + from bas_cargo_space target + where target.cargo_space_id = #{spaceId} + and target.warehouse_id = #{warehouseId} + and target.status = '3' + and target.tray_code = #{trayCode} + and not exists ( + select 1 from bas_stock stock with (updlock, holdlock) + where stock.cargo_space_id = target.cargo_space_id + ) + and exists ( + select 1 from mydo_job job with (updlock, holdlock) + where job.job_id = #{jobId} + and job.cargo_space_id = target.cargo_space_id + ) + + + + update mydo_job + set cargo_space_id = null, + roadway_id = null, + fault_flag = '1' + where job_id = #{jobId} + and cargo_space_id = #{spaceId} + + + + + + + + + + + + + + + + insert into alg.outbound_workflow ( + 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) + values ( + #{workflowId}, #{requestId}, #{stockoutDetailId}, #{warehouseId}, + #{sourceStockId}, #{selectedTrayCode}, #{sourceSpaceId}, #{laneCode}, + #{floorNo}, #{exitId}, #{status}, #{currentStep}, #{totalSteps}, + SYSUTCDATETIME(), SYSUTCDATETIME()) + + + + insert into alg.outbound_workflow_step ( + assignment_id, workflow_id, phase, source_space_id, target_space_id, + workflow_step, workflow_total_steps, depends_on_assignment_id, + stock_id, tray_code, status, wms_job_id, created_at) + values ( + #{assignmentId}, #{workflowId}, #{phase}, #{sourceSpaceId}, #{targetSpaceId}, + #{workflowStep}, #{workflowTotalSteps}, #{dependsOnAssignmentId}, + #{stockId}, #{trayCode}, #{status}, #{jobId}, SYSUTCDATETIME()) + + + + insert into alg.outbound_workflow_space ( + workflow_id, space_id, status, locked_at) + select #{workflowId}, space.cargo_space_id, 'ACTIVE', SYSUTCDATETIME() + from bas_cargo_space space with (updlock, holdlock) + where space.cargo_space_id = #{spaceId} + and space.warehouse_id = #{warehouseId} + and (space.locked is null or ltrim(rtrim(space.locked)) in ('', '0', 'N')) + and not exists ( + select 1 + from alg.outbound_workflow_space held with (updlock, holdlock) + where held.space_id = space.cargo_space_id + and held.status = 'ACTIVE' + ); + update space + set space.locked = 'Y' + from bas_cargo_space space + inner join alg.outbound_workflow_space held + on held.space_id = space.cargo_space_id + and held.workflow_id = #{workflowId} + and held.status = 'ACTIVE' + where space.cargo_space_id = #{spaceId} + and space.warehouse_id = #{warehouseId}; + + + + step.assignment_id as assignmentId, + step.workflow_id as workflowId, + workflow.stockout_detail_id as stockoutDetailId, + workflow.warehouse_id as warehouseId, + workflow.floor_no as floorNo, + workflow.request_id as requestId, + workflow.exit_id as exitId, + step.phase as phase, + step.source_space_id as sourceSpaceId, + step.target_space_id as targetSpaceId, + step.workflow_step as workflowStep, + step.workflow_total_steps as workflowTotalSteps, + step.depends_on_assignment_id as dependsOnAssignmentId, + step.stock_id as stockId, + step.tray_code as trayCode, + step.status as status, + step.wms_job_id as jobId + + + + + + insert into mydo_job ( + job_id, status, on_line_type, task_no, tray_code, + roadway_id, cargo_space_id, target_roadway_id, target_cargo_space_id, + priority, execution_phase, in_or_out, create_time, exit_id, warehouse_id) + select #{assignmentId}, '1', '1', #{assignmentId}, #{trayCode}, + source_space.roadway_id, #{sourceSpaceId}, + target_space.roadway_id, #{targetSpaceId}, + 10, '0', '28', convert(varchar(19), getdate(), 120), + #{exitId}, #{warehouseId} + from bas_cargo_space source_space with (updlock, holdlock) + inner join bas_cargo_space target_space with (updlock, holdlock) + on target_space.cargo_space_id = #{targetSpaceId} + and target_space.warehouse_id = #{warehouseId} + inner join bas_stock source_stock with (updlock, holdlock) + on source_stock.stock_id = #{stockId} + and source_stock.tray_code = #{trayCode} + and source_stock.cargo_space_id = source_space.cargo_space_id + and source_stock.warehouse_id = #{warehouseId} + inner join alg.outbound_workflow_space source_lock with (updlock, holdlock) + on source_lock.workflow_id = #{workflowId} + and source_lock.space_id = source_space.cargo_space_id + and source_lock.status = 'ACTIVE' + inner join alg.outbound_workflow_space target_lock with (updlock, holdlock) + on target_lock.workflow_id = #{workflowId} + and target_lock.space_id = target_space.cargo_space_id + and target_lock.status = 'ACTIVE' + where source_space.cargo_space_id = #{sourceSpaceId} + and source_space.warehouse_id = #{warehouseId} + and source_space.status = '2' + and source_space.tray_code = #{trayCode} + and source_space.locked = 'Y' + and source_stock.stock_status = '1' + and source_stock.product_status = '1' + and source_stock.quantity > source_stock.out_quantity + and target_space.status = '1' + and nullif(ltrim(rtrim(target_space.tray_code)), '') is null + and target_space.locked = 'Y' + and #{phase} in ('MOVE_OUT', 'RESTORE') + and not exists ( + select 1 + from bas_stock target_stock with (updlock, holdlock) + where target_stock.cargo_space_id = target_space.cargo_space_id + ) + and not exists ( + select 1 from mydo_job job with (updlock, holdlock) + where job.job_id = #{assignmentId} + ) + + + + update step with (updlock, rowlock) + set step.status = 'RUNNING', + step.wms_job_id = #{jobId}, + step.started_at = SYSUTCDATETIME() + from alg.outbound_workflow_step step + left join alg.outbound_workflow_step dependency + on dependency.assignment_id = step.depends_on_assignment_id + where step.assignment_id = #{assignmentId} + and step.status = 'PENDING' + and (step.depends_on_assignment_id is null or dependency.status = 'COMPLETED'); + update workflow + set workflow.current_step = step.workflow_step, + workflow.updated_at = SYSUTCDATETIME() + from alg.outbound_workflow workflow + inner join alg.outbound_workflow_step step + on step.workflow_id = workflow.workflow_id + where step.assignment_id = #{assignmentId} + and step.status = 'RUNNING' + and step.wms_job_id = #{jobId}; + + + + + + + + + + update step with (updlock, rowlock) + set step.status = 'COMPLETED', + step.completed_at = SYSUTCDATETIME() + from alg.outbound_workflow_step step + where step.assignment_id = #{assignmentId} + and step.status in ('RUNNING', 'AT_EXIT'); + update workflow + set workflow.current_step = step.workflow_step, + workflow.updated_at = SYSUTCDATETIME() + from alg.outbound_workflow workflow + inner join alg.outbound_workflow_step step + on step.workflow_id = workflow.workflow_id + where step.assignment_id = #{assignmentId} + and step.status = 'COMPLETED'; + + + + update step with (updlock, rowlock) + set step.status = 'AT_EXIT', + step.at_exit_at = SYSUTCDATETIME() + from alg.outbound_workflow_step step + where step.assignment_id = #{assignmentId} + and step.phase = 'OUTBOUND' + and step.status = 'RUNNING'; + update workflow + set workflow.current_step = step.workflow_step, + workflow.updated_at = SYSUTCDATETIME() + from alg.outbound_workflow workflow + inner join alg.outbound_workflow_step step + on step.workflow_id = workflow.workflow_id + where step.assignment_id = #{assignmentId} + and step.status = 'AT_EXIT'; + + + + update space with (updlock, rowlock) + set space.locked = 'N' + from bas_cargo_space space + where space.cargo_space_id = #{spaceId} + and space.warehouse_id = #{warehouseId} + and space.locked = 'Y' + and exists ( + select 1 + from alg.outbound_workflow_space held with (updlock, holdlock) + where held.workflow_id = #{workflowId} + and held.space_id = #{spaceId} + and held.space_id = space.cargo_space_id + and held.status = 'ACTIVE' + ) + + + + update space with (updlock, rowlock) + set space.locked = 'Y' + from bas_cargo_space space + where space.cargo_space_id = #{spaceId} + and space.warehouse_id = #{warehouseId} + and space.status = '1' + and (space.locked is null or ltrim(rtrim(space.locked)) in ('', '0', 'N')) + and nullif(ltrim(rtrim(space.tray_code)), '') is null + and not exists ( + select 1 + from bas_stock stock with (updlock, holdlock) + where stock.cargo_space_id = space.cargo_space_id + ) + and exists ( + select 1 + from alg.outbound_workflow_space held with (updlock, holdlock) + where held.workflow_id = #{workflowId} + and held.space_id = #{spaceId} + and held.space_id = space.cargo_space_id + and held.status = 'ACTIVE' + ) + + + + + + update workflow with (updlock, rowlock) + set workflow.status = 'COMPLETED', + workflow.current_step = workflow.total_steps, + workflow.updated_at = SYSUTCDATETIME(), + workflow.completed_at = SYSUTCDATETIME() + from alg.outbound_workflow workflow + where workflow.workflow_id = #{workflowId} + and workflow.status = 'ACTIVE' + and not exists ( + select 1 + from alg.outbound_workflow_step step with (updlock, holdlock) + where step.workflow_id = workflow.workflow_id + and step.status <> 'COMPLETED' + ) + + + + update space with (updlock, rowlock) + set space.locked = 'N' + from bas_cargo_space space + inner join alg.outbound_workflow_space held with (updlock, holdlock) + on held.space_id = space.cargo_space_id + and held.workflow_id = #{workflowId} + and held.status = 'ACTIVE' + where space.locked = 'Y'; + update alg.outbound_workflow_space + set status = 'RELEASED', released_at = SYSUTCDATETIME() + where workflow_id = #{workflowId} + and status = 'ACTIVE'; + + + + + + + + + + + + diff --git a/ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java new file mode 100644 index 0000000..f390cb9 --- /dev/null +++ b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java @@ -0,0 +1,225 @@ +package com.ruoyi.algorithm.wms; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class WmsAlgorithmMapperWorkflowSqlTest { + + @Test + void startStockReadLocksLiveStockAndRequiresAnAvailableOccupiedSpace() throws IOException { + String statement = statement("select", "selectOutboundWorkflowStartStockForUpdate"); + + assertThat(statement) + .contains("from bas_stock stock with (updlock, holdlock)") + .contains("inner join bas_cargo_space space with (updlock, holdlock)") + .contains("stock.stock_id = #{stockid}") + .contains("space.status = '2'") + .contains("ltrim(rtrim(space.locked)) in ('', '0', 'n')"); + } + + @Test + void shallowCommitLocksTheExactLiveSelectionAndRejectsAnyOuterBlocker() throws IOException { + String statement = statement("select", "selectOutboundShallowCommitStockForUpdate"); + + assertThat(statement) + .contains("from bas_stock stock with (updlock, holdlock)") + .contains("inner join bas_cargo_space source with (updlock, holdlock)") + .doesNotContain("cross apply") + .doesNotContain("lane_guard") + .contains("stock.stock_id = #{stockid}") + .contains("stock.warehouse_id = #{warehouseid}") + .contains("stock.cargo_space_id = #{spaceid}") + .contains("stock.tray_code = #{traycode}") + .contains("stock.spare4 = #{projectno}") + .contains("stock.stock_status = '1'") + .contains("stock.product_status = '1'") + .contains("stock.quantity > stock.out_quantity") + .contains("source.status = '2'") + .contains("source.tray_code = #{traycode}") + .contains("ltrim(rtrim(source.locked)) in ('', '0', 'n')") + .contains("from bas_cargo_space outer_space with (updlock, holdlock)") + .contains("outer_space.group_coding_no = source.group_coding_no") + .contains("try_convert(int, outer_space.group_coding) > try_convert(int, source.group_coding)") + .contains("isnull(outer_space.status, '') <> '1'") + .doesNotContain("<") + .doesNotContain(">") + .contains("nullif(ltrim(rtrim(outer_space.tray_code)), '') is not null") + .contains("ltrim(rtrim(outer_space.locked)) not in ('', '0', 'n')") + .contains("from bas_stock outer_stock with (updlock, holdlock)") + .contains("outer_stock.cargo_space_id = outer_space.cargo_space_id"); + } + + @Test + void relocationInsertRequiresExactSourceEmptyTargetAndWorkflowOwnedLocks() throws IOException { + String statement = statement("insert", "insertOutboundWorkflowMoveJob"); + + assertThat(statement) + .contains("source_stock.stock_id = #{stockid}") + .contains("source_stock.tray_code = #{traycode}") + .contains("source_stock.cargo_space_id = source_space.cargo_space_id") + .contains("target_space.status = '1'") + .contains("nullif(ltrim(rtrim(target_space.tray_code)), '') is null") + .contains("from bas_stock target_stock with (updlock, holdlock)") + .contains("source_lock.workflow_id = #{workflowid}") + .contains("target_lock.workflow_id = #{workflowid}") + .contains("source_lock.status = 'active'") + .contains("target_lock.status = 'active'"); + } + + @Test + void selectiveUnlockIsScopedAndPreservesWorkflowOwnership() throws IOException { + String statement = statement("update", "unlockOutboundWorkflowSpace"); + + assertThat(statement) + .contains("held.workflow_id = #{workflowid}") + .contains("held.space_id = #{spaceid}") + .contains("space.warehouse_id = #{warehouseid}") + .doesNotContain("update alg.outbound_workflow_space") + .doesNotContain("status = 'released'"); + } + + @Test + void restoreRelockIsScopedToActiveOwnershipAndAnEmptyAvailableTarget() throws IOException { + String statement = statement("update", "relockOutboundWorkflowSpace"); + + assertThat(statement) + .contains("space.cargo_space_id = #{spaceid}") + .contains("space.warehouse_id = #{warehouseid}") + .contains("space.status = '1'") + .contains("ltrim(rtrim(space.locked)) in ('', '0', 'n')") + .contains("nullif(ltrim(rtrim(space.tray_code)), '') is null") + .contains("from bas_stock stock with (updlock, holdlock)") + .contains("held.workflow_id = #{workflowid}") + .contains("held.space_id = #{spaceid}") + .contains("held.status = 'active'"); + } + + @Test + void finalUnlockStillReleasesPhysicalLocksAndActiveOwnershipRows() throws IOException { + String statement = statement("update", "unlockOutboundWorkflowSpaces"); + + assertThat(statement) + .contains("set space.locked = 'n'") + .contains("held.workflow_id = #{workflowid}") + .contains("held.status = 'active'") + .contains("update alg.outbound_workflow_space") + .contains("set status = 'released'") + .contains("where workflow_id = #{workflowid}") + .contains("and status = 'active'"); + } + + @Test + void activeWorkflowOwnershipCheckUsesRequestWarehouseAndFloor() throws IOException { + String statement = statement("select", "countActiveOutboundWorkflow"); + + assertThat(statement) + .contains("workflow.request_id = #{requestid}") + .contains("workflow.warehouse_id = #{warehouseid}") + .contains("workflow.floor_no = #{floorno}") + .contains("workflow.status = 'active'"); + } + + @Test + void tidyAdmissionCountsDoNotTakeUpdateOrRangeLocks() throws IOException { + assertThat(statement("select", "countActiveBusinessExecution")) + .doesNotContain("updlock") + .doesNotContain("holdlock"); + assertThat(statement("select", "countActiveManualTidy")) + .doesNotContain("updlock") + .doesNotContain("holdlock"); + } + + @Test + void tidyDrainIgnoresPendingBusinessDocumentsUntilTheyHaveAnExecutionTarget() throws IOException { + assertThat(statement("select", "countActiveBusinessExecution")) + .contains("job.status in ('2', '3')") + .contains("job.status = '1'") + .contains("nullif(ltrim(rtrim(job.cargo_space_id)), '') is not null"); + } + + @Test + void tidyModeLockReadsOnlyTheModeRow() throws IOException { + assertThat(statement("select", "selectTidyModeForUpdate")) + .contains("from alg.tidy_mode mode with (updlock, holdlock)") + .contains("0 as draincount") + .contains("0 as activetidycount") + .doesNotContain("") + .contains("column=\"workflowid\" javatype=\"java.lang.string\"") + .contains("column=\"origin\" javatype=\"com.ruoyi.algorithm.wms.tidy.tidyorigin\"") + .contains("column=\"activestep\" javatype=\"_int\""); + assertThat(statement("select", "selectActiveTidyWorkflows")) + .contains("resultmap=\"tidyworkflowstatemap\""); + } + + @Test + void tidyRecoveryProjectionMapsWorkflowJobInventoryAndLockEvidence() throws IOException { + String mapping = statement("resultmap", "tidyRecoveryEvidenceMap"); + assertThat(mapping) + .contains("type=\"com.ruoyi.algorithm.wms.tidy.tidyrecoveryevidence\"") + .contains("column=\"workflowid\"") + .contains("column=\"stepid\"") + .contains("column=\"activejobid\"") + .contains("column=\"finishedjobid\"") + .contains("column=\"sourcestockrows\"") + .contains("column=\"targetstockrows\"") + .contains("column=\"activelockcount\""); + } + + @Test + void tidyRecoveryQueryUsesOnlyWmsLocalAuthoritativeTables() throws IOException { + String query = statement("select", "selectTidyRecoveryEvidence"); + assertThat(query) + .contains("from alg.tidy_workflow workflow") + .contains("outer apply") + .contains("from alg.tidy_step step") + .contains("left join dbo.mydo_job active_job") + .contains("left join dbo.mydo_job_finish finished_job") + .contains("from dbo.bas_stock") + .contains("left join dbo.bas_cargo_space source_space") + .contains("left join dbo.bas_cargo_space target_space") + .contains("from alg.tidy_space_lock") + .contains("concat('tdy-', workflow.workflow_id, '-', active_step.step_id)") + .doesNotContain("wcs_task_id"); + } + + private String statement(String element, String id) throws IOException { + String xml = mapperXml().toLowerCase().replaceAll("\\s+", " "); + String opening = "<" + element + " id=\"" + id.toLowerCase() + "\""; + int start = xml.indexOf(opening); + assertThat(start).as("mapper statement %s", id).isGreaterThanOrEqualTo(0); + int end = xml.indexOf("", start); + assertThat(end).as("closing tag for mapper statement %s", id).isGreaterThan(start); + return xml.substring(start, end); + } + + private String mapperXml() throws IOException { + try (InputStream stream = getClass().getResourceAsStream( + "/mapper/algorithm/WmsAlgorithmMapper.xml")) { + if (stream == null) { + throw new IOException("WmsAlgorithmMapper.xml not found on the test classpath"); + } + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } +}