一件代发拣货
This commit is contained in:
@@ -34,7 +34,7 @@ public interface PrintPdfClient {
|
||||
}
|
||||
|
||||
record Label(String topCode, String warehouseText, String cartonText,
|
||||
String routeText, String qrContent) {
|
||||
String routeText, String qrContent, String skuCode) {
|
||||
}
|
||||
|
||||
record FreightA4PdfRequest(String templateCode, PageSpec pageSpec,
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Task;
|
||||
import com.baiyue.firstleg.service.DropshipPalletizationService;
|
||||
import com.baiyue.firstleg.service.FirstlegScopeGuard;
|
||||
import com.baiyue.firstleg.service.InboundCartonLabelPrintService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 国内 PDA 一件代发合托;与装柜 PDA 分离。 */
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/first-leg/pda-dropship")
|
||||
public class PlatformDropshipPalletizationController {
|
||||
@Resource private DropshipPalletizationService service;
|
||||
@Resource private InboundCartonLabelPrintService printService;
|
||||
|
||||
@GetMapping("/tasks/pending")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<List<DropshipPalletizationDtos.PendingTask>> pending(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) @Size(max = 64) String keyword) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.pending(user.userId(), keyword));
|
||||
}
|
||||
|
||||
@GetMapping("/tasks/{taskId}")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:view')")
|
||||
public ApiResponse<Task> task(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@RequestParam @Positive long warehouseId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.task(taskId, warehouseId, user.userId()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/claim")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> claim(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.ClaimRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
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,
|
||||
@PathVariable @Positive long taskId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.OpenPalletRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.open(taskId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/scan")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<DropshipPalletizationDtos.ScanResult> scan(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId, @PathVariable @Positive long palletId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.ScanRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.scan(taskId, palletId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/tasks/{taskId}/pallets/{palletId}/seal")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:pda:work')")
|
||||
public ApiResponse<Task> seal(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long taskId,
|
||||
@PathVariable @Positive long palletId,
|
||||
@Valid @RequestBody DropshipPalletizationDtos.SealPalletRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.seal(taskId, palletId, request, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@GetMapping("/inbound-orders/{inboundId}/pallets")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:list')")
|
||||
public ApiResponse<List<Pallet>> inboundPallets(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long inboundId,
|
||||
@RequestParam @Positive long accountId) {
|
||||
FirstlegScopeGuard.platform(user, accountId);
|
||||
return ApiResponse.success(service.inboundPallets(accountId, inboundId));
|
||||
}
|
||||
|
||||
@GetMapping("/inbound-orders/{inboundId}/pallets/{palletId}/print")
|
||||
@PreAuthorize("hasAuthority('firstleg:inbound:list')")
|
||||
public ResponseEntity<byte[]> printPallet(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long inboundId,
|
||||
@PathVariable @Positive long palletId,
|
||||
@RequestParam @Positive long accountId,
|
||||
@RequestHeader(value = "X-Request-Id", required = false) String requestId) {
|
||||
FirstlegScopeGuard.platform(user, accountId);
|
||||
Pallet pallet = service.inboundPallets(accountId, inboundId).stream()
|
||||
.filter(item -> item.palletId() == palletId).findFirst()
|
||||
.orElseThrow(() -> new com.baiyue.common.core.exception.BizException(
|
||||
com.baiyue.common.core.api.ResultCode.NOT_FOUND, "托盘不存在"));
|
||||
return printService.generateDropshipPallet(pallet, requestId);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ public final class InboundProcessStatus {
|
||||
public static final String FEE_CONFIRMING = "FEE_CONFIRMING";
|
||||
/** 费用已确认,正在执行托盘、库位占用和入库收口。 */
|
||||
public static final String INBOUNDING = "INBOUNDING";
|
||||
/** 一件代发已完成测量但未真实入库,等待 PDA 合托。 */
|
||||
public static final String WAIT_PALLETIZE = "WAIT_PALLETIZE";
|
||||
/** 入库和费用发布完成,等待分柜。 */
|
||||
public static final String WAIT_SPLIT = "WAIT_SPLIT";
|
||||
/** 已进入装柜作业,PDA 可执行拣货。 */
|
||||
@@ -33,7 +35,7 @@ public final class InboundProcessStatus {
|
||||
|
||||
public static final Set<String> VALUES = Set.of(
|
||||
PREPARING, WAIT_MEASURE, MEASURING, MEASURE_PENDING, MEASURE_REJECTED,
|
||||
WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, WAIT_SPLIT,
|
||||
WAIT_PRICING, FEE_CONFIRMING, INBOUNDING, WAIT_PALLETIZE, WAIT_SPLIT,
|
||||
LOADING, LOADED, DELIVERED, VOIDED
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.baiyue.firstleg.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/** 一件代发测量完成后的独立 PDA 合托契约。 */
|
||||
public final class DropshipPalletizationDtos {
|
||||
private DropshipPalletizationDtos() {}
|
||||
|
||||
public record ClaimRequest(@Positive long warehouseId) {}
|
||||
|
||||
public record OpenPalletRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 128) String operationId) {}
|
||||
|
||||
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 SealPalletRequest(@Positive long warehouseId,
|
||||
@NotBlank @Size(max = 128) String operationId,
|
||||
@Positive int expectedVersion) {}
|
||||
|
||||
public record PendingTask(Long taskId, Long inboundId, String inboundCode, Long tenantId,
|
||||
String customerName, Long warehouseId, String warehouseName,
|
||||
Integer totalCartons, Integer allocatedCartons, 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,
|
||||
Long assigneeId, String assigneeName, Integer version,
|
||||
List<Pallet> pallets) {}
|
||||
|
||||
public record Pallet(Long palletId, String palletNo, String palletStatus,
|
||||
Integer cartonCount, Integer skuCount, Integer version,
|
||||
List<SkuItem> items, LocalDateTime completedAt) {}
|
||||
|
||||
public record SkuItem(Long inboundId, String inboundCode, Long inboundDetailId,
|
||||
String skuCode, String cargoName, Integer cartonCount,
|
||||
List<String> cartonMarks) {}
|
||||
|
||||
public record ScanResult(String skuCode, String cargoName, String inboundCode,
|
||||
int addedCartons, int allocatedCartons, int remainingCartons,
|
||||
Pallet pallet) {}
|
||||
}
|
||||
@@ -15,8 +15,17 @@ public final class InboundCartonLabels {
|
||||
public record Label(Long id, Long inboundDetailId, Integer cargoItemNo, String cargoName,
|
||||
Integer detailPackageCount, String boxCode, Integer totalCount,
|
||||
Integer cartonIndex, String boxSpecification, BigDecimal lengthCm,
|
||||
BigDecimal widthCm, BigDecimal heightCm, String topCode,
|
||||
BigDecimal widthCm, BigDecimal heightCm, String skuCode, String topCode,
|
||||
String warehouseText, String cartonText, String routeText) {
|
||||
public Label(Long id, Long inboundDetailId, Integer cargoItemNo, String cargoName,
|
||||
Integer detailPackageCount, String boxCode, Integer totalCount,
|
||||
Integer cartonIndex, String boxSpecification, BigDecimal lengthCm,
|
||||
BigDecimal widthCm, BigDecimal heightCm, String topCode,
|
||||
String warehouseText, String cartonText, String routeText) {
|
||||
this(id, inboundDetailId, cargoItemNo, cargoName, detailPackageCount, boxCode,
|
||||
totalCount, cartonIndex, boxSpecification, lengthCm, widthCm, heightCm,
|
||||
null, topCode, warehouseText, cartonText, routeText);
|
||||
}
|
||||
}
|
||||
|
||||
public record Resolution(Long inboundId, Long orderId, Long inboundDetailId, String boxCode,
|
||||
|
||||
@@ -8,11 +8,18 @@ import java.util.List;
|
||||
public record InboundOrderDetail(
|
||||
InboundOrderSummary summary,
|
||||
String publicRemark,
|
||||
List<DropshipPalletizationDtos.Pallet> dropshipPallets,
|
||||
List<PalletLocationSummary> palletLocations,
|
||||
List<FileSummary> files,
|
||||
List<InternalNoteSummary> internalNotes,
|
||||
List<StatusHistorySummary> statusHistory
|
||||
) {
|
||||
public InboundOrderDetail(InboundOrderSummary summary, String publicRemark,
|
||||
List<PalletLocationSummary> palletLocations, List<FileSummary> files,
|
||||
List<InternalNoteSummary> internalNotes,
|
||||
List<StatusHistorySummary> statusHistory) {
|
||||
this(summary, publicRemark, List.of(), palletLocations, files, internalNotes, statusHistory);
|
||||
}
|
||||
public record PalletLocationSummary(
|
||||
Integer allocationNo, Long warehouseId, Long palletId, String palletCode,
|
||||
BigDecimal palletCapacityCbm, BigDecimal allocatedVolumeCbm, Integer allocatedPackageCount,
|
||||
|
||||
@@ -14,7 +14,7 @@ public record UnifiedInboundQuery(
|
||||
Long tenantId,
|
||||
@Size(max = 64) String keyword,
|
||||
@Size(max = 64) String orderNo,
|
||||
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_SPLIT",
|
||||
@Schema(allowableValues = {"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_PALLETIZE", "WAIT_SPLIT",
|
||||
"LOADING", "LOADED", "DELIVERED"})
|
||||
@Size(max = 32) String mainStatus,
|
||||
Long customerId,
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.baiyue.firstleg.mapper;
|
||||
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.PendingTask;
|
||||
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 java.time.LocalDateTime;
|
||||
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);
|
||||
|
||||
@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.*,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);
|
||||
|
||||
@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);
|
||||
|
||||
@Select("SELECT * FROM fl_dropship_pallet WHERE open_operation_id=#{operationId}")
|
||||
PalletRow palletByOpenOperation(@Param("operationId") String operationId);
|
||||
|
||||
@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 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 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;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_SPLIT', measure_review_status = 'APPROVED',
|
||||
SET process_status = CASE WHEN fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
THEN 'WAIT_PALLETIZE' ELSE 'WAIT_SPLIT' END,
|
||||
measure_review_status = 'APPROVED',
|
||||
actual_gross_weight_kg = (SELECT SUM(weight.gross_weight_kg)
|
||||
FROM fl_inbound_weight_result weight
|
||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
||||
@@ -41,7 +43,9 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
volume_weight_kg = (SELECT COALESCE(SUM(item.volume_weight_kg), 0)
|
||||
FROM fl_inbound_measurement_item item
|
||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
||||
inbounded_at = CURRENT_TIMESTAMP(3), updated_by = #{operatorId}, version = version + 1
|
||||
inbounded_at = CASE WHEN fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
THEN NULL ELSE CURRENT_TIMESTAMP(3) END,
|
||||
updated_by = #{operatorId}, version = version + 1
|
||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||
AND process_status = 'MEASURE_PENDING' AND measure_review_status = 'PENDING'
|
||||
AND version = #{expectedVersion}
|
||||
@@ -65,7 +69,10 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_inbound_order
|
||||
SET process_status = 'WAIT_SPLIT', inbounded_at = CURRENT_TIMESTAMP(3),
|
||||
SET process_status = CASE WHEN fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
THEN 'WAIT_PALLETIZE' ELSE 'WAIT_SPLIT' END,
|
||||
inbounded_at = CASE WHEN fulfillment_type='ONE_CLICK_DROPSHIP'
|
||||
THEN NULL ELSE CURRENT_TIMESTAMP(3) END,
|
||||
updated_by = #{operatorId}, version = version + 1
|
||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||
AND process_status = 'MEASURE_REJECTED' AND measure_review_status = 'REJECTED'
|
||||
|
||||
@@ -80,7 +80,7 @@ public interface LoadingCompletedEventMapper {
|
||||
""")
|
||||
List<PalletRow> palletRows(@Param("planId") long planId);
|
||||
|
||||
/** 一件代发现场逐箱扫描结果;同一实物托盘可以包含多个客户,但行仍保留客户与入仓单归属。 */
|
||||
/** 一件代发测量后合托的逐箱结果;装柜只读取已冻结事实,不再现场重新打托。 */
|
||||
@Select("""
|
||||
SELECT carton.tenant_id AS account_id,carton.inbound_id AS source_inbound_id,
|
||||
pallet.id AS pallet_id,1 AS actual_binding,
|
||||
@@ -88,20 +88,28 @@ public interface LoadingCompletedEventMapper {
|
||||
COUNT(*) OVER (PARTITION BY pallet.id,carton.tenant_id,carton.inbound_id) AS allocated_package_count,
|
||||
carton.inbound_detail_id AS source_inbound_detail_id,carton.sku_code,
|
||||
item.product_name_zh,item.package_count,item.product_quantity,carton.box_code
|
||||
FROM fl_loading_work_pallet pallet
|
||||
JOIN fl_loading_work_pallet_carton carton ON carton.work_pallet_id=pallet.id
|
||||
JOIN fl_loading_plan_item item ON item.id=carton.loading_plan_item_id AND item.deleted=0
|
||||
JOIN fl_loading_pick_task task ON task.id=pallet.pick_task_id
|
||||
AND task.loading_plan_id=pallet.loading_plan_id
|
||||
WHERE pallet.loading_plan_id=#{planId} AND pallet.pallet_status='SEALED'
|
||||
FROM fl_dropship_pallet pallet
|
||||
JOIN fl_dropship_pallet_carton carton ON carton.pallet_id=pallet.id
|
||||
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'
|
||||
ORDER BY carton.tenant_id,carton.inbound_id,pallet.id,item.inbound_detail_id,carton.id
|
||||
""")
|
||||
List<PalletRow> actualPalletRows(@Param("planId") long planId);
|
||||
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_work_pallet WHERE loading_plan_id=#{planId} AND pallet_status<>'CANCELLED'")
|
||||
@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'
|
||||
""")
|
||||
int workPalletCount(@Param("planId") long planId);
|
||||
|
||||
@Select("SELECT COUNT(*) FROM fl_loading_work_pallet WHERE loading_plan_id=#{planId} AND pallet_status='OPEN'")
|
||||
@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='OPEN'
|
||||
""")
|
||||
int openWorkPalletCount(@Param("planId") long planId);
|
||||
|
||||
@Getter @Setter
|
||||
|
||||
@@ -244,7 +244,7 @@ public class InboundOrderRepository {
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.joining("、"));
|
||||
return new InboundOrderDetail(InboundOrderSummary.from(order, transport, pickup, locationCodes),
|
||||
order.getPublicRemark(),
|
||||
order.getPublicRemark(), List.of(),
|
||||
palletSummaries, fileSummaries, noteSummaries, historySummaries);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
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.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 jakarta.annotation.Resource;
|
||||
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;
|
||||
|
||||
/** 测量完成即下发的一件代发合托,不创建国内仓库托盘、库位或库存。 */
|
||||
@Service
|
||||
public class DropshipPalletizationService {
|
||||
@Resource private DropshipPalletizationMapper mapper;
|
||||
@Resource private FirstlegDocumentNumberGenerator numbers;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@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 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()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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 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 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 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 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); }
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ import java.util.Set;
|
||||
public class FirstlegDocumentNumberGenerator {
|
||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd", Locale.ROOT);
|
||||
private static final Set<String> SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "CAB", "LP");
|
||||
private static final Set<String> SUPPORTED_PREFIXES = Set.of("OB", "TA", "CB", "CAB", "LP", "DP");
|
||||
private static final long MAX_DAILY_SEQUENCE = 9_999L;
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
+24
-1
@@ -19,6 +19,7 @@ import org.springframework.util.StringUtils;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
||||
|
||||
/** Allocates stable carton codes and forwards immutable label facts to Print. */
|
||||
@Service
|
||||
@@ -91,6 +92,28 @@ public class InboundCartonLabelPrintService {
|
||||
return ResponseEntity.status(response.getStatusCode()).headers(headers).body(response.getBody());
|
||||
}
|
||||
|
||||
/** 一件代发托盘标签:托盘号、入仓码、SKU箱数和托盘二维码。 */
|
||||
public ResponseEntity<byte[]> generateDropshipPallet(Pallet pallet, String requestId) {
|
||||
if (pallet == null || !StringUtils.hasText(pallet.palletNo()) || pallet.items().isEmpty()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "只有已完成且包含货物的一件代发托盘可以打印");
|
||||
}
|
||||
String inboundCodes = pallet.items().stream().map(item -> item.inboundCode())
|
||||
.filter(StringUtils::hasText).distinct().collect(java.util.stream.Collectors.joining("/"));
|
||||
String skuSummary = pallet.items().stream()
|
||||
.map(item -> item.skuCode() + "×" + item.cartonCount())
|
||||
.collect(java.util.stream.Collectors.joining(" / "));
|
||||
PrintPdfClient.Label label = new PrintPdfClient.Label(pallet.palletNo(), inboundCodes,
|
||||
"CTNS:" + pallet.cartonCount(), skuSummary, pallet.palletNo(), null);
|
||||
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
||||
"INBOUND_MARK_V1", new PrintPdfClient.PageSpec(PAGE_MM, PAGE_MM),
|
||||
pallet.palletNo() + "-托盘唛.pdf", List.of(label));
|
||||
try {
|
||||
return requirePdf(printClient.generateInboundMarks(internalToken, requestId, request));
|
||||
} catch (FeignException | NoFallbackAvailableException exception) {
|
||||
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "打印服务暂不可用,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> generatePdf(String fileName, List<InboundCartonLabels.Label> labels,
|
||||
String requestId) {
|
||||
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
||||
@@ -105,7 +128,7 @@ public class InboundCartonLabelPrintService {
|
||||
|
||||
private PrintPdfClient.Label toPrintLabel(InboundCartonLabels.Label label) {
|
||||
return new PrintPdfClient.Label(label.topCode(), label.warehouseText(), label.cartonText(),
|
||||
label.routeText(), label.boxCode());
|
||||
label.routeText(), label.boxCode(), label.skuCode());
|
||||
}
|
||||
|
||||
private ResponseEntity<byte[]> requirePdf(ResponseEntity<byte[]> response) {
|
||||
|
||||
@@ -291,7 +291,7 @@ public class InboundCartonLabelService implements CartonLabelBindingResolver {
|
||||
new InboundCartonLabels.Label(entity.getId(), entity.getInboundDetailId(), entity.getCargoItemNo(),
|
||||
entity.getCargoName(), entity.getDetailPackageCount(), entity.getBoxCode(),
|
||||
entity.getTotalCartonCount(), entity.getCartonIndex(), entity.getBoxSpecification(),
|
||||
entity.getLengthCm(), entity.getWidthCm(), entity.getHeightCm(), inbound.getInboundCode(),
|
||||
entity.getLengthCm(), entity.getWidthCm(), entity.getHeightCm(), entity.getSkuCode(), inbound.getInboundCode(),
|
||||
fallback(inbound.getWarehouseName(), "WAREHOUSE"),
|
||||
"CTNS:" + entity.getTotalCartonCount() + "-" + entity.getCartonIndex(),
|
||||
fallback(inbound.getChannelName(), fallback(inbound.getChannelCode(), "ROUTE"))))
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.springframework.util.StringUtils;
|
||||
public class InboundOrderQueryService {
|
||||
@Resource
|
||||
private InboundOrderRepository repository;
|
||||
@Resource
|
||||
private DropshipPalletizationService dropshipPalletization;
|
||||
|
||||
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
|
||||
validateQuery(query);
|
||||
@@ -30,7 +32,11 @@ public class InboundOrderQueryService {
|
||||
if (order == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
}
|
||||
return repository.detail(tenantId, order);
|
||||
InboundOrderDetail detail = repository.detail(tenantId, order);
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType())) return detail;
|
||||
return new InboundOrderDetail(detail.summary(), detail.publicRemark(),
|
||||
dropshipPalletization.inboundPallets(tenantId, inboundId), detail.palletLocations(),
|
||||
detail.files(), detail.internalNotes(), detail.statusHistory());
|
||||
}
|
||||
|
||||
private void validateQuery(InboundOrderQuery query) {
|
||||
|
||||
@@ -33,6 +33,8 @@ public class MeasurementReviewService {
|
||||
private InboundIdempotencyService idempotency;
|
||||
@Resource
|
||||
private InboundProperties properties;
|
||||
@Resource
|
||||
private DropshipPalletizationService dropshipPalletization;
|
||||
|
||||
@Transactional
|
||||
public void approve(long tenantId, long operatorId, String operatorName, long inboundId,
|
||||
@@ -117,8 +119,13 @@ public class MeasurementReviewService {
|
||||
}
|
||||
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), result,
|
||||
request.reason(), false, null, false, operatorId, operatorName));
|
||||
if (approved && "ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType())) {
|
||||
dropshipPalletization.createTask(order);
|
||||
}
|
||||
String approvedStatus = "ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType())
|
||||
? "WAIT_PALLETIZE" : "WAIT_SPLIT";
|
||||
repository.insertHistory(history(tenantId, inboundId, "MEASURE_PENDING",
|
||||
approved ? "WAIT_SPLIT" : "MEASURE_REJECTED",
|
||||
approved ? approvedStatus : "MEASURE_REJECTED",
|
||||
approved ? "MEASURE_APPROVE" : "MEASURE_REJECT", request.reason(), operatorId, operatorName));
|
||||
idempotency.complete(context, inboundId);
|
||||
}
|
||||
@@ -152,9 +159,13 @@ public class MeasurementReviewService {
|
||||
if (repository.releaseRejected(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) {
|
||||
throw concurrentConflict();
|
||||
}
|
||||
if ("ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType())) {
|
||||
dropshipPalletization.createTask(order);
|
||||
}
|
||||
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), "REJECTED",
|
||||
null, true, request.reason(), true, operatorId, operatorName));
|
||||
repository.insertHistory(history(tenantId, inboundId, "MEASURE_REJECTED", "WAIT_SPLIT",
|
||||
repository.insertHistory(history(tenantId, inboundId, "MEASURE_REJECTED",
|
||||
"ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType()) ? "WAIT_PALLETIZE" : "WAIT_SPLIT",
|
||||
"MEASURE_EXCEPTION_RELEASE", request.reason(), operatorId, operatorName));
|
||||
idempotency.complete(context, inboundId);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class UnifiedInboundQueryService {
|
||||
private static final Set<String> MAIN_STATUSES = Set.of(
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
|
||||
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_PALLETIZE", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
|
||||
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
|
||||
|
||||
@Resource private InboundOrderRepository inbounds;
|
||||
@@ -165,6 +165,7 @@ public class UnifiedInboundQueryService {
|
||||
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
||||
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING" -> "WAIT_MEASURE";
|
||||
case "WAIT_SPLIT" -> "WAIT_SPLIT";
|
||||
case "WAIT_PALLETIZE" -> "WAIT_PALLETIZE";
|
||||
case "LOADING", "LOADED", "DELIVERED" -> status;
|
||||
default -> null;
|
||||
};
|
||||
@@ -175,6 +176,7 @@ public class UnifiedInboundQueryService {
|
||||
return switch (mainStatus) {
|
||||
case "PENDING_REVIEW" -> "APPROVE_ORDER";
|
||||
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
|
||||
case "WAIT_PALLETIZE" -> "PALLETIZE_DROPSHIP";
|
||||
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
|
||||
: "CONFIRMED".equals(preloadStatus) ? null
|
||||
: preloadStatus == null ? "SMART_SPLIT" : "CONFIRM_PRELOAD";
|
||||
@@ -195,6 +197,7 @@ public class UnifiedInboundQueryService {
|
||||
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
||||
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING");
|
||||
case "WAIT_SPLIT" -> List.of("WAIT_SPLIT");
|
||||
case "WAIT_PALLETIZE" -> List.of("WAIT_PALLETIZE");
|
||||
case "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
|
||||
default -> List.of("__NO_MATCH__");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user