提交
This commit is contained in:
+6
@@ -451,6 +451,11 @@ public class InboundPdaWorkflowService {
|
|||||||
InboundPdaOperation replay = replay(customerId, "PARTIAL_COMPLETE", request.idempotencyKey(), request);
|
InboundPdaOperation replay = replay(customerId, "PARTIAL_COMPLETE", request.idempotencyKey(), request);
|
||||||
if (replay != null) return read(replay, PdaResponses.Task.class);
|
if (replay != null) return read(replay, PdaResponses.Task.class);
|
||||||
InboundPdaTask task = requireActiveTask(customerId, plan);
|
InboundPdaTask task = requireActiveTask(customerId, plan);
|
||||||
|
List<Long> unloadPhotoFileIds = unloadPhotoFileIds(task);
|
||||||
|
if (unloadPhotoFileIds.isEmpty() || task.getUnloadPhotoUploadedAt() == null
|
||||||
|
|| task.getEstimatedEmptyReturnAt() == null) {
|
||||||
|
throw conflict("Upload unload photos before partially completing the PDA inbound task");
|
||||||
|
}
|
||||||
List<InboundPlanLine> lines = repository.lines(plan.getId());
|
List<InboundPlanLine> lines = repository.lines(plan.getId());
|
||||||
List<InboundPlanPalletAllocation> allocations = repository.allocations(plan.getId());
|
List<InboundPlanPalletAllocation> allocations = repository.allocations(plan.getId());
|
||||||
int expected = lines.stream().mapToInt(line -> value(line.getRequiredPackageCount())).sum();
|
int expected = lines.stream().mapToInt(line -> value(line.getRequiredPackageCount())).sum();
|
||||||
@@ -468,6 +473,7 @@ public class InboundPdaWorkflowService {
|
|||||||
task.setStatus("PARTIAL_INBOUND"); task.setCompletedAt(now); task.setUpdatedAt(now);
|
task.setStatus("PARTIAL_INBOUND"); task.setCompletedAt(now); task.setUpdatedAt(now);
|
||||||
task.setVersion(value(task.getVersion()) + 1); repository.update(task);
|
task.setVersion(value(task.getVersion()) + 1); repository.update(task);
|
||||||
plan.setStatus("PARTIAL_INBOUND"); plan.setUpdatedAt(now); repository.update(plan);
|
plan.setStatus("PARTIAL_INBOUND"); plan.setUpdatedAt(now); repository.update(plan);
|
||||||
|
unloadCompletedOutbox.publish(plan, task, unloadPhotoFileIds);
|
||||||
PdaResponses.Task result = taskResponse(task, plan, planNo);
|
PdaResponses.Task result = taskResponse(task, plan, planNo);
|
||||||
saveOperation(customerId, task.getId(), null, "PARTIAL_COMPLETE", request.idempotencyKey(), request, result);
|
saveOperation(customerId, task.getId(), null, "PARTIAL_COMPLETE", request.idempotencyKey(), request, result);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+13
-10
@@ -72,7 +72,7 @@ public class InboundStockReceiptService {
|
|||||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
if (receivedAllocationIds.equals(expectedAllocationIds)) {
|
if (receivedAllocationIds.equals(expectedAllocationIds)) {
|
||||||
if (!allowPartial) activateDropship(customerId, task.getId(), orderById, existing);
|
if (!allowPartial) activateDropship(customerId, task.getId(), orderById, existing);
|
||||||
if (!allowPartial) completeOrders(customerId, plan, orderById, receivedAt);
|
completeOrders(customerId, plan, orderById, receivedAt, allowPartial);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw conflict("PDA stock-in receipts are incomplete; repair the task receipts before retrying");
|
throw conflict("PDA stock-in receipts are incomplete; repair the task receipts before retrying");
|
||||||
@@ -109,29 +109,32 @@ public class InboundStockReceiptService {
|
|||||||
throw conflict("Inbound SKU package snapshots were not fully received");
|
throw conflict("Inbound SKU package snapshots were not fully received");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!allowPartial) {
|
if (!allowPartial) activateDropship(customerId, task.getId(), orderById, created);
|
||||||
activateDropship(customerId, task.getId(), orderById, created);
|
completeOrders(customerId, plan, orderById, receivedAt, allowPartial);
|
||||||
completeOrders(customerId, plan, orderById, receivedAt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void completeOrders(String customerId, InboundPlan plan, Map<Long, LastlegOrder> orderById,
|
private void completeOrders(String customerId, InboundPlan plan, Map<Long, LastlegOrder> orderById,
|
||||||
LocalDateTime completedAt) {
|
LocalDateTime completedAt, boolean partial) {
|
||||||
|
String subOrderStatus = partial ? "INBOUND_PLAN:PARTIAL_INBOUND" : "INBOUND_PLAN:COMPLETED";
|
||||||
for (LastlegOrder snapshot : orderById.values()) {
|
for (LastlegOrder snapshot : orderById.values()) {
|
||||||
LastlegOrder order = orders.lockById(customerId, snapshot.getId());
|
LastlegOrder order = orders.lockById(customerId, snapshot.getId());
|
||||||
if (order == null) throw conflict("Inbound tail-leg order disappeared while completing stock-in");
|
if (order == null) throw conflict("Inbound tail-leg order disappeared while completing stock-in");
|
||||||
if ("INBOUND_COMPLETED".equals(order.getCurrentStatus())) continue;
|
if ("INBOUND_COMPLETED".equals(order.getCurrentStatus())
|
||||||
|
&& subOrderStatus.equals(order.getSubOrderStatus())) continue;
|
||||||
String before = order.getCurrentStatus();
|
String before = order.getCurrentStatus();
|
||||||
order.setCurrentStatus("INBOUND_COMPLETED");
|
order.setCurrentStatus("INBOUND_COMPLETED");
|
||||||
order.setSubOrderStatus("INBOUND_PLAN:COMPLETED");
|
order.setSubOrderStatus(subOrderStatus);
|
||||||
order.setUpdatedAt(completedAt);
|
order.setUpdatedAt(completedAt);
|
||||||
orders.updateById(order);
|
orders.updateById(order);
|
||||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory();
|
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory();
|
||||||
history.setOrderId(order.getId());
|
history.setOrderId(order.getId());
|
||||||
history.setFromStatus(before);
|
history.setFromStatus(before);
|
||||||
history.setToStatus("INBOUND_COMPLETED");
|
history.setToStatus("INBOUND_COMPLETED");
|
||||||
history.setEventId("PDA_INBOUND_COMPLETE:" + plan.getPlanNo());
|
history.setEventId((partial ? "PDA_PARTIAL_INBOUND_COMPLETE:" : "PDA_INBOUND_COMPLETE:")
|
||||||
history.setRemark("PDA inbound completed and inventory stocked in");
|
+ plan.getPlanNo());
|
||||||
|
history.setRemark(partial
|
||||||
|
? "PDA inbound partially completed and actual inventory stocked in"
|
||||||
|
: "PDA inbound completed and inventory stocked in");
|
||||||
history.setCreatedAt(completedAt);
|
history.setCreatedAt(completedAt);
|
||||||
orderHistories.insert(history);
|
orderHistories.insert(history);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -472,6 +472,24 @@ class InboundPdaWorkflowServiceTest {
|
|||||||
assertEquals("PARTIAL_INBOUND", plan.getStatus());
|
assertEquals("PARTIAL_INBOUND", plan.getStatus());
|
||||||
verify(stockReceipts).stockIn(eq("100"), eq(plan), eq(task), eq(List.of(targetLine)),
|
verify(stockReceipts).stockIn(eq("100"), eq(plan), eq(task), eq(List.of(targetLine)),
|
||||||
eq(List.of(target)), eq(List.of(targetPallet)), any(), eq(true));
|
eq(List.of(target)), eq(List.of(targetPallet)), any(), eq(true));
|
||||||
|
verify(unloadCompletedOutbox).publish(eq(plan), eq(task), eq(List.of(101L)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void partialCompleteRequiresUnloadPhotosBeforeStockIn() {
|
||||||
|
target.setPickedPackageCount(2);
|
||||||
|
task.setUnloadPhotoFileIds(null);
|
||||||
|
task.setUnloadPhotoUploadedAt(null);
|
||||||
|
task.setEstimatedEmptyReturnAt(null);
|
||||||
|
activeTask();
|
||||||
|
|
||||||
|
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||||
|
() -> service.partialComplete("100", "LIP1",
|
||||||
|
new PdaRequests.ForceComplete("partial-no-photo", "7", "worker")));
|
||||||
|
|
||||||
|
assertEquals(409, exception.getStatusCode().value());
|
||||||
|
verify(stockReceipts, never()).stockIn(any(), any(), any(), any(), any(), any(), any(), eq(true));
|
||||||
|
verify(unloadCompletedOutbox, never()).publish(any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+18
@@ -179,6 +179,24 @@ class InboundStockReceiptServiceTest {
|
|||||||
assertEquals(10L, activationReceipts.getValue().get(0).getInventoryPositionId());
|
assertEquals(10L, activationReceipts.getValue().get(0).getInventoryPositionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void partialStockInSynchronizesOrderWithoutActivatingDropship() {
|
||||||
|
Fixture fixture = fixture(5, 2, 2, 10);
|
||||||
|
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||||
|
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||||
|
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||||
|
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 2, 0, 0, 0, false, false));
|
||||||
|
|
||||||
|
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||||
|
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), true);
|
||||||
|
|
||||||
|
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
|
||||||
|
assertEquals("INBOUND_PLAN:PARTIAL_INBOUND", fixture.order.getSubOrderStatus());
|
||||||
|
verify(orders).updateById(fixture.order);
|
||||||
|
verify(orderHistories).insert(any(LastlegOrderStatusHistory.class));
|
||||||
|
verify(dropshipOrders, never()).activateAfterStockIn(any(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
private Fixture fixture(int required, int planned, int picked, int quantity) {
|
private Fixture fixture(int required, int planned, int picked, int quantity) {
|
||||||
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1");
|
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1");
|
||||||
InboundPdaTask task = new InboundPdaTask(); task.setId(2L); task.setOperatorId("operator");
|
InboundPdaTask task = new InboundPdaTask(); task.setId(2L); task.setOperatorId("operator");
|
||||||
|
|||||||
Reference in New Issue
Block a user