feat: gate overseas receiving and harden phase4 workflows

This commit is contained in:
2026-08-25 10:31:04 +08:00
parent cb78bc6b41
commit 52b9fc382f
93 changed files with 2779 additions and 201 deletions
@@ -0,0 +1,53 @@
package com.baiyue.lastleg.pda.warehouse.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseRequests;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseResponses;
import com.baiyue.lastleg.pda.warehouse.service.PdaWarehouseService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
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;
@RestController
@RequestMapping("/api/pda/v1/last-leg")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
public class PdaWarehouseController {
@Resource private PdaWarehouseService service;
@PostMapping("/inventory/resolve")
public ApiResponse<PdaWarehouseResponses.InventoryScan> inventory(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody PdaWarehouseRequests.Scan request) {
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
return ApiResponse.success(service.inventory(identity.overseasWarehouseId(), request));
}
@PostMapping("/locations/resolve")
public ApiResponse<PdaWarehouseResponses.Location> location(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody PdaWarehouseRequests.Scan request) {
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
return ApiResponse.success(service.location(identity.overseasWarehouseId(), request));
}
@PostMapping("/pallets/resolve")
public ApiResponse<PdaWarehouseResponses.Pallet> pallet(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody PdaWarehouseRequests.Scan request) {
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
return ApiResponse.success(service.pallet(identity.overseasWarehouseId(), request));
}
@GetMapping("/operations")
public ApiResponse<PageResult<PdaWarehouseResponses.Operation>> operations(
@AuthenticationPrincipal AuthenticatedUser user, @Valid PdaWarehouseRequests.HistoryQuery query) {
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
return ApiResponse.success(service.history(identity.overseasWarehouseId(), identity.operatorKey(), query));
}
}
@@ -11,7 +11,7 @@ import java.util.List;
@Mapper
public interface PdaWarehouseMapper extends BaseMapper<PdaOperationHistory> {
String INVENTORY_SELECT = "SELECT b.customer_id,b.customer_name,b.id balance_id,p.id position_id," +
String INVENTORY_SELECT = "SELECT DISTINCT b.customer_id,b.customer_name,b.id balance_id,p.id position_id," +
"COALESCE(NULLIF(r.sku_code,''),b.sku_code) sku_code," +
"COALESCE(NULLIF(r.product_name_zh,''),b.product_name_zh) product_name_zh," +
"COALESCE(NULLIF(r.product_name_en,''),b.product_name_en) product_name_en,b.inbound_code," +
@@ -0,0 +1,101 @@
package com.baiyue.lastleg.pda.warehouse.service;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseRequests;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseResponses;
import com.baiyue.lastleg.pda.warehouse.entity.PdaOperationHistory;
import com.baiyue.lastleg.pda.warehouse.mapper.PdaWarehouseMapper;
import com.baiyue.lastleg.pda.warehouse.model.PdaInventoryProjection;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
@Service
public class PdaWarehouseService {
@Resource private PdaWarehouseMapper mapper;
@Resource private WarehouseResourceGateway warehouse;
public PdaWarehouseResponses.InventoryScan inventory(long warehouseId, PdaWarehouseRequests.Scan request) {
String code = code(request.code());
List<PdaWarehouseResponses.InventoryItem> items = items(mapper.scanInventory(String.valueOf(warehouseId), code));
if (items.isEmpty()) throw notFound("Inventory code was not found in the bound warehouse");
return new PdaWarehouseResponses.InventoryScan(code, items);
}
public PdaWarehouseResponses.Location location(long warehouseId, PdaWarehouseRequests.Scan request) {
WarehouseResourceGateway.WarehouseLocationReference location = warehouse.resolveLocation(warehouseId,
code(request.code()));
List<PdaWarehouseResponses.InventoryItem> inventory = items(mapper.inventoryByLocation(
String.valueOf(warehouseId), location.locationId(), location.locationCode(), "__NO_CARTON__"));
long packages = inventory.stream().mapToLong(this::packages).sum();
int positions = (int) inventory.stream().map(PdaWarehouseResponses.InventoryItem::positionId).distinct().count();
return new PdaWarehouseResponses.Location(location.locationId(), location.locationCode(),
location.locationType(), location.maxLoadKg(), location.status(), packages, positions);
}
public PdaWarehouseResponses.Pallet pallet(long warehouseId, PdaWarehouseRequests.Scan request) {
WarehouseResourceGateway.WarehousePalletReference pallet = warehouse.resolvePallet(code(request.code()));
if (pallet.warehouseId() != warehouseId) throw conflict("Pallet belongs to another overseas warehouse");
WarehouseResourceGateway.WarehouseLocationReference location = warehouse.resolveLocation(warehouseId,
pallet.locationId());
List<PdaWarehouseResponses.InventoryItem> inventory = items(mapper.inventoryByPallet(
String.valueOf(warehouseId), pallet.palletId(), "__NO_CARTON__"));
long packages = inventory.stream().mapToLong(this::packages).sum();
int positions = (int) inventory.stream().map(PdaWarehouseResponses.InventoryItem::positionId).distinct().count();
return new PdaWarehouseResponses.Pallet(pallet.palletId(), pallet.palletNo(), pallet.status(), pallet.version(),
location.locationId(), location.locationCode(), packages, positions, inventory);
}
public PageResult<PdaWarehouseResponses.Operation> history(long warehouseId, String operatorKey,
PdaWarehouseRequests.HistoryQuery query) {
long offset = (query.page() - 1) * query.size();
List<PdaWarehouseResponses.Operation> rows = mapper.history(warehouseId, operatorKey,
trim(query.operationType()), offset, query.size());
long total = mapper.countHistory(warehouseId, operatorKey, trim(query.operationType()));
return new PageResult<>(rows, query.page(), query.size(), total,
total == 0 ? 0 : (total + query.size() - 1) / query.size());
}
public void record(long warehouseId, String customerId, String operatorKey, String operationType,
String businessKey, String status, String idempotencyKey, String requestHash, String summary) {
PdaOperationHistory history = new PdaOperationHistory();
history.setId(IdWorker.getId()); history.setOverseasWarehouseId(warehouseId);
history.setCustomerId(trim(customerId)); history.setOperatorKey(operatorKey);
history.setOperationType(operationType); history.setBusinessKey(businessKey); history.setStatus(status);
history.setIdempotencyKey(trim(idempotencyKey)); history.setRequestHash(trim(requestHash));
history.setSummary(trim(summary)); history.setOccurredAt(LocalDateTime.now()); history.setCreatedAt(history.getOccurredAt());
mapper.insert(history);
}
private List<PdaWarehouseResponses.InventoryItem> items(List<PdaInventoryProjection> values) {
LinkedHashMap<String, PdaWarehouseResponses.InventoryItem> result = new LinkedHashMap<>();
for (PdaInventoryProjection value : values) {
String key = value.getPositionId() + ":" + text(value.getSkuCode()) + ":" + text(value.getCartonMark());
result.putIfAbsent(key, new PdaWarehouseResponses.InventoryItem(value.getCustomerId(), value.getCustomerName(),
number(value.getBalanceId()), number(value.getPositionId()), value.getSkuCode(),
first(value.getProductNameZh(), value.getProductNameEn()), value.getInboundCode(), value.getCartonMark(),
number(value.getPalletId()), value.getPalletCode(), value.getLocationId(), value.getLocationCode(),
number(value.getAvailablePackages()), number(value.getHoldPackages()), number(value.getReceivingPackages()),
value.getStatus(), number(value.getVersion())));
}
return List.copyOf(result.values());
}
private long packages(PdaWarehouseResponses.InventoryItem value) {
return value.availablePackages() + value.holdPackages() + value.receivingPackages();
}
private static String code(String value) { String result = trim(value); if (result == null) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Scan code is required"); return result; }
private static String trim(String value) { return value == null || value.isBlank() ? null : value.trim(); }
private static String text(String value) { return value == null ? "" : value; }
private static String first(String first, String second) { String value = trim(first); return value == null ? trim(second) : value; }
private static long number(Long value) { return value == null ? 0 : value; }
private static ResponseStatusException notFound(String message) { return new ResponseStatusException(HttpStatus.NOT_FOUND, message); }
private static ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
}