feat(phase4): automate overseas PDA inbound lifecycle
This commit is contained in:
+11
@@ -6,6 +6,8 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InboundPlanCartonMapper extends BaseMapper<InboundPlanCarton> {
|
||||
@Select("""
|
||||
@@ -31,4 +33,13 @@ public interface InboundPlanCartonMapper extends BaseMapper<InboundPlanCarton> {
|
||||
WHERE l.plan_id=#{planId})
|
||||
""")
|
||||
boolean hasLabelPool(@Param("planId") Long planId);
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT c.* FROM ll_inbound_plan_carton c
|
||||
JOIN ll_order_detail d ON d.source_inbound_detail_id=c.source_inbound_detail_id
|
||||
JOIN ll_inbound_plan_line l ON l.order_id=d.order_id
|
||||
WHERE l.plan_id=#{planId} AND c.customer_id=l.customer_id
|
||||
ORDER BY c.id
|
||||
""")
|
||||
List<InboundPlanCarton> listExpected(@Param("planId") Long planId);
|
||||
}
|
||||
|
||||
+6
-1
@@ -11,7 +11,8 @@ public final class PdaResponses {
|
||||
LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt,
|
||||
List<Long> unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt,
|
||||
LocalDateTime estimatedEmptyReturnAt,
|
||||
List<Pallet> pallets, List<Allocation> allocations) {}
|
||||
List<Pallet> pallets, List<Allocation> allocations,
|
||||
List<ExpectedCarton> expectedCartons) {}
|
||||
|
||||
public record Pallet(long planPalletId, String palletNo, String destinationWarehouseId,
|
||||
String destinationWarehouseCode, String destinationWarehouseName,
|
||||
@@ -24,6 +25,10 @@ public final class PdaResponses {
|
||||
int countedPackageCount, int pickedPackageCount,
|
||||
String status, int version) {}
|
||||
|
||||
public record ExpectedCarton(String cartonMark, String skuCode,
|
||||
String destinationWarehouseId, String destinationWarehouseCode,
|
||||
long planPalletId, String status) {}
|
||||
|
||||
public record UnloadPhotoFile(long fileId, String fileName, String contentType, long sizeBytes) {}
|
||||
|
||||
public record Operation(String result, long allocationId, int requiredPackageCount,
|
||||
|
||||
+1
@@ -63,6 +63,7 @@ public class PdaWorkflowRepository {
|
||||
public List<InboundPlanCarton> cartons(Long planId) {
|
||||
return cartons.selectList(new LambdaQueryWrapper<InboundPlanCarton>().eq(InboundPlanCarton::getPlanId, planId));
|
||||
}
|
||||
public List<InboundPlanCarton> expectedCartons(Long planId) { return cartons.listExpected(planId); }
|
||||
public InboundPlanLine lineForCarton(Long planId, InboundPlanCarton carton) {
|
||||
return cartons.findPlanLine(planId, carton.getSourceInboundDetailId());
|
||||
}
|
||||
|
||||
+15
-1
@@ -650,9 +650,23 @@ public class InboundPdaWorkflowService {
|
||||
InboundPdaWorkflowService.value(value.getPickedPackageCount()), value.getStatus(),
|
||||
InboundPdaWorkflowService.value(value.getVersion()));
|
||||
}).toList();
|
||||
List<PdaResponses.ExpectedCarton> expectedCartons = repository.expectedCartons(task.getPlanId()).stream()
|
||||
.map(value -> new PdaResponses.ExpectedCarton(value.getCartonMark(), value.getSkuCode(),
|
||||
value.getDestinationWarehouseId(), value.getDestinationWarehouseCode(),
|
||||
recommendedPallet(value, planPallets), value.getStatus()))
|
||||
.toList();
|
||||
return new PdaResponses.Task(task.getId(), task.getTaskNo(), planNo, task.getStatus(),
|
||||
task.getPushedAt(), task.getStartedAt(), task.getCompletedAt(), unloadPhotoFileIds(task),
|
||||
task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt(), pallets, allocations);
|
||||
task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt(), pallets, allocations,
|
||||
expectedCartons);
|
||||
}
|
||||
|
||||
private long recommendedPallet(InboundPlanCarton carton, List<InboundPlanPallet> pallets) {
|
||||
if (carton.getPlanPalletId() != null) return carton.getPlanPalletId();
|
||||
return pallets.stream().filter(value -> Objects.equals(value.getDestinationWarehouseId(), carton.getDestinationWarehouseId())
|
||||
|| (carton.getDestinationWarehouseId() == null
|
||||
&& Objects.equals(value.getDestinationWarehouseCode(), carton.getDestinationWarehouseCode())))
|
||||
.map(InboundPlanPallet::getId).findFirst().orElse(0L);
|
||||
}
|
||||
|
||||
private List<Long> unloadPhotoFileIds(InboundPdaTask task) {
|
||||
|
||||
+2
-1
@@ -224,7 +224,8 @@ public class InboundPlanService {
|
||||
|
||||
private void validateOrders(List<SourceOrderSnapshot> orders) {
|
||||
for (SourceOrderSnapshot order : orders) {
|
||||
if (!"PENDING_ASSIGNMENT".equals(order.currentStatus())) {
|
||||
if (!("PENDING_ASSIGNMENT".equals(order.currentStatus())
|
||||
|| "DESTINATION_ASSIGNED".equals(order.currentStatus()))) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT,
|
||||
"Inbound code " + order.inboundCode() + " is not ready for an inbound plan");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.baiyue.lastleg.dto.LastlegOrderSummary;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
@@ -35,6 +37,7 @@ public class LastlegOrderService {
|
||||
@Resource private LastlegOrderMapper orderMapper;
|
||||
@Resource private LastlegOrderDetailMapper detailMapper;
|
||||
@Resource private LastlegOrderStatusHistoryMapper historyMapper;
|
||||
@Resource private InboundPlanCartonMapper cartonMapper;
|
||||
|
||||
public PageResult<LastlegOrderSummary> page(String customerId, LastlegOrderRequests.Query query) {
|
||||
Page<LastlegOrder> page = orderMapper.selectPage(
|
||||
@@ -88,8 +91,17 @@ public class LastlegOrderService {
|
||||
detailUpdate.setDestinationWarehouseCode(request.warehouseCode());
|
||||
detailUpdate.setDestinationWarehouseName(request.warehouseName());
|
||||
detailMapper.update(detailUpdate, new LambdaQueryWrapper<LastlegOrderDetail>()
|
||||
.eq(LastlegOrderDetail::getCustomerId, order.getCustomerId())
|
||||
.eq(LastlegOrderDetail::getOrderId, order.getId()));
|
||||
|
||||
InboundPlanCarton cartonUpdate = new InboundPlanCarton();
|
||||
cartonUpdate.setDestinationWarehouseId(request.warehouseId());
|
||||
cartonUpdate.setDestinationWarehouseCode(request.warehouseCode());
|
||||
cartonUpdate.setDestinationWarehouseName(request.warehouseName());
|
||||
cartonMapper.update(cartonUpdate, new LambdaQueryWrapper<InboundPlanCarton>()
|
||||
.eq(InboundPlanCarton::getCustomerId, order.getCustomerId())
|
||||
.eq(InboundPlanCarton::getOrderId, order.getId()));
|
||||
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory();
|
||||
history.setCustomerId(order.getCustomerId());
|
||||
history.setOrderId(order.getId());
|
||||
|
||||
+6
@@ -101,6 +101,9 @@ class InboundPdaWorkflowServiceTest {
|
||||
when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet));
|
||||
when(repository.allocations(plan.getId())).thenReturn(List.of(target));
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
InboundPlanCarton expected = carton("BOX-INSTRUCTION", "EXPECTED", targetLine.getId(), "WH-A");
|
||||
expected.setSkuCode("SKU-A");
|
||||
when(repository.expectedCartons(plan.getId())).thenReturn(List.of(expected));
|
||||
|
||||
var result = service.task("100", "LIP1");
|
||||
|
||||
@@ -108,6 +111,9 @@ class InboundPdaWorkflowServiceTest {
|
||||
assertEquals(target.getId(), result.allocations().get(0).allocationId());
|
||||
assertEquals("RK-A", result.allocations().get(0).inboundCode());
|
||||
assertEquals(3, result.allocations().get(0).plannedPackageCount());
|
||||
assertEquals("BOX-INSTRUCTION", result.expectedCartons().get(0).cartonMark());
|
||||
assertEquals("SKU-A", result.expectedCartons().get(0).skuCode());
|
||||
assertEquals(targetPallet.getId(), result.expectedCartons().get(0).planPalletId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+18
@@ -95,6 +95,24 @@ class InboundPlanServiceTest {
|
||||
assertEquals("CONT-1", result.containerNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsAConsistentDestinationAssignedOrder() {
|
||||
SourceOrderSnapshot assigned = new SourceOrderSnapshot(2L, "100", "CONT-1", "RK-2",
|
||||
"DESTINATION_ASSIGNED", null, new BigDecimal("1.0000"), 10,
|
||||
"2090000000000000001", "LAX-02", "Los Angeles 02", 1, 0);
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(assigned));
|
||||
when(orderMapper.markPlanned(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt(), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.convert("100", 2L);
|
||||
|
||||
assertEquals(2L, result.sourceOrderId());
|
||||
ArgumentCaptor<InboundPlanLine> line = ArgumentCaptor.forClass(InboundPlanLine.class);
|
||||
verify(lineMapper).insert(line.capture());
|
||||
assertEquals("2090000000000000001", line.getValue().getDestinationWarehouseId());
|
||||
verify(planMapper).insert(any(InboundPlan.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedConversionReturnsExistingPlanWithoutWriting() {
|
||||
InboundPlan existing = new InboundPlan();
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.baiyue.lastleg.dto.LastlegOrderRequests;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
@@ -31,6 +33,7 @@ class LastlegOrderServiceTest {
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderDetailMapper details;
|
||||
@Mock LastlegOrderStatusHistoryMapper histories;
|
||||
@Mock InboundPlanCartonMapper cartons;
|
||||
LastlegOrderService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -39,6 +42,7 @@ class LastlegOrderServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderMapper", orders);
|
||||
ReflectionTestUtils.setField(service, "detailMapper", details);
|
||||
ReflectionTestUtils.setField(service, "historyMapper", histories);
|
||||
ReflectionTestUtils.setField(service, "cartonMapper", cartons);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,6 +80,10 @@ class LastlegOrderServiceTest {
|
||||
ArgumentCaptor<LastlegOrderDetail> cargo = ArgumentCaptor.forClass(LastlegOrderDetail.class);
|
||||
verify(details).update(cargo.capture(), any());
|
||||
assertEquals("LAX-02", cargo.getValue().getDestinationWarehouseCode());
|
||||
ArgumentCaptor<InboundPlanCarton> carton = ArgumentCaptor.forClass(InboundPlanCarton.class);
|
||||
verify(cartons).update(carton.capture(), any());
|
||||
assertEquals("wh-2", carton.getValue().getDestinationWarehouseId());
|
||||
assertEquals("LAX-02", carton.getValue().getDestinationWarehouseCode());
|
||||
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
|
||||
verify(histories).insert(history.capture());
|
||||
assertEquals("100", history.getValue().getCustomerId());
|
||||
@@ -93,6 +101,7 @@ class LastlegOrderServiceTest {
|
||||
assertThrows(ResponseStatusException.class, () -> service.transferDestination("100", request));
|
||||
verify(orders, never()).updateById(any(LastlegOrder.class));
|
||||
verify(details, never()).update(any(), any());
|
||||
verify(cartons, never()).update(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user