提交
This commit is contained in:
+5
-4
@@ -38,23 +38,24 @@ public class SmartDispatchController {
|
||||
description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_LIST)
|
||||
public ApiResponse<List<DispatchPalletCandidate>> candidates(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam List<Long> planIds,
|
||||
@RequestParam(required = false) String orderNo,
|
||||
@RequestParam(required = false) String fbaWarehouseCode,
|
||||
@RequestParam(required = false) String sourceStage) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.candidates(service.resolveSingleCustomerScope(), orderNo,
|
||||
return ApiResponse.success(service.candidates(service.resolveCustomerScopeForPlans(planIds), planIds, orderNo,
|
||||
fbaWarehouseCode, sourceStage));
|
||||
}
|
||||
|
||||
@PostMapping("/pre-arrange")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_EDIT + "')")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_SMART_DISPATCH + "')")
|
||||
@Operation(summary = "Create a pending-departure dispatch and lock all selected pallets",
|
||||
description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_EDIT)
|
||||
description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_SMART_DISPATCH)
|
||||
public ApiResponse<DispatchResponse> preArrange(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody DispatchRequests.PreArrange request) {
|
||||
requirePlatform(user);
|
||||
String scopedCustomer = service.resolveSingleCustomerScope();
|
||||
String scopedCustomer = service.resolveCustomerScopeForPallets(request.planPalletIds());
|
||||
String operatorId = user.userId() == null ? user.username() : String.valueOf(user.userId());
|
||||
return ApiResponse.success(service.preArrange(scopedCustomer, operatorId, request));
|
||||
}
|
||||
|
||||
+18
-1
@@ -15,6 +15,21 @@ public interface DispatchCandidateMapper {
|
||||
"ORDER BY customer_id LIMIT 2")
|
||||
List<String> findCustomerScopes();
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT DISTINCT customer_id FROM ll_inbound_plan WHERE id IN " +
|
||||
"<foreach collection='planIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||
"AND customer_id IS NOT NULL AND customer_id != '' ORDER BY customer_id LIMIT 2" +
|
||||
"</script>")
|
||||
List<String> findCustomerScopesByPlanIds(@Param("planIds") List<Long> planIds);
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT DISTINCT ip.customer_id FROM ll_inbound_plan_pallet p " +
|
||||
"JOIN ll_inbound_plan ip ON ip.id=p.plan_id WHERE p.id IN " +
|
||||
"<foreach collection='planPalletIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||
"AND ip.customer_id IS NOT NULL AND ip.customer_id != '' ORDER BY ip.customer_id LIMIT 2" +
|
||||
"</script>")
|
||||
List<String> findCustomerScopesByPlanPalletIds(@Param("planPalletIds") List<Long> planPalletIds);
|
||||
|
||||
@Select("<script>" +
|
||||
"SELECT p.plan_id AS planId,p.id AS planPalletId,p.warehouse_pallet_id AS warehousePalletId," +
|
||||
"MIN(l.order_id) AS orderId,MIN(o.overseas_warehouse_id) AS overseasWarehouseId,MIN(o.overseas_warehouse_name) AS overseasWarehouseName,MIN(o.order_no) AS orderNo," +
|
||||
@@ -31,7 +46,8 @@ public interface DispatchCandidateMapper {
|
||||
"LEFT JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id " +
|
||||
"LEFT JOIN ll_inbound_plan_line l ON l.id=a.line_id " +
|
||||
"LEFT JOIN ll_order o ON o.id=l.order_id " +
|
||||
"WHERE ip.customer_id=#{customerId} " +
|
||||
"WHERE ip.customer_id=#{customerId} AND p.plan_id IN " +
|
||||
"<foreach collection='planIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
|
||||
"<if test='orderNo != null and orderNo != \"\"'> AND o.order_no LIKE CONCAT('%',#{orderNo},'%')</if> " +
|
||||
"<if test='fbaWarehouseCode != null and fbaWarehouseCode != \"\"'> AND l.fba_warehouse_code=#{fbaWarehouseCode}</if> " +
|
||||
"<if test='sourceStage != null and sourceStage != \"\"'> AND " +
|
||||
@@ -50,6 +66,7 @@ public interface DispatchCandidateMapper {
|
||||
"ORDER BY p.plan_id,p.sequence_no" +
|
||||
"</script>")
|
||||
List<DispatchPalletCandidate> available(@Param("customerId") String customerId,
|
||||
@Param("planIds") List<Long> planIds,
|
||||
@Param("orderNo") String orderNo,
|
||||
@Param("fbaWarehouseCode") String fbaWarehouseCode,
|
||||
@Param("sourceStage") String sourceStage);
|
||||
|
||||
+36
-2
@@ -50,13 +50,26 @@ public class DispatchService {
|
||||
return customerIds.isEmpty() ? null : customerIds.get(0);
|
||||
}
|
||||
|
||||
public List<DispatchPalletCandidate> candidates(String customerId, String orderNo,
|
||||
public String resolveCustomerScopeForPlans(List<Long> planIds) {
|
||||
return requireSingleCustomerScope(candidates.findCustomerScopesByPlanIds(normalizeIds(planIds, "Plan IDs")),
|
||||
"Selected inbound plans");
|
||||
}
|
||||
|
||||
public String resolveCustomerScopeForPallets(List<Long> planPalletIds) {
|
||||
return requireSingleCustomerScope(
|
||||
candidates.findCustomerScopesByPlanPalletIds(normalizeIds(planPalletIds, "Plan pallet IDs")),
|
||||
"Selected pallets");
|
||||
}
|
||||
|
||||
public List<DispatchPalletCandidate> candidates(String customerId, List<Long> planIds, String orderNo,
|
||||
String fbaWarehouseCode, String sourceStage) {
|
||||
if (StringUtils.hasText(sourceStage) && !Set.of("PLAN", "RECEIVED").contains(sourceStage)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "sourceStage must be PLAN or RECEIVED");
|
||||
}
|
||||
if (!StringUtils.hasText(customerId)) return List.of();
|
||||
return candidates.available(customerId, trimToNull(orderNo), trimToNull(fbaWarehouseCode), trimToNull(sourceStage));
|
||||
List<Long> scopedPlanIds = normalizeIds(planIds, "Plan IDs");
|
||||
return candidates.available(customerId, scopedPlanIds, trimToNull(orderNo), trimToNull(fbaWarehouseCode),
|
||||
trimToNull(sourceStage));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -278,4 +291,25 @@ public class DispatchService {
|
||||
private static String trimToNull(String value) {
|
||||
return StringUtils.hasText(value) ? value.trim() : null;
|
||||
}
|
||||
|
||||
private static List<Long> normalizeIds(List<Long> ids, String label) {
|
||||
if (ids == null || ids.isEmpty() || ids.stream().anyMatch(Objects::isNull)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, label + " must be non-empty");
|
||||
}
|
||||
List<Long> normalized = ids.stream().distinct().sorted().toList();
|
||||
if (normalized.size() > 500) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, label + " cannot exceed 500 items");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String requireSingleCustomerScope(List<String> customerIds, String source) {
|
||||
if (customerIds == null || customerIds.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, source + " were not found");
|
||||
}
|
||||
if (customerIds.size() > 1) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, source + " must belong to exactly one customer");
|
||||
}
|
||||
return customerIds.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user