一件代发拣货
This commit is contained in:
@@ -16702,3 +16702,82 @@ CALL baiyue_firstleg.assert_unified_final_schema();
|
|||||||
DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema;
|
DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema;
|
||||||
SELECT 'PASS' AS migration_status,
|
SELECT 'PASS' AS migration_status,
|
||||||
'统一增量全部执行完成;父子订单、履约分片、装柜门禁、订舱拖车、报关船期、船司查询、仓库角色、费用、Finance人工财务、公共资源、可靠审计及IAM安全事件结构校验通过' AS message;
|
'统一增量全部执行完成;父子订单、履约分片、装柜门禁、订舱拖车、报关船期、船司查询、仓库角色、费用、Finance人工财务、公共资源、可靠审计及IAM安全事件结构校验通过' AS message;
|
||||||
|
|
||||||
|
|
||||||
|
-- 一件代发测量完成后的独立 PDA 合托;不代表国内仓库存入库。
|
||||||
|
USE baiyue_firstleg;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_task (
|
||||||
|
id BIGINT UNSIGNED NOT NULL COMMENT '合托任务ID',
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键',
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID',
|
||||||
|
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID',
|
||||||
|
task_status VARCHAR(24) NOT NULL COMMENT 'PENDING/CLAIMED/COMPLETED',
|
||||||
|
total_cartons INT UNSIGNED NOT NULL COMMENT '测量确认总箱数',
|
||||||
|
allocated_cartons INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已上托箱数',
|
||||||
|
assignee_id BIGINT UNSIGNED NULL,
|
||||||
|
assignee_name VARCHAR(64) NULL,
|
||||||
|
claimed_at DATETIME(3) NULL,
|
||||||
|
completed_at DATETIME(3) NULL,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_task_inbound (tenant_id,inbound_id),
|
||||||
|
KEY idx_dropship_task_operator (task_status,assignee_id,warehouse_id,created_at),
|
||||||
|
CONSTRAINT fk_dropship_task_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order(id),
|
||||||
|
CONSTRAINT chk_dropship_task_status CHECK (task_status IN ('PENDING','CLAIMED','COMPLETED')),
|
||||||
|
CONSTRAINT chk_dropship_task_cartons CHECK (allocated_cartons<=total_cartons)
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发测量后PDA合托任务';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet (
|
||||||
|
id BIGINT UNSIGNED NOT NULL COMMENT '合托托盘ID',
|
||||||
|
task_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
warehouse_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
pallet_no VARCHAR(32) NULL COMMENT '封托时生成的DP托盘号',
|
||||||
|
pallet_status VARCHAR(16) NOT NULL COMMENT 'OPEN/COMPLETED',
|
||||||
|
open_operation_id VARCHAR(128) NOT NULL,
|
||||||
|
seal_operation_id VARCHAR(128) NULL,
|
||||||
|
created_by BIGINT UNSIGNED NOT NULL,
|
||||||
|
created_by_name VARCHAR(64) NULL,
|
||||||
|
completed_by BIGINT UNSIGNED NULL,
|
||||||
|
completed_by_name VARCHAR(64) NULL,
|
||||||
|
completed_at DATETIME(3) NULL,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_no (pallet_no),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_open_op (open_operation_id),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_seal_op (seal_operation_id),
|
||||||
|
KEY idx_dropship_pallet_inbound (tenant_id,inbound_id,pallet_status),
|
||||||
|
CONSTRAINT fk_dropship_pallet_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id),
|
||||||
|
CONSTRAINT chk_dropship_pallet_status CHECK (pallet_status IN ('OPEN','COMPLETED'))
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发合托形成的可打印托盘';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton (
|
||||||
|
id BIGINT UNSIGNED NOT NULL,
|
||||||
|
task_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
pallet_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_detail_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
carton_label_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
sku_code VARCHAR(128) NOT NULL,
|
||||||
|
cargo_name VARCHAR(255) NULL,
|
||||||
|
box_code VARCHAR(64) NOT NULL,
|
||||||
|
operation_id VARCHAR(128) NOT NULL,
|
||||||
|
bound_by BIGINT UNSIGNED NOT NULL,
|
||||||
|
bound_by_name VARCHAR(64) NULL,
|
||||||
|
bound_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_carton_task_label (task_id,carton_label_id),
|
||||||
|
KEY idx_dropship_carton_pallet_sku (pallet_id,sku_code,id),
|
||||||
|
KEY idx_dropship_carton_inbound (tenant_id,inbound_id,pallet_id),
|
||||||
|
KEY idx_dropship_carton_operation (operation_id),
|
||||||
|
CONSTRAINT fk_dropship_carton_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id),
|
||||||
|
CONSTRAINT fk_dropship_carton_pallet FOREIGN KEY (pallet_id) REFERENCES fl_dropship_pallet(id),
|
||||||
|
CONSTRAINT fk_dropship_carton_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id)
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发托盘内逐箱SKU和入仓单明细';
|
||||||
|
|||||||
+2
-1
@@ -15,7 +15,8 @@
|
|||||||
| 履约任务/分配 | `controller/PlatformFulfillmentAssignmentController.java`、`service/FulfillmentTaskService.java` |
|
| 履约任务/分配 | `controller/PlatformFulfillmentAssignmentController.java`、`service/FulfillmentTaskService.java` |
|
||||||
| 入库单查询/聚合 | `controller/PlatformInboundOrderController.java`、`service/InboundOrderQueryService.java` |
|
| 入库单查询/聚合 | `controller/PlatformInboundOrderController.java`、`service/InboundOrderQueryService.java` |
|
||||||
| 平台测量/OCR | `controller/PlatformInboundMeasurementController.java`、`service/InboundMeasurementService.java`、`service/CartonMeasurementService.java`;手工单箱测量也从该 Controller 与 `CartonMeasurementService` 进入 |
|
| 平台测量/OCR | `controller/PlatformInboundMeasurementController.java`、`service/InboundMeasurementService.java`、`service/CartonMeasurementService.java`;手工单箱测量也从该 Controller 与 `CartonMeasurementService` 进入 |
|
||||||
| 箱唛箱码生成、打印与测量绑定 | 审核建单生成钩子 `controller/InternalInboundOrderController.java`;生成/解析 `service/InboundCartonLabelService.java`、`repository/InboundCartonLabelRepository.java`;平台与客户打印 `controller/PlatformInboundOrderController.java`、`MerchantInboundCartonLabelController.java`、`service/InboundCartonLabelPrintService.java`。`fl_inbound_carton_label.sku_code` 是箱唛的 SKU 冻结快照,生成时经 `client/OrderCargoClient.java` 获取;既有记录按 `inbound_detail_id` 回填。`ONE_CLICK_DROPSHIP` 缺 SKU 时禁止生成箱唛。`box_code`、二维码内容和现有打印版式不携带 SKU,不能因该绑定改变。基线/升级 SQL 为 `src/main/resources/db/phase2-inbound-carton-label.sql`、`phase2-carton-label-versioning-migration.sql`、`../deploy/sql/头程数据库统一增量-MySQL8.sql` |
|
| 箱唛箱码生成、打印与测量绑定 | 审核建单生成钩子 `controller/InternalInboundOrderController.java`;生成/解析 `service/InboundCartonLabelService.java`、`repository/InboundCartonLabelRepository.java`;平台与客户打印 `controller/PlatformInboundOrderController.java`、`MerchantInboundCartonLabelController.java`、`service/InboundCartonLabelPrintService.java`。`fl_inbound_carton_label.sku_code` 是箱唛的 SKU 冻结快照,生成时经 `client/OrderCargoClient.java` 获取;既有记录按 `inbound_detail_id` 回填。`ONE_CLICK_DROPSHIP` 缺 SKU 时禁止生成箱唛,打印面显示 SKU;`box_code` 与二维码内容仍只携带稳定箱码。基线/升级 SQL 为 `src/main/resources/db/phase2-inbound-carton-label.sql`、`phase2-carton-label-versioning-migration.sql`、`../deploy/sql/头程数据库统一增量-MySQL8.sql` |
|
||||||
|
| 一件代发测量后 PDA 合托 | `controller/PlatformDropshipPalletizationController.java`、`service/DropshipPalletizationService.java`、`mapper/DropshipPalletizationMapper.java`;测量通过进入 `WAIT_PALLETIZE` 并下发独立任务,不创建国内库存,封托生成 `DP` 托盘号,全部箱数完成后进入 `WAIT_SPLIT`。契约 `src/main/resources/openapi/dropship-palletization-v1.yaml` |
|
||||||
| 墨家设备测量上传 | `controller/OpenApiInboundMeasurementController.java`、`dto/MojiaMeasurementDtos.java`、`service/MojiaMeasurementOpenApiService.java`、`security/MeasurementRequestAuthenticator.java`;接口契约见 `src/main/resources/openapi/firstleg-v1.yaml` 与 `../../docs/墨家设备测量上传接口说明-V3.md` |
|
| 墨家设备测量上传 | `controller/OpenApiInboundMeasurementController.java`、`dto/MojiaMeasurementDtos.java`、`service/MojiaMeasurementOpenApiService.java`、`security/MeasurementRequestAuthenticator.java`;接口契约见 `src/main/resources/openapi/firstleg-v1.yaml` 与 `../../docs/墨家设备测量上传接口说明-V3.md` |
|
||||||
| 审核与初始计价 | `controller/PlatformInboundMeasurementReviewController.java`、`service/MeasurementReviewService.java`、`service/InitialInboundPricingService.java`、`dto/MeasurementApprovalResponse.java` |
|
| 审核与初始计价 | `controller/PlatformInboundMeasurementReviewController.java`、`service/MeasurementReviewService.java`、`service/InitialInboundPricingService.java`、`dto/MeasurementApprovalResponse.java` |
|
||||||
| FBA 主数据/计费 | `controller/PlatformFbaMasterDataController.java`、`PlatformFbaPricingController.java`、`service/FbaPricingService.java` |
|
| FBA 主数据/计费 | `controller/PlatformFbaMasterDataController.java`、`PlatformFbaPricingController.java`、`service/FbaPricingService.java` |
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public interface PrintPdfClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
record Label(String topCode, String warehouseText, String cartonText,
|
record Label(String topCode, String warehouseText, String cartonText,
|
||||||
String routeText, String qrContent) {
|
String routeText, String qrContent, String skuCode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
record FreightA4PdfRequest(String templateCode, PageSpec pageSpec,
|
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 FEE_CONFIRMING = "FEE_CONFIRMING";
|
||||||
/** 费用已确认,正在执行托盘、库位占用和入库收口。 */
|
/** 费用已确认,正在执行托盘、库位占用和入库收口。 */
|
||||||
public static final String INBOUNDING = "INBOUNDING";
|
public static final String INBOUNDING = "INBOUNDING";
|
||||||
|
/** 一件代发已完成测量但未真实入库,等待 PDA 合托。 */
|
||||||
|
public static final String WAIT_PALLETIZE = "WAIT_PALLETIZE";
|
||||||
/** 入库和费用发布完成,等待分柜。 */
|
/** 入库和费用发布完成,等待分柜。 */
|
||||||
public static final String WAIT_SPLIT = "WAIT_SPLIT";
|
public static final String WAIT_SPLIT = "WAIT_SPLIT";
|
||||||
/** 已进入装柜作业,PDA 可执行拣货。 */
|
/** 已进入装柜作业,PDA 可执行拣货。 */
|
||||||
@@ -33,7 +35,7 @@ public final class InboundProcessStatus {
|
|||||||
|
|
||||||
public static final Set<String> VALUES = Set.of(
|
public static final Set<String> VALUES = Set.of(
|
||||||
PREPARING, WAIT_MEASURE, MEASURING, MEASURE_PENDING, MEASURE_REJECTED,
|
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
|
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,
|
public record Label(Long id, Long inboundDetailId, Integer cargoItemNo, String cargoName,
|
||||||
Integer detailPackageCount, String boxCode, Integer totalCount,
|
Integer detailPackageCount, String boxCode, Integer totalCount,
|
||||||
Integer cartonIndex, String boxSpecification, BigDecimal lengthCm,
|
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) {
|
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,
|
public record Resolution(Long inboundId, Long orderId, Long inboundDetailId, String boxCode,
|
||||||
|
|||||||
@@ -8,11 +8,18 @@ import java.util.List;
|
|||||||
public record InboundOrderDetail(
|
public record InboundOrderDetail(
|
||||||
InboundOrderSummary summary,
|
InboundOrderSummary summary,
|
||||||
String publicRemark,
|
String publicRemark,
|
||||||
|
List<DropshipPalletizationDtos.Pallet> dropshipPallets,
|
||||||
List<PalletLocationSummary> palletLocations,
|
List<PalletLocationSummary> palletLocations,
|
||||||
List<FileSummary> files,
|
List<FileSummary> files,
|
||||||
List<InternalNoteSummary> internalNotes,
|
List<InternalNoteSummary> internalNotes,
|
||||||
List<StatusHistorySummary> statusHistory
|
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(
|
public record PalletLocationSummary(
|
||||||
Integer allocationNo, Long warehouseId, Long palletId, String palletCode,
|
Integer allocationNo, Long warehouseId, Long palletId, String palletCode,
|
||||||
BigDecimal palletCapacityCbm, BigDecimal allocatedVolumeCbm, Integer allocatedPackageCount,
|
BigDecimal palletCapacityCbm, BigDecimal allocatedVolumeCbm, Integer allocatedPackageCount,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public record UnifiedInboundQuery(
|
|||||||
Long tenantId,
|
Long tenantId,
|
||||||
@Size(max = 64) String keyword,
|
@Size(max = 64) String keyword,
|
||||||
@Size(max = 64) String orderNo,
|
@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"})
|
"LOADING", "LOADED", "DELIVERED"})
|
||||||
@Size(max = 32) String mainStatus,
|
@Size(max = 32) String mainStatus,
|
||||||
Long customerId,
|
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("""
|
||||||
UPDATE fl_inbound_order
|
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)
|
actual_gross_weight_kg = (SELECT SUM(weight.gross_weight_kg)
|
||||||
FROM fl_inbound_weight_result weight
|
FROM fl_inbound_weight_result weight
|
||||||
WHERE weight.tenant_id = #{tenantId} AND weight.inbound_id = #{inboundId}
|
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)
|
volume_weight_kg = (SELECT COALESCE(SUM(item.volume_weight_kg), 0)
|
||||||
FROM fl_inbound_measurement_item item
|
FROM fl_inbound_measurement_item item
|
||||||
WHERE item.tenant_id = #{tenantId} AND item.measurement_id = #{measurementId}),
|
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
|
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||||
AND process_status = 'MEASURE_PENDING' AND measure_review_status = 'PENDING'
|
AND process_status = 'MEASURE_PENDING' AND measure_review_status = 'PENDING'
|
||||||
AND version = #{expectedVersion}
|
AND version = #{expectedVersion}
|
||||||
@@ -65,7 +69,10 @@ public interface InboundOrderMapper extends BaseMapper<InboundOrderEntity> {
|
|||||||
|
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE fl_inbound_order
|
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
|
updated_by = #{operatorId}, version = version + 1
|
||||||
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
WHERE tenant_id = #{tenantId} AND id = #{inboundId} AND deleted = 0
|
||||||
AND process_status = 'MEASURE_REJECTED' AND measure_review_status = 'REJECTED'
|
AND process_status = 'MEASURE_REJECTED' AND measure_review_status = 'REJECTED'
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public interface LoadingCompletedEventMapper {
|
|||||||
""")
|
""")
|
||||||
List<PalletRow> palletRows(@Param("planId") long planId);
|
List<PalletRow> palletRows(@Param("planId") long planId);
|
||||||
|
|
||||||
/** 一件代发现场逐箱扫描结果;同一实物托盘可以包含多个客户,但行仍保留客户与入仓单归属。 */
|
/** 一件代发测量后合托的逐箱结果;装柜只读取已冻结事实,不再现场重新打托。 */
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT carton.tenant_id AS account_id,carton.inbound_id AS source_inbound_id,
|
SELECT carton.tenant_id AS account_id,carton.inbound_id AS source_inbound_id,
|
||||||
pallet.id AS pallet_id,1 AS actual_binding,
|
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,
|
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,
|
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
|
item.product_name_zh,item.package_count,item.product_quantity,carton.box_code
|
||||||
FROM fl_loading_work_pallet pallet
|
FROM fl_dropship_pallet pallet
|
||||||
JOIN fl_loading_work_pallet_carton carton ON carton.work_pallet_id=pallet.id
|
JOIN fl_dropship_pallet_carton carton ON carton.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_plan_item item ON item.loading_plan_id=#{planId}
|
||||||
JOIN fl_loading_pick_task task ON task.id=pallet.pick_task_id
|
AND item.account_id=carton.tenant_id AND item.inbound_id=carton.inbound_id
|
||||||
AND task.loading_plan_id=pallet.loading_plan_id
|
AND item.inbound_detail_id=carton.inbound_detail_id AND item.deleted=0
|
||||||
WHERE pallet.loading_plan_id=#{planId} AND pallet.pallet_status='SEALED'
|
WHERE pallet.pallet_status='COMPLETED'
|
||||||
ORDER BY carton.tenant_id,carton.inbound_id,pallet.id,item.inbound_detail_id,carton.id
|
ORDER BY carton.tenant_id,carton.inbound_id,pallet.id,item.inbound_detail_id,carton.id
|
||||||
""")
|
""")
|
||||||
List<PalletRow> actualPalletRows(@Param("planId") long planId);
|
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);
|
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);
|
int openWorkPalletCount(@Param("planId") long planId);
|
||||||
|
|
||||||
@Getter @Setter
|
@Getter @Setter
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ public class InboundOrderRepository {
|
|||||||
.filter(StringUtils::hasText)
|
.filter(StringUtils::hasText)
|
||||||
.collect(Collectors.joining("、"));
|
.collect(Collectors.joining("、"));
|
||||||
return new InboundOrderDetail(InboundOrderSummary.from(order, transport, pickup, locationCodes),
|
return new InboundOrderDetail(InboundOrderSummary.from(order, transport, pickup, locationCodes),
|
||||||
order.getPublicRemark(),
|
order.getPublicRemark(), List.of(),
|
||||||
palletSummaries, fileSummaries, noteSummaries, historySummaries);
|
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 {
|
public class FirstlegDocumentNumberGenerator {
|
||||||
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
private static final ZoneId BUSINESS_ZONE = ZoneId.of("Asia/Shanghai");
|
||||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd", Locale.ROOT);
|
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 static final long MAX_DAILY_SEQUENCE = 9_999L;
|
||||||
|
|
||||||
private final JdbcTemplate jdbc;
|
private final JdbcTemplate jdbc;
|
||||||
|
|||||||
+24
-1
@@ -19,6 +19,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
|
||||||
|
|
||||||
/** Allocates stable carton codes and forwards immutable label facts to Print. */
|
/** Allocates stable carton codes and forwards immutable label facts to Print. */
|
||||||
@Service
|
@Service
|
||||||
@@ -91,6 +92,28 @@ public class InboundCartonLabelPrintService {
|
|||||||
return ResponseEntity.status(response.getStatusCode()).headers(headers).body(response.getBody());
|
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,
|
private ResponseEntity<byte[]> generatePdf(String fileName, List<InboundCartonLabels.Label> labels,
|
||||||
String requestId) {
|
String requestId) {
|
||||||
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
PrintPdfClient.InboundMarkPdfRequest request = new PrintPdfClient.InboundMarkPdfRequest(
|
||||||
@@ -105,7 +128,7 @@ public class InboundCartonLabelPrintService {
|
|||||||
|
|
||||||
private PrintPdfClient.Label toPrintLabel(InboundCartonLabels.Label label) {
|
private PrintPdfClient.Label toPrintLabel(InboundCartonLabels.Label label) {
|
||||||
return new PrintPdfClient.Label(label.topCode(), label.warehouseText(), label.cartonText(),
|
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) {
|
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(),
|
new InboundCartonLabels.Label(entity.getId(), entity.getInboundDetailId(), entity.getCargoItemNo(),
|
||||||
entity.getCargoName(), entity.getDetailPackageCount(), entity.getBoxCode(),
|
entity.getCargoName(), entity.getDetailPackageCount(), entity.getBoxCode(),
|
||||||
entity.getTotalCartonCount(), entity.getCartonIndex(), entity.getBoxSpecification(),
|
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"),
|
fallback(inbound.getWarehouseName(), "WAREHOUSE"),
|
||||||
"CTNS:" + entity.getTotalCartonCount() + "-" + entity.getCartonIndex(),
|
"CTNS:" + entity.getTotalCartonCount() + "-" + entity.getCartonIndex(),
|
||||||
fallback(inbound.getChannelName(), fallback(inbound.getChannelCode(), "ROUTE"))))
|
fallback(inbound.getChannelName(), fallback(inbound.getChannelCode(), "ROUTE"))))
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import org.springframework.util.StringUtils;
|
|||||||
public class InboundOrderQueryService {
|
public class InboundOrderQueryService {
|
||||||
@Resource
|
@Resource
|
||||||
private InboundOrderRepository repository;
|
private InboundOrderRepository repository;
|
||||||
|
@Resource
|
||||||
|
private DropshipPalletizationService dropshipPalletization;
|
||||||
|
|
||||||
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
|
public PageResult<InboundOrderSummary> page(Long tenantId, InboundOrderQuery query) {
|
||||||
validateQuery(query);
|
validateQuery(query);
|
||||||
@@ -30,7 +32,11 @@ public class InboundOrderQueryService {
|
|||||||
if (order == null) {
|
if (order == null) {
|
||||||
throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
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) {
|
private void validateQuery(InboundOrderQuery query) {
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public class MeasurementReviewService {
|
|||||||
private InboundIdempotencyService idempotency;
|
private InboundIdempotencyService idempotency;
|
||||||
@Resource
|
@Resource
|
||||||
private InboundProperties properties;
|
private InboundProperties properties;
|
||||||
|
@Resource
|
||||||
|
private DropshipPalletizationService dropshipPalletization;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void approve(long tenantId, long operatorId, String operatorName, long inboundId,
|
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,
|
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), result,
|
||||||
request.reason(), false, null, false, operatorId, operatorName));
|
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",
|
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));
|
approved ? "MEASURE_APPROVE" : "MEASURE_REJECT", request.reason(), operatorId, operatorName));
|
||||||
idempotency.complete(context, inboundId);
|
idempotency.complete(context, inboundId);
|
||||||
}
|
}
|
||||||
@@ -152,9 +159,13 @@ public class MeasurementReviewService {
|
|||||||
if (repository.releaseRejected(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) {
|
if (repository.releaseRejected(tenantId, inboundId, operatorId, request.expectedVersion()) != 1) {
|
||||||
throw concurrentConflict();
|
throw concurrentConflict();
|
||||||
}
|
}
|
||||||
|
if ("ONE_CLICK_DROPSHIP".equals(order.getFulfillmentType())) {
|
||||||
|
dropshipPalletization.createTask(order);
|
||||||
|
}
|
||||||
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), "REJECTED",
|
repository.insertReview(reviewRecord(tenantId, inboundId, request.measurementId(), "REJECTED",
|
||||||
null, true, request.reason(), true, operatorId, operatorName));
|
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));
|
"MEASURE_EXCEPTION_RELEASE", request.reason(), operatorId, operatorName));
|
||||||
idempotency.complete(context, inboundId);
|
idempotency.complete(context, inboundId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class UnifiedInboundQueryService {
|
public class UnifiedInboundQueryService {
|
||||||
private static final Set<String> MAIN_STATUSES = Set.of(
|
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");
|
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
|
||||||
|
|
||||||
@Resource private InboundOrderRepository inbounds;
|
@Resource private InboundOrderRepository inbounds;
|
||||||
@@ -165,6 +165,7 @@ public class UnifiedInboundQueryService {
|
|||||||
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
||||||
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING" -> "WAIT_MEASURE";
|
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING" -> "WAIT_MEASURE";
|
||||||
case "WAIT_SPLIT" -> "WAIT_SPLIT";
|
case "WAIT_SPLIT" -> "WAIT_SPLIT";
|
||||||
|
case "WAIT_PALLETIZE" -> "WAIT_PALLETIZE";
|
||||||
case "LOADING", "LOADED", "DELIVERED" -> status;
|
case "LOADING", "LOADED", "DELIVERED" -> status;
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
@@ -175,6 +176,7 @@ public class UnifiedInboundQueryService {
|
|||||||
return switch (mainStatus) {
|
return switch (mainStatus) {
|
||||||
case "PENDING_REVIEW" -> "APPROVE_ORDER";
|
case "PENDING_REVIEW" -> "APPROVE_ORDER";
|
||||||
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
|
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
|
||||||
|
case "WAIT_PALLETIZE" -> "PALLETIZE_DROPSHIP";
|
||||||
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
|
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
|
||||||
: "CONFIRMED".equals(preloadStatus) ? null
|
: "CONFIRMED".equals(preloadStatus) ? null
|
||||||
: preloadStatus == null ? "SMART_SPLIT" : "CONFIRM_PRELOAD";
|
: 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",
|
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
|
||||||
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING");
|
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING");
|
||||||
case "WAIT_SPLIT" -> List.of("WAIT_SPLIT");
|
case "WAIT_SPLIT" -> List.of("WAIT_SPLIT");
|
||||||
|
case "WAIT_PALLETIZE" -> List.of("WAIT_PALLETIZE");
|
||||||
case "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
|
case "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
|
||||||
default -> List.of("__NO_MATCH__");
|
default -> List.of("__NO_MATCH__");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS fl_inbound_order (
|
|||||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID,不建立跨库外键',
|
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID,不建立跨库外键',
|
||||||
order_no VARCHAR(32) NOT NULL COMMENT '订单号快照',
|
order_no VARCHAR(32) NOT NULL COMMENT '订单号快照',
|
||||||
inbound_code VARCHAR(40) NOT NULL COMMENT '全平台唯一入仓码',
|
inbound_code VARCHAR(40) NOT NULL COMMENT '全平台唯一入仓码',
|
||||||
process_status VARCHAR(24) NOT NULL DEFAULT 'WAIT_MEASURE' COMMENT '入库流程状态:WAIT_MEASURE待测量、MEASURING测量中、MEASURE_PENDING待审核、MEASURE_REJECTED测量驳回、WAIT_PRICING待计费、FEE_CONFIRMING费用确认中、INBOUNDING入库收口中、WAIT_SPLIT待分柜',
|
process_status VARCHAR(24) NOT NULL DEFAULT 'WAIT_MEASURE' COMMENT '入库流程状态:WAIT_MEASURE待测量、MEASURE_PENDING待审核、WAIT_PALLETIZE一件代发待合托、WAIT_SPLIT待分柜',
|
||||||
order_status_snapshot VARCHAR(24) NOT NULL COMMENT '订单状态快照',
|
order_status_snapshot VARCHAR(24) NOT NULL COMMENT '订单状态快照',
|
||||||
measure_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '测量审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
measure_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '测量审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
||||||
fee_status VARCHAR(24) NOT NULL DEFAULT 'UNPRICED' COMMENT '费用状态:UNPRICED未计费、CALCULATED已计算、CONFIRMED已确认、SENT已发送、VOIDED已作废',
|
fee_status VARCHAR(24) NOT NULL DEFAULT 'UNPRICED' COMMENT '费用状态:UNPRICED未计费、CALCULATED已计算、CONFIRMED已确认、SENT已发送、VOIDED已作废',
|
||||||
@@ -847,3 +847,81 @@ CREATE TABLE IF NOT EXISTS fl_customer_dimension_config (
|
|||||||
CONSTRAINT chk_customer_dimension_edge_addition
|
CONSTRAINT chk_customer_dimension_edge_addition
|
||||||
CHECK (edge_addition_cm >= 0.001 AND edge_addition_cm <= 100.000)
|
CHECK (edge_addition_cm >= 0.001 AND edge_addition_cm <= 100.000)
|
||||||
) ENGINE=InnoDB COMMENT='客户固定四箱规尺寸配置';
|
) ENGINE=InnoDB COMMENT='客户固定四箱规尺寸配置';
|
||||||
|
-- 一件代发测量完成后进入 WAIT_PALLETIZE,完成合托后才进入 WAIT_SPLIT。
|
||||||
|
|
||||||
|
|
||||||
|
-- 一件代发测量完成后的独立 PDA 合托;不代表国内仓库存入库。
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_task (
|
||||||
|
id BIGINT UNSIGNED NOT NULL COMMENT '合托任务ID',
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键',
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID',
|
||||||
|
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID',
|
||||||
|
task_status VARCHAR(24) NOT NULL COMMENT 'PENDING/CLAIMED/COMPLETED',
|
||||||
|
total_cartons INT UNSIGNED NOT NULL COMMENT '测量确认总箱数',
|
||||||
|
allocated_cartons INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已上托箱数',
|
||||||
|
assignee_id BIGINT UNSIGNED NULL,
|
||||||
|
assignee_name VARCHAR(64) NULL,
|
||||||
|
claimed_at DATETIME(3) NULL,
|
||||||
|
completed_at DATETIME(3) NULL,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_task_inbound (tenant_id,inbound_id),
|
||||||
|
KEY idx_dropship_task_operator (task_status,assignee_id,warehouse_id,created_at),
|
||||||
|
CONSTRAINT fk_dropship_task_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order(id),
|
||||||
|
CONSTRAINT chk_dropship_task_status CHECK (task_status IN ('PENDING','CLAIMED','COMPLETED')),
|
||||||
|
CONSTRAINT chk_dropship_task_cartons CHECK (allocated_cartons<=total_cartons)
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发测量后PDA合托任务';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet (
|
||||||
|
id BIGINT UNSIGNED NOT NULL COMMENT '合托托盘ID',
|
||||||
|
task_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
warehouse_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
pallet_no VARCHAR(32) NULL COMMENT '封托时生成的DP托盘号',
|
||||||
|
pallet_status VARCHAR(16) NOT NULL COMMENT 'OPEN/COMPLETED',
|
||||||
|
open_operation_id VARCHAR(128) NOT NULL,
|
||||||
|
seal_operation_id VARCHAR(128) NULL,
|
||||||
|
created_by BIGINT UNSIGNED NOT NULL,
|
||||||
|
created_by_name VARCHAR(64) NULL,
|
||||||
|
completed_by BIGINT UNSIGNED NULL,
|
||||||
|
completed_by_name VARCHAR(64) NULL,
|
||||||
|
completed_at DATETIME(3) NULL,
|
||||||
|
version INT NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_no (pallet_no),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_open_op (open_operation_id),
|
||||||
|
UNIQUE KEY uk_dropship_pallet_seal_op (seal_operation_id),
|
||||||
|
KEY idx_dropship_pallet_inbound (tenant_id,inbound_id,pallet_status),
|
||||||
|
CONSTRAINT fk_dropship_pallet_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id),
|
||||||
|
CONSTRAINT chk_dropship_pallet_status CHECK (pallet_status IN ('OPEN','COMPLETED'))
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发合托形成的可打印托盘';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton (
|
||||||
|
id BIGINT UNSIGNED NOT NULL,
|
||||||
|
task_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
pallet_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
inbound_detail_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
carton_label_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
sku_code VARCHAR(128) NOT NULL,
|
||||||
|
cargo_name VARCHAR(255) NULL,
|
||||||
|
box_code VARCHAR(64) NOT NULL,
|
||||||
|
operation_id VARCHAR(128) NOT NULL,
|
||||||
|
bound_by BIGINT UNSIGNED NOT NULL,
|
||||||
|
bound_by_name VARCHAR(64) NULL,
|
||||||
|
bound_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uk_dropship_carton_task_label (task_id,carton_label_id),
|
||||||
|
KEY idx_dropship_carton_pallet_sku (pallet_id,sku_code,id),
|
||||||
|
KEY idx_dropship_carton_inbound (tenant_id,inbound_id,pallet_id),
|
||||||
|
KEY idx_dropship_carton_operation (operation_id),
|
||||||
|
CONSTRAINT fk_dropship_carton_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id),
|
||||||
|
CONSTRAINT fk_dropship_carton_pallet FOREIGN KEY (pallet_id) REFERENCES fl_dropship_pallet(id),
|
||||||
|
CONSTRAINT fk_dropship_carton_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id)
|
||||||
|
) ENGINE=InnoDB COMMENT='一件代发托盘内逐箱SKU和入仓单明细';
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: 一件代发 PDA 合托 API
|
||||||
|
version: 1.0.0
|
||||||
|
description: 测量完成后独立下发;不创建国内库存。PDA 扫箱唛识别 SKU,并按箱数合托。
|
||||||
|
paths:
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/pending:
|
||||||
|
get:
|
||||||
|
summary: 查询待领取或本人已领取的一件代发合托任务
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}:
|
||||||
|
get:
|
||||||
|
summary: 查询任务、托盘、入仓单、SKU 和箱数明细
|
||||||
|
parameters:
|
||||||
|
- { name: taskId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
- { name: warehouseId, in: query, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}/claim:
|
||||||
|
post:
|
||||||
|
summary: 领取合托任务
|
||||||
|
parameters: [{ name: taskId, in: path, required: true, schema: { type: integer, format: int64 } }]
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}/pallets/open:
|
||||||
|
post:
|
||||||
|
summary: 开启一个现场托盘
|
||||||
|
parameters: [{ name: taskId, in: path, required: true, schema: { type: integer, format: int64 } }]
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}/pallets/{palletId}/scan:
|
||||||
|
post:
|
||||||
|
summary: 扫箱唛识别 SKU 并填写上托箱数
|
||||||
|
parameters:
|
||||||
|
- { name: taskId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
- { name: palletId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/tasks/{taskId}/pallets/{palletId}/seal:
|
||||||
|
post:
|
||||||
|
summary: 完成合托并生成 DP 托盘号
|
||||||
|
parameters:
|
||||||
|
- { name: taskId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
- { name: palletId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/inbound-orders/{inboundId}/pallets:
|
||||||
|
get:
|
||||||
|
summary: 入仓单查看绑定的一个或多个托盘及各自箱数
|
||||||
|
parameters: [{ name: inboundId, in: path, required: true, schema: { type: integer, format: int64 } }]
|
||||||
|
responses: { '200': { description: OK } }
|
||||||
|
/api/platform/v1/first-leg/pda-dropship/inbound-orders/{inboundId}/pallets/{palletId}/print:
|
||||||
|
get:
|
||||||
|
summary: 打印托盘号、入仓码和 SKU 箱数托盘唛
|
||||||
|
parameters:
|
||||||
|
- { name: inboundId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
- { name: palletId, in: path, required: true, schema: { type: integer, format: int64 } }
|
||||||
|
responses:
|
||||||
|
'200': { description: PDF, content: { application/pdf: { schema: { type: string, format: binary } } } }
|
||||||
@@ -1140,7 +1140,7 @@ components:
|
|||||||
orderId: {type: integer, format: int64, nullable: true}
|
orderId: {type: integer, format: int64, nullable: true}
|
||||||
orderNo: {type: string, nullable: true}
|
orderNo: {type: string, nullable: true}
|
||||||
inboundCode: {type: string, nullable: true}
|
inboundCode: {type: string, nullable: true}
|
||||||
processStatus: {type: string, nullable: true, description: Firstleg 内部状态;入库完成记录进入 WAIT_SPLIT}
|
processStatus: {type: string, nullable: true, description: Firstleg 内部状态;一件代发测量后先进入 WAIT_PALLETIZE,合托完成再进入 WAIT_SPLIT}
|
||||||
orderStatusSnapshot: {type: string, nullable: true}
|
orderStatusSnapshot: {type: string, nullable: true}
|
||||||
measureReviewStatus: {type: string, nullable: true}
|
measureReviewStatus: {type: string, nullable: true}
|
||||||
feeStatus: {type: string, nullable: true}
|
feeStatus: {type: string, nullable: true}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import static org.mockito.Mockito.when;
|
|||||||
class InboundOrderQueryServiceTest {
|
class InboundOrderQueryServiceTest {
|
||||||
@MockBean
|
@MockBean
|
||||||
private InboundOrderRepository repository;
|
private InboundOrderRepository repository;
|
||||||
|
@MockBean
|
||||||
|
private DropshipPalletizationService dropshipPalletization;
|
||||||
@Resource
|
@Resource
|
||||||
private InboundOrderQueryService service;
|
private InboundOrderQueryService service;
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class MeasurementReviewServiceTest {
|
|||||||
@MockBean private InboundMeasurementRepository measurements;
|
@MockBean private InboundMeasurementRepository measurements;
|
||||||
@MockBean private InboundIdempotencyService idempotency;
|
@MockBean private InboundIdempotencyService idempotency;
|
||||||
@MockBean private InboundProperties properties;
|
@MockBean private InboundProperties properties;
|
||||||
|
@MockBean private DropshipPalletizationService dropshipPalletization;
|
||||||
@Resource private MeasurementReviewService service;
|
@Resource private MeasurementReviewService service;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ public final class InboundMarkPdfDtos {
|
|||||||
String warehouseText,
|
String warehouseText,
|
||||||
String cartonText,
|
String cartonText,
|
||||||
String routeText,
|
String routeText,
|
||||||
String qrContent
|
String qrContent,
|
||||||
|
String skuCode
|
||||||
) {
|
) {
|
||||||
|
public Label(String topCode, String warehouseText, String cartonText,
|
||||||
|
String routeText, String qrContent) {
|
||||||
|
this(topCode, warehouseText, cartonText, routeText, qrContent, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ public class InboundMarkV1Renderer {
|
|||||||
drawCenteredText(stream, units, font, label.warehouseText(), 36, 26);
|
drawCenteredText(stream, units, font, label.warehouseText(), 36, 26);
|
||||||
drawCenteredText(stream, units, font, label.cartonText(), 22, 37);
|
drawCenteredText(stream, units, font, label.cartonText(), 22, 37);
|
||||||
drawCenteredText(stream, units, font, label.routeText(), 21, 47);
|
drawCenteredText(stream, units, font, label.routeText(), 21, 47);
|
||||||
|
if (label.skuCode() != null) {
|
||||||
|
drawCenteredText(stream, units, font, "SKU: " + label.skuCode(), 14, 55);
|
||||||
|
}
|
||||||
qrRenderer.draw(stream, qrMatrix, QR_LEFT_PT, QR_BOTTOM_PT, QR_SIZE_PT);
|
qrRenderer.draw(stream, qrMatrix, QR_LEFT_PT, QR_BOTTOM_PT, QR_SIZE_PT);
|
||||||
drawCenteredText(stream, units, font, label.qrContent(), 12, 85.5);
|
drawCenteredText(stream, units, font, label.qrContent(), 12, 85.5);
|
||||||
drawCenteredText(stream, units, font, "MADE IN CHINA", 14, 92);
|
drawCenteredText(stream, units, font, "MADE IN CHINA", 14, 92);
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ public class InboundMarkPdfService {
|
|||||||
normalizeRequired(label.warehouseText(), field(index, "warehouseText")),
|
normalizeRequired(label.warehouseText(), field(index, "warehouseText")),
|
||||||
normalizeRequired(label.cartonText(), field(index, "cartonText")),
|
normalizeRequired(label.cartonText(), field(index, "cartonText")),
|
||||||
normalizeRequired(label.routeText(), field(index, "routeText")),
|
normalizeRequired(label.routeText(), field(index, "routeText")),
|
||||||
normalizeQr(label.qrContent(), field(index, "qrContent"))));
|
normalizeQr(label.qrContent(), field(index, "qrContent")),
|
||||||
|
normalizeOptional(label.skuCode(), field(index, "skuCode"))));
|
||||||
}
|
}
|
||||||
return new NormalizedRequest(normalizeFileName(request.downloadFileName()), List.copyOf(labels));
|
return new NormalizedRequest(normalizeFileName(request.downloadFileName()), List.copyOf(labels));
|
||||||
}
|
}
|
||||||
@@ -165,6 +166,14 @@ public class InboundMarkPdfService {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeOptional(String value, String field) {
|
||||||
|
if (value == null || value.isBlank()) return null;
|
||||||
|
String normalized = Normalizer.normalize(value.trim(), Normalizer.Form.NFC);
|
||||||
|
if (normalized.codePointCount(0, normalized.length()) > 64) invalid(field + " must not exceed 64 characters");
|
||||||
|
normalized.codePoints().forEach(codePoint -> { if (isForbiddenControl(codePoint)) invalid(field + " contains a control character"); });
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isForbiddenControl(int codePoint) {
|
private boolean isForbiddenControl(int codePoint) {
|
||||||
int type = Character.getType(codePoint);
|
int type = Character.getType(codePoint);
|
||||||
return Character.isISOControl(codePoint)
|
return Character.isISOControl(codePoint)
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ components:
|
|||||||
minLength: 1
|
minLength: 1
|
||||||
maxLength: 34
|
maxLength: 34
|
||||||
pattern: '^[\\x20-\\x7E]+$'
|
pattern: '^[\\x20-\\x7E]+$'
|
||||||
|
skuCode:
|
||||||
|
type: string
|
||||||
|
maxLength: 64
|
||||||
|
nullable: true
|
||||||
|
description: 一件代发箱唛显示的 SKU;二维码仍只使用 qrContent 稳定箱码
|
||||||
FreightA4PdfRequest:
|
FreightA4PdfRequest:
|
||||||
type: object
|
type: object
|
||||||
required: [templateCode, pageSpec, documents]
|
required: [templateCode, pageSpec, documents]
|
||||||
|
|||||||
Reference in New Issue
Block a user