feat(javaTest): model source-aligned task phases
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** 一条父任务不可变的移动阶段顺序,以及没有生成通道阶段的明确原因。 */
|
||||
public final class BusinessPhasePlan {
|
||||
private final List<BusinessTaskLeg> legs;
|
||||
private final List<String> skippedStageReasons;
|
||||
|
||||
public BusinessPhasePlan(List<BusinessTaskLeg> legs) {
|
||||
this(legs, Collections.<String>emptyList());
|
||||
}
|
||||
|
||||
public BusinessPhasePlan(List<BusinessTaskLeg> legs, List<String> skippedStageReasons) {
|
||||
if (legs == null || legs.isEmpty() || skippedStageReasons == null) {
|
||||
throw new IllegalArgumentException("business legs and skipped reasons are required");
|
||||
}
|
||||
List<BusinessTaskLeg> copy = new ArrayList<BusinessTaskLeg>(legs);
|
||||
for (int i = 0; i < copy.size(); i++) {
|
||||
if (copy.get(i) == null) {
|
||||
throw new IllegalArgumentException("business leg is required");
|
||||
}
|
||||
// 相邻阶段到同一站点不会产生真实移动,通常说明错误地把业务目标当成通道前端。
|
||||
if (i > 0 && copy.get(i - 1).getTargetSite()
|
||||
.equals(copy.get(i).getTargetSite())) {
|
||||
throw new IllegalArgumentException("ZERO_LENGTH_PHASE:"
|
||||
+ copy.get(i).getPhase());
|
||||
}
|
||||
}
|
||||
this.legs = Collections.unmodifiableList(copy);
|
||||
this.skippedStageReasons = Collections.unmodifiableList(
|
||||
new ArrayList<String>(skippedStageReasons));
|
||||
}
|
||||
|
||||
/** 返回中心调度必须逐段规划和执行的业务顺序。 */
|
||||
public List<BusinessTaskLeg> getLegs() { return legs; }
|
||||
|
||||
/** 返回指定阶段;阶段不适用于当前业务类型时明确报错,避免调用方误取其他路段。 */
|
||||
public BusinessTaskLeg leg(BusinessTaskPhase phase) {
|
||||
for (BusinessTaskLeg leg : legs) {
|
||||
if (leg.getPhase() == phase) return leg;
|
||||
}
|
||||
throw new IllegalArgumentException("phase is not planned: " + phase);
|
||||
}
|
||||
|
||||
/** 返回因货位不在通道组或端点本身就是前端而跳过的阶段原因。 */
|
||||
public List<String> getSkippedStageReasons() { return skippedStageReasons; }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import com.ft546.wcs.demo.model.DispatchTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** 根据父任务业务类型和权威通道前端,生成需要独立规划的移动阶段。 */
|
||||
public final class BusinessPhasePlanner {
|
||||
public BusinessPhasePlan plan(HistoricalSimulationTask task) {
|
||||
if (task == null || blank(task.getPhysicalSource()) || blank(task.getPhysicalTarget())) {
|
||||
throw new IllegalArgumentException("business task and endpoints are required");
|
||||
}
|
||||
if (task.getPhysicalSource().equals(task.getPhysicalTarget())) {
|
||||
throw new IllegalArgumentException("business source and target must differ");
|
||||
}
|
||||
|
||||
List<BusinessTaskLeg> legs = new ArrayList<BusinessTaskLeg>();
|
||||
List<String> skipped = new ArrayList<String>();
|
||||
|
||||
// 所有业务都先让中心调度选车,并把空车送到真实取货点;到达 ACK 后货物才转为 LOADED。
|
||||
legs.add(BusinessTaskLeg.emptyToPickup(task.getPhysicalSource()));
|
||||
|
||||
boolean sourceOperation = task.getTaskType() == DispatchTask.TaskType.OUTBOUND
|
||||
|| task.getTaskType() == DispatchTask.TaskType.RELOCATION;
|
||||
boolean targetOperation = task.getTaskType() == DispatchTask.TaskType.INBOUND
|
||||
|| task.getTaskType() == DispatchTask.TaskType.RELOCATION;
|
||||
boolean returnToTargetFront = targetOperation && task.requiresTargetFrontMovement();
|
||||
|
||||
// 出库和移库必须载货退出源货道;没有源前端时保留为普通点对点作业而不猜测站点。
|
||||
if (sourceOperation) {
|
||||
if (task.requiresSourceFrontMovement()) {
|
||||
legs.add(BusinessTaskLeg.loadedReturnToSourceFront(task.getSourceFrontSite()));
|
||||
} else if (task.getSourceFrontSite() == null) {
|
||||
skipped.add("SOURCE_NOT_IN_CARGO_GROUP");
|
||||
} else {
|
||||
skipped.add("SOURCE_IS_FRONT");
|
||||
}
|
||||
}
|
||||
|
||||
// 放货是唯一业务完成点;若之后还要空车回目标前端,此时只归档业务而不释放车辆。
|
||||
legs.add(BusinessTaskLeg.loadedToPlace(
|
||||
task.getPhysicalTarget(), !returnToTargetFront));
|
||||
|
||||
// 入库和移库在放货后自动回目标通道前端,完成后才允许车辆接收下一条任务。
|
||||
if (targetOperation) {
|
||||
if (returnToTargetFront) {
|
||||
legs.add(BusinessTaskLeg.emptyReturnToTargetFront(task.getTargetFrontSite()));
|
||||
} else if (task.getTargetFrontSite() == null) {
|
||||
skipped.add("TARGET_NOT_IN_CARGO_GROUP");
|
||||
} else {
|
||||
skipped.add("TARGET_IS_FRONT");
|
||||
}
|
||||
}
|
||||
|
||||
// A/B 两端都没有生产货位分组时,用一个稳定原因替代两个实现细节原因,便于 UI 审计。
|
||||
if (task.getSourceFrontSite() == null && task.getTargetFrontSite() == null) {
|
||||
skipped.clear();
|
||||
skipped.add("NO_CHANNEL_OPERATION");
|
||||
}
|
||||
return new BusinessPhasePlan(legs, skipped);
|
||||
}
|
||||
|
||||
private boolean blank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
/**
|
||||
* 业务父任务中一次需要真实规划和移动的路段。
|
||||
*
|
||||
* <p>路段结束语义随目标一起固定,模拟器到达目标并收到 PLC ACK 后,才能据此改变货物状态、
|
||||
* 归档业务或释放车辆,避免仅凭历史完成状态跳过真实移动。
|
||||
*/
|
||||
public final class BusinessTaskLeg {
|
||||
private final BusinessTaskPhase phase;
|
||||
private final String targetSite;
|
||||
private final CargoState cargoStateOnArrival;
|
||||
private final boolean completesBusiness;
|
||||
private final boolean releasesVehicle;
|
||||
|
||||
private BusinessTaskLeg(BusinessTaskPhase phase, String targetSite,
|
||||
CargoState cargoStateOnArrival,
|
||||
boolean completesBusiness, boolean releasesVehicle) {
|
||||
if (phase == null || cargoStateOnArrival == null || blank(targetSite)) {
|
||||
throw new IllegalArgumentException(
|
||||
"phase, target site and arrival cargo state are required");
|
||||
}
|
||||
this.phase = phase;
|
||||
this.targetSite = targetSite.trim();
|
||||
this.cargoStateOnArrival = cargoStateOnArrival;
|
||||
this.completesBusiness = completesBusiness;
|
||||
this.releasesVehicle = releasesVehicle;
|
||||
}
|
||||
|
||||
/** 空车到取货点并完成取货确认,因此下一段开始时货物已经绑定到该车。 */
|
||||
public static BusinessTaskLeg emptyToPickup(String targetSite) {
|
||||
return new BusinessTaskLeg(BusinessTaskPhase.EMPTY_TO_PICKUP,
|
||||
targetSite, CargoState.LOADED, false, false);
|
||||
}
|
||||
|
||||
/** 出库或移库取货后,载货车辆先退出源货道到通道前端。 */
|
||||
public static BusinessTaskLeg loadedReturnToSourceFront(String targetSite) {
|
||||
return new BusinessTaskLeg(BusinessTaskPhase.LOADED_RETURN_TO_SOURCE_FRONT,
|
||||
targetSite, CargoState.LOADED, false, false);
|
||||
}
|
||||
|
||||
/** 载货到业务目标并完成放货;是否立即释放取决于后面是否还有空车回前端。 */
|
||||
public static BusinessTaskLeg loadedToPlace(String targetSite,
|
||||
boolean releasesVehicle) {
|
||||
return new BusinessTaskLeg(BusinessTaskPhase.LOADED_TO_PLACE,
|
||||
targetSite, CargoState.DEPOSITED, true, releasesVehicle);
|
||||
}
|
||||
|
||||
/** 入库或移库放货后,车辆空车回目标货道前端,此时才解除父任务占车关系。 */
|
||||
public static BusinessTaskLeg emptyReturnToTargetFront(String targetSite) {
|
||||
return new BusinessTaskLeg(BusinessTaskPhase.EMPTY_RETURN_TO_TARGET_FRONT,
|
||||
targetSite, CargoState.DEPOSITED, false, true);
|
||||
}
|
||||
|
||||
public BusinessTaskPhase getPhase() { return phase; }
|
||||
public String getTargetSite() { return targetSite; }
|
||||
public CargoState getCargoStateOnArrival() { return cargoStateOnArrival; }
|
||||
public boolean completesBusiness() { return completesBusiness; }
|
||||
public boolean releasesVehicle() { return releasesVehicle; }
|
||||
|
||||
private static boolean blank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
/**
|
||||
* 一条业务父任务在中心调度中的阶段名称。
|
||||
*
|
||||
* <p>其中移动阶段会进入 {@link BusinessTaskLeg};其余名称用于后续 PLC、换层、归档和故障证据,
|
||||
* 使报告能区分“正在去取货”“货物已完成”和“车辆已释放”。
|
||||
*/
|
||||
public enum BusinessTaskPhase {
|
||||
WAITING_ASSIGNMENT,
|
||||
FLOOR_TRANSFER_TO_PICKUP,
|
||||
EMPTY_TO_PICKUP,
|
||||
PICKUP_CONFIRMED,
|
||||
LOADED_RETURN_TO_SOURCE_FRONT,
|
||||
LOADED_TO_PLACE,
|
||||
CARGO_DEPOSITED,
|
||||
EMPTY_RETURN_TO_TARGET_FRONT,
|
||||
BUSINESS_COMPLETED,
|
||||
VEHICLE_RELEASED,
|
||||
FAILED
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
/** 记录父任务货物是尚未取走、正在车上,还是已经放到业务目标。 */
|
||||
public enum CargoState {
|
||||
EMPTY,
|
||||
LOADED,
|
||||
DEPOSITED
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
/** 区分业务完成后是否还需要车辆回前端,以及该清理动作的最终结果。 */
|
||||
public enum VehicleReleaseStatus {
|
||||
NOT_REQUIRED,
|
||||
PENDING,
|
||||
COMPLETED,
|
||||
FAILED
|
||||
}
|
||||
Reference in New Issue
Block a user