feat(wcs): persist transport orders and workflows
This commit is contained in:
@@ -53,6 +53,29 @@ public final class TransportOrder {
|
||||
sourceCode, destinationCode, priority, createdAt);
|
||||
}
|
||||
|
||||
public static TransportOrder restore(String orderId, String externalRequestId,
|
||||
TransportOrderType type, String loadId,
|
||||
String sourceCode, String destinationCode,
|
||||
int priority, TransportOrderState state,
|
||||
String workflowId, String resultCode,
|
||||
String stateReason, long stateVersion,
|
||||
Instant createdAt, Instant updatedAt) {
|
||||
if (stateVersion < 0L) {
|
||||
throw new IllegalArgumentException("stateVersion must not be negative");
|
||||
}
|
||||
TransportOrder order = new TransportOrder(orderId, externalRequestId, type,
|
||||
loadId, sourceCode, destinationCode, priority, createdAt);
|
||||
order.state = Objects.requireNonNull(state, "state");
|
||||
order.workflowId = workflowId;
|
||||
order.resultCode = resultCode;
|
||||
order.stateReason = stateReason;
|
||||
order.stateVersion = stateVersion;
|
||||
order.eventSequence = stateVersion + 1L;
|
||||
order.updatedAt = Objects.requireNonNull(updatedAt, "updatedAt");
|
||||
order.uncommittedEvents.clear();
|
||||
return order;
|
||||
}
|
||||
|
||||
public void plan(String workflowId) {
|
||||
requireState(TransportOrderState.ACCEPTED);
|
||||
this.workflowId = requireText(workflowId, "workflowId");
|
||||
|
||||
@@ -85,6 +85,15 @@ public final class WorkflowInstance {
|
||||
return Collections.unmodifiableList(new ArrayList<TaskStep>(steps.values()));
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getMandatoryDependencies() {
|
||||
Map<String, List<String>> copy = new LinkedHashMap<String, List<String>>();
|
||||
for (Map.Entry<String, List<String>> entry : mandatoryDependencies.entrySet()) {
|
||||
copy.put(entry.getKey(), Collections.unmodifiableList(
|
||||
new ArrayList<String>(entry.getValue())));
|
||||
}
|
||||
return Collections.unmodifiableMap(copy);
|
||||
}
|
||||
|
||||
private DependencyState dependencyState(String stepKey) {
|
||||
boolean allSucceeded = true;
|
||||
for (String dependencyKey : mandatoryDependencies.get(stepKey)) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.TaskStep;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrder;
|
||||
import com.ruoyi.wcs.domain.task.WorkflowInstance;
|
||||
import com.ruoyi.wcs.port.out.TransportOrderRepository;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class MyBatisTransportOrderRepository implements TransportOrderRepository {
|
||||
private final TransportOrderMapper mapper;
|
||||
|
||||
public MyBatisTransportOrderRepository(TransportOrderMapper mapper) {
|
||||
this.mapper = Objects.requireNonNull(mapper, "mapper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrder findByExternalRequestId(String externalRequestId) {
|
||||
TransportOrderRow row = mapper.selectByExternalRequestId(externalRequestId);
|
||||
return row == null ? null : row.toDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrder findByOrderId(String orderId) {
|
||||
TransportOrderRow row = mapper.selectByOrderId(orderId);
|
||||
return row == null ? null : row.toDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransportOrder order, WorkflowInstance workflow) {
|
||||
Objects.requireNonNull(order, "order");
|
||||
Objects.requireNonNull(workflow, "workflow");
|
||||
if (mapper.insertOrder(TransportOrderRow.fromDomain(order)) != 1) {
|
||||
throw new IllegalStateException("transport order insert did not affect one row");
|
||||
}
|
||||
if (mapper.insertWorkflow(WorkflowRow.fromDomain(workflow)) != 1) {
|
||||
throw new IllegalStateException("workflow insert did not affect one row");
|
||||
}
|
||||
for (TaskStep step : workflow.getSteps()) {
|
||||
if (mapper.insertTaskStep(TaskStepRow.fromDomain(
|
||||
workflow.getWorkflowId(), step)) != 1) {
|
||||
throw new IllegalStateException("task step insert did not affect one row");
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, List<String>> entry
|
||||
: workflow.getMandatoryDependencies().entrySet()) {
|
||||
for (String dependency : entry.getValue()) {
|
||||
if (mapper.insertTaskDependency(new TaskDependencyRow(
|
||||
workflow.getWorkflowId(), entry.getKey(), dependency)) != 1) {
|
||||
throw new IllegalStateException(
|
||||
"task dependency insert did not affect one row");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransportOrder order) {
|
||||
Objects.requireNonNull(order, "order");
|
||||
TransportOrderRow existing = mapper.selectByOrderId(order.getOrderId());
|
||||
if (existing == null) {
|
||||
if (mapper.insertOrder(TransportOrderRow.fromDomain(order)) != 1) {
|
||||
throw new IllegalStateException("transport order insert did not affect one row");
|
||||
}
|
||||
return;
|
||||
}
|
||||
long expectedVersion = order.getStateVersion() - 1L;
|
||||
if (mapper.updateState(TransportOrderRow.fromDomain(order), expectedVersion) != 1) {
|
||||
throw new OptimisticLockingFailureException(
|
||||
"transport order state changed concurrently: " + order.getOrderId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
public final class TaskDependencyRow {
|
||||
private String workflowId;
|
||||
private String stepKey;
|
||||
private String dependsOnStepKey;
|
||||
|
||||
public TaskDependencyRow() {
|
||||
}
|
||||
|
||||
public TaskDependencyRow(String workflowId, String stepKey,
|
||||
String dependsOnStepKey) {
|
||||
this.workflowId = workflowId;
|
||||
this.stepKey = stepKey;
|
||||
this.dependsOnStepKey = dependsOnStepKey;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public String getStepKey() {
|
||||
return stepKey;
|
||||
}
|
||||
|
||||
public void setStepKey(String stepKey) {
|
||||
this.stepKey = stepKey;
|
||||
}
|
||||
|
||||
public String getDependsOnStepKey() {
|
||||
return dependsOnStepKey;
|
||||
}
|
||||
|
||||
public void setDependsOnStepKey(String dependsOnStepKey) {
|
||||
this.dependsOnStepKey = dependsOnStepKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.TaskStep;
|
||||
|
||||
public final class TaskStepRow {
|
||||
private String stepId;
|
||||
private String workflowId;
|
||||
private String stepKey;
|
||||
private String stepType;
|
||||
private boolean required;
|
||||
private String stepState;
|
||||
private String reasonCode;
|
||||
private String vehicleId;
|
||||
private long stateVersion;
|
||||
|
||||
public static TaskStepRow fromDomain(String workflowId, TaskStep step) {
|
||||
TaskStepRow row = new TaskStepRow();
|
||||
row.stepId = step.getStepId();
|
||||
row.workflowId = workflowId;
|
||||
row.stepKey = step.getStepKey();
|
||||
row.stepType = step.getType().name();
|
||||
row.required = step.isRequired();
|
||||
row.stepState = step.getState().name();
|
||||
row.reasonCode = step.getReasonCode();
|
||||
row.vehicleId = step.getVehicleId();
|
||||
row.stateVersion = step.getStateVersion();
|
||||
return row;
|
||||
}
|
||||
|
||||
public String getStepId() {
|
||||
return stepId;
|
||||
}
|
||||
|
||||
public void setStepId(String stepId) {
|
||||
this.stepId = stepId;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public String getStepKey() {
|
||||
return stepKey;
|
||||
}
|
||||
|
||||
public void setStepKey(String stepKey) {
|
||||
this.stepKey = stepKey;
|
||||
}
|
||||
|
||||
public String getStepType() {
|
||||
return stepType;
|
||||
}
|
||||
|
||||
public void setStepType(String stepType) {
|
||||
this.stepType = stepType;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public String getStepState() {
|
||||
return stepState;
|
||||
}
|
||||
|
||||
public void setStepState(String stepState) {
|
||||
this.stepState = stepState;
|
||||
}
|
||||
|
||||
public String getReasonCode() {
|
||||
return reasonCode;
|
||||
}
|
||||
|
||||
public void setReasonCode(String reasonCode) {
|
||||
this.reasonCode = reasonCode;
|
||||
}
|
||||
|
||||
public String getVehicleId() {
|
||||
return vehicleId;
|
||||
}
|
||||
|
||||
public void setVehicleId(String vehicleId) {
|
||||
this.vehicleId = vehicleId;
|
||||
}
|
||||
|
||||
public long getStateVersion() {
|
||||
return stateVersion;
|
||||
}
|
||||
|
||||
public void setStateVersion(long stateVersion) {
|
||||
this.stateVersion = stateVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface TransportOrderMapper {
|
||||
TransportOrderRow selectByExternalRequestId(
|
||||
@Param("externalRequestId") String externalRequestId);
|
||||
|
||||
TransportOrderRow selectByOrderId(@Param("orderId") String orderId);
|
||||
|
||||
int insertOrder(TransportOrderRow row);
|
||||
|
||||
int updateState(@Param("row") TransportOrderRow row,
|
||||
@Param("expectedVersion") long expectedVersion);
|
||||
|
||||
int insertWorkflow(WorkflowRow row);
|
||||
|
||||
int insertTaskStep(TaskStepRow row);
|
||||
|
||||
int insertTaskDependency(TaskDependencyRow row);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.TransportOrder;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrderState;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrderType;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public final class TransportOrderRow {
|
||||
private String orderId;
|
||||
private String externalRequestId;
|
||||
private String orderType;
|
||||
private String loadId;
|
||||
private String sourceCode;
|
||||
private String destinationCode;
|
||||
private int priority;
|
||||
private String orderState;
|
||||
private String workflowId;
|
||||
private String resultCode;
|
||||
private String stateReason;
|
||||
private long stateVersion;
|
||||
private Instant createdTime;
|
||||
private Instant updatedTime;
|
||||
|
||||
public static TransportOrderRow fromDomain(TransportOrder order) {
|
||||
TransportOrderRow row = new TransportOrderRow();
|
||||
row.orderId = order.getOrderId();
|
||||
row.externalRequestId = order.getExternalRequestId();
|
||||
row.orderType = order.getType().name();
|
||||
row.loadId = order.getLoadId();
|
||||
row.sourceCode = order.getSourceCode();
|
||||
row.destinationCode = order.getDestinationCode();
|
||||
row.priority = order.getPriority();
|
||||
row.orderState = order.getState().name();
|
||||
row.workflowId = order.getWorkflowId();
|
||||
row.resultCode = order.getResultCode();
|
||||
row.stateReason = order.getStateReason();
|
||||
row.stateVersion = order.getStateVersion();
|
||||
row.createdTime = order.getCreatedAt();
|
||||
row.updatedTime = order.getUpdatedAt();
|
||||
return row;
|
||||
}
|
||||
|
||||
public TransportOrder toDomain() {
|
||||
return TransportOrder.restore(
|
||||
orderId,
|
||||
externalRequestId,
|
||||
TransportOrderType.valueOf(orderType),
|
||||
loadId,
|
||||
sourceCode,
|
||||
destinationCode,
|
||||
priority,
|
||||
TransportOrderState.valueOf(orderState),
|
||||
workflowId,
|
||||
resultCode,
|
||||
stateReason,
|
||||
stateVersion,
|
||||
createdTime,
|
||||
updatedTime);
|
||||
}
|
||||
|
||||
public String getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(String orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
public String getExternalRequestId() {
|
||||
return externalRequestId;
|
||||
}
|
||||
|
||||
public void setExternalRequestId(String externalRequestId) {
|
||||
this.externalRequestId = externalRequestId;
|
||||
}
|
||||
|
||||
public String getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
public void setOrderType(String orderType) {
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public String getLoadId() {
|
||||
return loadId;
|
||||
}
|
||||
|
||||
public void setLoadId(String loadId) {
|
||||
this.loadId = loadId;
|
||||
}
|
||||
|
||||
public String getSourceCode() {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
public void setSourceCode(String sourceCode) {
|
||||
this.sourceCode = sourceCode;
|
||||
}
|
||||
|
||||
public String getDestinationCode() {
|
||||
return destinationCode;
|
||||
}
|
||||
|
||||
public void setDestinationCode(String destinationCode) {
|
||||
this.destinationCode = destinationCode;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getOrderState() {
|
||||
return orderState;
|
||||
}
|
||||
|
||||
public void setOrderState(String orderState) {
|
||||
this.orderState = orderState;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public String getResultCode() {
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
public void setResultCode(String resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public String getStateReason() {
|
||||
return stateReason;
|
||||
}
|
||||
|
||||
public void setStateReason(String stateReason) {
|
||||
this.stateReason = stateReason;
|
||||
}
|
||||
|
||||
public long getStateVersion() {
|
||||
return stateVersion;
|
||||
}
|
||||
|
||||
public void setStateVersion(long stateVersion) {
|
||||
this.stateVersion = stateVersion;
|
||||
}
|
||||
|
||||
public Instant getCreatedTime() {
|
||||
return createdTime;
|
||||
}
|
||||
|
||||
public void setCreatedTime(Instant createdTime) {
|
||||
this.createdTime = createdTime;
|
||||
}
|
||||
|
||||
public Instant getUpdatedTime() {
|
||||
return updatedTime;
|
||||
}
|
||||
|
||||
public void setUpdatedTime(Instant updatedTime) {
|
||||
this.updatedTime = updatedTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.WorkflowInstance;
|
||||
|
||||
public final class WorkflowRow {
|
||||
private String workflowId;
|
||||
private String orderId;
|
||||
private String workflowState;
|
||||
private long stateVersion;
|
||||
|
||||
public static WorkflowRow fromDomain(WorkflowInstance workflow) {
|
||||
WorkflowRow row = new WorkflowRow();
|
||||
row.workflowId = workflow.getWorkflowId();
|
||||
row.orderId = workflow.getOrderId();
|
||||
row.workflowState = "ACTIVE";
|
||||
row.stateVersion = 0L;
|
||||
return row;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public String getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(String orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
public String getWorkflowState() {
|
||||
return workflowState;
|
||||
}
|
||||
|
||||
public void setWorkflowState(String workflowState) {
|
||||
this.workflowState = workflowState;
|
||||
}
|
||||
|
||||
public long getStateVersion() {
|
||||
return stateVersion;
|
||||
}
|
||||
|
||||
public void setStateVersion(long stateVersion) {
|
||||
this.stateVersion = stateVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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.wcs.infrastructure.persistence.task.TransportOrderMapper">
|
||||
<resultMap id="TransportOrderRowMap"
|
||||
type="com.ruoyi.wcs.infrastructure.persistence.task.TransportOrderRow">
|
||||
<id property="orderId" column="order_id"/>
|
||||
<result property="externalRequestId" column="external_request_id"/>
|
||||
<result property="orderType" column="order_type"/>
|
||||
<result property="loadId" column="load_id"/>
|
||||
<result property="sourceCode" column="source_code"/>
|
||||
<result property="destinationCode" column="destination_code"/>
|
||||
<result property="priority" column="priority"/>
|
||||
<result property="orderState" column="order_state"/>
|
||||
<result property="workflowId" column="workflow_id"/>
|
||||
<result property="resultCode" column="result_code"/>
|
||||
<result property="stateReason" column="state_reason"/>
|
||||
<result property="stateVersion" column="state_version"/>
|
||||
<result property="createdTime" column="created_time"/>
|
||||
<result property="updatedTime" column="updated_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="OrderColumns">
|
||||
order_id, external_request_id, order_type, load_id, source_code,
|
||||
destination_code, priority, order_state, workflow_id, result_code,
|
||||
state_reason, state_version, created_time, updated_time
|
||||
</sql>
|
||||
|
||||
<select id="selectByExternalRequestId" resultMap="TransportOrderRowMap">
|
||||
SELECT <include refid="OrderColumns"/>
|
||||
FROM dbo.wcs_transport_order
|
||||
WHERE external_request_id = #{externalRequestId}
|
||||
</select>
|
||||
|
||||
<select id="selectByOrderId" resultMap="TransportOrderRowMap">
|
||||
SELECT <include refid="OrderColumns"/>
|
||||
FROM dbo.wcs_transport_order
|
||||
WHERE order_id = #{orderId}
|
||||
</select>
|
||||
|
||||
<insert id="insertOrder">
|
||||
INSERT INTO dbo.wcs_transport_order (
|
||||
order_id, external_request_id, order_type, load_id, source_code,
|
||||
destination_code, priority, order_state, workflow_id, result_code,
|
||||
state_reason, state_version, created_time, updated_time
|
||||
) VALUES (
|
||||
#{orderId}, #{externalRequestId}, #{orderType}, #{loadId}, #{sourceCode},
|
||||
#{destinationCode}, #{priority}, #{orderState}, #{workflowId}, #{resultCode},
|
||||
#{stateReason}, #{stateVersion}, #{createdTime}, #{updatedTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateState">
|
||||
UPDATE dbo.wcs_transport_order
|
||||
SET order_state = #{row.orderState},
|
||||
workflow_id = #{row.workflowId},
|
||||
result_code = #{row.resultCode},
|
||||
state_reason = #{row.stateReason},
|
||||
state_version = #{row.stateVersion},
|
||||
updated_time = #{row.updatedTime}
|
||||
WHERE order_id = #{row.orderId}
|
||||
AND state_version = #{expectedVersion}
|
||||
</update>
|
||||
|
||||
<insert id="insertWorkflow">
|
||||
INSERT INTO dbo.wcs_workflow_instance (
|
||||
workflow_id, order_id, workflow_state, state_version
|
||||
) VALUES (
|
||||
#{workflowId}, #{orderId}, #{workflowState}, #{stateVersion}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertTaskStep">
|
||||
INSERT INTO dbo.wcs_task_step (
|
||||
step_id, workflow_id, step_key, step_type, required_flag,
|
||||
step_state, reason_code, vehicle_id, state_version
|
||||
) VALUES (
|
||||
#{stepId}, #{workflowId}, #{stepKey}, #{stepType}, #{required},
|
||||
#{stepState}, #{reasonCode}, #{vehicleId}, #{stateVersion}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertTaskDependency">
|
||||
INSERT INTO dbo.wcs_task_dependency (
|
||||
workflow_id, step_key, depends_on_step_key
|
||||
) VALUES (
|
||||
#{workflowId}, #{stepKey}, #{dependsOnStepKey}
|
||||
)
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.TaskStepType;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrder;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrderType;
|
||||
import com.ruoyi.wcs.domain.task.WorkflowDefinition;
|
||||
import com.ruoyi.wcs.domain.task.WorkflowInstance;
|
||||
import com.ruoyi.wcs.domain.task.WorkflowStepDefinition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class MyBatisTransportOrderRepositoryTest {
|
||||
|
||||
@Test
|
||||
void rejectsAStateUpdateWhenThePersistedVersionChanged() {
|
||||
RecordingMapper mapper = new RecordingMapper();
|
||||
TransportOrder order = order();
|
||||
order.plan("WF-1");
|
||||
mapper.selected = TransportOrderRow.fromDomain(order);
|
||||
order.start();
|
||||
mapper.updateCount = 0;
|
||||
|
||||
MyBatisTransportOrderRepository repository =
|
||||
new MyBatisTransportOrderRepository(mapper);
|
||||
|
||||
assertThrows(OptimisticLockingFailureException.class,
|
||||
() -> repository.save(order));
|
||||
assertEquals(1L, mapper.expectedVersion);
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertsANewOrderWorkflowAndItsInitialSteps() {
|
||||
RecordingMapper mapper = new RecordingMapper();
|
||||
TransportOrder order = order();
|
||||
WorkflowDefinition definition = new WorkflowDefinition(
|
||||
TransportOrderType.INBOUND,
|
||||
Arrays.asList(
|
||||
new WorkflowStepDefinition("MOVE", TaskStepType.MOVE,
|
||||
true, true, Collections.<String>emptyList()),
|
||||
new WorkflowStepDefinition("DROP", TaskStepType.UNLOAD,
|
||||
true, true, Collections.singletonList("MOVE"))));
|
||||
WorkflowInstance workflow = WorkflowInstance.create("WF-1", "ORDER-1", definition);
|
||||
order.plan(workflow.getWorkflowId());
|
||||
|
||||
new MyBatisTransportOrderRepository(mapper).save(order, workflow);
|
||||
|
||||
assertEquals(1, mapper.orderInserts);
|
||||
assertEquals(1, mapper.workflowInserts);
|
||||
assertEquals(2, mapper.stepInserts);
|
||||
assertEquals(1, mapper.dependencyInserts);
|
||||
}
|
||||
|
||||
private TransportOrder order() {
|
||||
return TransportOrder.accept(
|
||||
"ORDER-1", "REQ-1", TransportOrderType.INBOUND,
|
||||
"LOAD-1", "PORT-1", "SPACE-1", 50,
|
||||
Instant.parse("2026-08-04T00:00:00Z"));
|
||||
}
|
||||
|
||||
private static final class RecordingMapper implements TransportOrderMapper {
|
||||
private TransportOrderRow selected;
|
||||
private int updateCount = 1;
|
||||
private long expectedVersion = -1L;
|
||||
private int orderInserts;
|
||||
private int workflowInserts;
|
||||
private int stepInserts;
|
||||
private int dependencyInserts;
|
||||
|
||||
@Override
|
||||
public TransportOrderRow selectByExternalRequestId(String externalRequestId) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderRow selectByOrderId(String orderId) {
|
||||
return selected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertOrder(TransportOrderRow row) {
|
||||
orderInserts++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateState(TransportOrderRow row, long expectedVersion) {
|
||||
this.expectedVersion = expectedVersion;
|
||||
return updateCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertWorkflow(WorkflowRow row) {
|
||||
workflowInserts++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertTaskStep(TaskStepRow row) {
|
||||
stepInserts++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertTaskDependency(TaskDependencyRow row) {
|
||||
dependencyInserts++;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TransportOrderMapperXmlTest {
|
||||
|
||||
@Test
|
||||
void registersAllFoundationPersistenceStatements() {
|
||||
String resource = "mapper/wcs/task/TransportOrderMapper.xml";
|
||||
InputStream input = getClass().getClassLoader().getResourceAsStream(resource);
|
||||
assertNotNull(input, "missing " + resource);
|
||||
|
||||
Configuration configuration = new Configuration();
|
||||
XMLMapperBuilder builder = new XMLMapperBuilder(
|
||||
input, configuration, resource, configuration.getSqlFragments());
|
||||
builder.parse();
|
||||
|
||||
String namespace = TransportOrderMapper.class.getName() + ".";
|
||||
assertTrue(configuration.hasStatement(namespace + "selectByExternalRequestId"));
|
||||
assertTrue(configuration.hasStatement(namespace + "insertOrder"));
|
||||
assertTrue(configuration.hasStatement(namespace + "updateState"));
|
||||
assertTrue(configuration.hasStatement(namespace + "insertWorkflow"));
|
||||
assertTrue(configuration.hasStatement(namespace + "insertTaskStep"));
|
||||
assertTrue(configuration.hasStatement(namespace + "insertTaskDependency"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ruoyi.wcs.infrastructure.persistence.task;
|
||||
|
||||
import com.ruoyi.wcs.domain.task.TransportOrder;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrderType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TransportOrderRowMapperTest {
|
||||
|
||||
@Test
|
||||
void preservesOrderStateAndVersionAcrossPersistenceMapping() {
|
||||
TransportOrder source = TransportOrder.accept(
|
||||
"ORDER-1", "REQ-1", TransportOrderType.OUTBOUND,
|
||||
"LOAD-1", "SPACE-1", "PORT-1", 80,
|
||||
Instant.parse("2026-08-04T00:00:00Z"));
|
||||
source.plan("WF-1");
|
||||
source.start();
|
||||
source.clearUncommittedEvents();
|
||||
|
||||
TransportOrderRow row = TransportOrderRow.fromDomain(source);
|
||||
TransportOrder restored = row.toDomain();
|
||||
|
||||
assertEquals(source.getState(), restored.getState());
|
||||
assertEquals(2L, restored.getStateVersion());
|
||||
assertEquals("WF-1", restored.getWorkflowId());
|
||||
assertTrue(restored.getUncommittedEvents().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsNullableLoadAndResultFieldsWithoutInventingValues() {
|
||||
TransportOrder source = TransportOrder.accept(
|
||||
"ORDER-2", "REQ-2", TransportOrderType.INVENTORY,
|
||||
null, "SPACE-2", "SPACE-2", 10,
|
||||
Instant.parse("2026-08-04T00:00:00Z"));
|
||||
|
||||
TransportOrder restored = TransportOrderRow.fromDomain(source).toDomain();
|
||||
|
||||
assertEquals(null, restored.getLoadId());
|
||||
assertEquals(null, restored.getResultCode());
|
||||
}
|
||||
}
|
||||
85
sql/wcs/V001__wcs_task_foundation.sql
Normal file
85
sql/wcs/V001__wcs_task_foundation.sql
Normal file
@@ -0,0 +1,85 @@
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
IF OBJECT_ID('dbo.wcs_transport_order', 'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.wcs_transport_order (
|
||||
order_id varchar(64) NOT NULL PRIMARY KEY,
|
||||
external_request_id varchar(128) NOT NULL,
|
||||
order_type varchar(32) NOT NULL,
|
||||
load_id varchar(128) NULL,
|
||||
source_code varchar(128) NOT NULL,
|
||||
destination_code varchar(128) NOT NULL,
|
||||
priority int NOT NULL,
|
||||
order_state varchar(32) NOT NULL,
|
||||
workflow_id varchar(64) NULL,
|
||||
result_code varchar(64) NULL,
|
||||
state_reason nvarchar(512) NULL,
|
||||
state_version bigint NOT NULL,
|
||||
created_time datetime2(3) NOT NULL,
|
||||
updated_time datetime2(3) NOT NULL,
|
||||
CONSTRAINT uk_wcs_transport_order_external_request
|
||||
UNIQUE (external_request_id)
|
||||
);
|
||||
END;
|
||||
|
||||
IF OBJECT_ID('dbo.wcs_workflow_instance', 'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.wcs_workflow_instance (
|
||||
workflow_id varchar(64) NOT NULL PRIMARY KEY,
|
||||
order_id varchar(64) NOT NULL,
|
||||
workflow_state varchar(32) NOT NULL,
|
||||
state_version bigint NOT NULL,
|
||||
CONSTRAINT fk_wcs_workflow_order FOREIGN KEY (order_id)
|
||||
REFERENCES dbo.wcs_transport_order(order_id),
|
||||
CONSTRAINT uk_wcs_workflow_order UNIQUE (order_id)
|
||||
);
|
||||
END;
|
||||
|
||||
IF OBJECT_ID('dbo.wcs_task_step', 'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.wcs_task_step (
|
||||
step_id varchar(128) NOT NULL PRIMARY KEY,
|
||||
workflow_id varchar(64) NOT NULL,
|
||||
step_key varchar(64) NOT NULL,
|
||||
step_type varchar(32) NOT NULL,
|
||||
required_flag bit NOT NULL,
|
||||
step_state varchar(32) NOT NULL,
|
||||
reason_code varchar(128) NULL,
|
||||
vehicle_id varchar(64) NULL,
|
||||
state_version bigint NOT NULL,
|
||||
CONSTRAINT fk_wcs_task_step_workflow FOREIGN KEY (workflow_id)
|
||||
REFERENCES dbo.wcs_workflow_instance(workflow_id),
|
||||
CONSTRAINT uk_wcs_task_step_key UNIQUE (workflow_id, step_key)
|
||||
);
|
||||
END;
|
||||
|
||||
IF OBJECT_ID('dbo.wcs_task_dependency', 'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.wcs_task_dependency (
|
||||
workflow_id varchar(64) NOT NULL,
|
||||
step_key varchar(64) NOT NULL,
|
||||
depends_on_step_key varchar(64) NOT NULL,
|
||||
CONSTRAINT pk_wcs_task_dependency PRIMARY KEY (
|
||||
workflow_id, step_key, depends_on_step_key),
|
||||
CONSTRAINT fk_wcs_task_dependency_workflow FOREIGN KEY (workflow_id)
|
||||
REFERENCES dbo.wcs_workflow_instance(workflow_id)
|
||||
);
|
||||
END;
|
||||
|
||||
IF OBJECT_ID('dbo.wcs_domain_event', 'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.wcs_domain_event (
|
||||
event_id varchar(128) NOT NULL PRIMARY KEY,
|
||||
aggregate_id varchar(64) NOT NULL,
|
||||
event_sequence bigint NOT NULL,
|
||||
event_type varchar(64) NOT NULL,
|
||||
occurred_time datetime2(3) NOT NULL,
|
||||
payload_json nvarchar(max) NULL,
|
||||
published_flag bit NOT NULL CONSTRAINT df_wcs_domain_event_published DEFAULT (0),
|
||||
CONSTRAINT uk_wcs_domain_event_sequence
|
||||
UNIQUE (aggregate_id, event_sequence)
|
||||
);
|
||||
END;
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
19
sql/wcs/verify_V001__wcs_task_foundation.sql
Normal file
19
sql/wcs/verify_V001__wcs_task_foundation.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT name AS table_name
|
||||
FROM sys.tables
|
||||
WHERE name IN (
|
||||
'wcs_transport_order',
|
||||
'wcs_workflow_instance',
|
||||
'wcs_task_step',
|
||||
'wcs_task_dependency',
|
||||
'wcs_domain_event'
|
||||
)
|
||||
ORDER BY name;
|
||||
|
||||
SELECT i.name AS unique_constraint_name
|
||||
FROM sys.indexes i
|
||||
JOIN sys.tables t ON t.object_id = i.object_id
|
||||
WHERE t.name = 'wcs_transport_order'
|
||||
AND i.name = 'uk_wcs_transport_order_external_request'
|
||||
AND i.is_unique = 1;
|
||||
Reference in New Issue
Block a user