feat(wcs): coordinate conveyor ports and charging
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package com.ruoyi.wcs.application.device;
|
||||
|
||||
import com.ruoyi.wcs.domain.equipment.AvailableChargers;
|
||||
import com.ruoyi.wcs.domain.equipment.ChargingPolicyDecision;
|
||||
import com.ruoyi.wcs.domain.equipment.ConveyorPortState;
|
||||
import com.ruoyi.wcs.domain.equipment.DeviceCapability;
|
||||
import com.ruoyi.wcs.domain.equipment.DeviceDescriptor;
|
||||
import com.ruoyi.wcs.domain.equipment.DeviceType;
|
||||
import com.ruoyi.wcs.domain.equipment.PendingWorkload;
|
||||
import com.ruoyi.wcs.domain.equipment.PortMode;
|
||||
import com.ruoyi.wcs.domain.equipment.VehicleSnapshot;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
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.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ConveyorAndChargingCoordinatorTest {
|
||||
|
||||
@Test
|
||||
void locksPortAgainstNewOutboundAssignmentDuringInboundSwitchPermission() {
|
||||
ConveyorCoordinator coordinator = new ConveyorCoordinator();
|
||||
coordinator.register(new ConveyorPortState("PORT-1", PortMode.OUTBOUND));
|
||||
|
||||
coordinator.grantModeSwitch("PORT-1", PortMode.INBOUND);
|
||||
|
||||
assertFalse(coordinator.canAssignOutbound("PORT-1"));
|
||||
assertTrue(coordinator.isSwitchLocked("PORT-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refusesModeSwitchWhileJobsRemainBoundToThePort() {
|
||||
ConveyorCoordinator coordinator = new ConveyorCoordinator();
|
||||
ConveyorPortState port = new ConveyorPortState("PORT-1", PortMode.OUTBOUND);
|
||||
port.bindJob("JOB-1");
|
||||
coordinator.register(port);
|
||||
|
||||
assertFalse(coordinator.requestModeSwitch("PORT-1", PortMode.INBOUND));
|
||||
assertTrue(coordinator.canAssignOutbound("PORT-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forcedChargeWinsOverOrdinaryReadyTask() {
|
||||
ChargingCoordinator charging = new ChargingCoordinator(20, 40);
|
||||
|
||||
ChargingPolicyDecision decision = charging.decide(vehicle(15,
|
||||
VehicleSnapshot.OccupationState.IDLE),
|
||||
new PendingWorkload(0, 3, Duration.ofMinutes(2)), chargers());
|
||||
|
||||
assertEquals(ChargingPolicyDecision.Type.FORCED_CHARGE,
|
||||
decision.getType());
|
||||
assertEquals("CHARGER-NEAR", decision.getChargerId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void batteryThresholdNeverInterruptsAnActivePhysicalTask() {
|
||||
ChargingCoordinator charging = new ChargingCoordinator(20, 40);
|
||||
|
||||
ChargingPolicyDecision decision = charging.decide(vehicle(10,
|
||||
VehicleSnapshot.OccupationState.BUSY),
|
||||
new PendingWorkload(0, 0, Duration.ZERO), chargers());
|
||||
|
||||
assertEquals(ChargingPolicyDecision.Type.NOT_ELIGIBLE,
|
||||
decision.getType());
|
||||
}
|
||||
|
||||
private static AvailableChargers chargers() {
|
||||
return new AvailableChargers(Arrays.asList(
|
||||
new AvailableChargers.Charger("CHARGER-FAR", 8),
|
||||
new AvailableChargers.Charger("CHARGER-NEAR", 2)));
|
||||
}
|
||||
|
||||
private static VehicleSnapshot vehicle(
|
||||
int battery, VehicleSnapshot.OccupationState occupation) {
|
||||
DeviceDescriptor descriptor = new DeviceDescriptor("V1",
|
||||
DeviceType.FOUR_WAY_SHUTTLE, "F1",
|
||||
Collections.singleton(DeviceCapability.CHARGING));
|
||||
return new VehicleSnapshot(descriptor,
|
||||
VehicleSnapshot.ConnectionState.ONLINE,
|
||||
VehicleSnapshot.OperatingMode.AUTO,
|
||||
VehicleSnapshot.HealthState.NORMAL, occupation,
|
||||
"A", null, battery, occupation == VehicleSnapshot.OccupationState.BUSY
|
||||
? "CMD-1" : null, "F1", Instant.EPOCH);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user