feat: support overseas warehouse locations and A0 overflow
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
package com.baiyue.lastleg.pda.move.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.pda.move.dto.PlatformInventoryMoveRequest;
|
||||
import com.baiyue.lastleg.pda.move.dto.PdaInventoryMoveResponse;
|
||||
import com.baiyue.lastleg.pda.move.service.PdaInventoryMoveService;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
/** Browser/platform entry point; the PDA entry point remains restricted to warehouse operators. */
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/last-leg/inventory-moves")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INVENTORY_EDIT + "')")
|
||||
public class PlatformInventoryMoveController {
|
||||
@Resource private PdaInventoryMoveService service;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<PdaInventoryMoveResponse> move(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody PlatformInventoryMoveRequest request) {
|
||||
requirePlatform(user);
|
||||
String operator = user.userId() == null ? user.username() : String.valueOf(user.userId());
|
||||
return ApiResponse.success(service.movePlatform(request.overseasWarehouseId(), operator,
|
||||
request.asPdaRequest()));
|
||||
}
|
||||
|
||||
private static void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if (!"PLATFORM".equals(user.appCode()) || user.tenantId() != 0L) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Platform identity is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.lastleg.pda.move.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/** Platform/admin variant of the pallet put-away command. */
|
||||
public record PlatformInventoryMoveRequest(
|
||||
@Positive long overseasWarehouseId,
|
||||
@NotBlank @Size(max = 128) String sourcePalletCode,
|
||||
@NotBlank @Size(max = 128) String targetLocationCode,
|
||||
@PositiveOrZero int expectedPalletVersion,
|
||||
@NotBlank @Size(max = 128) String idempotencyKey,
|
||||
@NotBlank @Size(max = 500) String reason
|
||||
) {
|
||||
public PdaInventoryMoveRequest asPdaRequest() {
|
||||
return new PdaInventoryMoveRequest(sourcePalletCode, targetLocationCode,
|
||||
expectedPalletVersion, idempotencyKey, reason);
|
||||
}
|
||||
}
|
||||
+21
@@ -45,6 +45,22 @@ public class PdaInventoryMoveService {
|
||||
|
||||
@Transactional
|
||||
public PdaInventoryMoveResponse move(long warehouseId, String operatorKey, PdaInventoryMoveRequest request) {
|
||||
return moveInternal(warehouseId, operatorKey, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform/admin put-away uses the same command, idempotency record and physical
|
||||
* transition as the PDA flow. Keeping one implementation prevents the two entry
|
||||
* points from drifting on A0, virtual-location and inventory-position semantics.
|
||||
*/
|
||||
@Transactional
|
||||
public PdaInventoryMoveResponse movePlatform(long warehouseId, String operatorKey,
|
||||
PdaInventoryMoveRequest request) {
|
||||
return moveInternal(warehouseId, "PLATFORM:" + required(operatorKey, "Operator is required"), request);
|
||||
}
|
||||
|
||||
private PdaInventoryMoveResponse moveInternal(long warehouseId, String operatorKey,
|
||||
PdaInventoryMoveRequest request) {
|
||||
String key = required(request.idempotencyKey(), "Idempotency key is required");
|
||||
String requestHash = hash(request);
|
||||
PdaInventoryMove existing = moves.find(warehouseId, operatorKey, key);
|
||||
@@ -58,6 +74,11 @@ public class PdaInventoryMoveService {
|
||||
if (!Set.of("ACTIVE", "IDLE", "OCCUPIED").contains(target.status())) {
|
||||
throw conflict("Target location is not available");
|
||||
}
|
||||
// A virtual receiving location is an intake staging position, not a put-away
|
||||
// destination. A0 is deliberately allowed as a shared overflow location.
|
||||
if ("VIRTUAL".equalsIgnoreCase(target.locationType())) {
|
||||
throw conflict("Virtual receiving location cannot be used as a put-away target");
|
||||
}
|
||||
boolean recoveringRemoteSuccess = pallet.locationId() == target.locationId()
|
||||
&& pallet.version() == request.expectedPalletVersion() + 1;
|
||||
if (!recoveringRemoteSuccess && pallet.version() != request.expectedPalletVersion()) {
|
||||
|
||||
Reference in New Issue
Block a user