feat(wcs): persist transport orders and workflows
This commit is contained in:
@@ -53,6 +53,29 @@ public final class TransportOrder {
|
|||||||
sourceCode, destinationCode, priority, createdAt);
|
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) {
|
public void plan(String workflowId) {
|
||||||
requireState(TransportOrderState.ACCEPTED);
|
requireState(TransportOrderState.ACCEPTED);
|
||||||
this.workflowId = requireText(workflowId, "workflowId");
|
this.workflowId = requireText(workflowId, "workflowId");
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ public final class WorkflowInstance {
|
|||||||
return Collections.unmodifiableList(new ArrayList<TaskStep>(steps.values()));
|
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) {
|
private DependencyState dependencyState(String stepKey) {
|
||||||
boolean allSucceeded = true;
|
boolean allSucceeded = true;
|
||||||
for (String dependencyKey : mandatoryDependencies.get(stepKey)) {
|
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