feat: project local tidy recovery evidence

This commit is contained in:
zgw
2026-07-23 15:15:57 +08:00
parent 2a329a9312
commit a545986409
4 changed files with 1238 additions and 0 deletions

View File

@@ -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<TidyWorkflowState> selectActiveTidyWorkflows(@Param("warehouseId") String warehouseId);
List<TidyRecoveryEvidence> 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<String> selectActiveManualTidyVehicleIds(@Param("warehouseId") String warehouseId);
int assignTidyWorkflowVehicle(@Param("workflowId") String workflowId,
@Param("vehicleId") String vehicleId);
List<InboundTaskItem> 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<OutboundStockReference> selectOutboundStockReferences(@Param("warehouseId") String warehouseId,
@Param("spaceId") String spaceId,
@Param("trayCode") String trayCode,
@Param("projectNo") String projectNo);
List<OutboundStockReference> 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);
}

View File

@@ -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) { }

View File

@@ -0,0 +1,838 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.algorithm.wms.WmsAlgorithmMapper">
<select id="claimTidyLane" resultType="com.ruoyi.algorithm.wms.tidy.TidyLaneClaim">
exec alg.usp_claim_tidy_lane
@warehouse_id = #{warehouseId},
@claimed_by = #{claimedBy},
@lease_seconds = #{leaseSeconds}
</select>
<select id="claimTidyLaneFromCandidates" resultType="com.ruoyi.algorithm.wms.tidy.TidyLaneClaim">
exec alg.usp_claim_tidy_lane
@warehouse_id = #{warehouseId},
@eligible_lane_codes = #{eligibleLaneCodes},
@claimed_by = #{claimedBy},
@lease_seconds = #{leaseSeconds}
</select>
<select id="releaseTidyLane" resultType="int">
exec alg.usp_release_tidy_lane
@warehouse_id = #{warehouseId},
@lane_code = #{laneCode},
@claim_token = #{claimToken},
@claimed_version = #{claimedVersion},
@completed = #{completed},
@last_error = #{lastError}
</select>
<select id="countActiveAutomaticTidy" resultType="int">
select count(*) from alg.tidy_workflow
where origin = 'AUTO_TIDY'
and status in ('CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY')
</select>
<select id="selectLaneCodeForSpace" resultType="string">
select lane_code from alg.space_topology
where warehouse_id = #{warehouseId} and space_id = #{spaceId}
</select>
<select id="selectDatabaseName" resultType="string">select DB_NAME()</select>
<insert id="insertTidyWorkflow">
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>
<insert id="insertTidyStep">
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>
<insert id="lockTidyResource">
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>
<insert id="insertTidyMoveJob">
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})
)
</insert>
<select id="selectTidyWorkflowIdByJob" resultType="string">
select top (1) workflow_id from alg.tidy_step
where wms_job_id = #{jobId}
or #{jobId} = concat('TDY-', workflow_id, '-', step_id)
</select>
<select id="selectTidyStepIdByJob" resultType="string">
select top (1) step_id from alg.tidy_step
where wms_job_id = #{jobId}
or #{jobId} = concat('TDY-', workflow_id, '-', step_id)
</select>
<insert id="insertTidyCallbackEventIfAbsent">
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;
</insert>
<update id="completeTidyStep">
update alg.tidy_step
set status = 'COMPLETED', completed_at = SYSUTCDATETIME()
where step_id = #{stepId} and status in ('PENDING', 'DISPATCHED', 'RUNNING')
</update>
<select id="selectNextTidyStepId" resultType="string">
select top (1) nextStep.step_id
from alg.tidy_step nextStep
where nextStep.workflow_id = #{workflowId}
and nextStep.status = 'PENDING'
and not exists (
select 1 from alg.tidy_step dependency
where dependency.workflow_id = nextStep.workflow_id
and dependency.step_no &lt; nextStep.step_no
and dependency.status &lt;&gt; 'COMPLETED')
order by nextStep.step_no
</select>
<update id="completeTidyWorkflow">
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 &lt;&gt; 'COMPLETED')
</update>
<update id="releaseTidyResources">
update alg.tidy_space_lock
set status = 'RELEASED', released_at = SYSUTCDATETIME()
where workflow_id = #{workflowId} and status = 'ACTIVE'
</update>
<update id="pauseTidyWorkflow">
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>
<update id="pauseTidyStep">
update alg.tidy_step
set status = 'PAUSED_RECOVERY'
where workflow_id = #{workflowId}
and step_no = #{stepNo}
and status in ('PENDING', 'DISPATCHED', 'RUNNING')
</update>
<resultMap id="tidyWorkflowStateMap" type="com.ruoyi.algorithm.wms.tidy.TidyWorkflowState">
<constructor>
<arg column="workflowId" javaType="java.lang.String" />
<arg column="origin" javaType="com.ruoyi.algorithm.wms.tidy.TidyOrigin" />
<arg column="status" javaType="java.lang.String" />
<arg column="laneCode" javaType="java.lang.String" />
<arg column="sourceSpaceId" javaType="java.lang.String" />
<arg column="targetSpaceId" javaType="java.lang.String" />
<arg column="trayCode" javaType="java.lang.String" />
<arg column="wcsTaskId" javaType="java.lang.String" />
<arg column="activeStep" javaType="_int" />
</constructor>
</resultMap>
<select id="selectActiveTidyWorkflows" resultMap="tidyWorkflowStateMap">
select workflow.workflow_id as workflowId,
workflow.origin as origin,
workflow.status as status,
workflow.lane_code as laneCode,
workflow.source_space_id as sourceSpaceId,
workflow.target_space_id as targetSpaceId,
workflow.tray_code as trayCode,
coalesce(workflow.wcs_task_id, job.task_no) as wcsTaskId,
active_step.step_no as activeStep
from alg.tidy_workflow workflow
cross apply (
select top (1) step.step_no, step.step_id
from alg.tidy_step step
where step.workflow_id = workflow.workflow_id
and step.status in ('PENDING', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY')
order by step.step_no
) active_step
left join dbo.mydo_job job
on job.job_id = concat('TDY-', workflow.workflow_id, '-', active_step.step_id)
where workflow.warehouse_id = #{warehouseId}
and workflow.status in (
'CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY'
)
</select>
<sql id="tidyModeProjection">
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
</sql>
<select id="selectTidyMode" resultType="com.ruoyi.algorithm.wms.tidy.TidyModeSnapshot">
select <include refid="tidyModeProjection" />
from alg.tidy_mode mode
where mode.warehouse_id = #{warehouseId}
</select>
<select id="selectTidyModeForUpdate" resultType="com.ruoyi.algorithm.wms.tidy.TidyModeSnapshot">
select
mode.warehouse_id as warehouseId,
mode.requested_enabled as requestedEnabled,
mode.effective_state as effectiveState,
0 as drainCount,
0 as activeTidyCount,
mode.status_reason as statusReason,
mode.mode_version as version,
mode.updated_by as updatedBy,
mode.updated_at as updatedAt
from alg.tidy_mode mode with (updlock, holdlock)
where mode.warehouse_id = #{warehouseId}
</select>
<update id="setTidyModeRequest">
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>
<select id="countActiveBusinessExecution" resultType="int">
select count(*)
from mydo_job job
where job.warehouse_id = #{warehouseId}
and (
job.status in ('2', '3')
or (
job.status = '1'
and nullif(ltrim(rtrim(job.cargo_space_id)), '') is not null
)
)
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
)
</select>
<select id="countActiveManualTidy" resultType="int">
select count(*)
from alg.tidy_workflow workflow
where workflow.warehouse_id = #{warehouseId}
and workflow.origin = 'MANUAL_TIDY'
and workflow.status in (
'CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY'
)
</select>
<select id="selectActiveManualTidyVehicleIds" resultType="string">
select vehicle_id
from alg.tidy_workflow
where warehouse_id = #{warehouseId}
and origin = 'MANUAL_TIDY'
and vehicle_id is not null
and status in ('CLAIMED', 'WAITING_DISPATCH', 'DISPATCHED', 'RUNNING', 'PAUSED_RECOVERY')
</select>
<update id="assignTidyWorkflowVehicle">
update alg.tidy_workflow
set vehicle_id = #{vehicleId}, updated_at = SYSUTCDATETIME()
where workflow_id = #{workflowId} and vehicle_id is null
</update>
<select id="selectInboundTaskItems" resultType="com.ruoyi.algorithm.wms.InboundTaskItem">
select coalesce(nullif(ltrim(rtrim(isd.spare4)), ''),
nullif(ltrim(rtrim(bs.spare4)), '')) as projectNo,
mjd.product_id as productId,
mjd.lot_no as lotNo
from mydo_job_detail mjd
left join in_stockin_detail isd on isd.stockin_detail_id = mjd.stockin_detail_id
left join bas_stock bs on bs.stock_id = mjd.stock_id
where mjd.job_id = #{jobId}
order by mjd.job_detail_id
</select>
<update id="claimInboundSpace">
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>
<update id="assignInboundJob">
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>
<update id="releaseInboundSpace">
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>
<update id="clearInboundJobAssignment">
update mydo_job
set cargo_space_id = null,
roadway_id = null,
fault_flag = '1'
where job_id = #{jobId}
and cargo_space_id = #{spaceId}
</update>
<select id="selectOutboundStockReference" resultType="com.ruoyi.algorithm.wms.OutboundStockReference">
select stock.stock_id as stockId,
stock.tray_code as trayCode,
stock.cargo_space_id as spaceId,
stock.warehouse_id as warehouseId,
stock.spare4 as projectNo,
stock.product_id as productId
from bas_stock stock
where stock.stock_id = #{stockId}
and stock.stock_status = '1'
and stock.product_status = '1'
and stock.quantity &gt; stock.out_quantity
</select>
<select id="selectOutboundStockReferences" resultType="com.ruoyi.algorithm.wms.OutboundStockReference">
select stock.stock_id as stockId,
stock.tray_code as trayCode,
stock.cargo_space_id as spaceId,
stock.warehouse_id as warehouseId,
stock.spare4 as projectNo,
stock.product_id as productId
from bas_stock stock with (updlock, rowlock)
inner join bas_cargo_space space on space.cargo_space_id = stock.cargo_space_id
where stock.warehouse_id = #{warehouseId}
and stock.cargo_space_id = #{spaceId}
and stock.tray_code = #{trayCode}
and stock.spare4 = #{projectNo}
and stock.stock_status = '1'
and stock.product_status = '1'
and stock.quantity &gt; stock.out_quantity
and space.locked = 'N'
order by stock.stock_id
</select>
<select id="selectOutboundStocksAtSpace" resultType="com.ruoyi.algorithm.wms.OutboundStockReference">
select stock.stock_id as stockId,
stock.tray_code as trayCode,
stock.cargo_space_id as spaceId,
stock.warehouse_id as warehouseId,
stock.spare4 as projectNo,
stock.product_id as productId
from bas_stock stock with (updlock, holdlock)
where stock.warehouse_id = #{warehouseId}
and stock.cargo_space_id = #{spaceId}
and stock.stock_status = '1'
and stock.product_status = '1'
and stock.quantity &gt; stock.out_quantity
order by stock.stock_id
</select>
<select id="selectOutboundShallowCommitStockForUpdate"
resultType="com.ruoyi.algorithm.wms.OutboundStockReference"><![CDATA[
select stock.stock_id as stockId,
stock.tray_code as trayCode,
stock.cargo_space_id as spaceId,
stock.warehouse_id as warehouseId,
stock.spare4 as projectNo,
stock.product_id as productId
from bas_stock stock with (updlock, holdlock)
inner join bas_cargo_space source with (updlock, holdlock)
on source.cargo_space_id = stock.cargo_space_id
and source.warehouse_id = stock.warehouse_id
where stock.stock_id = #{stockId}
and stock.warehouse_id = #{warehouseId}
and stock.cargo_space_id = #{spaceId}
and stock.tray_code = #{trayCode}
and (stock.spare4 = #{projectNo}
or (#{projectNo} is null and nullif(ltrim(rtrim(stock.spare4)), '') is null))
and stock.stock_status = '1'
and stock.product_status = '1'
and stock.quantity > stock.out_quantity
and source.status = '2'
and source.tray_code = #{trayCode}
and (source.locked is null or ltrim(rtrim(source.locked)) in ('', '0', 'N'))
and nullif(ltrim(rtrim(source.group_coding_no)), '') is not null
and try_convert(int, source.group_coding) is not null
and not exists (
select 1
from bas_cargo_space outer_space with (updlock, holdlock)
where outer_space.warehouse_id = source.warehouse_id
and outer_space.group_coding_no = source.group_coding_no
and try_convert(int, outer_space.group_coding)
> try_convert(int, source.group_coding)
and (
isnull(outer_space.status, '') <> '1'
or nullif(ltrim(rtrim(outer_space.tray_code)), '') is not null
or (outer_space.locked is not null
and ltrim(rtrim(outer_space.locked)) not in ('', '0', 'N'))
or exists (
select 1
from bas_stock outer_stock with (updlock, holdlock)
where outer_stock.cargo_space_id = outer_space.cargo_space_id
)
)
)
]]></select>
<select id="selectOutboundWorkflowStartStockForUpdate"
resultType="com.ruoyi.algorithm.wms.OutboundStockReference">
select stock.stock_id as stockId,
stock.tray_code as trayCode,
stock.cargo_space_id as spaceId,
stock.warehouse_id as warehouseId,
stock.spare4 as projectNo,
stock.product_id as productId
from bas_stock stock with (updlock, holdlock)
inner join bas_cargo_space space with (updlock, holdlock)
on space.cargo_space_id = stock.cargo_space_id
and space.warehouse_id = stock.warehouse_id
where stock.stock_id = #{stockId}
and stock.stock_status = '1'
and stock.product_status = '1'
and stock.quantity &gt; stock.out_quantity
and space.status = '2'
and (space.locked is null or ltrim(rtrim(space.locked)) in ('', '0', 'N'))
</select>
<select id="countActiveOutboundWorkflow" resultType="int">
select count(1)
from alg.outbound_workflow workflow with (updlock, holdlock)
where workflow.request_id = #{requestId}
and workflow.warehouse_id = #{warehouseId}
and workflow.floor_no = #{floorNo}
and workflow.status = 'ACTIVE'
</select>
<insert id="insertOutboundWorkflow">
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>
<insert id="insertOutboundWorkflowStep">
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>
<insert id="lockOutboundWorkflowSpace">
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};
</insert>
<sql id="outboundWorkflowStepColumns">
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
</sql>
<select id="selectNextPendingWorkflowStep"
resultType="com.ruoyi.algorithm.wms.OutboundWorkflowStepState">
select top (1) <include refid="outboundWorkflowStepColumns"/>
from alg.outbound_workflow_step step with (updlock, holdlock)
inner join alg.outbound_workflow workflow with (updlock, holdlock)
on workflow.workflow_id = step.workflow_id
left join alg.outbound_workflow_step dependency with (updlock, holdlock)
on dependency.assignment_id = step.depends_on_assignment_id
where step.workflow_id = #{workflowId}
and workflow.status = 'ACTIVE'
and step.status = 'PENDING'
and (step.depends_on_assignment_id is null or dependency.status = 'COMPLETED')
order by step.workflow_step, step.assignment_id
</select>
<insert id="insertOutboundWorkflowMoveJob">
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 &gt; 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}
)
</insert>
<update id="markOutboundWorkflowStepRunning">
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>
<select id="selectOutboundWorkflowStepByJobId"
resultType="com.ruoyi.algorithm.wms.OutboundWorkflowStepState">
select top (1) <include refid="outboundWorkflowStepColumns"/>
from alg.outbound_workflow_step step with (updlock, holdlock)
inner join alg.outbound_workflow workflow with (updlock, holdlock)
on workflow.workflow_id = step.workflow_id
where step.wms_job_id = #{jobId}
and step.status = 'RUNNING'
and workflow.status = 'ACTIVE'
order by step.workflow_step
</select>
<select id="selectOutboundWorkflowStepForTray"
resultType="com.ruoyi.algorithm.wms.OutboundWorkflowStepState">
select top (1) <include refid="outboundWorkflowStepColumns"/>
from alg.outbound_workflow_step step with (updlock, holdlock)
inner join alg.outbound_workflow workflow with (updlock, holdlock)
on workflow.workflow_id = step.workflow_id
where step.tray_code = #{trayCode}
and step.phase = 'OUTBOUND'
and step.status = 'AT_EXIT'
and workflow.status = 'ACTIVE'
order by step.workflow_step
</select>
<select id="countStockAtSpaceByTray" resultType="int">
select count(1)
from bas_stock stock with (updlock, holdlock)
where stock.cargo_space_id = #{spaceId}
and stock.tray_code = #{trayCode}
</select>
<update id="markOutboundWorkflowStepCompleted">
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>
<update id="markOutboundWorkflowStepAtExit">
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>
<update id="unlockOutboundWorkflowSpace">
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>
<update id="relockOutboundWorkflowSpace">
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>
<select id="selectActiveOutboundJobId" resultType="string">
select top (1) job.job_id
from mydo_job job with (updlock, holdlock)
inner join mydo_job_detail detail with (updlock, holdlock)
on detail.job_id = job.job_id
where detail.stock_id = #{stockId}
and detail.in_or_out_d_id = #{stockoutDetailId}
and try_convert(int, job.in_or_out) &gt;= 20
and try_convert(int, job.in_or_out) &lt;&gt; 28
order by job.create_time desc, job.job_id desc
</select>
<update id="completeOutboundWorkflow">
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 &lt;&gt; 'COMPLETED'
)
</update>
<update id="unlockOutboundWorkflowSpaces">
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';
</update>
<resultMap id="tidyRecoveryEvidenceMap" type="com.ruoyi.algorithm.wms.tidy.TidyRecoveryEvidence">
<constructor>
<arg column="workflowId" javaType="java.lang.String"/><arg column="origin" javaType="com.ruoyi.algorithm.wms.tidy.TidyOrigin"/><arg column="workflowStatus" javaType="java.lang.String"/><arg column="activeLockCount" javaType="_int"/>
<arg column="stepId" javaType="java.lang.String"/><arg column="stepNo" javaType="java.lang.Integer"/><arg column="stepStatus" javaType="java.lang.String"/><arg column="sourceSpaceId" javaType="java.lang.String"/><arg column="targetSpaceId" javaType="java.lang.String"/><arg column="trayCode" javaType="java.lang.String"/><arg column="activeTidyJobCount" javaType="_int"/>
<arg column="activeJobId" javaType="java.lang.String"/><arg column="activeJobStatus" javaType="java.lang.String"/><arg column="activeJobInOrOut" javaType="java.lang.String"/><arg column="activeJobTrayCode" javaType="java.lang.String"/><arg column="activeJobSourceSpaceId" javaType="java.lang.String"/><arg column="activeJobTargetSpaceId" javaType="java.lang.String"/><arg column="finishedJobId" javaType="java.lang.String"/><arg column="finishedJobStatus" javaType="java.lang.String"/><arg column="finishedJobInOrOut" javaType="java.lang.String"/><arg column="finishedJobTrayCode" javaType="java.lang.String"/><arg column="finishedJobSourceSpaceId" javaType="java.lang.String"/><arg column="finishedJobTargetSpaceId" javaType="java.lang.String"/><arg column="sourceStockRows" javaType="_int"/><arg column="targetStockRows" javaType="_int"/><arg column="sourceSpaceStatus" javaType="java.lang.String"/><arg column="sourceSpaceTrayCode" javaType="java.lang.String"/><arg column="targetSpaceStatus" javaType="java.lang.String"/><arg column="targetSpaceTrayCode" javaType="java.lang.String"/>
</constructor>
</resultMap>
<select id="selectTidyRecoveryEvidence" resultMap="tidyRecoveryEvidenceMap">
select workflow.workflow_id workflowId, workflow.origin origin, workflow.status workflowStatus,
(select count(*) from alg.tidy_space_lock held where held.workflow_id = workflow.workflow_id and held.status = 'ACTIVE') activeLockCount,
active_step.step_id stepId, active_step.step_no stepNo, active_step.status stepStatus, active_step.source_space_id sourceSpaceId, active_step.target_space_id targetSpaceId, active_step.tray_code trayCode,
(select count(*) from dbo.mydo_job workflow_job where workflow_job.job_id like concat('TDY-', workflow.workflow_id, '-%')) activeTidyJobCount,
active_job.job_id activeJobId, active_job.status activeJobStatus, active_job.in_or_out activeJobInOrOut, active_job.tray_code activeJobTrayCode, active_job.cargo_space_id activeJobSourceSpaceId, active_job.target_cargo_space_id activeJobTargetSpaceId,
finished_job.job_id finishedJobId, finished_job.status finishedJobStatus, finished_job.in_or_out finishedJobInOrOut, finished_job.tray_code finishedJobTrayCode, finished_job.cargo_space_id finishedJobSourceSpaceId, finished_job.target_cargo_space_id finishedJobTargetSpaceId,
(select count(*) from dbo.bas_stock source_stock where source_stock.cargo_space_id = active_step.source_space_id and source_stock.tray_code = active_step.tray_code) sourceStockRows,
(select count(*) from dbo.bas_stock target_stock where target_stock.cargo_space_id = active_step.target_space_id and target_stock.tray_code = active_step.tray_code) targetStockRows,
source_space.status sourceSpaceStatus, source_space.tray_code sourceSpaceTrayCode, target_space.status targetSpaceStatus, target_space.tray_code targetSpaceTrayCode
from alg.tidy_workflow workflow
outer apply (select top (1) step.step_id, step.step_no, step.status, step.source_space_id, step.target_space_id, step.tray_code from alg.tidy_step step where step.workflow_id = workflow.workflow_id and step.status in ('PENDING','DISPATCHED','RUNNING','PAUSED_RECOVERY') order by step.step_no) active_step
left join dbo.mydo_job active_job on active_job.job_id = concat('TDY-', workflow.workflow_id, '-', active_step.step_id)
left join dbo.mydo_job_finish finished_job on finished_job.job_id = concat('TDY-', workflow.workflow_id, '-', active_step.step_id)
left join dbo.bas_cargo_space source_space on source_space.cargo_space_id = active_step.source_space_id
left join dbo.bas_cargo_space target_space on target_space.cargo_space_id = active_step.target_space_id
where workflow.warehouse_id = #{warehouseId} and workflow.status in ('CLAIMED','WAITING_DISPATCH','DISPATCHED','RUNNING','PAUSED_RECOVERY')
</select>
</mapper>

View File

@@ -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("&lt;")
.doesNotContain("&gt;")
.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("<include")
.doesNotContain("mydo_job")
.doesNotContain("tidy_workflow");
}
@Test
void candidateLaneClaimDelegatesFilteringToTheAtomicClaimProcedure() throws IOException {
assertThat(statement("select", "claimTidyLaneFromCandidates"))
.contains("exec alg.usp_claim_tidy_lane")
.contains("@eligible_lane_codes = #{eligiblelanecodes}")
.doesNotContain("foreach");
}
@Test
void activeTidyWorkflowUsesAnExplicitDatabaseShapedConstructorMapping() throws IOException {
assertThat(statement("resultmap", "tidyWorkflowStateMap"))
.contains("<constructor>")
.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("</" + element + ">", 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);
}
}
}