feat(lastleg): expose inbound PDA work instructions

This commit is contained in:
2026-08-24 15:25:34 +08:00
parent 95b72afdcc
commit ef9f253f47
5 changed files with 102 additions and 3 deletions
@@ -26,6 +26,7 @@ public class InboundPalletAssignmentController {
@PathVariable String planNo, @PathVariable long planPalletId, @PathVariable String planNo, @PathVariable long planPalletId,
@RequestParam(required = false) String customerId, @RequestParam(required = false) String customerId,
@Valid @RequestBody PdaRequests.Assign request) { @Valid @RequestBody PdaRequests.Assign request) {
requireOperate(user);
service.assign(scope(user, customerId), planNo, planPalletId, request); service.assign(scope(user, customerId), planNo, planPalletId, request);
return ApiResponse.success(null); return ApiResponse.success(null);
} }
@@ -40,4 +41,12 @@ public class InboundPalletAssignmentController {
} }
return String.valueOf(user.tenantId()); return String.valueOf(user.tenantId());
} }
private void requireOperate(AuthenticatedUser user) {
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
if (user.permissions() == null || !user.permissions().contains(InboundPdaController.OPERATE_PERMISSION)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
InboundPdaController.OPERATE_PERMISSION + " permission is required");
}
}
} }
@@ -10,7 +10,19 @@ public final class PdaResponses {
public record Task(long taskId, String taskNo, String planNo, String status, public record Task(long taskId, String taskNo, String planNo, String status,
LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt, LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt,
List<Long> unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt, List<Long> unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt,
LocalDateTime estimatedEmptyReturnAt) {} LocalDateTime estimatedEmptyReturnAt,
List<Pallet> pallets, List<Allocation> allocations) {}
public record Pallet(long planPalletId, String palletNo, String destinationWarehouseId,
String destinationWarehouseCode, String destinationWarehouseName,
long warehousePalletId, long warehouseLocationId,
String assignmentStatus, int actualPackageCount) {}
public record Allocation(long allocationId, long lineId, long planPalletId,
long orderId, String inboundCode, String fbaWarehouseCode,
int requiredPackageCount, int plannedPackageCount,
int countedPackageCount, int pickedPackageCount,
String status, int version) {}
public record UnloadPhotoFile(long fileId, String fileName, String contentType, long sizeBytes) {} public record UnloadPhotoFile(long fileId, String fileName, String contentType, long sizeBytes) {}
@@ -629,9 +629,30 @@ public class InboundPdaWorkflowService {
} }
private PdaResponses.Task task(InboundPdaTask task, String planNo) { private PdaResponses.Task task(InboundPdaTask task, String planNo) {
List<InboundPlanPallet> planPallets = repository.pallets(task.getPlanId());
List<InboundPlanPalletAllocation> planAllocations = repository.allocations(task.getPlanId());
Map<Long, InboundPlanLine> lines = repository.lines(task.getPlanId()).stream()
.collect(java.util.stream.Collectors.toMap(InboundPlanLine::getId, value -> value));
List<PdaResponses.Pallet> pallets = planPallets.stream().map(value -> new PdaResponses.Pallet(
value.getId(), value.getPalletNo(), value.getDestinationWarehouseId(),
value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(),
Objects.requireNonNullElse(value.getWarehousePalletId(), 0L),
Objects.requireNonNullElse(value.getWarehouseLocationId(), 0L),
value.getAssignmentStatus(), InboundPdaWorkflowService.value(value.getActualPackageCount()))).toList();
List<PdaResponses.Allocation> allocations = planAllocations.stream().map(value -> {
InboundPlanLine line = lines.get(value.getLineId());
if (line == null) throw conflict("Inbound allocation line is missing");
return new PdaResponses.Allocation(value.getId(), value.getLineId(), value.getPalletId(),
line.getOrderId(), line.getInboundCode(), line.getFbaWarehouseCode(),
InboundPdaWorkflowService.value(line.getRequiredPackageCount()),
InboundPdaWorkflowService.value(value.getPlannedPackageCount()),
InboundPdaWorkflowService.value(value.getCountedPackageCount()),
InboundPdaWorkflowService.value(value.getPickedPackageCount()), value.getStatus(),
InboundPdaWorkflowService.value(value.getVersion()));
}).toList();
return new PdaResponses.Task(task.getId(), task.getTaskNo(), planNo, task.getStatus(), return new PdaResponses.Task(task.getId(), task.getTaskNo(), planNo, task.getStatus(),
task.getPushedAt(), task.getStartedAt(), task.getCompletedAt(), unloadPhotoFileIds(task), task.getPushedAt(), task.getStartedAt(), task.getCompletedAt(), unloadPhotoFileIds(task),
task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt()); task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt(), pallets, allocations);
} }
private List<Long> unloadPhotoFileIds(InboundPdaTask task) { private List<Long> unloadPhotoFileIds(InboundPdaTask task) {
@@ -0,0 +1,42 @@
package com.baiyue.lastleg.inboundplan.pda.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.inboundplan.pda.dto.PdaRequests;
import com.baiyue.lastleg.inboundplan.pda.service.InboundPdaWorkflowService;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class InboundPalletAssignmentControllerTest {
private final InboundPdaWorkflowService service = mock(InboundPdaWorkflowService.class);
private final InboundPalletAssignmentController controller = controller();
private final PdaRequests.Assign request = new PdaRequests.Assign(1L, 2L, 3L, "assign-key");
@Test
void assignmentRequiresPdaOperatePermissionAndExplicitPlatformCustomer() {
assertThrows(ResponseStatusException.class,
() -> controller.assign(user(Set.of()), "PLAN-1", 4L, "100", request));
assertThrows(ResponseStatusException.class,
() -> controller.assign(user(Set.of(InboundPdaController.OPERATE_PERMISSION)), "PLAN-1", 4L, null, request));
controller.assign(user(Set.of(InboundPdaController.OPERATE_PERMISSION)), "PLAN-1", 4L, "100", request);
verify(service).assign("100", "PLAN-1", 4L, request);
}
private AuthenticatedUser user(Set<String> permissions) {
return new AuthenticatedUser(7L, 0L, 0L, 8L, "operator", "PLATFORM", "PDA", permissions);
}
private InboundPalletAssignmentController controller() {
InboundPalletAssignmentController value = new InboundPalletAssignmentController();
ReflectionTestUtils.setField(value, "service", service);
return value;
}
}
@@ -95,6 +95,21 @@ class InboundPdaWorkflowServiceTest {
verify(repository, never()).insert(any(InboundPdaTask.class)); verify(repository, never()).insert(any(InboundPdaTask.class));
} }
@Test
void taskReturnsPalletAndAllocationWorkInstructions() {
when(repository.task("100", plan.getId())).thenReturn(task);
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));
var result = service.task("100", "LIP1");
assertEquals(targetPallet.getId(), result.pallets().get(0).planPalletId());
assertEquals(target.getId(), result.allocations().get(0).allocationId());
assertEquals("RK-A", result.allocations().get(0).inboundCode());
assertEquals(3, result.allocations().get(0).plannedPackageCount());
}
@Test @Test
void assignmentReservesWarehouseResourcesAndPersistsTheLock() { void assignmentReservesWarehouseResourcesAndPersistsTheLock() {
targetPallet.setDestinationWarehouseId("88"); targetPallet.setDestinationWarehouseId("88");
@@ -427,7 +442,7 @@ class InboundPdaWorkflowServiceTest {
assertEquals("COMPLETED", result.status()); assertEquals("COMPLETED", result.status());
verify(stockReceipts, never()).stockIn(any(), any(), any(), any(), any(), any(), any()); verify(stockReceipts, never()).stockIn(any(), any(), any(), any(), any(), any(), any());
verify(repository, never()).allocations(plan.getId()); verify(repository, never()).update(task);
} }
@Test @Test