提交
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@ public final class LastlegOrderRequests {
|
||||
@Min(1) long page,
|
||||
@Min(1) @Max(200) long size,
|
||||
String orderNo,
|
||||
String orderType,
|
||||
String inboundCode,
|
||||
String sourceOrderNo,
|
||||
String containerNo,
|
||||
String currentStatus,
|
||||
String status,
|
||||
String warehouse,
|
||||
String transferWarehouseCode,
|
||||
Boolean abnormal,
|
||||
Boolean detained,
|
||||
|
||||
+6
-6
@@ -33,7 +33,7 @@ public class InboundPlanController {
|
||||
|
||||
/** 按入仓计划明细分页查询可预约的入库订单行。 */
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_LIST + "')")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_CREATE + "')")
|
||||
@Operation(summary = "分页查询入库订单", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_LIST)
|
||||
public ApiResponse<PageResult<InboundPlanListResponse>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -43,7 +43,7 @@ public class InboundPlanController {
|
||||
}
|
||||
|
||||
@PostMapping("/convert")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_CREATE + "')")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_LIST + "')")
|
||||
/** 将入仓单转换为入仓计划。 */
|
||||
@Operation(summary = "入仓单转入仓计划", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_CREATE)
|
||||
public ApiResponse<InboundPlanResponse> convert(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -72,9 +72,9 @@ public class InboundPlanController {
|
||||
}
|
||||
|
||||
@PostMapping("/fba-appointments/batch")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_EDIT + "')")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT + "')")
|
||||
/** 为选中的入仓明细创建 FBA 预约。 */
|
||||
@Operation(summary = "创建 FBA 预约", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_EDIT)
|
||||
@Operation(summary = "创建 FBA 预约", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT)
|
||||
public ApiResponse<FbaAppointmentResponse> batchFbaAppointment(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody BatchFbaAppointmentRequest request) {
|
||||
@@ -95,8 +95,8 @@ public class InboundPlanController {
|
||||
}
|
||||
|
||||
@PutMapping("/fba-appointments/{appointmentNo}")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_EDIT + "')")
|
||||
@Operation(summary = "编辑 FBA 预约", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_EDIT)
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT + "')")
|
||||
@Operation(summary = "编辑 FBA 预约", description = "Required authority: " + OverseasPermissions.INBOUND_ORDER_FBA_APPOINTMENT)
|
||||
public ApiResponse<FbaAppointmentResponse> updateFbaAppointment(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String appointmentNo,
|
||||
|
||||
@@ -8,6 +8,10 @@ public final class OverseasPermissions {
|
||||
public static final String INBOUND_ORDER_LIST = "platform:overseas-warehouse:inbound-order:list";
|
||||
public static final String INBOUND_ORDER_CREATE = "platform:overseas-warehouse:inbound-order:create";
|
||||
public static final String INBOUND_ORDER_EDIT = "platform:overseas-warehouse:inbound-order:edit";
|
||||
public static final String INBOUND_ORDER_FBA_APPOINTMENT =
|
||||
"platform:overseas-warehouse:inbound-order:fba-appointment";
|
||||
public static final String INBOUND_ORDER_SMART_DISPATCH =
|
||||
"platform:overseas-warehouse:inbound-order:smart-dispatch";
|
||||
public static final String RELABEL_ORDER_LIST = "platform:overseas-warehouse:relabel-order:list";
|
||||
public static final String RELABEL_ORDER_CREATE = "platform:overseas-warehouse:relabel-order:create";
|
||||
public static final String RELABEL_ORDER_STATUS = "platform:overseas-warehouse:relabel-order:status";
|
||||
|
||||
@@ -123,13 +123,20 @@ public class LastlegOrderService {
|
||||
if (query.createdFrom() != null && query.createdTo() != null && query.createdFrom().isAfter(query.createdTo())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "createdFrom must not be after createdTo");
|
||||
}
|
||||
String currentStatus = StringUtils.hasText(query.currentStatus()) ? query.currentStatus() : query.status();
|
||||
return new LambdaQueryWrapper<LastlegOrder>()
|
||||
.eq(StringUtils.hasText(customerId), LastlegOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.orderNo()), LastlegOrder::getOrderNo, query.orderNo())
|
||||
.eq(StringUtils.hasText(query.orderType()), LastlegOrder::getOrderType, query.orderType())
|
||||
.like(StringUtils.hasText(query.inboundCode()), LastlegOrder::getInboundCode, query.inboundCode())
|
||||
.like(StringUtils.hasText(query.sourceOrderNo()), LastlegOrder::getSourceOrderNo, query.sourceOrderNo())
|
||||
.like(StringUtils.hasText(query.containerNo()), LastlegOrder::getContainerNo, query.containerNo())
|
||||
.eq(StringUtils.hasText(query.status()), LastlegOrder::getCurrentStatus, query.status())
|
||||
.eq(StringUtils.hasText(currentStatus), LastlegOrder::getCurrentStatus, currentStatus)
|
||||
.and(StringUtils.hasText(query.warehouse()), wrapper -> wrapper
|
||||
.like(LastlegOrder::getOverseasWarehouseId, query.warehouse())
|
||||
.or().like(LastlegOrder::getOverseasWarehouseName, query.warehouse())
|
||||
.or().like(LastlegOrder::getTransferWarehouseCode, query.warehouse())
|
||||
.or().like(LastlegOrder::getTransferWarehouseName, query.warehouse()))
|
||||
.eq(StringUtils.hasText(query.transferWarehouseCode()), LastlegOrder::getTransferWarehouseCode,
|
||||
query.transferWarehouseCode())
|
||||
.eq(query.abnormal() != null, LastlegOrder::getAbnormal, query.abnormal())
|
||||
|
||||
Reference in New Issue
Block a user