feat: complete dropship palletization workflow
This commit is contained in:
@@ -63,12 +63,13 @@ public interface OrderCargoClient {
|
||||
}
|
||||
|
||||
record CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
String skuNo, Integer packageCount, BigDecimal lengthCm, BigDecimal widthCm,
|
||||
String skuNo, Integer packageCount, Integer unitsPerCarton,
|
||||
BigDecimal lengthCm, BigDecimal widthCm,
|
||||
BigDecimal heightCm) {
|
||||
public CargoLabelBinding(Long cargoId, Long orderId, Integer itemNo, String cargoName,
|
||||
Integer packageCount, BigDecimal lengthCm, BigDecimal widthCm,
|
||||
BigDecimal heightCm) {
|
||||
this(cargoId, orderId, itemNo, cargoName, null, packageCount, lengthCm, widthCm, heightCm);
|
||||
this(cargoId, orderId, itemNo, cargoName, null, packageCount, null, lengthCm, widthCm, heightCm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-10
@@ -62,23 +62,44 @@ public class PlatformDropshipPalletizationController {
|
||||
return ApiResponse.success(service.claim(taskId, request.warehouseId(), user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/open")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Pallet> open(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@GetMapping("/tasks/{taskId}/cartons/resolve")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<DropshipPalletizationDtos.Carton> resolveCarton(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.OpenPalletRequest request) {
|
||||
@RequestParam @Positive long warehouseId,
|
||||
@RequestParam @Size(max = 80) String boxCode) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.open(taskId, request, user.userId(), user.username()));
|
||||
return ApiResponse.success(service.resolveCarton(taskId, warehouseId, boxCode, user.userId()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/scan")
|
||||
@PostMapping("/tasks/{taskId}/cartons/count")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<DropshipPalletizationDtos.ScanResult> scan(
|
||||
public ApiResponse<DropshipPalletizationDtos.CountResult> countCarton(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId, @PathVariable @Positive long palletId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.ScanRequest request) {
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.CountCartonRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.scan(taskId, palletId, request, user.userId(), user.username()));
|
||||
return ApiResponse.success(service.countCarton(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/bind-pending")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> bindPending(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.BindPendingRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.bindPending(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/cartons/{cartonId}/move")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> moveCarton(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long cartonId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.MoveCartonRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.moveCarton(taskId, cartonId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/seal")
|
||||
|
||||
+10
-47
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.controller;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PalletPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
|
||||
@@ -24,11 +25,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import com.baiyue.firstleg.service.LoadingWorkPalletService;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.OpenRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.ScanRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.SealRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.Pallet;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -56,8 +52,6 @@ public class PlatformLoadingPdaController {
|
||||
private LoadingPickService service;
|
||||
@Resource
|
||||
private CabinetLoadingEvidenceService evidenceService;
|
||||
@Resource
|
||||
private LoadingWorkPalletService palletService;
|
||||
|
||||
@GetMapping("/tasks")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
@@ -168,6 +162,15 @@ public class PlatformLoadingPdaController {
|
||||
return ApiResponse.success(service.pick(taskId, itemId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/pick")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> pickPallet(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody PalletPickRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.pickPallet(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/items/{itemId}/partial")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> partial(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -226,44 +229,4 @@ public class PlatformLoadingPdaController {
|
||||
return ApiResponse.success(service.operations(taskId));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}/pallets")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<List<Pallet>> pallets(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(palletService.list(taskId, warehouseId));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/open")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Pallet> openPallet(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody OpenRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(palletService.open(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
/** 扫到其他未封托托盘上的箱唛时,同时完成按箱移托。 */
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/cartons/scan")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Pallet> scanCarton(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long palletId,
|
||||
@Valid @RequestBody ScanRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(palletService.scan(taskId, palletId, request,
|
||||
user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/seal")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Pallet> sealPallet(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long palletId,
|
||||
@Valid @RequestBody SealRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(palletService.seal(taskId, palletId, request,
|
||||
user.userId(), user.username()));
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -24,6 +24,10 @@ public class PlatformSmartLoadingController {
|
||||
public ApiResponse<Plan> assign(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assign(planId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/inbound-assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> assignInbound(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignInboundRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assignInbound(planId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/pallet-assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> assignPallet(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignPalletRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assignPallet(planId,r,u.userId(),u.username()));}
|
||||
@DeleteMapping("/plans/{planId}/pallet-assignments/{palletId}") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:delete')")
|
||||
public ApiResponse<Plan> removePallet(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@PathVariable @Positive long palletId,@Valid @RequestBody RemovePalletRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.removePallet(planId,palletId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/pending-review-assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
public ApiResponse<Plan> assignPendingReview(@AuthenticationPrincipal AuthenticatedUser u,@PathVariable @Positive long planId,@Valid @RequestBody AssignPendingReviewRequest r){FirstlegScopeGuard.platform(u);return ApiResponse.success(service.assignPendingReview(planId,r,u.userId(),u.username()));}
|
||||
@PostMapping("/plans/{planId}/group-assignments") @PreAuthorize("hasAuthority('platform:firstleg:smart-loading:assign')")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
@@ -14,13 +15,23 @@ public final class DropshipPalletizationDtos {
|
||||
|
||||
public record ClaimRequest(@Positive long warehouseId) {}
|
||||
|
||||
public record OpenPalletRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
public enum DiscrepancyAction { MATCH, ACCEPT_VARIANCE }
|
||||
|
||||
public record ScanRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 64) String boxCode,
|
||||
@Positive @Max(2000) int cartonCount,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
public record CountCartonRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 80) String boxCode,
|
||||
@Positive int actualSkuQuantity,
|
||||
@NotNull DiscrepancyAction discrepancyAction,
|
||||
@Size(max = 500) String discrepancyRemark,
|
||||
@Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String palletNo,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
|
||||
public record BindPendingRequest(@Positive long warehouseId,
|
||||
@NotBlank @Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String palletNo,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
|
||||
public record MoveCartonRequest(@Positive long warehouseId,
|
||||
@Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String targetPalletNo,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
|
||||
public record SealPalletRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 128) String operationId,
|
||||
@@ -28,24 +39,34 @@ public final class DropshipPalletizationDtos {
|
||||
|
||||
public record PendingTask(Long taskId, Long inboundId, String inboundCode, Long tenantId,
|
||||
String customerName, Long warehouseId, String warehouseName,
|
||||
Integer totalCartons, Integer allocatedCartons, String taskStatus,
|
||||
Integer totalCartons, Integer countedCartons, Integer boundCartons,
|
||||
Integer unboundCartons, Integer discrepancyCartons, String taskStatus,
|
||||
LocalDateTime createdAt) {}
|
||||
|
||||
public record Task(Long taskId, Long inboundId, String inboundCode, Long tenantId,
|
||||
String customerName, Long warehouseId, String warehouseName,
|
||||
Integer totalCartons, Integer allocatedCartons, String taskStatus,
|
||||
Integer totalCartons, Integer countedCartons, Integer boundCartons,
|
||||
Integer unboundCartons, Integer discrepancyCartons, String taskStatus,
|
||||
Long assigneeId, String assigneeName, Integer version,
|
||||
List<Pallet> pallets) {}
|
||||
List<Pallet> pallets, List<Carton> unbound) {}
|
||||
|
||||
public record Pallet(Long palletId, String palletNo, String palletStatus,
|
||||
Integer cartonCount, Integer skuCount, Integer version,
|
||||
List<SkuItem> items, LocalDateTime completedAt) {}
|
||||
public record Pallet(Long palletId, String palletNo, String status,
|
||||
Integer cartonCount, Integer skuCount, Integer discrepancyCartons,
|
||||
java.math.BigDecimal weightKg, java.math.BigDecimal volumeCbm,
|
||||
Integer version, List<SkuItem> items, List<Carton> cartons,
|
||||
LocalDateTime sealedAt) {}
|
||||
|
||||
public record SkuItem(Long inboundId, String inboundCode, Long inboundDetailId,
|
||||
String skuCode, String cargoName, Integer cartonCount,
|
||||
String skuCode, String cargoName, Integer cartonCount, Integer skuQuantity,
|
||||
List<String> cartonMarks) {}
|
||||
|
||||
public record ScanResult(String skuCode, String cargoName, String inboundCode,
|
||||
int addedCartons, int allocatedCartons, int remainingCartons,
|
||||
Pallet pallet) {}
|
||||
public record Carton(Long cartonId, String boxCode, Long inboundId, String inboundCode,
|
||||
Long inboundDetailId, String skuCode, String cargoName,
|
||||
Integer expectedSkuQuantity, Integer actualSkuQuantity,
|
||||
String discrepancyStatus, String discrepancyRemark,
|
||||
String countStatus, Long palletId, String palletNo,
|
||||
Boolean canCount, String blockReason) {}
|
||||
|
||||
public record CountResult(Carton carton, Integer countedCartons, Integer boundCartons,
|
||||
Integer unboundCartons, Integer remainingCartons, Pallet pallet) {}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,10 @@ public final class LoadingListDtos {
|
||||
String chineseName,
|
||||
String englishName,
|
||||
String goodsImageUrl,
|
||||
String unitType,
|
||||
Long palletId,
|
||||
String palletNo,
|
||||
Boolean indivisible,
|
||||
int packageCount,
|
||||
int palletCount,
|
||||
BigDecimal grossWeightKg,
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 一件代发PDA现场打托接口模型。 */
|
||||
public final class LoadingPalletDtos {
|
||||
private LoadingPalletDtos() {}
|
||||
|
||||
public record OpenRequest(
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@Min(1) Long warehousePalletId,
|
||||
@NotBlank @Size(max = 64) String palletCode,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {}
|
||||
|
||||
public record ScanRequest(
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@NotBlank @Size(max = 80) String boxCode,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {}
|
||||
|
||||
public record SealRequest(
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {}
|
||||
|
||||
public record Carton(Long id, String boxCode, String skuCode, Long tenantId,
|
||||
Long inboundId, Long inboundDetailId, Integer unitsPerCarton) {}
|
||||
|
||||
public record SkuSummary(String skuCode, Integer cartonCount, Long productQuantity) {}
|
||||
|
||||
public record Pallet(Long id, String palletCode, Long warehousePalletId, String status,
|
||||
Integer version, Integer cartonCount, Integer skuCount,
|
||||
Integer customerCount, List<SkuSummary> skus, List<Carton> cartons) {}
|
||||
}
|
||||
@@ -23,6 +23,13 @@ public final class LoadingPickDtos {
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {}
|
||||
|
||||
public record PalletPickRequest(
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
@NotBlank @Pattern(regexp = "DP-[0-9]{6}-[0-9]{4}") String palletNo,
|
||||
@NotNull @Min(0) Integer expectedTaskVersion,
|
||||
@NotBlank @Size(max = 128) String operationId
|
||||
) {}
|
||||
|
||||
/** 部分拣货为该 PDA 明细的最终实际拣出箱数,不是增量。 */
|
||||
public record PartialPickRequest(
|
||||
@NotNull @Min(1) Long warehouseId,
|
||||
|
||||
@@ -22,11 +22,14 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
public record GroupMemberCandidate(Long accountId, Long inboundId, Long orderId, String orderNo,
|
||||
String inboundCode, String customerName, Integer availablePackageCount,
|
||||
String orderStatusSnapshot, LocalDateTime expectedArrivalAt) {}
|
||||
public record PalletCandidate(Long palletId,String palletNo,String unitType,Boolean indivisible,
|
||||
Integer cartonCount,Integer skuCount,BigDecimal weightKg,BigDecimal volumeCbm) {}
|
||||
public record InboundCandidate(Long accountId, Long inboundId, Long orderId, String orderNo, String inboundCode,
|
||||
Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId,
|
||||
String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsSummary,
|
||||
Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg,
|
||||
BigDecimal averageUnitVolumeCbm, BigDecimal availableVolumeCbm,
|
||||
String unitType,Long palletId,String palletNo,Boolean indivisible,
|
||||
Long coLoadGroupId, String groupNo, Integer memberCount, Integer groupTotalBoxes,
|
||||
Boolean locked, Boolean canAssign, String blockReason,
|
||||
String orderStatusSnapshot, LocalDateTime expectedArrivalAt,
|
||||
@@ -66,6 +69,10 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
public record AssignGroupRequest(@NotNull @Positive Long inboundId,
|
||||
@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record AssignPalletRequest(@NotNull @Positive Long palletId,@NotNull @Min(0) Integer expectedVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record RemovePalletRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record RemoveGroupRequest(@NotNull @Min(0) Integer expectedPlanVersion,
|
||||
@NotBlank @Size(max=128) String operationId) {}
|
||||
public record RecommendRequest(@NotNull @Min(0) Integer expectedVersion,
|
||||
|
||||
@@ -23,6 +23,7 @@ public class InboundCartonLabelEntity {
|
||||
private Integer cargoItemNo;
|
||||
private String cargoName;
|
||||
private String skuCode;
|
||||
private Integer unitsPerCarton;
|
||||
private Integer detailPackageCount;
|
||||
private String inboundCode;
|
||||
private String boxCode;
|
||||
|
||||
@@ -6,6 +6,7 @@ public class LoadingPlanItemEntity {
|
||||
private Long inboundId; private Long inboundDetailId; private Long orderId; private String orderNo; private String inboundCode; private Long destinationWarehouseId;
|
||||
private String destinationWarehouseCode; private String destinationWarehouseName; private String skuCode; private String productNameZh; private String productNameEn;
|
||||
private Long productImageFileId; private String productImageUrl; private Integer packageCount; private Integer productQuantity; private Integer unitsPerCarton;
|
||||
private String unitType; private Long palletId; private String palletNo; private Boolean indivisible;
|
||||
private Integer palletCount; private BigDecimal grossWeightKg; private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
|
||||
private String fbaTrackingNo; private String shipmentNo; private String shippingMark; private Integer loadSequence; private String positionSnapshot;
|
||||
private String internalRemark; private String customerRemark; private String warehouseRemark; private LocalDateTime frozenAt; private Long frozenBy;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 一件代发箱唛与现场实际托盘的当前绑定事实。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_loading_work_pallet_carton")
|
||||
public class LoadingWorkPalletCartonEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long pickTaskId;
|
||||
private Long workPalletId;
|
||||
private Long loadingPlanItemId;
|
||||
private Long pickItemId;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long inboundDetailId;
|
||||
private Long cartonLabelId;
|
||||
private String boxCode;
|
||||
private String skuCode;
|
||||
private Integer unitsPerCarton;
|
||||
private Long boundBy;
|
||||
private String boundByName;
|
||||
private String operationId;
|
||||
@Version
|
||||
private Integer version;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.baiyue.firstleg.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 一件代发装柜现场的实际托盘。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("fl_loading_work_pallet")
|
||||
public class LoadingWorkPalletEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long pickTaskId;
|
||||
private Long loadingPlanId;
|
||||
private Long warehouseId;
|
||||
private Long warehousePalletId;
|
||||
private String palletCode;
|
||||
private String openOperationId;
|
||||
private String sealOperationId;
|
||||
private String palletStatus;
|
||||
private Long createdBy;
|
||||
private String createdByName;
|
||||
private Long sealedBy;
|
||||
private String sealedByName;
|
||||
private LocalDateTime sealedAt;
|
||||
@Version
|
||||
private Integer version;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+158
-179
@@ -1,203 +1,182 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.PendingTask;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.PalletCandidate;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/** 一件代发合托持久化;任务、托盘和逐箱绑定均独立于装柜拣货。 */
|
||||
/** 一件代发逐箱清点、待绑托与封托事实。 */
|
||||
@Mapper
|
||||
public interface DropshipPalletizationMapper {
|
||||
@Insert("""
|
||||
INSERT IGNORE INTO fl_dropship_pallet_task
|
||||
(id,tenant_id,inbound_id,warehouse_id,task_status,total_cartons,allocated_cartons,version,created_at,updated_at)
|
||||
VALUES(#{id},#{tenantId},#{inboundId},#{warehouseId},'PENDING',#{totalCartons},0,1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insertTask(@Param("id") long id, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("warehouseId") long warehouseId,
|
||||
@Param("totalCartons") int totalCartons);
|
||||
@Insert("""
|
||||
INSERT IGNORE INTO fl_dropship_pallet_task
|
||||
(id,tenant_id,inbound_id,warehouse_id,task_status,total_cartons,counted_cartons,allocated_cartons,discrepancy_cartons,version,created_at,updated_at)
|
||||
VALUES(#{id},#{tenantId},#{inboundId},#{warehouseId},'PENDING',#{totalCartons},0,0,0,1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))""")
|
||||
int insertTask(@Param("id") long id,@Param("tenantId") long tenantId,@Param("inboundId") long inboundId,
|
||||
@Param("warehouseId") long warehouseId,@Param("totalCartons") int totalCartons);
|
||||
|
||||
@Select("""
|
||||
SELECT t.id task_id,t.inbound_id,o.inbound_code,t.tenant_id,o.customer_name,t.warehouse_id,
|
||||
o.warehouse_name,t.total_cartons,t.allocated_cartons,t.task_status,t.created_at
|
||||
FROM fl_dropship_pallet_task t JOIN fl_inbound_order o ON o.id=t.inbound_id AND o.tenant_id=t.tenant_id
|
||||
WHERE t.task_status IN ('PENDING','CLAIMED')
|
||||
AND (t.assignee_id IS NULL OR t.assignee_id=#{operatorId})
|
||||
AND (#{keyword} IS NULL OR o.inbound_code LIKE CONCAT('%',#{keyword},'%')
|
||||
OR o.customer_name LIKE CONCAT('%',#{keyword},'%'))
|
||||
ORDER BY t.created_at,t.id
|
||||
""")
|
||||
List<PendingTask> pending(@Param("operatorId") long operatorId, @Param("keyword") String keyword);
|
||||
@Select("""
|
||||
SELECT t.id task_id,t.inbound_id,o.inbound_code,t.tenant_id,o.customer_name,t.warehouse_id,
|
||||
o.warehouse_name,t.total_cartons,t.counted_cartons,t.allocated_cartons bound_cartons,
|
||||
t.counted_cartons-t.allocated_cartons unbound_cartons,t.discrepancy_cartons,t.task_status,t.created_at
|
||||
FROM fl_dropship_pallet_task t JOIN fl_inbound_order o ON o.id=t.inbound_id AND o.tenant_id=t.tenant_id
|
||||
WHERE t.task_status IN ('PENDING','CLAIMED') AND (t.assignee_id IS NULL OR t.assignee_id=#{operatorId})
|
||||
AND (#{keyword} IS NULL OR o.inbound_code LIKE CONCAT('%',#{keyword},'%') OR o.customer_name LIKE CONCAT('%',#{keyword},'%'))
|
||||
ORDER BY t.created_at,t.id""")
|
||||
List<PendingTask> pending(@Param("operatorId") long operatorId,@Param("keyword") String keyword);
|
||||
|
||||
@Select("""
|
||||
SELECT t.*,o.inbound_code,o.customer_name,o.warehouse_name,o.process_status,o.fulfillment_type
|
||||
FROM fl_dropship_pallet_task t JOIN fl_inbound_order o ON o.id=t.inbound_id AND o.tenant_id=t.tenant_id
|
||||
WHERE t.id=#{taskId} AND t.warehouse_id=#{warehouseId}
|
||||
""")
|
||||
TaskRow task(@Param("taskId") long taskId, @Param("warehouseId") long warehouseId);
|
||||
@Select("""
|
||||
SELECT t.*,o.inbound_code,o.customer_name,o.warehouse_name,o.process_status,o.fulfillment_type
|
||||
FROM fl_dropship_pallet_task t JOIN fl_inbound_order o ON o.id=t.inbound_id AND o.tenant_id=t.tenant_id
|
||||
WHERE t.id=#{taskId} AND t.warehouse_id=#{warehouseId}""")
|
||||
TaskRow task(@Param("taskId") long taskId,@Param("warehouseId") long warehouseId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task
|
||||
SET task_status='CLAIMED',assignee_id=#{operatorId},assignee_name=#{operatorName},
|
||||
claimed_at=COALESCE(claimed_at,CURRENT_TIMESTAMP(3)),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND warehouse_id=#{warehouseId} AND task_status IN ('PENDING','CLAIMED')
|
||||
AND (assignee_id IS NULL OR assignee_id=#{operatorId})
|
||||
""")
|
||||
int claim(@Param("taskId") long taskId, @Param("warehouseId") long warehouseId,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task SET task_status='CLAIMED',assignee_id=#{operatorId},assignee_name=#{operatorName},
|
||||
claimed_at=COALESCE(claimed_at,CURRENT_TIMESTAMP(3)),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND warehouse_id=#{warehouseId} AND task_status IN ('PENDING','CLAIMED')
|
||||
AND (assignee_id IS NULL OR assignee_id=#{operatorId})""")
|
||||
int claim(@Param("taskId") long taskId,@Param("warehouseId") long warehouseId,
|
||||
@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_dropship_pallet
|
||||
(id,task_id,tenant_id,inbound_id,warehouse_id,pallet_status,open_operation_id,version,created_by,created_by_name,created_at,updated_at)
|
||||
VALUES(#{id},#{taskId},#{tenantId},#{inboundId},#{warehouseId},'OPEN',#{operationId},1,
|
||||
#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insertPallet(@Param("id") long id, @Param("taskId") long taskId,
|
||||
@Param("tenantId") long tenantId, @Param("inboundId") long inboundId,
|
||||
@Param("warehouseId") long warehouseId, @Param("operationId") String operationId,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName);
|
||||
@Insert("""
|
||||
INSERT INTO fl_dropship_pallet
|
||||
(id,task_id,tenant_id,inbound_id,warehouse_id,pallet_no,pallet_status,open_operation_id,version,created_by,created_by_name,created_at,updated_at)
|
||||
VALUES(#{id},#{taskId},#{tenantId},#{inboundId},#{warehouseId},#{palletNo},'OPEN',#{operationId},1,
|
||||
#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))""")
|
||||
int insertPallet(@Param("id") long id,@Param("taskId") long taskId,@Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId,@Param("warehouseId") long warehouseId,@Param("palletNo") String palletNo,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE open_operation_id=#{operationId}")
|
||||
PalletRow palletByOpenOperation(@Param("operationId") String operationId);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE pallet_no=#{palletNo}") PalletRow palletByNo(@Param("palletNo") String palletNo);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE seal_operation_id=#{operationId}") PalletRow palletBySealOperation(@Param("operationId") String operationId);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE id=#{palletId} AND task_id=#{taskId} AND warehouse_id=#{warehouseId}")
|
||||
PalletRow pallet(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("warehouseId") long warehouseId);
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE task_id=#{taskId} ORDER BY id") List<PalletRow> pallets(@Param("taskId") long taskId);
|
||||
@Select("SELECT p.* FROM fl_dropship_pallet p JOIN fl_dropship_pallet_task t ON t.id=p.task_id WHERE t.tenant_id=#{tenantId} AND t.inbound_id=#{inboundId} ORDER BY p.id")
|
||||
List<PalletRow> palletsByInbound(@Param("tenantId") long tenantId,@Param("inboundId") long inboundId);
|
||||
@Select("SELECT inbound_code FROM fl_inbound_order WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0")
|
||||
String inboundCode(@Param("tenantId") long tenantId,@Param("inboundId") long inboundId);
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE seal_operation_id=#{operationId}")
|
||||
PalletRow palletBySealOperation(@Param("operationId") String operationId);
|
||||
@Select("""
|
||||
SELECT l.id carton_label_id,l.inbound_detail_id,l.sku_code,l.cargo_name,l.units_per_carton,l.inbound_code,l.box_code
|
||||
FROM fl_inbound_carton_label l JOIN fl_dropship_pallet_task t ON t.inbound_id=l.inbound_id AND t.tenant_id=l.tenant_id
|
||||
WHERE t.id=#{taskId} AND l.box_code=#{boxCode} AND l.batch_status='ACTIVE'""")
|
||||
LabelRow label(@Param("taskId") long taskId,@Param("boxCode") String boxCode);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.count_operation_id=#{operationId}") CartonRow cartonByCountOperation(@Param("operationId") String operationId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.id=#{cartonId}") CartonRow carton(@Param("taskId") long taskId,@Param("cartonId") long cartonId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.carton_label_id=#{labelId}")
|
||||
CartonRow countedByLabel(@Param("taskId") long taskId,@Param("labelId") long labelId);
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE id=#{palletId} AND task_id=#{taskId} AND warehouse_id=#{warehouseId}")
|
||||
PalletRow pallet(@Param("taskId") long taskId, @Param("palletId") long palletId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
@Insert("""
|
||||
INSERT INTO fl_dropship_pallet_carton
|
||||
(id,task_id,pallet_id,tenant_id,inbound_id,inbound_detail_id,carton_label_id,sku_code,cargo_name,box_code,
|
||||
expected_sku_quantity,actual_sku_quantity,discrepancy_status,discrepancy_remark,count_operation_id,last_operation_id,
|
||||
counted_by,counted_by_name,counted_at,bound_by,bound_by_name,bound_at)
|
||||
VALUES(#{id},#{taskId},#{palletId},#{tenantId},#{inboundId},#{detailId},#{labelId},#{skuCode},#{cargoName},#{boxCode},
|
||||
#{expectedQuantity},#{actualQuantity},#{discrepancyStatus},#{discrepancyRemark},#{operationId},#{operationId},
|
||||
#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3),CASE WHEN #{palletId} IS NULL THEN NULL ELSE #{operatorId} END,
|
||||
CASE WHEN #{palletId} IS NULL THEN NULL ELSE #{operatorName} END,CASE WHEN #{palletId} IS NULL THEN NULL ELSE CURRENT_TIMESTAMP(3) END)""")
|
||||
int insertCarton(@Param("id") long id,@Param("taskId") long taskId,@Param("palletId") Long palletId,
|
||||
@Param("tenantId") long tenantId,@Param("inboundId") long inboundId,@Param("detailId") long detailId,
|
||||
@Param("labelId") long labelId,@Param("skuCode") String skuCode,@Param("cargoName") String cargoName,
|
||||
@Param("boxCode") String boxCode,@Param("expectedQuantity") int expectedQuantity,@Param("actualQuantity") int actualQuantity,
|
||||
@Param("discrepancyStatus") String discrepancyStatus,@Param("discrepancyRemark") String discrepancyRemark,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE task_id=#{taskId} ORDER BY id")
|
||||
List<PalletRow> pallets(@Param("taskId") long taskId);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task SET counted_cartons=counted_cartons+1,allocated_cartons=allocated_cartons+#{boundCount},
|
||||
discrepancy_cartons=discrepancy_cartons+#{discrepancyCount},version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND task_status='CLAIMED' AND assignee_id=#{operatorId} AND counted_cartons<total_cartons
|
||||
AND allocated_cartons+#{boundCount}<=total_cartons""")
|
||||
int addCounted(@Param("taskId") long taskId,@Param("operatorId") long operatorId,
|
||||
@Param("boundCount") int boundCount,@Param("discrepancyCount") int discrepancyCount);
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet_carton WHERE task_id=#{taskId} AND pallet_id IS NULL") int unboundCount(@Param("taskId") long taskId);
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet_carton WHERE task_id=#{taskId} AND bind_operation_id=#{operationId}")
|
||||
int bindOperationCount(@Param("taskId") long taskId,@Param("operationId") String operationId);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_carton SET pallet_id=#{palletId},bind_operation_id=#{operationId},last_operation_id=#{operationId},
|
||||
bound_by=#{operatorId},bound_by_name=#{operatorName},bound_at=CURRENT_TIMESTAMP(3) WHERE task_id=#{taskId} AND pallet_id IS NULL""")
|
||||
int bindPending(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("operationId") String operationId,
|
||||
@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Update("UPDATE fl_dropship_pallet_task SET allocated_cartons=allocated_cartons+#{delta},version=version+1,updated_at=CURRENT_TIMESTAMP(3) WHERE id=#{taskId} AND assignee_id=#{operatorId} AND allocated_cartons+#{delta} BETWEEN 0 AND counted_cartons")
|
||||
int adjustBound(@Param("taskId") long taskId,@Param("operatorId") long operatorId,@Param("delta") int delta);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_carton SET pallet_id=#{targetPalletId},last_operation_id=#{operationId},
|
||||
bound_by=CASE WHEN #{targetPalletId} IS NULL THEN NULL ELSE #{operatorId} END,
|
||||
bound_by_name=CASE WHEN #{targetPalletId} IS NULL THEN NULL ELSE #{operatorName} END,
|
||||
bound_at=CASE WHEN #{targetPalletId} IS NULL THEN NULL ELSE CURRENT_TIMESTAMP(3) END
|
||||
WHERE id=#{cartonId} AND task_id=#{taskId} AND last_operation_id<>#{operationId}""")
|
||||
int moveCarton(@Param("taskId") long taskId,@Param("cartonId") long cartonId,@Param("targetPalletId") Long targetPalletId,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT p.* FROM fl_dropship_pallet p JOIN fl_dropship_pallet_task t ON t.id=p.task_id
|
||||
WHERE t.tenant_id=#{tenantId} AND t.inbound_id=#{inboundId} ORDER BY p.id
|
||||
""")
|
||||
List<PalletRow> palletsByInbound(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet SET pallet_status='SEALED',seal_operation_id=#{operationId},completed_by=#{operatorId},
|
||||
completed_by_name=#{operatorName},completed_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{palletId} AND task_id=#{taskId} AND pallet_status='OPEN' AND version=#{expectedVersion}
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId})
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.task_id=#{taskId} AND c.pallet_id IS NULL)""")
|
||||
int seal(@Param("taskId") long taskId,@Param("palletId") long palletId,@Param("expectedVersion") int expectedVersion,
|
||||
@Param("operationId") String operationId,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet WHERE task_id=#{taskId} AND pallet_status='OPEN'") int openPalletCount(@Param("taskId") long taskId);
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task SET task_status='COMPLETED',completed_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND task_status='CLAIMED' AND counted_cartons=total_cartons AND allocated_cartons=total_cartons
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet p WHERE p.task_id=#{taskId} AND p.pallet_status='OPEN')""") int completeTask(@Param("taskId") long taskId);
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order o SET o.process_status='WAIT_SPLIT',o.pallet_count=(SELECT COUNT(*) FROM fl_dropship_pallet p
|
||||
WHERE p.inbound_id=o.id AND p.pallet_status='SEALED'),o.updated_by=#{operatorId},o.version=o.version+1
|
||||
WHERE o.id=#{inboundId} AND o.tenant_id=#{tenantId} AND o.process_status='WAIT_PALLETIZE'
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_task t WHERE t.id=#{taskId} AND t.task_status='COMPLETED')""")
|
||||
int completeInbound(@Param("taskId") long taskId,@Param("tenantId") long tenantId,@Param("inboundId") long inboundId,@Param("operatorId") long operatorId);
|
||||
|
||||
@Select("SELECT inbound_code FROM fl_inbound_order WHERE tenant_id=#{tenantId} AND id=#{inboundId} AND deleted=0")
|
||||
String inboundCode(@Param("tenantId") long tenantId, @Param("inboundId") long inboundId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.pallet_id=#{palletId} ORDER BY c.sku_code,c.inbound_detail_id,c.id") List<CartonRow> cartons(@Param("palletId") long palletId);
|
||||
@Select("SELECT c.*,p.pallet_no FROM fl_dropship_pallet_carton c LEFT JOIN fl_dropship_pallet p ON p.id=c.pallet_id WHERE c.task_id=#{taskId} AND c.pallet_id IS NULL ORDER BY c.counted_at,c.id") List<CartonRow> unboundCartons(@Param("taskId") long taskId);
|
||||
@Select("""
|
||||
SELECT COALESCE(o.actual_gross_weight_kg*COUNT(c.id)/NULLIF(o.total_package_count,0),0) weight_kg,
|
||||
COALESCE(o.actual_volume_cbm*COUNT(c.id)/NULLIF(o.total_package_count,0),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
LEFT JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,o.actual_gross_weight_kg,o.actual_volume_cbm,o.total_package_count""")
|
||||
PalletMetrics metrics(@Param("palletId") long palletId);
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,'SEALED_PALLET' unit_type,TRUE indivisible,COUNT(c.id) carton_count,
|
||||
COUNT(DISTINCT c.sku_code) sku_count,
|
||||
COALESCE(o.actual_gross_weight_kg*COUNT(c.id)/NULLIF(o.total_package_count,0),0) weight_kg,
|
||||
COALESCE(o.actual_volume_cbm*COUNT(c.id)/NULLIF(o.total_package_count,0),0) volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id
|
||||
WHERE p.inbound_id=#{inboundId} AND p.pallet_status='SEALED'
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_loading_plan_item i WHERE i.pallet_id=p.id AND i.deleted=0)
|
||||
GROUP BY p.id,p.pallet_no,o.actual_gross_weight_kg,o.actual_volume_cbm,o.total_package_count ORDER BY p.id
|
||||
""") List<PalletCandidate> palletCandidates(@Param("inboundId") long inboundId);
|
||||
|
||||
@Select("""
|
||||
SELECT l.id carton_label_id,l.inbound_detail_id,l.sku_code,l.cargo_name,l.detail_package_count,
|
||||
l.inbound_code,l.box_code
|
||||
FROM fl_inbound_carton_label l
|
||||
JOIN fl_dropship_pallet_task t ON t.inbound_id=l.inbound_id AND t.tenant_id=l.tenant_id
|
||||
WHERE t.id=#{taskId} AND l.box_code=#{boxCode} AND l.batch_status='ACTIVE'
|
||||
""")
|
||||
LabelRow label(@Param("taskId") long taskId, @Param("boxCode") String boxCode);
|
||||
@Select("""
|
||||
SELECT p.id pallet_id,p.pallet_no,p.pallet_status,p.tenant_id account_id,p.inbound_id,o.order_id,o.order_no,
|
||||
o.inbound_code,o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,
|
||||
o.destination_code destination_warehouse_code,o.destination_name destination_warehouse_name,o.customer_name,
|
||||
c.inbound_detail_id,c.sku_code,MAX(c.cargo_name) cargo_name,COUNT(c.id) carton_count,
|
||||
SUM(c.actual_sku_quantity) actual_sku_quantity,
|
||||
COALESCE(o.actual_gross_weight_kg/NULLIF(o.total_package_count,0),0) unit_gross_weight_kg,
|
||||
COALESCE(o.actual_net_weight_kg/NULLIF(o.total_package_count,0),0) unit_net_weight_kg,
|
||||
COALESCE(o.actual_volume_cbm/NULLIF(o.total_package_count,0),0) unit_volume_cbm
|
||||
FROM fl_dropship_pallet p JOIN fl_inbound_order o ON o.id=p.inbound_id AND o.tenant_id=p.tenant_id
|
||||
JOIN fl_dropship_pallet_carton c ON c.pallet_id=p.id WHERE p.id=#{palletId}
|
||||
GROUP BY p.id,p.pallet_no,p.pallet_status,p.tenant_id,p.inbound_id,o.order_id,o.order_no,o.inbound_code,
|
||||
o.warehouse_id,o.channel_code,o.channel_name,o.destination_warehouse_id,o.destination_code,o.destination_name,
|
||||
o.customer_name,c.inbound_detail_id,c.sku_code,o.actual_gross_weight_kg,o.actual_net_weight_kg,o.actual_volume_cbm,o.total_package_count
|
||||
ORDER BY c.inbound_detail_id
|
||||
""") List<PalletLine> sealedPalletLines(@Param("palletId") long palletId);
|
||||
|
||||
@Select("""
|
||||
SELECT l.id carton_label_id,l.inbound_detail_id,l.sku_code,l.cargo_name,l.detail_package_count,
|
||||
l.inbound_code,l.box_code
|
||||
FROM fl_inbound_carton_label l
|
||||
WHERE l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId}
|
||||
AND l.inbound_detail_id=#{detailId} AND l.batch_status='ACTIVE'
|
||||
AND NOT EXISTS (SELECT 1 FROM fl_dropship_pallet_carton c
|
||||
WHERE c.task_id=#{taskId} AND c.carton_label_id=l.id)
|
||||
ORDER BY CASE WHEN l.box_code=#{preferredBoxCode} THEN 0 ELSE 1 END,l.carton_index,l.id
|
||||
LIMIT #{count}
|
||||
""")
|
||||
List<LabelRow> unboundLabels(@Param("taskId") long taskId, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("detailId") long detailId,
|
||||
@Param("preferredBoxCode") String preferredBoxCode,
|
||||
@Param("count") int count);
|
||||
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet_carton WHERE operation_id=#{operationId}")
|
||||
int operationCount(@Param("operationId") String operationId);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_dropship_pallet_carton
|
||||
(id,task_id,pallet_id,tenant_id,inbound_id,inbound_detail_id,carton_label_id,sku_code,cargo_name,
|
||||
box_code,operation_id,bound_by,bound_by_name,bound_at)
|
||||
VALUES(#{id},#{taskId},#{palletId},#{tenantId},#{inboundId},#{detailId},#{labelId},#{skuCode},#{cargoName},
|
||||
#{boxCode},#{operationId},#{operatorId},#{operatorName},CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insertCarton(@Param("id") long id, @Param("taskId") long taskId,
|
||||
@Param("palletId") long palletId, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("detailId") long detailId,
|
||||
@Param("labelId") long labelId, @Param("skuCode") String skuCode,
|
||||
@Param("cargoName") String cargoName, @Param("boxCode") String boxCode,
|
||||
@Param("operationId") String operationId, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task
|
||||
SET allocated_cartons=allocated_cartons+#{count},version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND task_status='CLAIMED' AND assignee_id=#{operatorId}
|
||||
AND allocated_cartons+#{count}<=total_cartons
|
||||
""")
|
||||
int addAllocated(@Param("taskId") long taskId, @Param("operatorId") long operatorId,
|
||||
@Param("count") int count);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet
|
||||
SET pallet_no=#{palletNo},pallet_status='COMPLETED',seal_operation_id=#{operationId},
|
||||
completed_by=#{operatorId},completed_by_name=#{operatorName},completed_at=CURRENT_TIMESTAMP(3),
|
||||
version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{palletId} AND task_id=#{taskId} AND pallet_status='OPEN' AND version=#{expectedVersion}
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId})
|
||||
""")
|
||||
int seal(@Param("taskId") long taskId, @Param("palletId") long palletId,
|
||||
@Param("expectedVersion") int expectedVersion, @Param("palletNo") String palletNo,
|
||||
@Param("operationId") String operationId, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("SELECT COUNT(*) FROM fl_dropship_pallet WHERE task_id=#{taskId} AND pallet_status='OPEN'")
|
||||
int openPalletCount(@Param("taskId") long taskId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_dropship_pallet_task
|
||||
SET task_status='COMPLETED',completed_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{taskId} AND task_status='CLAIMED' AND allocated_cartons=total_cartons
|
||||
AND NOT EXISTS(SELECT 1 FROM fl_dropship_pallet p WHERE p.task_id=#{taskId} AND p.pallet_status='OPEN')
|
||||
""")
|
||||
int completeTask(@Param("taskId") long taskId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order o
|
||||
SET o.process_status='WAIT_SPLIT',o.pallet_count=(SELECT COUNT(*) FROM fl_dropship_pallet p
|
||||
WHERE p.inbound_id=o.id AND p.pallet_status='COMPLETED'),o.updated_by=#{operatorId},o.version=o.version+1
|
||||
WHERE o.id=#{inboundId} AND o.tenant_id=#{tenantId} AND o.process_status='WAIT_PALLETIZE'
|
||||
AND EXISTS(SELECT 1 FROM fl_dropship_pallet_task t WHERE t.id=#{taskId} AND t.task_status='COMPLETED')
|
||||
""")
|
||||
int completeInbound(@Param("taskId") long taskId, @Param("tenantId") long tenantId,
|
||||
@Param("inboundId") long inboundId, @Param("operatorId") long operatorId);
|
||||
|
||||
@Select("""
|
||||
SELECT c.* FROM fl_dropship_pallet_carton c WHERE c.pallet_id=#{palletId}
|
||||
ORDER BY c.sku_code,c.inbound_detail_id,c.id
|
||||
""")
|
||||
List<CartonRow> cartons(@Param("palletId") long palletId);
|
||||
|
||||
@Getter @Setter class TaskRow {
|
||||
private Long id; private Long tenantId; private Long inboundId; private Long warehouseId;
|
||||
private String inboundCode; private String customerName; private String warehouseName;
|
||||
private String taskStatus; private Integer totalCartons; private Integer allocatedCartons;
|
||||
private Long assigneeId; private String assigneeName; private Integer version;
|
||||
private String processStatus; private String fulfillmentType;
|
||||
}
|
||||
@Getter @Setter class PalletRow {
|
||||
private Long id; private Long taskId; private Long tenantId; private Long inboundId;
|
||||
private Long warehouseId; private String palletNo; private String palletStatus;
|
||||
private String openOperationId; private String sealOperationId; private Integer version;
|
||||
private LocalDateTime completedAt;
|
||||
}
|
||||
@Getter @Setter class LabelRow {
|
||||
private Long cartonLabelId; private Long inboundDetailId; private String skuCode;
|
||||
private String cargoName; private Integer detailPackageCount; private String inboundCode;
|
||||
private String boxCode;
|
||||
}
|
||||
@Getter @Setter class CartonRow {
|
||||
private Long inboundId; private Long inboundDetailId; private String skuCode;
|
||||
private String cargoName; private String boxCode;
|
||||
}
|
||||
@Getter @Setter class TaskRow { private Long id,tenantId,inboundId,warehouseId,assigneeId; private String inboundCode,customerName,warehouseName,taskStatus,assigneeName,processStatus,fulfillmentType; private Integer totalCartons,countedCartons,allocatedCartons,discrepancyCartons,version; }
|
||||
@Getter @Setter class PalletRow { private Long id,taskId,tenantId,inboundId,warehouseId; private String palletNo,palletStatus,sealOperationId; private Integer version; private LocalDateTime completedAt; }
|
||||
@Getter @Setter class LabelRow { private Long cartonLabelId,inboundDetailId; private String skuCode,cargoName,inboundCode,boxCode; private Integer unitsPerCarton; }
|
||||
@Getter @Setter class CartonRow { private Long id,taskId,palletId,inboundId,inboundDetailId; private String palletNo,skuCode,cargoName,boxCode,discrepancyStatus,discrepancyRemark; private Integer expectedSkuQuantity,actualSkuQuantity; }
|
||||
record PalletMetrics(BigDecimal weightKg,BigDecimal volumeCbm) {}
|
||||
@Getter @Setter class PalletLine { private Long palletId,tenantId,accountId,inboundId,orderId,warehouseId,destinationWarehouseId,inboundDetailId; private String palletNo,palletStatus,orderNo,inboundCode,channelCode,channelName,destinationWarehouseCode,destinationWarehouseName,customerName,skuCode,cargoName; private Integer cartonCount,actualSkuQuantity; private BigDecimal unitGrossWeightKg,unitNetWeightKg,unitVolumeCbm; }
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public interface LoadingCompletedEventMapper {
|
||||
JOIN fl_loading_plan_item item ON item.loading_plan_id=#{planId}
|
||||
AND item.account_id=carton.tenant_id AND item.inbound_id=carton.inbound_id
|
||||
AND item.inbound_detail_id=carton.inbound_detail_id AND item.deleted=0
|
||||
WHERE pallet.pallet_status='COMPLETED'
|
||||
WHERE pallet.pallet_status='SEALED'
|
||||
ORDER BY carton.tenant_id,carton.inbound_id,pallet.id,item.inbound_detail_id,carton.id
|
||||
""")
|
||||
List<PalletRow> actualPalletRows(@Param("planId") long planId);
|
||||
@@ -99,7 +99,7 @@ public interface LoadingCompletedEventMapper {
|
||||
@Select("""
|
||||
SELECT COUNT(DISTINCT p.id) FROM fl_dropship_pallet p
|
||||
JOIN fl_loading_plan_item i ON i.loading_plan_id=#{planId} AND i.account_id=p.tenant_id
|
||||
AND i.inbound_id=p.inbound_id AND i.deleted=0 WHERE p.pallet_status='COMPLETED'
|
||||
AND i.inbound_id=p.inbound_id AND i.deleted=0 WHERE p.pallet_status='SEALED'
|
||||
""")
|
||||
int workPalletCount(@Param("planId") long planId);
|
||||
|
||||
|
||||
@@ -171,7 +171,8 @@ public interface LoadingListQueryMapper {
|
||||
SELECT i.id AS item_id, i.account_id, i.customer_name, i.order_id, i.order_no,
|
||||
i.inbound_id, i.inbound_detail_id, i.inbound_code, b.container_no,
|
||||
i.sku_code, i.destination_warehouse_name, i.product_name_zh,
|
||||
product_name_en, product_image_url, i.package_count,
|
||||
product_name_en, product_image_url, i.unit_type, i.pallet_id,
|
||||
i.pallet_no, i.indivisible, i.package_count,
|
||||
COALESCE(NULLIF(i.pallet_count,0),
|
||||
CEIL(COALESCE(NULLIF(inbound.pallet_count,0),
|
||||
(SELECT COUNT(*) FROM fl_inbound_weight_result weight
|
||||
@@ -420,6 +421,10 @@ public interface LoadingListQueryMapper {
|
||||
private String productNameZh;
|
||||
private String productNameEn;
|
||||
private String productImageUrl;
|
||||
private String unitType;
|
||||
private Long palletId;
|
||||
private String palletNo;
|
||||
private Boolean indivisible;
|
||||
private Integer packageCount;
|
||||
private Integer palletCount;
|
||||
private BigDecimal grossWeightKg;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingWorkPalletCartonEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingWorkPalletCartonMapper extends BaseMapper<LoadingWorkPalletCartonEntity> {
|
||||
@Select("""
|
||||
SELECT label.id carton_label_id,label.tenant_id,label.inbound_id,label.inbound_detail_id,
|
||||
label.box_code,plan_item.id loading_plan_item_id,pick_item.id pick_item_id,
|
||||
plan_item.sku_code,plan_item.units_per_carton
|
||||
FROM fl_inbound_carton_label label
|
||||
JOIN fl_loading_plan_item plan_item
|
||||
ON plan_item.account_id=label.tenant_id AND plan_item.inbound_id=label.inbound_id
|
||||
AND plan_item.inbound_detail_id=label.inbound_detail_id AND plan_item.deleted=0
|
||||
JOIN fl_loading_pick_item pick_item ON pick_item.loading_plan_item_id=plan_item.id
|
||||
JOIN fl_loading_pick_task task ON task.id=pick_item.pick_task_id
|
||||
AND task.loading_plan_id=plan_item.loading_plan_id
|
||||
JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
JOIN fl_inbound_order inbound_order ON inbound_order.id=plan_item.inbound_id
|
||||
AND inbound_order.tenant_id=plan_item.account_id AND inbound_order.deleted=0
|
||||
WHERE task.id=#{taskId} AND task.task_status='LOADING' AND booking.warehouse_id=#{warehouseId}
|
||||
AND label.box_code=#{boxCode} AND label.batch_status='ACTIVE'
|
||||
AND inbound_order.fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
AND pick_item.item_status<>'SKIPPED'
|
||||
ORDER BY label.tenant_id,label.id
|
||||
""")
|
||||
List<CartonCandidate> selectCandidates(@Param("taskId") long taskId,
|
||||
@Param("warehouseId") long warehouseId,
|
||||
@Param("boxCode") String boxCode);
|
||||
|
||||
@Select("SELECT * FROM fl_loading_work_pallet_carton WHERE pick_task_id=#{taskId} AND carton_label_id=#{labelId}")
|
||||
LoadingWorkPalletCartonEntity selectBinding(@Param("taskId") long taskId,
|
||||
@Param("labelId") long labelId);
|
||||
|
||||
@Select("SELECT * FROM fl_loading_work_pallet_carton WHERE operation_id=#{operationId}")
|
||||
LoadingWorkPalletCartonEntity selectByOperation(@Param("operationId") String operationId);
|
||||
|
||||
@Select("SELECT * FROM fl_loading_work_pallet_carton WHERE work_pallet_id=#{palletId} ORDER BY id")
|
||||
List<LoadingWorkPalletCartonEntity> selectPalletCartons(@Param("palletId") long palletId);
|
||||
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_work_pallet_carton WHERE work_pallet_id=#{palletId}")
|
||||
int countPalletCartons(@Param("palletId") long palletId);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT carton.pick_item_id
|
||||
FROM fl_loading_work_pallet_carton carton
|
||||
JOIN fl_loading_pick_item pick_item ON pick_item.id=carton.pick_item_id
|
||||
WHERE carton.pick_task_id=#{taskId}
|
||||
GROUP BY carton.pick_item_id,pick_item.picked_package_count
|
||||
HAVING COUNT(*)>pick_item.picked_package_count
|
||||
) unreconciled
|
||||
""")
|
||||
int countUnreconciled(@Param("taskId") long taskId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_loading_work_pallet_carton
|
||||
SET work_pallet_id=#{targetPalletId},bound_by=#{operatorId},bound_by_name=#{operatorName},
|
||||
operation_id=#{operationId},version=version+1
|
||||
WHERE id=#{id} AND version=#{version}
|
||||
""")
|
||||
int move(@Param("id") long id, @Param("version") int version,
|
||||
@Param("targetPalletId") long targetPalletId,
|
||||
@Param("operatorId") long operatorId, @Param("operatorName") String operatorName,
|
||||
@Param("operationId") String operationId);
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
class CartonCandidate {
|
||||
private Long cartonLabelId;
|
||||
private Long tenantId;
|
||||
private Long inboundId;
|
||||
private Long inboundDetailId;
|
||||
private String boxCode;
|
||||
private Long loadingPlanItemId;
|
||||
private Long pickItemId;
|
||||
private String skuCode;
|
||||
private Integer unitsPerCarton;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.firstleg.entity.LoadingWorkPalletEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface LoadingWorkPalletMapper extends BaseMapper<LoadingWorkPalletEntity> {
|
||||
@Select("""
|
||||
SELECT task.id task_id,task.loading_plan_id,task.task_status,booking.warehouse_id,
|
||||
assignment.assignee_id,
|
||||
SUM(CASE WHEN inbound_order.fulfillment_type='ONE_CLICK_DROPSHIP' THEN 1 ELSE 0 END) dropship_item_count
|
||||
FROM fl_loading_pick_task task
|
||||
JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
JOIN fl_loading_pick_item pick_item ON pick_item.pick_task_id=task.id
|
||||
JOIN fl_loading_plan_item plan_item ON plan_item.id=pick_item.loading_plan_item_id AND plan_item.deleted=0
|
||||
JOIN fl_inbound_order inbound_order ON inbound_order.id=plan_item.inbound_id
|
||||
AND inbound_order.tenant_id=plan_item.account_id AND inbound_order.deleted=0
|
||||
LEFT JOIN fl_loading_pick_assignment assignment ON assignment.pick_task_id=task.id
|
||||
WHERE task.id=#{taskId} AND booking.warehouse_id=#{warehouseId}
|
||||
GROUP BY task.id,task.loading_plan_id,task.task_status,booking.warehouse_id,assignment.assignee_id
|
||||
""")
|
||||
TaskContext selectTaskContext(@Param("taskId") long taskId, @Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("SELECT * FROM fl_loading_work_pallet WHERE open_operation_id=#{operationId}")
|
||||
LoadingWorkPalletEntity selectByOpenOperation(@Param("operationId") String operationId);
|
||||
|
||||
@Select("SELECT * FROM fl_loading_work_pallet WHERE seal_operation_id=#{operationId}")
|
||||
LoadingWorkPalletEntity selectBySealOperation(@Param("operationId") String operationId);
|
||||
|
||||
@Select("""
|
||||
SELECT pallet.*
|
||||
FROM fl_loading_work_pallet pallet
|
||||
JOIN fl_loading_pick_task task ON task.id=pallet.pick_task_id
|
||||
JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
WHERE pallet.pick_task_id=#{taskId} AND pallet.id=#{palletId}
|
||||
AND booking.warehouse_id=#{warehouseId}
|
||||
""")
|
||||
LoadingWorkPalletEntity selectScoped(@Param("taskId") long taskId,
|
||||
@Param("palletId") long palletId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("""
|
||||
SELECT pallet.*
|
||||
FROM fl_loading_work_pallet pallet
|
||||
JOIN fl_loading_pick_task task ON task.id=pallet.pick_task_id
|
||||
JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id=plan.container_booking_id
|
||||
WHERE pallet.pick_task_id=#{taskId} AND booking.warehouse_id=#{warehouseId}
|
||||
AND pallet.pallet_status<>'CANCELLED'
|
||||
ORDER BY pallet.id
|
||||
""")
|
||||
List<LoadingWorkPalletEntity> selectTaskPallets(@Param("taskId") long taskId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_loading_work_pallet
|
||||
SET pallet_status='SEALED',seal_operation_id=#{operationId},sealed_by=#{operatorId},
|
||||
sealed_by_name=#{operatorName},sealed_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{palletId} AND pick_task_id=#{taskId} AND warehouse_id=#{warehouseId}
|
||||
AND pallet_status='OPEN' AND version=#{expectedVersion}
|
||||
""")
|
||||
int seal(@Param("taskId") long taskId, @Param("palletId") long palletId,
|
||||
@Param("warehouseId") long warehouseId, @Param("expectedVersion") int expectedVersion,
|
||||
@Param("operationId") String operationId, @Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
record TaskContext(Long taskId, Long loadingPlanId, String taskStatus, Long warehouseId,
|
||||
Long assigneeId, Integer dropshipItemCount) {}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
SELECT carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id,COUNT(*),
|
||||
COALESCE(batch.total_volume_cbm/NULLIF(source.total_package_count,0),0)
|
||||
FROM fl_dropship_pallet_carton carton
|
||||
JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='COMPLETED'
|
||||
JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='SEALED'
|
||||
JOIN fl_inbound_order source ON source.id=carton.inbound_id AND source.tenant_id=carton.tenant_id
|
||||
AND source.fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
JOIN fl_inbound_measurement batch ON batch.inbound_id=source.id AND batch.tenant_id=source.tenant_id
|
||||
@@ -399,7 +399,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
SELECT carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id,COUNT(*),
|
||||
COALESCE(batch.total_volume_cbm/NULLIF(source.total_package_count,0),0)
|
||||
FROM fl_dropship_pallet_carton carton
|
||||
JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='COMPLETED'
|
||||
JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='SEALED'
|
||||
JOIN fl_inbound_order source ON source.id=carton.inbound_id AND source.tenant_id=carton.tenant_id
|
||||
AND source.fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
JOIN fl_inbound_measurement batch ON batch.inbound_id=source.id AND batch.tenant_id=source.tenant_id
|
||||
|
||||
@@ -187,7 +187,8 @@ public class LoadingListRepository {
|
||||
row.getOrderId(), row.getOrderNo(), row.getInboundId(), row.getInboundDetailId(),
|
||||
row.getInboundCode(), row.getContainerNo(), row.getSkuCode(),
|
||||
row.getDestinationWarehouseName(), row.getProductNameZh(), row.getProductNameEn(),
|
||||
row.getProductImageUrl(), integer(row.getPackageCount()), integer(row.getPalletCount()),
|
||||
row.getProductImageUrl(), row.getUnitType(), row.getPalletId(), row.getPalletNo(),
|
||||
Boolean.TRUE.equals(row.getIndivisible()), integer(row.getPackageCount()), integer(row.getPalletCount()),
|
||||
decimal(row.getGrossWeightKg()), decimal(row.getNetWeightKg()), decimal(row.getVolumeCbm()),
|
||||
decimal(row.getDimensionalWeightKg()), value(row.getProductQuantity()),
|
||||
integer(row.getUnitsPerCarton()), row.getFbaTrackingNo(), row.getShipmentNo(), row.getShippingMark(),
|
||||
|
||||
@@ -97,6 +97,11 @@ public class SmartLoadingRepository {
|
||||
.eq(LoadingPlanItemEntity::getInboundId, inboundId)
|
||||
.eq(LoadingPlanItemEntity::getDeleted, false));
|
||||
}
|
||||
public List<LoadingPlanItemEntity> activeItemsByPallet(long palletId) {
|
||||
return items.selectList(new LambdaQueryWrapper<LoadingPlanItemEntity>()
|
||||
.eq(LoadingPlanItemEntity::getPalletId, palletId)
|
||||
.eq(LoadingPlanItemEntity::getDeleted, false));
|
||||
}
|
||||
public LoadingPlanEntity plan(long planId) { return plans.selectById(planId); }
|
||||
public LoadingPlanEntity planByBooking(long bookingId) {
|
||||
return plans.selectOne(new LambdaQueryWrapper<LoadingPlanEntity>()
|
||||
@@ -121,6 +126,13 @@ public class SmartLoadingRepository {
|
||||
.eq(LoadingPlanItemEntity::getInboundDetailId, detailId)
|
||||
.eq(LoadingPlanItemEntity::getDeleted, false));
|
||||
}
|
||||
public LoadingPlanItemEntity activePalletItem(long planId, long palletId, long detailId) {
|
||||
return items.selectOne(new LambdaQueryWrapper<LoadingPlanItemEntity>()
|
||||
.eq(LoadingPlanItemEntity::getLoadingPlanId, planId)
|
||||
.eq(LoadingPlanItemEntity::getPalletId, palletId)
|
||||
.eq(LoadingPlanItemEntity::getInboundDetailId, detailId)
|
||||
.eq(LoadingPlanItemEntity::getDeleted, false));
|
||||
}
|
||||
public LoadingPlanHistoryEntity operation(String operationId) {
|
||||
return histories.selectOne(new LambdaQueryWrapper<LoadingPlanHistoryEntity>()
|
||||
.eq(LoadingPlanHistoryEntity::getOperationId, operationId));
|
||||
|
||||
+164
-165
@@ -4,194 +4,193 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.ScanResult;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.SkuItem;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Task;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.*;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.CartonRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.LabelRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.TaskRow;
|
||||
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
/** 测量完成即下发的一件代发合托,不创建国内仓库托盘、库位或库存。 */
|
||||
/** 一件代发测量后的逐箱清点、待绑托和封托,不创建第二套装柜工作托盘。 */
|
||||
@Service
|
||||
public class DropshipPalletizationService {
|
||||
@Resource private DropshipPalletizationMapper mapper;
|
||||
@Resource private FirstlegDocumentNumberGenerator numbers;
|
||||
@Resource private DropshipPalletizationMapper mapper;
|
||||
|
||||
public void createTask(InboundOrderEntity inbound) {
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType())) return;
|
||||
if (inbound.getTotalPackageCount() == null || inbound.getTotalPackageCount() < 1) {
|
||||
throw invalid("一件代发测量完成箱数无效,不能下发合托任务");
|
||||
}
|
||||
mapper.insertTask(IdWorker.getId(), inbound.getTenantId(), inbound.getId(),
|
||||
inbound.getWarehouseId(), inbound.getTotalPackageCount());
|
||||
}
|
||||
public void createTask(InboundOrderEntity inbound) {
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType())) return;
|
||||
if (inbound.getTotalPackageCount()==null || inbound.getTotalPackageCount()<1) throw invalid("一件代发测量完成箱数无效,不能下发清点组托任务");
|
||||
mapper.insertTask(IdWorker.getId(),inbound.getTenantId(),inbound.getId(),inbound.getWarehouseId(),inbound.getTotalPackageCount());
|
||||
}
|
||||
|
||||
public List<DropshipPalletizationDtos.PendingTask> pending(long operatorId, String keyword) {
|
||||
return mapper.pending(operatorId, normalize(keyword));
|
||||
}
|
||||
public List<PendingTask> pending(long operatorId,String keyword){ return mapper.pending(operatorId,normalize(keyword)); }
|
||||
|
||||
@Transactional
|
||||
public Task claim(long taskId, long warehouseId, long operatorId, String operatorName) {
|
||||
if (mapper.claim(taskId, warehouseId, operatorId, operatorName) != 1) {
|
||||
throw conflict("任务已被其他人员领取或状态已变化");
|
||||
}
|
||||
return task(taskId, warehouseId, operatorId);
|
||||
}
|
||||
@Transactional
|
||||
public Task claim(long taskId,long warehouseId,long operatorId,String operatorName){
|
||||
if(mapper.claim(taskId,warehouseId,operatorId,operatorName)!=1) throw conflict("任务已被其他人员领取或状态已变化");
|
||||
return task(taskId,warehouseId,operatorId);
|
||||
}
|
||||
|
||||
public Task task(long taskId, long warehouseId, long operatorId) {
|
||||
TaskRow row = requireTask(taskId, warehouseId);
|
||||
if (row.getAssigneeId() != null && row.getAssigneeId() != operatorId) {
|
||||
throw conflict("任务已由其他人员领取");
|
||||
}
|
||||
return view(row);
|
||||
}
|
||||
public Task task(long taskId,long warehouseId,long operatorId){
|
||||
TaskRow row=requireTask(taskId,warehouseId);
|
||||
if(row.getAssigneeId()!=null && row.getAssigneeId()!=operatorId) throw conflict("任务已由其他人员领取");
|
||||
return view(row);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Pallet open(long taskId, DropshipPalletizationDtos.OpenPalletRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
TaskRow task = requireWorkingTask(taskId, request.warehouseId(), operatorId);
|
||||
String operationId = request.operationId().trim();
|
||||
PalletRow replay = mapper.palletByOpenOperation(operationId);
|
||||
if (replay != null) {
|
||||
if (replay.getTaskId() != taskId) throw conflict("开托幂等号已用于其他任务");
|
||||
return palletView(replay, task.getInboundCode());
|
||||
}
|
||||
long id = IdWorker.getId();
|
||||
mapper.insertPallet(id, taskId, task.getTenantId(), task.getInboundId(), task.getWarehouseId(),
|
||||
operationId, operatorId, operatorName);
|
||||
return palletView(mapper.pallet(taskId, id, request.warehouseId()), task.getInboundCode());
|
||||
}
|
||||
public Carton resolveCarton(long taskId,long warehouseId,String boxCode,long operatorId){
|
||||
TaskRow task=requireTask(taskId,warehouseId);
|
||||
if(task.getAssigneeId()!=null && task.getAssigneeId()!=operatorId) throw conflict("任务已由其他人员领取");
|
||||
LabelRow label=requireLabel(taskId,boxCode);
|
||||
CartonRow counted=mapper.countedByLabel(taskId,label.getCartonLabelId());
|
||||
if(counted!=null) return cartonView(counted,task.getInboundCode(),false,"箱码已清点");
|
||||
String block=null;
|
||||
if(!"CLAIMED".equals(task.getTaskStatus())) block="请先领取任务";
|
||||
else if(!Objects.equals(task.getAssigneeId(),operatorId)) block="任务不属于当前操作员";
|
||||
else if(!"WAIT_PALLETIZE".equals(task.getProcessStatus())) block="入仓单已不在待清点组托状态";
|
||||
return new Carton(null,label.getBoxCode(),task.getInboundId(),label.getInboundCode(),label.getInboundDetailId(),
|
||||
label.getSkuCode(),label.getCargoName(),label.getUnitsPerCarton(),null,null,null,"UNCOUNTED",null,null,
|
||||
block==null,block);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ScanResult scan(long taskId, long palletId, DropshipPalletizationDtos.ScanRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
TaskRow task = requireWorkingTask(taskId, request.warehouseId(), operatorId);
|
||||
PalletRow pallet = requireOpenPallet(taskId, palletId, request.warehouseId());
|
||||
String operationId = request.operationId().trim();
|
||||
LabelRow scanned = mapper.label(taskId, request.boxCode().trim());
|
||||
if (scanned == null) throw new BizException(ResultCode.NOT_FOUND, "箱唛不属于当前一件代发入仓单");
|
||||
if (!StringUtils.hasText(scanned.getSkuCode())) throw conflict("箱唛缺少SKU,不能合托");
|
||||
if (mapper.operationCount(operationId) > 0) {
|
||||
return scanResult(scanned, request.cartonCount(), task, pallet);
|
||||
}
|
||||
List<LabelRow> labels = mapper.unboundLabels(taskId, task.getTenantId(), task.getInboundId(),
|
||||
scanned.getInboundDetailId(), scanned.getBoxCode(), request.cartonCount());
|
||||
if (labels.size() != request.cartonCount()) {
|
||||
throw conflict("本SKU剩余未上托箱数不足,请刷新任务后重试");
|
||||
}
|
||||
for (LabelRow label : labels) {
|
||||
mapper.insertCarton(IdWorker.getId(), taskId, palletId, task.getTenantId(), task.getInboundId(),
|
||||
label.getInboundDetailId(), label.getCartonLabelId(), label.getSkuCode(), label.getCargoName(),
|
||||
label.getBoxCode(), operationId, operatorId, operatorName);
|
||||
}
|
||||
if (mapper.addAllocated(taskId, operatorId, labels.size()) != 1) {
|
||||
throw conflict("任务箱数已变化,请刷新后重试");
|
||||
}
|
||||
TaskRow refreshed = requireTask(taskId, request.warehouseId());
|
||||
return scanResult(scanned, labels.size(), refreshed,
|
||||
mapper.pallet(taskId, palletId, request.warehouseId()));
|
||||
}
|
||||
@Transactional
|
||||
public CountResult countCarton(long taskId,CountCartonRequest request,long operatorId,String operatorName){
|
||||
TaskRow task=requireWorkingTask(taskId,request.warehouseId(),operatorId);
|
||||
String operation=operation(request.operationId());
|
||||
CartonRow replay=mapper.cartonByCountOperation(operation);
|
||||
if(replay!=null){
|
||||
if(!Objects.equals(replay.getTaskId(),taskId) || !Objects.equals(replay.getBoxCode(),request.boxCode().trim())
|
||||
|| !Objects.equals(replay.getActualSkuQuantity(),request.actualSkuQuantity())) throw conflict("清点幂等号已用于不同操作");
|
||||
return countResult(task,replay);
|
||||
}
|
||||
LabelRow label=requireLabel(taskId,request.boxCode());
|
||||
if(mapper.countedByLabel(taskId,label.getCartonLabelId())!=null) throw conflict("箱码已清点,不能重复清点");
|
||||
int expected=requireExpectedQuantity(label);
|
||||
boolean mismatch=expected!=request.actualSkuQuantity();
|
||||
String remark=normalize(request.discrepancyRemark());
|
||||
if(!mismatch && request.discrepancyAction()!=DiscrepancyAction.MATCH) throw invalid("实收数量一致时差异动作必须为 MATCH");
|
||||
if(mismatch && request.discrepancyAction()!=DiscrepancyAction.ACCEPT_VARIANCE) throw conflict("实收数量存在差异,请确认差异处理结果");
|
||||
if(mismatch && !StringUtils.hasText(remark)) throw invalid("接受数量差异时必须填写处理说明");
|
||||
PalletRow pallet=StringUtils.hasText(request.palletNo())
|
||||
? getOrCreatePallet(task,request.palletNo().trim(),operation,operatorId,operatorName):null;
|
||||
long cartonId=IdWorker.getId();
|
||||
mapper.insertCarton(cartonId,taskId,pallet==null?null:pallet.getId(),task.getTenantId(),task.getInboundId(),
|
||||
label.getInboundDetailId(),label.getCartonLabelId(),label.getSkuCode(),label.getCargoName(),label.getBoxCode(),
|
||||
expected,request.actualSkuQuantity(),mismatch?"ACCEPTED_VARIANCE":"MATCH",remark,operation,operatorId,operatorName);
|
||||
if(mapper.addCounted(taskId,operatorId,pallet==null?0:1,mismatch?1:0)!=1) throw conflict("任务清点数量已变化,请刷新后重试");
|
||||
TaskRow refreshed=requireTask(taskId,request.warehouseId());
|
||||
return countResult(refreshed,mapper.carton(taskId,cartonId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task seal(long taskId, long palletId, DropshipPalletizationDtos.SealPalletRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
TaskRow task = requireWorkingTask(taskId, request.warehouseId(), operatorId);
|
||||
PalletRow replay = mapper.palletBySealOperation(request.operationId().trim());
|
||||
if (replay != null) {
|
||||
if (replay.getTaskId() != taskId || replay.getId() != palletId) {
|
||||
throw conflict("封托幂等号已用于其他托盘");
|
||||
}
|
||||
return view(requireTask(taskId, request.warehouseId()));
|
||||
}
|
||||
requireOpenPallet(taskId, palletId, request.warehouseId());
|
||||
String palletNo = numbers.next("DP");
|
||||
if (mapper.seal(taskId, palletId, request.expectedVersion(), palletNo, request.operationId().trim(),
|
||||
operatorId, operatorName) != 1) {
|
||||
throw conflict("空托盘、托盘状态或版本已变化,请刷新后重试");
|
||||
}
|
||||
TaskRow refreshed = requireTask(taskId, request.warehouseId());
|
||||
if (refreshed.getAllocatedCartons().equals(refreshed.getTotalCartons())
|
||||
&& mapper.openPalletCount(taskId) == 0) {
|
||||
if (mapper.completeTask(taskId) != 1
|
||||
|| mapper.completeInbound(taskId, refreshed.getTenantId(), refreshed.getInboundId(), operatorId) != 1) {
|
||||
throw conflict("合托完成状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
return view(requireTask(taskId, request.warehouseId()));
|
||||
}
|
||||
@Transactional
|
||||
public Task bindPending(long taskId,BindPendingRequest request,long operatorId,String operatorName){
|
||||
TaskRow task=requireWorkingTask(taskId,request.warehouseId(),operatorId);
|
||||
String operation=operation(request.operationId());
|
||||
if(mapper.bindOperationCount(taskId,operation)>0) return view(requireTask(taskId,request.warehouseId()));
|
||||
PalletRow pallet=getOrCreatePallet(task,request.palletNo().trim(),operation,operatorId,operatorName);
|
||||
int expected=mapper.unboundCount(taskId);
|
||||
if(expected==0) throw conflict("当前没有待绑托箱");
|
||||
int bound=mapper.bindPending(taskId,pallet.getId(),operation,operatorId,operatorName);
|
||||
if(bound!=expected || mapper.adjustBound(taskId,operatorId,bound)!=1) throw conflict("待绑托箱已变化,请刷新后重试");
|
||||
return view(requireTask(taskId,request.warehouseId()));
|
||||
}
|
||||
|
||||
public List<Pallet> inboundPallets(long tenantId, long inboundId) {
|
||||
String inboundCode = mapper.inboundCode(tenantId, inboundId);
|
||||
if (inboundCode == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
return mapper.palletsByInbound(tenantId, inboundId).stream()
|
||||
.map(row -> palletView(row, inboundCode)).toList();
|
||||
}
|
||||
@Transactional
|
||||
public Task moveCarton(long taskId,long cartonId,MoveCartonRequest request,long operatorId,String operatorName){
|
||||
TaskRow task=requireWorkingTask(taskId,request.warehouseId(),operatorId);
|
||||
CartonRow carton=mapper.carton(taskId,cartonId);
|
||||
if(carton==null) throw new BizException(ResultCode.NOT_FOUND,"清点箱不存在");
|
||||
if(carton.getPalletId()!=null) requireOpenPallet(taskId,carton.getPalletId(),request.warehouseId());
|
||||
PalletRow target=StringUtils.hasText(request.targetPalletNo())
|
||||
? getOrCreatePallet(task,request.targetPalletNo().trim(),operation(request.operationId()),operatorId,operatorName):null;
|
||||
if(Objects.equals(carton.getPalletId(),target==null?null:target.getId())) return view(task);
|
||||
if(mapper.moveCarton(taskId,cartonId,target==null?null:target.getId(),operation(request.operationId()),operatorId,operatorName)!=1)
|
||||
throw conflict("清点箱已被其他操作修改,请刷新后重试");
|
||||
int delta=carton.getPalletId()==null?1:(target==null?-1:0);
|
||||
if(delta!=0 && mapper.adjustBound(taskId,operatorId,delta)!=1) throw conflict("任务绑托数量已变化,请刷新后重试");
|
||||
return view(requireTask(taskId,request.warehouseId()));
|
||||
}
|
||||
|
||||
private ScanResult scanResult(LabelRow label, int added, TaskRow task, PalletRow pallet) {
|
||||
return new ScanResult(label.getSkuCode(), label.getCargoName(), label.getInboundCode(), added,
|
||||
task.getAllocatedCartons(), task.getTotalCartons() - task.getAllocatedCartons(),
|
||||
palletView(pallet, task.getInboundCode()));
|
||||
}
|
||||
@Transactional
|
||||
public Task seal(long taskId,long palletId,SealPalletRequest request,long operatorId,String operatorName){
|
||||
TaskRow task=requireWorkingTask(taskId,request.warehouseId(),operatorId);
|
||||
PalletRow replay=mapper.palletBySealOperation(operation(request.operationId()));
|
||||
if(replay!=null){ if(!Objects.equals(replay.getTaskId(),taskId)||!Objects.equals(replay.getId(),palletId)) throw conflict("封托幂等号已用于其他托盘"); return view(requireTask(taskId,request.warehouseId())); }
|
||||
requireOpenPallet(taskId,palletId,request.warehouseId());
|
||||
if(mapper.unboundCount(taskId)>0) throw conflict("仍有已清点待绑托箱,不能封托");
|
||||
if(mapper.seal(taskId,palletId,request.expectedVersion(),operation(request.operationId()),operatorId,operatorName)!=1)
|
||||
throw conflict("空托盘、托盘状态或版本已变化,请刷新后重试");
|
||||
TaskRow refreshed=requireTask(taskId,request.warehouseId());
|
||||
if(Objects.equals(refreshed.getCountedCartons(),refreshed.getTotalCartons()) &&
|
||||
Objects.equals(refreshed.getAllocatedCartons(),refreshed.getTotalCartons()) && mapper.openPalletCount(taskId)==0){
|
||||
if(mapper.completeTask(taskId)!=1 || mapper.completeInbound(taskId,refreshed.getTenantId(),refreshed.getInboundId(),operatorId)!=1)
|
||||
throw conflict("组托完成状态已变化,请刷新后重试");
|
||||
}
|
||||
return view(requireTask(taskId,request.warehouseId()));
|
||||
}
|
||||
|
||||
private Task view(TaskRow row) {
|
||||
return new Task(row.getId(), row.getInboundId(), row.getInboundCode(), row.getTenantId(),
|
||||
row.getCustomerName(), row.getWarehouseId(), row.getWarehouseName(), row.getTotalCartons(),
|
||||
row.getAllocatedCartons(), row.getTaskStatus(), row.getAssigneeId(), row.getAssigneeName(),
|
||||
row.getVersion(), mapper.pallets(row.getId()).stream()
|
||||
.map(pallet -> palletView(pallet, row.getInboundCode())).toList());
|
||||
}
|
||||
public List<Pallet> inboundPallets(long tenantId,long inboundId){
|
||||
String code=mapper.inboundCode(tenantId,inboundId);
|
||||
if(code==null) throw new BizException(ResultCode.NOT_FOUND,"入库单不存在");
|
||||
return mapper.palletsByInbound(tenantId,inboundId).stream().map(p->palletView(p,code)).toList();
|
||||
}
|
||||
|
||||
private Pallet palletView(PalletRow row, String inboundCode) {
|
||||
List<CartonRow> cartons = mapper.cartons(row.getId());
|
||||
Map<String, List<CartonRow>> groups = new LinkedHashMap<>();
|
||||
for (CartonRow carton : cartons) {
|
||||
groups.computeIfAbsent(carton.getInboundDetailId() + "|" + carton.getSkuCode(), ignored -> new ArrayList<>())
|
||||
.add(carton);
|
||||
}
|
||||
List<SkuItem> items = groups.values().stream().map(values -> {
|
||||
CartonRow first = values.get(0);
|
||||
return new SkuItem(first.getInboundId(), inboundCode, first.getInboundDetailId(), first.getSkuCode(),
|
||||
first.getCargoName(), values.size(), values.stream().map(CartonRow::getBoxCode).toList());
|
||||
}).toList();
|
||||
return new Pallet(row.getId(), row.getPalletNo(), row.getPalletStatus(), cartons.size(), items.size(),
|
||||
row.getVersion(), items, row.getCompletedAt());
|
||||
}
|
||||
private PalletRow getOrCreatePallet(TaskRow task,String palletNo,String operation,long operatorId,String operatorName){
|
||||
PalletRow existing=mapper.palletByNo(palletNo);
|
||||
if(existing!=null){
|
||||
if(!Objects.equals(existing.getTaskId(),task.getId()) || !Objects.equals(existing.getTenantId(),task.getTenantId())) throw conflict("托盘码已被其他客户或入仓任务占用");
|
||||
if(!"OPEN".equals(existing.getPalletStatus())) throw conflict("托盘已封托,不能继续绑定箱");
|
||||
return existing;
|
||||
}
|
||||
try{ mapper.insertPallet(IdWorker.getId(),task.getId(),task.getTenantId(),task.getInboundId(),task.getWarehouseId(),palletNo,operation,operatorId,operatorName); }
|
||||
catch(DuplicateKeyException e){ existing=mapper.palletByNo(palletNo); if(existing==null) throw conflict("托盘码已被并发占用"); }
|
||||
existing=mapper.palletByNo(palletNo);
|
||||
if(existing==null || !Objects.equals(existing.getTaskId(),task.getId())) throw conflict("托盘码已被其他任务占用");
|
||||
return existing;
|
||||
}
|
||||
|
||||
private TaskRow requireWorkingTask(long taskId, long warehouseId, long operatorId) {
|
||||
TaskRow task = requireTask(taskId, warehouseId);
|
||||
if (!"CLAIMED".equals(task.getTaskStatus())) throw conflict("请先领取待合托任务");
|
||||
if (task.getAssigneeId() == null || task.getAssigneeId() != operatorId) throw conflict("任务不属于当前操作员");
|
||||
if (!"WAIT_PALLETIZE".equals(task.getProcessStatus())) throw conflict("入仓单已不在待合托状态");
|
||||
return task;
|
||||
}
|
||||
private CountResult countResult(TaskRow task,CartonRow carton){
|
||||
Pallet p=carton.getPalletId()==null?null:palletView(mapper.pallet(task.getId(),carton.getPalletId(),task.getWarehouseId()),task.getInboundCode());
|
||||
return new CountResult(cartonView(carton,task.getInboundCode(),false,null),task.getCountedCartons(),task.getAllocatedCartons(),
|
||||
task.getCountedCartons()-task.getAllocatedCartons(),task.getTotalCartons()-task.getCountedCartons(),p);
|
||||
}
|
||||
|
||||
private TaskRow requireTask(long taskId, long warehouseId) {
|
||||
TaskRow task = mapper.task(taskId, warehouseId);
|
||||
if (task == null) throw new BizException(ResultCode.NOT_FOUND, "未找到当前仓库的一件代发合托任务");
|
||||
return task;
|
||||
}
|
||||
private Task view(TaskRow row){
|
||||
return new Task(row.getId(),row.getInboundId(),row.getInboundCode(),row.getTenantId(),row.getCustomerName(),row.getWarehouseId(),row.getWarehouseName(),
|
||||
row.getTotalCartons(),row.getCountedCartons(),row.getAllocatedCartons(),row.getCountedCartons()-row.getAllocatedCartons(),row.getDiscrepancyCartons(),
|
||||
row.getTaskStatus(),row.getAssigneeId(),row.getAssigneeName(),row.getVersion(),mapper.pallets(row.getId()).stream().map(p->palletView(p,row.getInboundCode())).toList(),
|
||||
mapper.unboundCartons(row.getId()).stream().map(c->cartonView(c,row.getInboundCode(),false,null)).toList());
|
||||
}
|
||||
|
||||
private PalletRow requireOpenPallet(long taskId, long palletId, long warehouseId) {
|
||||
PalletRow pallet = mapper.pallet(taskId, palletId, warehouseId);
|
||||
if (pallet == null) throw new BizException(ResultCode.NOT_FOUND, "托盘不存在");
|
||||
if (!"OPEN".equals(pallet.getPalletStatus())) throw conflict("托盘已完成,不能继续上托");
|
||||
return pallet;
|
||||
}
|
||||
private Pallet palletView(PalletRow row,String inboundCode){
|
||||
List<CartonRow> cartons=mapper.cartons(row.getId()); Map<String,List<CartonRow>> groups=new LinkedHashMap<>();
|
||||
for(CartonRow c:cartons) groups.computeIfAbsent(c.getInboundDetailId()+"|"+c.getSkuCode(),ignored->new ArrayList<>()).add(c);
|
||||
List<SkuItem> items=groups.values().stream().map(values->{CartonRow first=values.get(0);return new SkuItem(first.getInboundId(),inboundCode,
|
||||
first.getInboundDetailId(),first.getSkuCode(),first.getCargoName(),values.size(),values.stream().mapToInt(v->v.getActualSkuQuantity()==null?0:v.getActualSkuQuantity()).sum(),
|
||||
values.stream().map(CartonRow::getBoxCode).toList());}).toList();
|
||||
int discrepancies=(int)cartons.stream().filter(c->"ACCEPTED_VARIANCE".equals(c.getDiscrepancyStatus())).count();
|
||||
PalletMetrics metrics=mapper.metrics(row.getId());
|
||||
return new Pallet(row.getId(),row.getPalletNo(),row.getPalletStatus(),cartons.size(),items.size(),discrepancies,
|
||||
metrics==null?BigDecimal.ZERO:metrics.weightKg(),metrics==null?BigDecimal.ZERO:metrics.volumeCbm(),row.getVersion(),items,
|
||||
cartons.stream().map(c->cartonView(c,inboundCode,false,null)).toList(),row.getCompletedAt());
|
||||
}
|
||||
|
||||
private static String normalize(String value) { return StringUtils.hasText(value) ? value.trim() : null; }
|
||||
private static BizException conflict(String message) { return new BizException(ResultCode.CONFLICT, message); }
|
||||
private static BizException invalid(String message) { return new BizException(ResultCode.VALIDATION_FAILED, message); }
|
||||
private Carton cartonView(CartonRow row,String inboundCode,boolean canCount,String blockReason){
|
||||
return new Carton(row.getId(),row.getBoxCode(),row.getInboundId(),inboundCode,row.getInboundDetailId(),row.getSkuCode(),row.getCargoName(),
|
||||
row.getExpectedSkuQuantity(),row.getActualSkuQuantity(),row.getDiscrepancyStatus(),row.getDiscrepancyRemark(),
|
||||
row.getPalletId()==null?"COUNTED_UNBOUND":"BOUND",row.getPalletId(),row.getPalletNo(),canCount,blockReason);
|
||||
}
|
||||
private LabelRow requireLabel(long taskId,String boxCode){ LabelRow label=mapper.label(taskId,boxCode.trim()); if(label==null) throw new BizException(ResultCode.NOT_FOUND,"箱码不属于当前一件代发入仓单"); if(!StringUtils.hasText(label.getSkuCode())) throw conflict("箱码缺少SKU快照,不能清点"); requireExpectedQuantity(label); return label; }
|
||||
private int requireExpectedQuantity(LabelRow label){ if(label.getUnitsPerCarton()==null||label.getUnitsPerCarton()<1) throw conflict("箱码缺少每箱SKU数量快照,不能清点"); return label.getUnitsPerCarton(); }
|
||||
private TaskRow requireWorkingTask(long taskId,long warehouseId,long operatorId){ TaskRow task=requireTask(taskId,warehouseId); if(!"CLAIMED".equals(task.getTaskStatus())) throw conflict("请先领取待清点组托任务"); if(!Objects.equals(task.getAssigneeId(),operatorId)) throw conflict("任务不属于当前操作员"); if(!"WAIT_PALLETIZE".equals(task.getProcessStatus())) throw conflict("入仓单已不在待清点组托状态"); return task; }
|
||||
private TaskRow requireTask(long taskId,long warehouseId){ TaskRow task=mapper.task(taskId,warehouseId); if(task==null) throw new BizException(ResultCode.NOT_FOUND,"未找到当前仓库的一件代发清点组托任务"); return task; }
|
||||
private PalletRow requireOpenPallet(long taskId,long palletId,long warehouseId){ PalletRow p=mapper.pallet(taskId,palletId,warehouseId); if(p==null) throw new BizException(ResultCode.NOT_FOUND,"托盘不存在"); if(!"OPEN".equals(p.getPalletStatus())) throw conflict("托盘已封托,不能修改"); return p; }
|
||||
private static String operation(String v){String n=normalize(v);if(!StringUtils.hasText(n))throw invalid("操作幂等号不能为空");return n;}
|
||||
private static String normalize(String v){return StringUtils.hasText(v)?v.trim():null;}
|
||||
private static BizException conflict(String m){return new BizException(ResultCode.CONFLICT,m);}
|
||||
private static BizException invalid(String m){return new BizException(ResultCode.VALIDATION_FAILED,m);}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
&& cargoes.stream().anyMatch(cargo -> cargo.skuNo() == null || cargo.skuNo().isBlank())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "一件代发货物明细缺少SKU,不能生成箱唛");
|
||||
}
|
||||
if ("ONE_CLICK_DROPSHIP".equals(inbound.getFulfillmentType())
|
||||
&& cargoes.stream().anyMatch(cargo -> cargo.unitsPerCarton() == null || cargo.unitsPerCarton() < 1)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "一件代发货物明细缺少每箱SKU数量,不能生成箱唛");
|
||||
}
|
||||
existing.forEach(label -> cargoes.stream()
|
||||
.filter(cargo -> java.util.Objects.equals(cargo.cargoId(), label.getInboundDetailId()))
|
||||
.findFirst().ifPresent(cargo -> repository.initializeLegacySku(tenantId, inboundId,
|
||||
@@ -211,6 +215,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
label.setCargoItemNo(cargo.itemNo());
|
||||
label.setCargoName(cargo.cargoName());
|
||||
label.setSkuCode(cargo.skuNo());
|
||||
label.setUnitsPerCarton(cargo.unitsPerCarton());
|
||||
label.setDetailPackageCount(cargo.packageCount());
|
||||
label.setInboundCode(inbound.getInboundCode());
|
||||
label.setBoxCode(inbound.getInboundCode() + "#" + total + "-" + index);
|
||||
@@ -267,6 +272,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
String source = cargoes.stream().map(cargo -> String.join("|", String.valueOf(cargo.itemNo()),
|
||||
cargo.cargoName() == null ? "" : cargo.cargoName().trim(),
|
||||
cargo.skuNo() == null ? "" : cargo.skuNo().trim(), String.valueOf(cargo.packageCount()),
|
||||
String.valueOf(cargo.unitsPerCarton()),
|
||||
decimalOrEmpty(cargo.lengthCm()), decimalOrEmpty(cargo.widthCm()), decimalOrEmpty(cargo.heightCm())))
|
||||
.collect(java.util.stream.Collectors.joining("\n"));
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickItem;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PalletPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
||||
@@ -106,6 +107,8 @@ public class LoadingPickService {
|
||||
private InboundPalletAllocationRepository palletAllocations;
|
||||
@Resource
|
||||
private InboundHoldGuard inboundHoldGuard;
|
||||
@Resource
|
||||
private com.baiyue.firstleg.mapper.DropshipPalletizationMapper dropshipPalletizationMapper;
|
||||
|
||||
/** Opens the field task after planning and ocean release; the real container number may be filled later. */
|
||||
@Transactional
|
||||
@@ -326,6 +329,7 @@ public class LoadingPickService {
|
||||
if (item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在");
|
||||
}
|
||||
requirePackageUnit(item);
|
||||
if (repository.pick(itemId, request.pickedPackageCount(), request.version(), request.warehouseId()) != 1) {
|
||||
throw conflict("拣货数量超过计划箱数或明细版本已变化");
|
||||
}
|
||||
@@ -344,6 +348,41 @@ public class LoadingPickService {
|
||||
return view(requiredTask(taskId), request.warehouseId());
|
||||
}
|
||||
|
||||
/** 一件代发只扫描已封托托盘码,并在同一事务把该托全部计划行结算为已拣。 */
|
||||
@Transactional
|
||||
public Task pickPallet(long taskId,PalletPickRequest request,long operatorId,String operatorName){
|
||||
inboundHoldGuard.requireTaskNotHeld(taskId);
|
||||
String operationId=operation(request.operationId());
|
||||
String requestHash=hash("PICK_PALLET|"+taskId+'|'+request.warehouseId()+'|'+request.palletNo()
|
||||
+'|'+request.expectedTaskVersion());
|
||||
Task replay=replayTaskOperation(taskId,operationId,requestHash,operatorId); if(replay!=null)return replay;
|
||||
LoadingPickTaskEntity task=requiredAssignedLoadingTaskForUpdate(taskId,operatorId);
|
||||
replay=replayTaskOperationForUpdate(taskId,operationId,requestHash,operatorId); if(replay!=null)return replay;
|
||||
if(!Objects.equals(task.getVersion(),request.expectedTaskVersion()))throw conflict("PDA任务版本已变化,请刷新后重试");
|
||||
List<LoadingPlanItemEntity> planItems=smartLoadingRepository.items(task.getLoadingPlanId()).stream()
|
||||
.filter(item->request.palletNo().equals(item.getPalletNo()) && item.getPalletId()!=null
|
||||
&& Boolean.TRUE.equals(item.getIndivisible())).toList();
|
||||
if(planItems.isEmpty())throw new BizException(ResultCode.NOT_FOUND,"托盘不属于当前装柜任务");
|
||||
Long palletId=planItems.get(0).getPalletId();
|
||||
if(planItems.stream().anyMatch(item->!Objects.equals(item.getPalletId(),palletId)))throw conflict("托盘装柜快照不一致");
|
||||
var physical=dropshipPalletizationMapper.sealedPalletLines(palletId);
|
||||
if(physical.isEmpty()||physical.stream().anyMatch(line->!"SEALED".equals(line.getPalletStatus())))throw conflict("托盘已失效或尚未封托");
|
||||
for(LoadingPlanItemEntity planItem:planItems){
|
||||
LoadingPickItemEntity pickItem=repository.itemByPlanItem(planItem.getId());
|
||||
if(pickItem==null||!"PENDING".equals(pickItem.getItemStatus())||count(pickItem.getPickedPackageCount())!=0)
|
||||
throw conflict("托盘已有部分作业或明细已变化,禁止拆托处理");
|
||||
if(repository.pick(pickItem.getId(),pickItem.getPlannedPackageCount(),pickItem.getVersion(),request.warehouseId())!=1)
|
||||
throw conflict("托盘明细版本已变化,请刷新后重试");
|
||||
insertHistory(taskId,pickItem.getId(),"PAL-"+hash(operationId+'|'+pickItem.getId()).substring(0,48),
|
||||
requestHash,"PICK_PALLET",pickItem.getPlannedPackageCount(),null,operatorId,operatorName);
|
||||
}
|
||||
startPickingIfNeeded(task,operatorId,operatorName);
|
||||
if(repository.touchTask(taskId,request.expectedTaskVersion())!=1)throw conflict("PDA任务版本已变化,请刷新后重试");
|
||||
insertOperation(taskId,operationId,requestHash,"PICK_PALLET",operatorId,operatorName);
|
||||
settleAndFinish(task,operatorId,operatorName); repository.syncInboundProjections(taskId,operatorId,operatorName);
|
||||
return view(requiredTask(taskId),request.warehouseId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Task partial(long taskId, long itemId, PartialPickRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
@@ -364,7 +403,8 @@ public class LoadingPickService {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(task, request.warehouseId());
|
||||
}
|
||||
requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
LoadingPickItemEntity item = requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
requirePackageUnit(item);
|
||||
if (repository.partial(itemId, request.finalPickedPackageCount(), request.version(),
|
||||
request.warehouseId()) != 1) {
|
||||
throw conflict("部分拣货箱数必须大于已拣箱数且小于计划箱数,或明细版本已变化");
|
||||
@@ -397,6 +437,7 @@ public class LoadingPickService {
|
||||
return view(task, request.warehouseId());
|
||||
}
|
||||
LoadingPickItemEntity item = requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
requirePackageUnit(item);
|
||||
if (repository.hasPickedOrPartialItem(taskId, item.getInboundId())) {
|
||||
throw conflict("同一入库单已存在已拣或部分拣货明细,不能跳拣并恢复智能装柜候选");
|
||||
}
|
||||
@@ -443,6 +484,7 @@ public class LoadingPickService {
|
||||
|
||||
List<BatchLine> lines = validateBatchLines(task, request);
|
||||
validateInventorySnapshot(task, request, lines);
|
||||
lines.forEach(line -> requirePackageUnit(line.item()));
|
||||
for (BatchLine line : lines) {
|
||||
applyBatchLine(taskId, line, operationId, operatorId, operatorName);
|
||||
}
|
||||
@@ -1213,6 +1255,16 @@ public class LoadingPickService {
|
||||
/**
|
||||
* 逐项拣货后仅更新任务版本与投影,不能自动结算或推进装柜计划。
|
||||
*/
|
||||
private void requirePackageUnit(LoadingPickItemEntity item) {
|
||||
if (item.getLoadingPlanItemId() == null) return;
|
||||
LoadingPlanItemEntity planItem = smartLoadingRepository.item(
|
||||
requiredTask(item.getPickTaskId()).getLoadingPlanId(), item.getLoadingPlanItemId());
|
||||
if (planItem != null && (Boolean.TRUE.equals(planItem.getIndivisible())
|
||||
|| planItem.getPalletId() != null)) {
|
||||
throw conflict("一件代发已封托货物必须扫描托盘码整体拣货,不能按箱数处理");
|
||||
}
|
||||
}
|
||||
|
||||
private void touchSettleFinishAndSync(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
||||
if (repository.touchTask(task.getId(), task.getVersion()) != 1) {
|
||||
throw conflict("PDA任务版本已变化");
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.Carton;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.OpenRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.Pallet;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.ScanRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.SealRequest;
|
||||
import com.baiyue.firstleg.dto.LoadingPalletDtos.SkuSummary;
|
||||
import com.baiyue.firstleg.entity.LoadingWorkPalletCartonEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingWorkPalletEntity;
|
||||
import com.baiyue.firstleg.mapper.LoadingWorkPalletCartonMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingWorkPalletCartonMapper.CartonCandidate;
|
||||
import com.baiyue.firstleg.mapper.LoadingWorkPalletMapper;
|
||||
import com.baiyue.firstleg.mapper.LoadingWorkPalletMapper.TaskContext;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** 一件代发现场打托旁路;不改变既有拣货数量和仓库资源结算。 */
|
||||
@Service
|
||||
public class LoadingWorkPalletService {
|
||||
@Resource private LoadingWorkPalletMapper pallets;
|
||||
@Resource private LoadingWorkPalletCartonMapper cartons;
|
||||
@Resource private InboundHoldGuard inboundHoldGuard;
|
||||
|
||||
@Transactional
|
||||
public Pallet open(long taskId, OpenRequest request, long operatorId, String operatorName) {
|
||||
inboundHoldGuard.requireTaskNotHeld(taskId);
|
||||
TaskContext context = requireWorkTask(taskId, request.warehouseId(), operatorId);
|
||||
String code = request.palletCode().trim();
|
||||
String operationId = request.operationId().trim();
|
||||
LoadingWorkPalletEntity replay = pallets.selectByOpenOperation(operationId);
|
||||
if (replay != null) {
|
||||
if (replay.getPickTaskId() != taskId || !replay.getPalletCode().equals(code)) {
|
||||
throw conflict("开托幂等号已用于其他请求");
|
||||
}
|
||||
return view(replay);
|
||||
}
|
||||
LoadingWorkPalletEntity value = new LoadingWorkPalletEntity();
|
||||
value.setPickTaskId(taskId);
|
||||
value.setLoadingPlanId(context.loadingPlanId());
|
||||
value.setWarehouseId(request.warehouseId());
|
||||
value.setWarehousePalletId(request.warehousePalletId());
|
||||
value.setPalletCode(code);
|
||||
value.setOpenOperationId(operationId);
|
||||
value.setPalletStatus("OPEN");
|
||||
value.setCreatedBy(operatorId);
|
||||
value.setCreatedByName(operatorName);
|
||||
value.setVersion(0);
|
||||
try {
|
||||
pallets.insert(value);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
replay = pallets.selectByOpenOperation(operationId);
|
||||
if (replay == null || replay.getPickTaskId() != taskId || !replay.getPalletCode().equals(code)) {
|
||||
throw conflict("托盘号已存在或开托操作重复");
|
||||
}
|
||||
value = replay;
|
||||
}
|
||||
return view(value);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Pallet scan(long taskId, long palletId, ScanRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
inboundHoldGuard.requireTaskNotHeld(taskId);
|
||||
requireWorkTask(taskId, request.warehouseId(), operatorId);
|
||||
LoadingWorkPalletEntity target = requireOpenPallet(taskId, palletId, request.warehouseId());
|
||||
String operationId = request.operationId().trim();
|
||||
LoadingWorkPalletCartonEntity replay = cartons.selectByOperation(operationId);
|
||||
if (replay != null) {
|
||||
if (replay.getPickTaskId() != taskId || replay.getWorkPalletId() != palletId
|
||||
|| !replay.getBoxCode().equals(request.boxCode().trim())) {
|
||||
throw conflict("箱唛操作幂等号已用于其他请求");
|
||||
}
|
||||
return view(target);
|
||||
}
|
||||
List<CartonCandidate> candidates = cartons.selectCandidates(taskId, request.warehouseId(),
|
||||
request.boxCode().trim());
|
||||
if (candidates.isEmpty()) throw new BizException(ResultCode.NOT_FOUND, "箱唛不属于当前任务的一件代发货物");
|
||||
if (candidates.size() > 1) throw conflict("箱唛在当前多客户任务中不唯一,请检查箱唛数据");
|
||||
CartonCandidate candidate = candidates.get(0);
|
||||
if (candidate.getSkuCode() == null || candidate.getSkuCode().isBlank()) {
|
||||
throw conflict("一件代发箱唛缺少SKU,不能上托");
|
||||
}
|
||||
LoadingWorkPalletCartonEntity current = cartons.selectBinding(taskId, candidate.getCartonLabelId());
|
||||
if (current == null) {
|
||||
current = binding(taskId, palletId, candidate, operationId, operatorId, operatorName);
|
||||
try {
|
||||
cartons.insert(current);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
LoadingWorkPalletCartonEntity concurrent = cartons.selectBinding(taskId, candidate.getCartonLabelId());
|
||||
if (concurrent == null || concurrent.getWorkPalletId() != palletId) {
|
||||
throw conflict("箱唛已被其他托盘绑定");
|
||||
}
|
||||
}
|
||||
return view(target);
|
||||
}
|
||||
if (current.getWorkPalletId() == palletId) return view(target);
|
||||
LoadingWorkPalletEntity source = pallets.selectScoped(taskId, current.getWorkPalletId(), request.warehouseId());
|
||||
if (source == null || !"OPEN".equals(source.getPalletStatus())) {
|
||||
throw conflict("来源托盘已封托,不能直接移箱");
|
||||
}
|
||||
if (cartons.move(current.getId(), current.getVersion(), palletId, operatorId, operatorName, operationId) != 1) {
|
||||
throw conflict("箱唛绑定已变化,请刷新后重试");
|
||||
}
|
||||
return view(target);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Pallet seal(long taskId, long palletId, SealRequest request,
|
||||
long operatorId, String operatorName) {
|
||||
inboundHoldGuard.requireTaskNotHeld(taskId);
|
||||
requireWorkTask(taskId, request.warehouseId(), operatorId);
|
||||
LoadingWorkPalletEntity replay = pallets.selectBySealOperation(request.operationId().trim());
|
||||
if (replay != null) {
|
||||
if (replay.getId() != palletId || replay.getPickTaskId() != taskId) {
|
||||
throw conflict("封托幂等号已用于其他请求");
|
||||
}
|
||||
return view(replay);
|
||||
}
|
||||
requireOpenPallet(taskId, palletId, request.warehouseId());
|
||||
if (cartons.countPalletCartons(palletId) == 0) throw conflict("空托盘不能封托");
|
||||
if (cartons.countUnreconciled(taskId) > 0) {
|
||||
throw conflict("现场上托箱数超过已提交拣货箱数,请先完成原PDA拣货提交");
|
||||
}
|
||||
if (pallets.seal(taskId, palletId, request.warehouseId(), request.expectedVersion(),
|
||||
request.operationId().trim(), operatorId, operatorName) != 1) {
|
||||
throw conflict("托盘状态或版本已变化,请刷新后重试");
|
||||
}
|
||||
return view(pallets.selectScoped(taskId, palletId, request.warehouseId()));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Pallet> list(long taskId, long warehouseId) {
|
||||
TaskContext context = pallets.selectTaskContext(taskId, warehouseId);
|
||||
if (context == null) throw new BizException(ResultCode.NOT_FOUND, "未找到当前仓库的PDA任务");
|
||||
return pallets.selectTaskPallets(taskId, warehouseId).stream().map(this::view).toList();
|
||||
}
|
||||
|
||||
private TaskContext requireWorkTask(long taskId, long warehouseId, long operatorId) {
|
||||
TaskContext context = pallets.selectTaskContext(taskId, warehouseId);
|
||||
if (context == null) throw new BizException(ResultCode.NOT_FOUND, "未找到当前仓库的PDA任务");
|
||||
if (!"LOADING".equals(context.taskStatus())) throw conflict("PDA任务已完成,不能继续打托");
|
||||
if (context.dropshipItemCount() == null || context.dropshipItemCount() == 0) {
|
||||
throw conflict("当前任务没有一件代发货物");
|
||||
}
|
||||
if (context.assigneeId() == null || context.assigneeId() != operatorId) {
|
||||
throw conflict("请先领取PDA任务后再执行打托");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private LoadingWorkPalletEntity requireOpenPallet(long taskId, long palletId, long warehouseId) {
|
||||
LoadingWorkPalletEntity pallet = pallets.selectScoped(taskId, palletId, warehouseId);
|
||||
if (pallet == null) throw new BizException(ResultCode.NOT_FOUND, "现场托盘不存在");
|
||||
if (!"OPEN".equals(pallet.getPalletStatus())) throw conflict("托盘不是打托中状态");
|
||||
return pallet;
|
||||
}
|
||||
|
||||
private static LoadingWorkPalletCartonEntity binding(long taskId, long palletId,
|
||||
CartonCandidate source, String operationId,
|
||||
long operatorId, String operatorName) {
|
||||
LoadingWorkPalletCartonEntity value = new LoadingWorkPalletCartonEntity();
|
||||
value.setPickTaskId(taskId);
|
||||
value.setWorkPalletId(palletId);
|
||||
value.setLoadingPlanItemId(source.getLoadingPlanItemId());
|
||||
value.setPickItemId(source.getPickItemId());
|
||||
value.setTenantId(source.getTenantId());
|
||||
value.setInboundId(source.getInboundId());
|
||||
value.setInboundDetailId(source.getInboundDetailId());
|
||||
value.setCartonLabelId(source.getCartonLabelId());
|
||||
value.setBoxCode(source.getBoxCode());
|
||||
value.setSkuCode(source.getSkuCode());
|
||||
value.setUnitsPerCarton(source.getUnitsPerCarton());
|
||||
value.setBoundBy(operatorId);
|
||||
value.setBoundByName(operatorName);
|
||||
value.setOperationId(operationId);
|
||||
value.setVersion(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
private Pallet view(LoadingWorkPalletEntity pallet) {
|
||||
List<LoadingWorkPalletCartonEntity> values = cartons.selectPalletCartons(pallet.getId());
|
||||
Map<String, List<LoadingWorkPalletCartonEntity>> bySku = values.stream().collect(
|
||||
java.util.stream.Collectors.groupingBy(LoadingWorkPalletCartonEntity::getSkuCode,
|
||||
LinkedHashMap::new, java.util.stream.Collectors.toList()));
|
||||
List<SkuSummary> skus = bySku.entrySet().stream().map(entry -> new SkuSummary(entry.getKey(),
|
||||
entry.getValue().size(), entry.getValue().stream().map(LoadingWorkPalletCartonEntity::getUnitsPerCarton)
|
||||
.filter(Objects::nonNull).mapToLong(Integer::longValue).sum())).toList();
|
||||
List<Carton> cartonViews = values.stream().map(value -> new Carton(value.getId(), value.getBoxCode(),
|
||||
value.getSkuCode(), value.getTenantId(), value.getInboundId(), value.getInboundDetailId(),
|
||||
value.getUnitsPerCarton())).toList();
|
||||
return new Pallet(pallet.getId(), pallet.getPalletCode(), pallet.getWarehousePalletId(),
|
||||
pallet.getPalletStatus(), pallet.getVersion(), values.size(), bySku.size(),
|
||||
(int) values.stream().map(LoadingWorkPalletCartonEntity::getTenantId).distinct().count(),
|
||||
skus, cartonViews);
|
||||
}
|
||||
|
||||
private static BizException conflict(String message) {
|
||||
return new BizException(ResultCode.CONFLICT, message);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.math.BigDecimal;
|
||||
@Service
|
||||
public class MerchantInboundBillingService {
|
||||
private static final Set<String> PUBLISHED_BILLING_PROCESS_STATUSES = Set.of(
|
||||
InboundProcessStatus.WAIT_PALLETIZE,
|
||||
InboundProcessStatus.WAIT_SPLIT,
|
||||
InboundProcessStatus.LOADING,
|
||||
InboundProcessStatus.LOADED,
|
||||
|
||||
@@ -54,6 +54,7 @@ public class SmartLoadingService {
|
||||
"OA_NEW_YORK_TRUCK", Set.of("OA_NEW_YORK_TRUCK")
|
||||
);
|
||||
@Resource private SmartLoadingRepository repository;
|
||||
@Resource private com.baiyue.firstleg.mapper.DropshipPalletizationMapper dropshipPallets;
|
||||
@Resource private OrderCargoClient orderCargoClient;
|
||||
@Resource private PendingReviewOrderClient pendingReviewOrders;
|
||||
@Resource private InboundOrderCreationService inboundOrderCreationService;
|
||||
@@ -111,7 +112,7 @@ public class SmartLoadingService {
|
||||
Map<Long, List<Candidate>> grouped = candidates(bookingId, warehouseId).stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Candidate::inboundId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
return grouped.values().stream().map(this::inboundCandidate).toList();
|
||||
return grouped.values().stream().flatMap(details -> inboundCandidates(details).stream()).toList();
|
||||
}
|
||||
|
||||
private List<InboundCandidate> recommendationCandidates(ContainerBookingEntity booking) {
|
||||
@@ -121,7 +122,7 @@ public class SmartLoadingService {
|
||||
repository.planByBooking(booking.getId()), values).stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Candidate::inboundId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()));
|
||||
return grouped.values().stream().map(this::inboundCandidate).toList();
|
||||
return grouped.values().stream().flatMap(details -> inboundCandidates(details).stream()).toList();
|
||||
}
|
||||
|
||||
/** Global workbench candidates. A co-load group is one paging unit and can never be split across pages. */
|
||||
@@ -175,7 +176,7 @@ public class SmartLoadingService {
|
||||
List<InboundCandidate> all = repository.globalCandidatesByUnits(unitPage.records()).stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(Candidate::inboundId, LinkedHashMap::new,
|
||||
java.util.stream.Collectors.toList()))
|
||||
.values().stream().map(this::inboundCandidate).toList();
|
||||
.values().stream().flatMap(details -> inboundCandidates(details).stream()).toList();
|
||||
Map<String, InboundCandidate> units = new HashMap<>();
|
||||
Map<Long, List<InboundCandidate>> grouped = all.stream()
|
||||
.filter(candidate -> candidate.coLoadGroupId() != null)
|
||||
@@ -216,6 +217,7 @@ public class SmartLoadingService {
|
||||
new InboundCandidate(row.customerId(), null, row.orderId(), row.orderNo(), row.inboundCode(),
|
||||
row.warehouseId(), row.channelCode(), row.channelName(), null, null, null,
|
||||
row.customerName(), null, row.totalPackageCount(), null, null, null, null,
|
||||
"PACKAGE", null, null, false,
|
||||
null, null, null, null, false, true, null,
|
||||
"PENDING_REVIEW", row.expectedArrivalAt(), List.of())).toList();
|
||||
return new PageResult<>(records, source.page(), source.size(), source.total(), source.pages());
|
||||
@@ -266,7 +268,7 @@ public class SmartLoadingService {
|
||||
return repository.workbenchContainers(query, actualChannels(query.channelGroupCodes()));
|
||||
}
|
||||
|
||||
private InboundCandidate inboundCandidate(List<Candidate> details) {
|
||||
private List<InboundCandidate> inboundCandidates(List<Candidate> details) {
|
||||
Candidate first = details.get(0);
|
||||
int packages = details.stream().map(Candidate::availablePackageCount)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
@@ -279,13 +281,23 @@ public class SmartLoadingService {
|
||||
BigDecimal.valueOf(packages), 6, RoundingMode.HALF_UP);
|
||||
String goods = details.stream().map(Candidate::goodsName).filter(StringUtils::hasText)
|
||||
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining("、"));
|
||||
return new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
List<PalletCandidate> pallets=dropshipPallets.palletCandidates(first.inboundId());
|
||||
if(!pallets.isEmpty()) return pallets.stream().map(pallet -> new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
first.customerName(), goods, pallet.cartonCount(),
|
||||
pallet.cartonCount()==null||pallet.cartonCount()==0?null:pallet.weightKg().divide(BigDecimal.valueOf(pallet.cartonCount()),6,RoundingMode.HALF_UP),
|
||||
first.unitNetWeightKg(), pallet.cartonCount()==null||pallet.cartonCount()==0?null:pallet.volumeCbm().divide(BigDecimal.valueOf(pallet.cartonCount()),6,RoundingMode.HALF_UP),
|
||||
pallet.volumeCbm(),"SEALED_PALLET",pallet.palletId(),pallet.palletNo(),true,
|
||||
first.coLoadGroupId(), first.groupNo(), first.memberCount(),first.groupTotalBoxes(), first.locked(), canAssign(first), capabilityBlock(first),
|
||||
first.orderStatusSnapshot(),first.expectedArrivalAt(),List.of())).toList();
|
||||
return List.of(new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
first.customerName(), goods, packages, first.unitGrossWeightKg(), first.unitNetWeightKg(),
|
||||
averageVolume, volume, first.coLoadGroupId(), first.groupNo(), first.memberCount(),
|
||||
averageVolume, volume,"PACKAGE",null,null,false, first.coLoadGroupId(), first.groupNo(), first.memberCount(),
|
||||
first.groupTotalBoxes(), first.locked(), canAssign(first), capabilityBlock(first), first.orderStatusSnapshot(),
|
||||
first.expectedArrivalAt(), List.of());
|
||||
first.expectedArrivalAt(), List.of()));
|
||||
}
|
||||
|
||||
private InboundCandidate globalGroupCandidate(List<InboundCandidate> availableMembers) {
|
||||
@@ -317,7 +329,7 @@ public class SmartLoadingService {
|
||||
return new InboundCandidate(first.accountId(), first.inboundId(), first.orderId(), first.orderNo(),
|
||||
first.inboundCode(), first.warehouseId(), first.channelCode(), first.channelName(),
|
||||
first.destinationWarehouseId(), first.destinationWarehouseCode(), first.destinationWarehouseName(),
|
||||
customers, goods, packages, gross, net, averageVolume, volume, metadata.coLoadGroupId(), metadata.groupNo(),
|
||||
customers, goods, packages, gross, net, averageVolume, volume,"PACKAGE",null,null,false, metadata.coLoadGroupId(), metadata.groupNo(),
|
||||
metadata.memberCount(), metadata.groupTotalBoxes(),
|
||||
occupied,
|
||||
ordered.stream().allMatch(candidate -> Boolean.TRUE.equals(candidate.canAssign())),
|
||||
@@ -418,11 +430,52 @@ public class SmartLoadingService {
|
||||
@BusinessAudit(event = "SMART_LOADING_ITEM_ASSIGN", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "分配智能装柜明细")
|
||||
public Plan assign(long planId, AssignRequest request, long operatorId, String operatorName) {
|
||||
return assign(planId, request, operatorId, operatorName, false);
|
||||
return assign(planId, request, operatorId, operatorName, false, null, null, null, false, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_PALLET_ASSIGN", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "按已封托托盘分配智能装柜明细")
|
||||
public Plan assignPallet(long planId, AssignPalletRequest request, long operatorId, String operatorName) {
|
||||
String operation = operation(request.operationId());
|
||||
String requestHash = sha256("ASSIGN_PALLET|" + planId + '|' + request.palletId() + '|'
|
||||
+ request.expectedVersion());
|
||||
Plan replay = replay(operation, planId, "ADD_PALLET", null, requestHash);
|
||||
if (replay != null) return replay;
|
||||
LoadingPlanEntity original = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||
List<com.baiyue.firstleg.mapper.DropshipPalletizationMapper.PalletLine> lines =
|
||||
dropshipPallets.sealedPalletLines(request.palletId());
|
||||
if (lines.isEmpty() || lines.stream().anyMatch(line -> !"SEALED".equals(line.getPalletStatus()))) {
|
||||
throw conflict("托盘不存在或尚未封托");
|
||||
}
|
||||
var first = lines.get(0);
|
||||
inboundHoldGuard.requireInboundNotHeld(first.getAccountId(), first.getInboundId());
|
||||
if (!repository.activeItemsByPallet(request.palletId()).isEmpty()) throw conflict("托盘已被有效柜次占用");
|
||||
int sequence = 0;
|
||||
for (var line : lines) {
|
||||
LoadingPlanEntity current = repository.plan(planId);
|
||||
Candidate candidate = new Candidate(line.getAccountId(), line.getInboundId(), line.getInboundDetailId(),
|
||||
line.getOrderId(), line.getOrderNo(), line.getInboundCode(), line.getWarehouseId(), line.getChannelCode(),
|
||||
line.getChannelName(), line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(),
|
||||
line.getDestinationWarehouseName(), line.getCustomerName(), line.getCargoName(), line.getCartonCount(),
|
||||
line.getUnitGrossWeightKg(), line.getUnitNetWeightKg(), line.getUnitVolumeCbm(), null, null, null, null,
|
||||
false, null, "WAIT_SPLIT", null);
|
||||
String childOperation = "PAL-" + sha256(operation + '|' + line.getInboundDetailId()).substring(0, 48);
|
||||
assign(planId, new AssignRequest(line.getInboundId(), line.getInboundDetailId(), line.getCartonCount(),
|
||||
current.getVersion(), childOperation), operatorId, operatorName, true, request.palletId(),
|
||||
line.getPalletNo(), line.getActualSkuQuantity(), sequence++ == 0, candidate);
|
||||
}
|
||||
LoadingPlanEntity assigned = repository.plan(planId);
|
||||
history(planId, null, "ADD_PALLET", original.getPlanStatus(), assigned.getPlanStatus(), operation,
|
||||
json(values("palletId", request.palletId(), "version", request.expectedVersion())),
|
||||
json(values("requestHash", requestHash, "palletNo", first.getPalletNo(), "lineCount", lines.size())),
|
||||
operatorId, operatorName);
|
||||
return view(assigned, requireBooking(assigned.getContainerBookingId()));
|
||||
}
|
||||
|
||||
private Plan assign(long planId, AssignRequest request, long operatorId, String operatorName,
|
||||
boolean wholeGroupAssignment) {
|
||||
boolean wholeGroupAssignment, Long palletId, String palletNo,
|
||||
Integer actualSkuQuantity, boolean primaryPalletLine, Candidate frozenCandidate) {
|
||||
String operation = operation(request.operationId());
|
||||
String requestHash = sha256("ASSIGN|" + planId + '|'
|
||||
+ request.inboundId() + '|' + request.inboundDetailId() + '|' + request.packageCount()
|
||||
@@ -431,8 +484,14 @@ public class SmartLoadingService {
|
||||
if (replay != null) return replay;
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedVersion(), operatorId, operatorName);
|
||||
ContainerBookingEntity booking = requireBooking(plan.getContainerBookingId());
|
||||
Candidate candidate = repository.candidate(planId, request.inboundId(), request.inboundDetailId());
|
||||
Candidate candidate = frozenCandidate == null
|
||||
? repository.candidate(planId, request.inboundId(), request.inboundDetailId()) : frozenCandidate;
|
||||
if (candidate == null) throw conflict("入库货物不存在或尚未完成入库");
|
||||
InboundOrderEntity candidateInbound = repository.inbound(candidate.accountId(), candidate.inboundId());
|
||||
if (!wholeGroupAssignment && candidateInbound != null
|
||||
&& "ONE_CLICK_DROPSHIP".equals(candidateInbound.getFulfillmentType())) {
|
||||
throw conflict("一件代发必须按已封托托盘整体加入,不能按箱数拆分");
|
||||
}
|
||||
inboundHoldGuard.requireInboundNotHeld(candidate.accountId(), candidate.inboundId());
|
||||
if (!wholeGroupAssignment) requireStandalone(candidate.accountId(), candidate.inboundId());
|
||||
requireSameCustomerScope(booking, candidate);
|
||||
@@ -445,7 +504,9 @@ public class SmartLoadingService {
|
||||
operatorId, operatorName);
|
||||
}
|
||||
OrderCargoClient.CargoBinding cargo = loadCargo(candidate);
|
||||
LoadingPlanItemEntity existing = repository.activeItem(planId, candidate.accountId(), request.inboundDetailId());
|
||||
LoadingPlanItemEntity existing = palletId == null
|
||||
? repository.activeItem(planId, candidate.accountId(), request.inboundDetailId())
|
||||
: repository.activePalletItem(planId, palletId, request.inboundDetailId());
|
||||
int total = request.packageCount() + (existing == null ? 0 : existing.getPackageCount());
|
||||
int availableIncludingCurrent = candidate.availablePackageCount()
|
||||
+ (existing == null ? 0 : existing.getPackageCount());
|
||||
@@ -457,6 +518,14 @@ public class SmartLoadingService {
|
||||
LoadingPlanItemEntity changed;
|
||||
if (existing == null) {
|
||||
LoadingPlanItemEntity item = snapshot(planId, candidate, cargo, request.packageCount(), operatorId, operatorName, lines.size() + 1);
|
||||
if (palletId != null) {
|
||||
item.setUnitType("SEALED_PALLET");
|
||||
item.setPalletId(palletId);
|
||||
item.setPalletNo(palletNo);
|
||||
item.setIndivisible(true);
|
||||
item.setPalletCount(primaryPalletLine ? 1 : 0);
|
||||
item.setProductQuantity(actualSkuQuantity);
|
||||
}
|
||||
try { repository.insert(item); } catch (DuplicateKeyException e) { throw conflict("货物明细已被并发锁定"); }
|
||||
changed = item;
|
||||
} else {
|
||||
@@ -617,7 +686,7 @@ public class SmartLoadingService {
|
||||
.substring(0, 48);
|
||||
assign(planId, new AssignRequest(request.inboundId(), allocation.candidate().inboundDetailId(),
|
||||
allocation.packageCount(), current.getVersion(), childOperation), operatorId, operatorName,
|
||||
wholeGroupAssignment);
|
||||
wholeGroupAssignment, null, null, null, false, null);
|
||||
sequence++;
|
||||
}
|
||||
LoadingPlanEntity assigned = repository.plan(planId);
|
||||
@@ -859,6 +928,9 @@ public class SmartLoadingService {
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
||||
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
||||
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
||||
if (Boolean.TRUE.equals(item.getIndivisible()) || item.getPalletId() != null) {
|
||||
throw conflict("已封托托盘不可调整箱数,只能整托移除");
|
||||
}
|
||||
inboundHoldGuard.requireInboundNotHeld(item.getAccountId(), item.getInboundId());
|
||||
requireStandalone(item.getAccountId(), item.getInboundId());
|
||||
if (!Objects.equals(item.getVersion(), request.expectedItemVersion())) throw conflict("装柜明细版本冲突");
|
||||
@@ -971,6 +1043,9 @@ public class SmartLoadingService {
|
||||
LoadingPlanEntity plan = editing(planId, request.expectedPlanVersion(), operatorId, operatorName);
|
||||
LoadingPlanItemEntity item = repository.item(planId, itemId);
|
||||
if (item == null) throw new BizException(ResultCode.NOT_FOUND, "装柜明细不存在");
|
||||
if (Boolean.TRUE.equals(item.getIndivisible()) || item.getPalletId() != null) {
|
||||
throw conflict("已封托托盘不可删除单条明细,只能整托移除");
|
||||
}
|
||||
requireStandalone(item.getAccountId(), item.getInboundId());
|
||||
loadingPickService.remove(itemId);
|
||||
touch(plan, operatorId, operatorName);
|
||||
@@ -982,6 +1057,34 @@ public class SmartLoadingService {
|
||||
return view(repository.plan(planId), requireBooking(plan.getContainerBookingId()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_PALLET_REMOVE", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "整托移除一件代发装柜明细")
|
||||
public Plan removePallet(long planId,long palletId,RemovePalletRequest request,long operatorId,String operatorName){
|
||||
String operation=operation(request.operationId());
|
||||
String requestHash=sha256("REMOVE_PALLET|"+planId+'|'+palletId+'|'+request.expectedPlanVersion());
|
||||
Plan replay=replay(operation,planId,"REMOVE_PALLET",null,requestHash); if(replay!=null)return replay;
|
||||
LoadingPlanEntity plan=editing(planId,request.expectedPlanVersion(),operatorId,operatorName);
|
||||
List<LoadingPlanItemEntity> palletItems=repository.activeItemsByPallet(palletId).stream()
|
||||
.filter(item->Objects.equals(item.getLoadingPlanId(),planId)).toList();
|
||||
if(palletItems.isEmpty())throw new BizException(ResultCode.NOT_FOUND,"装柜计划中不存在该托盘");
|
||||
LoadingPickService.MutationSnapshot picks=loadingPickService.mutationSnapshot(planId);
|
||||
if(palletItems.stream().anyMatch(item->picks.pickedByPlanItem().getOrDefault(item.getId(),0)>0))
|
||||
throw conflict("托盘已发生实物拣货,不能移除");
|
||||
for(LoadingPlanItemEntity item:palletItems)loadingPickService.remove(item.getId());
|
||||
touch(plan,operatorId,operatorName);
|
||||
for(LoadingPlanItemEntity item:palletItems){
|
||||
if(repository.remove(planId,item.getId(),item.getVersion(),operatorId,operatorName)!=1)
|
||||
throw conflict("整托装柜明细已被并发修改");
|
||||
}
|
||||
LoadingPlanItemEntity first=palletItems.get(0);
|
||||
loadingModelBridge.removeIfUnallocated(planId,first.getAccountId(),first.getInboundId());
|
||||
history(planId,null,"REMOVE_PALLET",plan.getPlanStatus(),plan.getPlanStatus(),operation,
|
||||
json(values("palletId",palletId,"version",request.expectedPlanVersion())),
|
||||
json(values("requestHash",requestHash,"removedLineCount",palletItems.size())),operatorId,operatorName);
|
||||
return view(repository.plan(planId),requireBooking(plan.getContainerBookingId()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "SMART_LOADING_UNPICKED_REMOVE", resourceType = "LOADING_PLAN",
|
||||
resourceId = "#planId", description = "批量移除智能装柜未拣明细")
|
||||
@@ -1461,7 +1564,40 @@ public class SmartLoadingService {
|
||||
: "COMPLETED".equals(task.taskStatus()) ? "COMPLETED" : "IN_PROGRESS";
|
||||
return new Plan(p.getId(),p.getPlanNo(),p.getPlanStatus(),p.getContainerBookingId(),
|
||||
b.getContainerNo(),b.getVoyageNo(),p.getVersion(),mutable,mutable,blockedReason,finalStatus,
|
||||
task.taskId(),task.taskStatus(),task.taskVersion(),planCoLoadGroups(p.getId()),items);
|
||||
task.taskId(),task.taskStatus(),task.taskVersion(),planCoLoadGroups(p.getId()),collapsePalletUnits(items));
|
||||
}
|
||||
private List<LoadingPlanItemEntity> collapsePalletUnits(List<LoadingPlanItemEntity> items) {
|
||||
List<LoadingPlanItemEntity> result = new ArrayList<>();
|
||||
Map<Long,List<LoadingPlanItemEntity>> pallets = items.stream().filter(i -> i.getPalletId() != null)
|
||||
.collect(java.util.stream.Collectors.groupingBy(LoadingPlanItemEntity::getPalletId,
|
||||
LinkedHashMap::new, java.util.stream.Collectors.toList()));
|
||||
items.stream().filter(i -> i.getPalletId() == null).forEach(result::add);
|
||||
for (List<LoadingPlanItemEntity> lines : pallets.values()) {
|
||||
LoadingPlanItemEntity unit = lines.get(0);
|
||||
unit.setPackageCount(lines.stream().map(LoadingPlanItemEntity::getPackageCount)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||
unit.setProductQuantity(lines.stream().map(LoadingPlanItemEntity::getProductQuantity)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||
unit.setPalletCount(1);
|
||||
unit.setGrossWeightKg(sumDecimal(lines, LoadingPlanItemEntity::getGrossWeightKg));
|
||||
unit.setNetWeightKg(sumDecimal(lines, LoadingPlanItemEntity::getNetWeightKg));
|
||||
unit.setVolumeCbm(sumDecimal(lines, LoadingPlanItemEntity::getVolumeCbm));
|
||||
unit.setPickedPackageCount(lines.stream().map(LoadingPlanItemEntity::getPickedPackageCount)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||
unit.setTotalPackageCount(unit.getPackageCount());
|
||||
unit.setRemainingPackageCount(0);
|
||||
unit.setCanAdjust(false);
|
||||
unit.setCanRemove(lines.stream().allMatch(i -> Boolean.TRUE.equals(i.getCanRemove())));
|
||||
result.add(unit);
|
||||
}
|
||||
return result.stream().sorted(Comparator
|
||||
.comparing(LoadingPlanItemEntity::getLoadSequence, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(LoadingPlanItemEntity::getId, Comparator.nullsLast(Long::compareTo)))
|
||||
.toList();
|
||||
}
|
||||
private static BigDecimal sumDecimal(List<LoadingPlanItemEntity> lines,
|
||||
java.util.function.Function<LoadingPlanItemEntity,BigDecimal> getter) {
|
||||
return lines.stream().map(getter).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
private int adjustmentTotal(LoadingPlanItemEntity item) {
|
||||
int current = item.getPackageCount() == null ? 0 : item.getPackageCount();
|
||||
|
||||
Reference in New Issue
Block a user