diff --git a/javaTest/pom.xml b/javaTest/pom.xml new file mode 100644 index 0000000..9385f63 --- /dev/null +++ b/javaTest/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + com.ft546.wcs + wcs-dispatch-traffic-demo + 1.0.0-SNAPSHOT + FT546 WCS Dispatch and Traffic Demo + + + UTF-8 + 1.8 + 1.8 + 5.10.2 + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + com.ft546.wcs.demo.DemoApplication + + + + + diff --git a/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchQueueEntry.java b/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchQueueEntry.java new file mode 100644 index 0000000..917fb35 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchQueueEntry.java @@ -0,0 +1,125 @@ +package com.ft546.wcs.demo.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class DispatchQueueEntry { + public enum QueueType { + BUSINESS, + AVOIDANCE + } + + private final String queueId; + private final String taskId; + private final String vehicleId; + private final QueueType queueType; + private final String startSite; + private final String endSite; + private final int priority; + private int issued; + private int executionStatus; + private List path; + + private DispatchQueueEntry( + String queueId, + String taskId, + String vehicleId, + QueueType queueType, + String startSite, + String endSite, + int priority + ) { + this.queueId = queueId; + this.taskId = taskId; + this.vehicleId = vehicleId; + this.queueType = queueType; + this.startSite = startSite; + this.endSite = endSite; + this.priority = priority; + this.issued = 0; + this.executionStatus = 1; + this.path = Collections.emptyList(); + } + + public static DispatchQueueEntry business(DispatchTask task, String vehicleId) { + if (task == null) { + throw new IllegalArgumentException("task must not be null"); + } + if (vehicleId == null || vehicleId.trim().isEmpty()) { + throw new IllegalArgumentException("vehicle id must not be blank"); + } + return new DispatchQueueEntry( + task.getTaskId(), + task.getTaskId(), + vehicleId, + QueueType.BUSINESS, + task.getSourceSite(), + task.getTargetSite(), + task.getPriority() + ); + } + + public void markIssued(List issuedPath) { + if (issued != 0) { + throw new IllegalStateException("queue has already been issued"); + } + if (issuedPath == null || issuedPath.isEmpty()) { + throw new IllegalArgumentException("issued path must not be empty"); + } + this.path = Collections.unmodifiableList(new ArrayList(issuedPath)); + this.issued = 1; + this.executionStatus = 3; + } + + public void markCompleted() { + if (issued == 0) { + throw new IllegalStateException("unissued queue cannot complete"); + } + this.executionStatus = 4; + } + + public String getQueueId() { + return queueId; + } + + public String getTaskId() { + return taskId; + } + + public String getVehicleId() { + return vehicleId; + } + + public QueueType getQueueType() { + return queueType; + } + + public String getStartSite() { + return startSite; + } + + public String getEndSite() { + return endSite; + } + + public int getPriority() { + return priority; + } + + public int getIssued() { + return issued; + } + + public int getExecutionStatus() { + return executionStatus; + } + + public boolean isIssued() { + return issued == 1; + } + + public List getPath() { + return path; + } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchTask.java b/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchTask.java new file mode 100644 index 0000000..961a874 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/demo/model/DispatchTask.java @@ -0,0 +1,69 @@ +package com.ft546.wcs.demo.model; + +public final class DispatchTask { + public enum TaskType { + INBOUND, + OUTBOUND, + RELOCATION + } + + private final String taskId; + private final TaskType type; + private final String sourceSite; + private final String targetSite; + private final int floor; + private final int priority; + + public DispatchTask( + String taskId, + TaskType type, + String sourceSite, + String targetSite, + int floor, + int priority + ) { + this.taskId = requireText(taskId, "task id"); + if (type == null) { + throw new IllegalArgumentException("task type must not be null"); + } + this.type = type; + this.sourceSite = requireText(sourceSite, "source site"); + this.targetSite = requireText(targetSite, "target site"); + if (floor <= 0) { + throw new IllegalArgumentException("floor must be positive"); + } + this.floor = floor; + this.priority = priority; + } + + public String getTaskId() { + return taskId; + } + + public TaskType getType() { + return type; + } + + public String getSourceSite() { + return sourceSite; + } + + public String getTargetSite() { + return targetSite; + } + + public int getFloor() { + return floor; + } + + public int getPriority() { + return priority; + } + + private static String requireText(String value, String name) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(name + " must not be blank"); + } + return value; + } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/demo/model/Site.java b/javaTest/src/main/java/com/ft546/wcs/demo/model/Site.java new file mode 100644 index 0000000..ac6e060 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/demo/model/Site.java @@ -0,0 +1,66 @@ +package com.ft546.wcs.demo.model; + +import java.util.Objects; + +public final class Site { + private final String id; + private final int floor; + private final int x; + private final int y; + private final String channel; + + public Site(String id, int floor, int x, int y, String channel) { + this.id = requireText(id, "site id"); + if (floor <= 0) { + throw new IllegalArgumentException("floor must be positive"); + } + this.floor = floor; + this.x = x; + this.y = y; + this.channel = requireText(channel, "channel"); + } + + public String getId() { + return id; + } + + public int getFloor() { + return floor; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public String getChannel() { + return channel; + } + + private static String requireText(String value, String name) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(name + " must not be blank"); + } + return value; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Site)) { + return false; + } + Site site = (Site) other; + return id.equals(site.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/demo/model/VehicleSnapshot.java b/javaTest/src/main/java/com/ft546/wcs/demo/model/VehicleSnapshot.java new file mode 100644 index 0000000..af7960c --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/demo/model/VehicleSnapshot.java @@ -0,0 +1,98 @@ +package com.ft546.wcs.demo.model; + +public final class VehicleSnapshot { + private final String vehicleId; + private final int floor; + private final String currentSite; + private final int workMode; + private final int status; + private final int battery; + private final boolean connected; + private final boolean allowSend; + private final boolean cargo; + + public VehicleSnapshot( + String vehicleId, + int floor, + String currentSite, + int workMode, + int status, + int battery, + boolean connected, + boolean allowSend, + boolean cargo + ) { + this.vehicleId = requireText(vehicleId, "vehicle id"); + if (floor <= 0) { + throw new IllegalArgumentException("floor must be positive"); + } + this.floor = floor; + this.currentSite = requireText(currentSite, "current site"); + this.workMode = workMode; + this.status = status; + if (battery < 0 || battery > 100) { + throw new IllegalArgumentException("battery must be between 0 and 100"); + } + this.battery = battery; + this.connected = connected; + this.allowSend = allowSend; + this.cargo = cargo; + } + + public String getVehicleId() { + return vehicleId; + } + + public int getFloor() { + return floor; + } + + public String getCurrentSite() { + return currentSite; + } + + public int getWorkMode() { + return workMode; + } + + public int getStatus() { + return status; + } + + public int getBattery() { + return battery; + } + + public boolean isConnected() { + return connected; + } + + public boolean isAllowSend() { + return allowSend; + } + + public boolean isCargo() { + return cargo; + } + + public VehicleSnapshot at(String siteId, int newFloor) { + return new VehicleSnapshot( + vehicleId, + newFloor, + siteId, + workMode, + status, + battery, + connected, + allowSend, + cargo + ); + } + + private static String requireText(String value, String name) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(name + " must not be blank"); + } + return value; + } +} diff --git a/javaTest/src/test/java/com/ft546/wcs/demo/QueueLifecycleTest.java b/javaTest/src/test/java/com/ft546/wcs/demo/QueueLifecycleTest.java new file mode 100644 index 0000000..53285f0 --- /dev/null +++ b/javaTest/src/test/java/com/ft546/wcs/demo/QueueLifecycleTest.java @@ -0,0 +1,80 @@ +package com.ft546.wcs.demo; + +import com.ft546.wcs.demo.model.DispatchQueueEntry; +import com.ft546.wcs.demo.model.DispatchTask; +import com.ft546.wcs.demo.model.Site; +import com.ft546.wcs.demo.model.VehicleSnapshot; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class QueueLifecycleTest { + + @Test + void queueUsesLegacyLifecycleCodes() { + DispatchTask task = new DispatchTask( + "T-1", + DispatchTask.TaskType.OUTBOUND, + "A", + "B", + 1, + 2 + ); + DispatchQueueEntry queue = DispatchQueueEntry.business(task, "1"); + + assertEquals(0, queue.getIssued()); + assertEquals(1, queue.getExecutionStatus()); + + queue.markIssued(Arrays.asList("A", "B")); + + assertEquals(1, queue.getIssued()); + assertEquals(3, queue.getExecutionStatus()); + + queue.markCompleted(); + + assertEquals(4, queue.getExecutionStatus()); + } + + @Test + void queueCopiesPathAndRejectsCompletionBeforeIssue() { + DispatchTask task = new DispatchTask( + "T-2", + DispatchTask.TaskType.INBOUND, + "A", + "B", + 1, + 2 + ); + DispatchQueueEntry queue = DispatchQueueEntry.business(task, "1"); + List path = new java.util.ArrayList(Arrays.asList("A", "B")); + + assertThrows(IllegalStateException.class, queue::markCompleted); + + queue.markIssued(path); + path.add("C"); + + assertEquals(Arrays.asList("A", "B"), queue.getPath()); + assertThrows(UnsupportedOperationException.class, () -> queue.getPath().add("D")); + } + + @Test + void domainInputsRejectInvalidValues() { + assertThrows(IllegalArgumentException.class, + () -> new Site("", 1, 0, 0, "C1")); + assertThrows(IllegalArgumentException.class, + () -> new VehicleSnapshot("1", 1, "A", 5, 1, 101, true, true, false)); + assertThrows(IllegalArgumentException.class, + () -> new DispatchTask("", DispatchTask.TaskType.OUTBOUND, "A", "B", 1, 2)); + + VehicleSnapshot snapshot = new VehicleSnapshot( + "1", 1, "A", 5, 1, 80, true, true, false + ); + assertTrue(snapshot.isConnected()); + assertEquals("1", snapshot.getVehicleId()); + } +}