diff --git a/docs/firstleg-loading-to-lastleg-orders.md b/docs/firstleg-loading-to-lastleg-orders.md index 35cf437..adeabd0 100644 --- a/docs/firstleg-loading-to-lastleg-orders.md +++ b/docs/firstleg-loading-to-lastleg-orders.md @@ -88,7 +88,6 @@ PDA 部分拣货会生成本柜实装 `LOADED` 分片以及可继续分柜的 `R "loadingPlanNo": "LP202608130001", "containerBookingId": "208500001", "containerNo": "MSCU1234567", - "cabinetNo": "CAB20260813001", "containerType": "40HQ", "sealNo": "SEAL001", "voyageNo": "V001", @@ -142,12 +141,20 @@ PDA 部分拣货会生成本柜实装 `LOADED` 分片以及可继续分柜的 `R - 文件只传不可变文件引用、区域和摘要,不传二进制内容。 - V1 消费者忽略未知字段;已有字段不改变含义或类型。 +### 5.1 订单类型分支 + +`orderType`、`pallets`、`palletNo`、`skuPackages` 和 `cartonMarks` 必须正式加入头程公共事件契约及发送端,不能长期依赖尾程读取原始 JSON 扩展字段。三个服务升级到同一 `common-event-contract` 版本后再启用生产流量。 + +`ONE_CLICK_DROPSHIP` 的一笔最终实装入仓单可包含一个或多个来源托盘快照。消费时必须满足:所有托盘的 SKU 箱数之和等于订单箱数、SKU 数量之和等于订单数量、每个 SKU 的箱唛数等于其箱数、同一托盘内 SKU 不重复、同一订单内来源托盘号和箱唛不重复。通过后按来源托盘创建一件代发档案及托盘级 SKU 库存,并创建箱唛记录和不可变箱唛流水。`source_pallet_no` 是头程冻结快照;`warehouse_pallet_id/pallet_no` 是 Warehouse API 分配的海外仓实物托盘,两者不得混用。 + +非 `ONE_CLICK_DROPSHIP` 类型只创建 `ll_order`、冻结明细和初始状态历史,来源 `palletCount` 按 0 入库,不创建一件代发 SKU/箱唛库存。后续入仓 PDA 分配的托盘属于海外仓作业资源。 + ## 6. 尾程建单幂等 尾程订单表必须有以下唯一约束: ```text -UNIQUE (tenant_id, source_region, source_inbound_id) +UNIQUE (customer_id, source_region, source_inbound_id) ``` 建议同时保存: @@ -170,6 +177,8 @@ UNIQUE (tenant_id, source_region, source_inbound_id) 事件内任意一笔订单建单失败时,整个事件消费事务回滚,不允许出现同一客户同一柜次只生成部分尾程订单。 +同一实际集装箱的订单共享 `order_no`、`container_no` 和 `source_loading_plan_id`;`ll_order.id` 仍是每条订单独立的雪花 ID。整柜查询、整柜操作和转入库计划统一以 `containerNo` 聚合,不使用虚拟柜号 `cabinetNo`。数据库必须保留 `KEY idx_ll_order_container (customer_id, container_no)`。 + ## 7. 尾程订单初始状态 事件自动生成的订单建议使用: diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/domain/BridgeInbox.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/domain/BridgeInbox.java index 92d4151..96f15d5 100644 --- a/event-bridge-service/src/main/java/com/baiyue/eventbridge/domain/BridgeInbox.java +++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/domain/BridgeInbox.java @@ -20,7 +20,7 @@ public class BridgeInbox { private Integer eventVersion; private String sourceRegion; private String targetRegion; - private String tenantId; + private String customerId; private String aggregateType; private String aggregateId; private Long aggregateVersion; diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/EventInboxService.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/EventInboxService.java index 252ed0d..146bb4a 100644 --- a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/EventInboxService.java +++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/EventInboxService.java @@ -69,7 +69,7 @@ public class EventInboxService { inbox.setEventVersion(envelope.eventVersion()); inbox.setSourceRegion(envelope.sourceRegion().name()); inbox.setTargetRegion(envelope.targetRegion().name()); - inbox.setTenantId(envelope.tenantId()); + inbox.setCustomerId(envelope.tenantId()); inbox.setAggregateType(envelope.aggregateType()); inbox.setAggregateId(envelope.aggregateId()); inbox.setAggregateVersion(envelope.aggregateVersion()); diff --git a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java index def4b42..3f2a904 100644 --- a/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java +++ b/event-bridge-service/src/main/java/com/baiyue/eventbridge/service/HttpLastlegEventClient.java @@ -34,7 +34,7 @@ public class HttpLastlegEventClient implements LastlegEventClient { public String deliver(BridgeInbox row) throws Exception { EventEnvelope envelope = new EventEnvelope(row.getEventId(), row.getEventType(), row.getEventVersion(), row.getOccurredAt(), RegionCode.valueOf(row.getSourceRegion()), RegionCode.valueOf(row.getTargetRegion()), - row.getTenantId(), row.getAggregateType(), row.getAggregateId(), row.getAggregateVersion(), + row.getCustomerId(), row.getAggregateType(), row.getAggregateId(), row.getAggregateVersion(), row.getHandoverVersion(), row.getTraceId(), row.getCorrelationId(), row.getCausationId(), objectMapper.readTree(row.getPayloadJson()), row.getPayloadHash()); String body = objectMapper.writeValueAsString(envelope); diff --git a/event-bridge-service/src/main/resources/db/event-bridge.sql b/event-bridge-service/src/main/resources/db/event-bridge.sql index 3fd4368..4bbfe6b 100644 --- a/event-bridge-service/src/main/resources/db/event-bridge.sql +++ b/event-bridge-service/src/main/resources/db/event-bridge.sql @@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS bridge_outbox ( event_version INT NOT NULL COMMENT '事件契约版本', source_region VARCHAR(32) NOT NULL COMMENT '来源区域', target_region VARCHAR(32) NOT NULL COMMENT '目标区域', - tenant_id VARCHAR(128) NOT NULL COMMENT '租户标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户标识', aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合标识', aggregate_version BIGINT NOT NULL COMMENT '聚合版本', @@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS bridge_inbox ( event_version INT NOT NULL COMMENT '事件契约版本', source_region VARCHAR(32) NOT NULL COMMENT '来源区域', target_region VARCHAR(32) NOT NULL COMMENT '目标区域', - tenant_id VARCHAR(128) NOT NULL COMMENT '租户标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户标识', aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合标识', aggregate_version BIGINT NOT NULL COMMENT '聚合版本', diff --git a/lastleg/AGENTS.md b/lastleg/AGENTS.md index cff7ce4..69d0d4f 100644 --- a/lastleg/AGENTS.md +++ b/lastleg/AGENTS.md @@ -21,13 +21,17 @@ | 托盘分配算法 | `inboundplan/service/PalletAllocator.java` | | PDA 入仓、实收、上架、合托、完成 | `inboundplan/pda/controller/`、`inboundplan/pda/service/InboundPdaWorkflowService.java` | | 仓储资源调用 | `inboundplan/pda/gateway/WarehouseResourceGateway.java`、`HttpWarehouseResourceGateway.java` | +| 仓储转运、历史订单/入仓绑定、可选换标联动 | `transfer/controller/WarehouseTransferController.java`、`transfer/service/WarehouseTransferService.java` | +| 一件代发接收、SKU/箱唛库存 | `dropship/service/DropshipOrderService.java`、`dropship/entity/`、`dropship/mapper/` | | 数据库 | `src/main/resources/db/lastleg.sql` | | 测试 | `src/test/java/com/baiyue/lastleg/` | ## 事件建单规则 - 只接受冻结契约支持的事件类型和版本;校验事件 ID、租户、区域、Payload 哈希及必要字段。 -- 同一事件/loading plan 生成一个共享尾程单号;`payload.orders` 每项按 `sourceInboundId` 独立落一条 `ll_order`,并保存对应明细和初始状态历史。 +- 同一实际集装箱 `containerNo` 生成一个共享业务尾程单号;`payload.orders` 每项按 `sourceInboundId` 独立落一条雪花 ID `ll_order`,并保存对应明细和初始状态历史。整柜操作不使用 `cabinetNo`。 +- `ONE_CLICK_DROPSHIP` 每个来源入仓单必须携带一个或多个不可变来源托盘快照;每托按 SKU 携带箱数、数量和箱唛。校验订单汇总一致、托盘号/SKU/箱唛唯一后,按托建立档案及 SKU 库存,并建立箱唛和流水。仓库实物托盘由 Warehouse API 分配,不得覆盖来源托盘快照。 +- 其他订单类型只建立入仓单维度的尾程订单与冻结明细,来源 `palletCount` 强制为 0;后续 PDA 托盘属于海外仓作业资源。 - 整个事件消费在一个事务内完成;任意订单失败时不得留下部分订单。 - 相同事件重复投递返回既有结果,不重新生成单号或插入明细。来源入仓单唯一约束是最终防线。 - 内部接口先执行常量时间令牌校验;缺失或错误令牌不得进入消费事务。 @@ -44,4 +48,3 @@ - 事件契约变化同时检查 `common-event-contract` 版本、Event Bridge 消费方和旧 Payload 哈希兼容。 - 定向验证:`mvn -pl lastleg -am test`。 - 事件链路变更至少覆盖重复事件、相同来源入仓单、事务回滚、并发单号、内部令牌和租户越权测试。 - diff --git a/lastleg/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java b/lastleg/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java index fbfaf25..d3373c2 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/controller/LastlegOrderController.java @@ -51,27 +51,27 @@ public class LastlegOrderController { @Operation(summary = "Page tail-leg order rows") public ApiResponse> list( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid LastlegOrderRequests.Query query) { - return ApiResponse.success(orderService.page(scope(user, tenantId), query)); + return ApiResponse.success(orderService.page(scope(user, customerId), query)); } @GetMapping("/api/platform/v1/last-leg/orders/{id}") @Operation(summary = "Get one inbound-code-level tail-leg order") public ApiResponse detail( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable long id) { - return ApiResponse.success(orderService.detail(scope(user, tenantId), id)); + return ApiResponse.success(orderService.detail(scope(user, customerId), id)); } @GetMapping(value = "/api/platform/v1/last-leg/orders/export", produces = "text/csv") @Operation(summary = "Export filtered tail-leg orders as UTF-8 CSV") public ResponseEntity export( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid LastlegOrderRequests.Query query) { - byte[] body = orderService.exportCsv(scope(user, tenantId), query); + byte[] body = orderService.exportCsv(scope(user, customerId), query); return ResponseEntity.ok() .contentType(new MediaType("text", "csv", StandardCharsets.UTF_8)) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=lastleg-orders.csv") @@ -82,31 +82,31 @@ public class LastlegOrderController { @Operation(summary = "Transfer one order to a destination warehouse") public ApiResponse transferOne( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable long id, @Valid @RequestBody LastlegOrderRequests.SingleTransferDestination request) { - return ApiResponse.success(orderService.transferDestination(scope(user, tenantId), request.withOrderId(id))); + return ApiResponse.success(orderService.transferDestination(scope(user, customerId), request.withOrderId(id))); } @PutMapping("/api/platform/v1/last-leg/orders/destination") @Operation(summary = "Transfer multiple orders to a destination warehouse") public ApiResponse transferBatch( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody LastlegOrderRequests.TransferDestination request) { - return ApiResponse.success(orderService.transferDestination(scope(user, tenantId), request)); + return ApiResponse.success(orderService.transferDestination(scope(user, customerId), request)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Platform queries must specify a customer tenant"); + "Platform queries must specify a customer"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java index 585bcb8..49088ef 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java @@ -25,46 +25,46 @@ public class DeliveryTransferController { @Operation(summary = "Create one pending outbound order and one pending delivery order") public ApiResponse transfer( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody DeliveryRequests.Transfer request) { - return ApiResponse.success(service.transfer(scope(user, tenantId), dispatchNo, operator(user), request)); + return ApiResponse.success(service.transfer(scope(user, customerId), dispatchNo, operator(user), request)); } @GetMapping("/outbound-orders") public ApiResponse> outboundPage( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid DeliveryRequests.Query query) { - return ApiResponse.success(service.outboundPage(scope(user, tenantId), query)); + return ApiResponse.success(service.outboundPage(scope(user, customerId), query)); } @GetMapping("/outbound-orders/{outboundNo}") public ApiResponse outboundDetail( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @PathVariable String outboundNo, @Valid DeliveryRequests.PalletQuery query) { - return ApiResponse.success(service.outboundDetail(scope(user, tenantId), outboundNo, query)); + return ApiResponse.success(service.outboundDetail(scope(user, customerId), outboundNo, query)); } @GetMapping("/delivery-orders") public ApiResponse> deliveryPage( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid DeliveryRequests.Query query) { - return ApiResponse.success(service.deliveryPage(scope(user, tenantId), query)); + return ApiResponse.success(service.deliveryPage(scope(user, customerId), query)); } @GetMapping("/delivery-orders/{deliveryNo}") public ApiResponse deliveryDetail( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @PathVariable String deliveryNo, @Valid DeliveryRequests.PalletQuery query) { - return ApiResponse.success(service.deliveryDetail(scope(user, tenantId), deliveryNo, query)); + return ApiResponse.success(service.deliveryDetail(scope(user, customerId), deliveryNo, query)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant.trim(); + return requestedCustomer.trim(); } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java index 0aba9ef..ee1b4f8 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class DeliveryOrder { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private String deliveryNo; private Long outboundOrderId; private String outboundNo; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundOrder.java index 8b65e38..0f43879 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundOrder.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundOrder.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class OutboundOrder { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private String outboundNo; private Long dispatchOrderId; private String dispatchNo; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundPalletItem.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundPalletItem.java index 56c21c7..8145f8f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundPalletItem.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/entity/OutboundPalletItem.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class OutboundPalletItem { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private Long outboundOrderId; private Long warehousePalletId; private String palletNo; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOrderMapper.java index 4263d4e..7d2641c 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOrderMapper.java @@ -8,10 +8,10 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface DeliveryOrderMapper extends BaseMapper { - @Select("SELECT * FROM ll_delivery_order WHERE tenant_id=#{tenantId} AND delivery_no=#{deliveryNo} LIMIT 1") - DeliveryOrder findByNo(@Param("tenantId") String tenantId, @Param("deliveryNo") String deliveryNo); - @Select("SELECT * FROM ll_delivery_order WHERE tenant_id=#{tenantId} AND idempotency_key=#{key} LIMIT 1") - DeliveryOrder findByIdempotency(@Param("tenantId") String tenantId, @Param("key") String key); - @Select("SELECT * FROM ll_delivery_order WHERE tenant_id=#{tenantId} AND outbound_order_id=#{outboundOrderId} LIMIT 1 FOR UPDATE") - DeliveryOrder lockByOutbound(@Param("tenantId") String tenantId, @Param("outboundOrderId") Long outboundOrderId); + @Select("SELECT * FROM ll_delivery_order WHERE customer_id=#{customerId} AND delivery_no=#{deliveryNo} LIMIT 1") + DeliveryOrder findByNo(@Param("customerId") String customerId, @Param("deliveryNo") String deliveryNo); + @Select("SELECT * FROM ll_delivery_order WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + DeliveryOrder findByIdempotency(@Param("customerId") String customerId, @Param("key") String key); + @Select("SELECT * FROM ll_delivery_order WHERE customer_id=#{customerId} AND outbound_order_id=#{outboundOrderId} LIMIT 1 FOR UPDATE") + DeliveryOrder lockByOutbound(@Param("customerId") String customerId, @Param("outboundOrderId") Long outboundOrderId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java index af19598..e16968f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundOrderMapper.java @@ -8,10 +8,10 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface OutboundOrderMapper extends BaseMapper { - @Select("SELECT * FROM ll_outbound_order WHERE tenant_id=#{tenantId} AND outbound_no=#{outboundNo} LIMIT 1") - OutboundOrder findByNo(@Param("tenantId") String tenantId, @Param("outboundNo") String outboundNo); - @Select("SELECT * FROM ll_outbound_order WHERE tenant_id=#{tenantId} AND idempotency_key=#{key} LIMIT 1") - OutboundOrder findByIdempotency(@Param("tenantId") String tenantId, @Param("key") String key); - @Select("SELECT * FROM ll_outbound_order WHERE tenant_id=#{tenantId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1 FOR UPDATE") - OutboundOrder lockByDispatch(@Param("tenantId") String tenantId, @Param("dispatchOrderId") Long dispatchOrderId); + @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND outbound_no=#{outboundNo} LIMIT 1") + OutboundOrder findByNo(@Param("customerId") String customerId, @Param("outboundNo") String outboundNo); + @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + OutboundOrder findByIdempotency(@Param("customerId") String customerId, @Param("key") String key); + @Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1 FOR UPDATE") + OutboundOrder lockByDispatch(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPalletItemMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPalletItemMapper.java index 23cb606..32bb24a 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPalletItemMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPalletItemMapper.java @@ -9,6 +9,6 @@ import java.util.List; @Mapper public interface OutboundPalletItemMapper extends BaseMapper { - @Select("SELECT * FROM ll_outbound_pallet_item WHERE tenant_id=#{tenantId} AND outbound_order_id=#{outboundOrderId} ORDER BY id") - List findByOutbound(@Param("tenantId") String tenantId, @Param("outboundOrderId") Long outboundOrderId); + @Select("SELECT * FROM ll_outbound_pallet_item WHERE customer_id=#{customerId} AND outbound_order_id=#{outboundOrderId} ORDER BY id") + List findByOutbound(@Param("customerId") String customerId, @Param("outboundOrderId") Long outboundOrderId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java b/lastleg/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java index 0a3b62c..4b1f56f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java @@ -49,22 +49,22 @@ public class DeliveryTransferService { @Resource private LastlegOrderStatusHistoryMapper histories; @Transactional - public DeliveryResponses.TransferResult transfer(String tenantId, String dispatchNo, String operatorId, + public DeliveryResponses.TransferResult transfer(String customerId, String dispatchNo, String operatorId, DeliveryRequests.Transfer request) { - DispatchOrder found = dispatchOrders.findByDispatchNo(tenantId, dispatchNo); + DispatchOrder found = dispatchOrders.findByDispatchNo(customerId, dispatchNo); if (found == null) throw notFound("Dispatch order not found"); - DispatchOrder dispatch = dispatchOrders.lockById(tenantId, found.getId()); - OutboundOrder replay = outboundOrders.findByIdempotency(tenantId, request.idempotencyKey().trim()); + DispatchOrder dispatch = dispatchOrders.lockById(customerId, found.getId()); + OutboundOrder replay = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim()); if (replay != null) { if (!dispatch.getId().equals(replay.getDispatchOrderId())) throw conflict("Idempotency key belongs to another dispatch"); - return result(replay, requireDelivery(tenantId, replay.getId()), true); + return result(replay, requireDelivery(customerId, replay.getId()), true); } - OutboundOrder existing = outboundOrders.lockByDispatch(tenantId, dispatch.getId()); - if (existing != null) return result(existing, requireDelivery(tenantId, existing.getId()), true); + OutboundOrder existing = outboundOrders.lockByDispatch(customerId, dispatch.getId()); + if (existing != null) return result(existing, requireDelivery(customerId, existing.getId()), true); if (Set.of("CANCELLED", "DEPARTED", "PARTIAL_DEPARTED", "COMPLETED").contains(dispatch.getStatus())) { throw conflict("Dispatch cannot be transferred to delivery in its current status"); } - List items = dispatchItems.findByDispatchOrder(tenantId, dispatch.getId()); + List items = dispatchItems.findByDispatchOrder(customerId, dispatch.getId()); if (items.isEmpty()) throw conflict("Dispatch has no pallets"); if (items.stream().anyMatch(item -> item.getWarehousePalletId() == null)) { throw conflict("All dispatch pallets must be received before transfer to delivery"); @@ -73,7 +73,7 @@ public class DeliveryTransferService { LocalDateTime now = LocalDateTime.now(); OutboundOrder outbound = new OutboundOrder(); - outbound.setId(IdWorker.getId()); outbound.setTenantId(tenantId); + outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId); outbound.setOutboundNo("OUT" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId()); outbound.setDispatchOrderId(dispatch.getId()); outbound.setDispatchNo(dispatch.getDispatchNo()); outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); @@ -85,20 +85,20 @@ public class DeliveryTransferService { } catch (DuplicateKeyException e) { // The unique keys are the final concurrent guard. A client retry must observe // the document created by the winning transaction rather than receive a false failure. - OutboundOrder concurrent = outboundOrders.findByIdempotency(tenantId, request.idempotencyKey().trim()); + OutboundOrder concurrent = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim()); if (concurrent != null && dispatch.getId().equals(concurrent.getDispatchOrderId())) { - return result(concurrent, requireDelivery(tenantId, concurrent.getId()), true); + return result(concurrent, requireDelivery(customerId, concurrent.getId()), true); } - concurrent = outboundOrders.lockByDispatch(tenantId, dispatch.getId()); + concurrent = outboundOrders.lockByDispatch(customerId, dispatch.getId()); if (concurrent != null) { - return result(concurrent, requireDelivery(tenantId, concurrent.getId()), true); + return result(concurrent, requireDelivery(customerId, concurrent.getId()), true); } throw conflict("Dispatch was concurrently transferred to delivery"); } for (DispatchItem source : items) { OutboundPalletItem item = new OutboundPalletItem(); - item.setId(IdWorker.getId()); item.setTenantId(tenantId); item.setOutboundOrderId(outbound.getId()); + item.setId(IdWorker.getId()); item.setCustomerId(customerId); item.setOutboundOrderId(outbound.getId()); item.setWarehousePalletId(source.getWarehousePalletId()); item.setPalletNo(source.getPalletNo()); item.setDestinationWarehouseId(source.getDestinationWarehouseId()); item.setDestinationWarehouseCode(source.getDestinationWarehouseCode()); @@ -109,7 +109,7 @@ public class DeliveryTransferService { } DeliveryOrder delivery = new DeliveryOrder(); - delivery.setId(IdWorker.getId()); delivery.setTenantId(tenantId); + delivery.setId(IdWorker.getId()); delivery.setCustomerId(customerId); delivery.setDeliveryNo("DEL" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + delivery.getId()); delivery.setOutboundOrderId(outbound.getId()); delivery.setOutboundNo(outbound.getOutboundNo()); delivery.setDispatchOrderId(dispatch.getId()); delivery.setDispatchNo(dispatch.getDispatchNo()); @@ -117,13 +117,13 @@ public class DeliveryTransferService { delivery.setPackageCount(outbound.getPackageCount()); delivery.setPalletCount(outbound.getPalletCount()); delivery.setIdempotencyKey(request.idempotencyKey().trim()); delivery.setCreatedBy(operatorId); delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery); - bindTailOrders(tenantId, items, outbound.getOutboundNo(), delivery.getDeliveryNo(), request.remark(), now); + bindTailOrders(customerId, items, outbound.getOutboundNo(), delivery.getDeliveryNo(), request.remark(), now); return result(outbound, delivery, false); } - public PageResult outboundPage(String tenantId, DeliveryRequests.Query query) { + public PageResult outboundPage(String customerId, DeliveryRequests.Query query) { Page page = outboundOrders.selectPage(new Page<>(query.page(), query.size()), - new LambdaQueryWrapper().eq(OutboundOrder::getTenantId, tenantId) + new LambdaQueryWrapper().eq(OutboundOrder::getCustomerId, customerId) .like(StringUtils.hasText(query.documentNo()), OutboundOrder::getOutboundNo, query.documentNo()) .eq(StringUtils.hasText(query.warehouseId()), OutboundOrder::getOverseasWarehouseId, query.warehouseId()) .eq(StringUtils.hasText(query.status()), OutboundOrder::getStatus, query.status()) @@ -131,13 +131,13 @@ public class DeliveryTransferService { return page(page, page.getRecords().stream().map(this::outboundRow).toList()); } - public DeliveryResponses.OutboundDetail outboundDetail(String tenantId, String outboundNo, + public DeliveryResponses.OutboundDetail outboundDetail(String customerId, String outboundNo, DeliveryRequests.PalletQuery query) { - OutboundOrder outbound = outboundOrders.findByNo(tenantId, outboundNo); + OutboundOrder outbound = outboundOrders.findByNo(customerId, outboundNo); if (outbound == null) throw notFound("Outbound order not found"); - DeliveryOrder delivery = requireDelivery(tenantId, outbound.getId()); + DeliveryOrder delivery = requireDelivery(customerId, outbound.getId()); Page page = outboundPallets.selectPage(new Page<>(query.page(), query.size()), - new LambdaQueryWrapper().eq(OutboundPalletItem::getTenantId, tenantId) + new LambdaQueryWrapper().eq(OutboundPalletItem::getCustomerId, customerId) .eq(OutboundPalletItem::getOutboundOrderId, outbound.getId()) .eq(StringUtils.hasText(query.destinationWarehouseCode()), OutboundPalletItem::getDestinationWarehouseCode, query.destinationWarehouseCode()) @@ -146,9 +146,9 @@ public class DeliveryTransferService { page(page, page.getRecords().stream().map(this::palletRow).toList())); } - public PageResult deliveryPage(String tenantId, DeliveryRequests.Query query) { + public PageResult deliveryPage(String customerId, DeliveryRequests.Query query) { Page page = deliveryOrders.selectPage(new Page<>(query.page(), query.size()), - new LambdaQueryWrapper().eq(DeliveryOrder::getTenantId, tenantId) + new LambdaQueryWrapper().eq(DeliveryOrder::getCustomerId, customerId) .like(StringUtils.hasText(query.documentNo()), DeliveryOrder::getDeliveryNo, query.documentNo()) .eq(StringUtils.hasText(query.warehouseId()), DeliveryOrder::getOverseasWarehouseId, query.warehouseId()) .eq(StringUtils.hasText(query.status()), DeliveryOrder::getStatus, query.status()) @@ -156,16 +156,16 @@ public class DeliveryTransferService { return page(page, page.getRecords().stream().map(this::deliveryRow).toList()); } - public DeliveryResponses.OutboundDetail deliveryDetail(String tenantId, String deliveryNo, + public DeliveryResponses.OutboundDetail deliveryDetail(String customerId, String deliveryNo, DeliveryRequests.PalletQuery query) { - DeliveryOrder delivery = deliveryOrders.findByNo(tenantId, deliveryNo); + DeliveryOrder delivery = deliveryOrders.findByNo(customerId, deliveryNo); if (delivery == null) throw notFound("Delivery order not found"); OutboundOrder outbound = outboundOrders.selectById(delivery.getOutboundOrderId()); - if (outbound == null || !tenantId.equals(outbound.getTenantId())) throw notFound("Outbound order not found"); - return outboundDetail(tenantId, outbound.getOutboundNo(), query); + if (outbound == null || !customerId.equals(outbound.getCustomerId())) throw notFound("Outbound order not found"); + return outboundDetail(customerId, outbound.getOutboundNo(), query); } - private void bindTailOrders(String tenantId, List items, String outboundNo, String deliveryNo, + private void bindTailOrders(String customerId, List items, String outboundNo, String deliveryNo, String remark, LocalDateTime now) { Set orderIds = new LinkedHashSet<>(); items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).forEach(orderIds::add); @@ -182,7 +182,7 @@ public class DeliveryTransferService { } if (orderIds.isEmpty()) return; for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper() - .eq(LastlegOrder::getTenantId, tenantId).in(LastlegOrder::getId, orderIds))) { + .eq(LastlegOrder::getCustomerId, customerId).in(LastlegOrder::getId, orderIds))) { String from = order.getCurrentStatus(); order.setOutboundOrderNo(outboundNo); order.setDeliveryOrderNo(deliveryNo); order.setCurrentStatus("WAIT_OUTBOUND"); order.setUpdatedAt(now); tailOrders.updateById(order); @@ -194,8 +194,8 @@ public class DeliveryTransferService { } } - private DeliveryOrder requireDelivery(String tenantId, Long outboundId) { - DeliveryOrder value = deliveryOrders.lockByOutbound(tenantId, outboundId); + private DeliveryOrder requireDelivery(String customerId, Long outboundId) { + DeliveryOrder value = deliveryOrders.lockByOutbound(customerId, outboundId); if (value == null) throw new IllegalStateException("Delivery document is missing for outbound order"); return value; } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/controller/DispatchController.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/controller/DispatchController.java index 1ee53ca..b32a99b 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/controller/DispatchController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/controller/DispatchController.java @@ -40,20 +40,20 @@ public class DispatchController { @Operation(summary = "List unlocked planned or received pallets for intelligent pre-dispatch") public ApiResponse> candidates( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @RequestParam(required = false) String orderNo, @RequestParam(required = false) String fbaWarehouseCode, @RequestParam(required = false) String sourceStage) { - return ApiResponse.success(service.candidates(scope(user, tenantId), orderNo, fbaWarehouseCode, sourceStage)); + return ApiResponse.success(service.candidates(scope(user, customerId), orderNo, fbaWarehouseCode, sourceStage)); } @PostMapping("/pre-arrange") @Operation(summary = "Create a pending-departure dispatch and lock all selected pallets") public ApiResponse preArrange( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody DispatchRequests.PreArrange request) { - String scopedTenant = scope(user, tenantId); + String scopedTenant = scope(user, customerId); String operatorId = user.userId() == null ? user.username() : String.valueOf(user.userId()); return ApiResponse.success(service.preArrange(scopedTenant, operatorId, request)); } @@ -62,37 +62,37 @@ public class DispatchController { @Operation(summary = "Page dispatch orders") public ApiResponse> page( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid DispatchRequests.Query query) { - return ApiResponse.success(service.page(scope(user, tenantId), query)); + return ApiResponse.success(service.page(scope(user, customerId), query)); } @GetMapping("/{dispatchNo}") @Operation(summary = "Get dispatch details and all pallet destinations") public ApiResponse detail( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable String dispatchNo) { - return ApiResponse.success(service.detail(scope(user, tenantId), dispatchNo)); + return ApiResponse.success(service.detail(scope(user, customerId), dispatchNo)); } @PutMapping("/{dispatchNo}") @Operation(summary = "Edit vehicle, driver, appointment, route and departure data") public ApiResponse update( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable String dispatchNo, @Valid @RequestBody DispatchRequests.Update request) { - return ApiResponse.success(service.update(scope(user, tenantId), dispatchNo, request)); + return ApiResponse.success(service.update(scope(user, customerId), dispatchNo, request)); } @DeleteMapping("/{dispatchNo}") @Operation(summary = "Cancel a pending dispatch and release its pallet locks") public ApiResponse cancel( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable String dispatchNo) { - service.cancel(scope(user, tenantId), dispatchNo); + service.cancel(scope(user, customerId), dispatchNo); return ApiResponse.success(null); } @@ -100,24 +100,24 @@ public class DispatchController { @Operation(summary = "Ship all PDA-picked pallets and mark the dispatch departed") public ApiResponse depart( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable String dispatchNo, @Valid @RequestBody DispatchPdaRequests.Depart request) { - String scopedTenant = scope(user, tenantId); + String scopedTenant = scope(user, customerId); String operatorId = user.userId() == null ? user.username() : String.valueOf(user.userId()); return ApiResponse.success(pdaWorkflow.depart(scopedTenant, dispatchNo, operatorId, request)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchItem.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchItem.java index f131831..2fa8e73 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchItem.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchItem.java @@ -15,7 +15,7 @@ public class DispatchItem { @TableId(type = IdType.INPUT) private Long id; private Long dispatchOrderId; - private String tenantId; + private String customerId; private Long planId; private Long planPalletId; private Long warehousePalletId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchOrder.java index 6553140..78caeb7 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchOrder.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchOrder.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class DispatchOrder { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private String dispatchNo; private String overseasWarehouseId; private String overseasWarehouseName; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchPalletLock.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchPalletLock.java index 9a0847e..386d77e 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchPalletLock.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/entity/DispatchPalletLock.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class DispatchPalletLock { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private String sourcePalletKey; /** Equals sourcePalletKey while ACTIVE and is null after release; unique index enforces exclusivity. */ private String activeKey; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java index b5c35b8..680b1d5 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchCandidateMapper.java @@ -17,7 +17,7 @@ public interface DispatchCandidateMapper { "p.destination_warehouse_code AS destinationWarehouseCode,p.destination_warehouse_name AS destinationWarehouseName," + "MIN(l.fba_warehouse_code) AS fbaWarehouseCode," + "CASE WHEN p.warehouse_pallet_id IS NOT NULL THEN COALESCE((SELECT SUM(pos.available_packages) " + - "FROM ll_inventory_position pos WHERE pos.tenant_id=#{tenantId} AND pos.pallet_id=p.warehouse_pallet_id),0) " + + "FROM ll_inventory_position pos WHERE pos.customer_id=#{customerId} AND pos.pallet_id=p.warehouse_pallet_id),0) " + "ELSE COALESCE(SUM(CASE WHEN a.picked_package_count > 0 THEN a.picked_package_count " + "ELSE a.planned_package_count END),0) END AS packageCount,p.allocated_volume_cbm AS volumeCbm," + "CASE WHEN p.warehouse_pallet_id IS NULL THEN 'PLAN' ELSE 'RECEIVED' END AS sourceStage " + @@ -25,22 +25,22 @@ public interface DispatchCandidateMapper { "LEFT JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id " + "LEFT JOIN ll_inbound_plan_line l ON l.id=a.line_id " + "LEFT JOIN ll_order o ON o.id=l.order_id " + - "WHERE ip.tenant_id=#{tenantId} " + + "WHERE ip.customer_id=#{customerId} " + " AND o.order_no LIKE CONCAT('%',#{orderNo},'%') " + " AND l.fba_warehouse_code=#{fbaWarehouseCode} " + " AND " + "CASE WHEN p.warehouse_pallet_id IS NULL THEN 'PLAN' ELSE 'RECEIVED' END=#{sourceStage} " + "AND (p.warehouse_pallet_id IS NULL OR EXISTS (SELECT 1 FROM ll_inventory_position pos " + - "WHERE pos.tenant_id=#{tenantId} AND pos.pallet_id=p.warehouse_pallet_id AND pos.available_packages > 0)) " + + "WHERE pos.customer_id=#{customerId} AND pos.pallet_id=p.warehouse_pallet_id AND pos.available_packages > 0)) " + "AND NOT EXISTS (SELECT 1 FROM ll_dispatch_pallet_lock d " + - "WHERE d.tenant_id=ip.tenant_id AND d.status='ACTIVE' AND (" + + "WHERE d.customer_id=ip.customer_id AND d.status='ACTIVE' AND (" + "d.source_pallet_key=CONCAT('PLAN:',p.id) OR (p.warehouse_pallet_id IS NOT NULL AND " + "d.source_pallet_key=CONCAT('RECEIVED:',p.warehouse_pallet_id)))) " + "GROUP BY p.plan_id,p.id,p.warehouse_pallet_id,p.pallet_no,p.destination_warehouse_id," + "p.destination_warehouse_code,p.destination_warehouse_name,p.allocated_volume_cbm " + "ORDER BY p.plan_id,p.sequence_no" + "") - List available(@Param("tenantId") String tenantId, + List available(@Param("customerId") String customerId, @Param("orderNo") String orderNo, @Param("fbaWarehouseCode") String fbaWarehouseCode, @Param("sourceStage") String sourceStage); @@ -53,7 +53,7 @@ public interface DispatchCandidateMapper { "p.destination_warehouse_code AS destinationWarehouseCode,p.destination_warehouse_name AS destinationWarehouseName," + "MIN(l.fba_warehouse_code) AS fbaWarehouseCode," + "CASE WHEN p.warehouse_pallet_id IS NOT NULL THEN COALESCE((SELECT SUM(pos.available_packages) " + - "FROM ll_inventory_position pos WHERE pos.tenant_id=#{tenantId} AND pos.pallet_id=p.warehouse_pallet_id),0) " + + "FROM ll_inventory_position pos WHERE pos.customer_id=#{customerId} AND pos.pallet_id=p.warehouse_pallet_id),0) " + "ELSE COALESCE(SUM(CASE WHEN a.picked_package_count > 0 THEN a.picked_package_count " + "ELSE a.planned_package_count END),0) END AS packageCount,p.allocated_volume_cbm AS volumeCbm," + "CASE WHEN p.warehouse_pallet_id IS NULL THEN 'PLAN' ELSE 'RECEIVED' END AS sourceStage " + @@ -61,12 +61,12 @@ public interface DispatchCandidateMapper { "LEFT JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=p.id " + "LEFT JOIN ll_inbound_plan_line l ON l.id=a.line_id " + "LEFT JOIN ll_order o ON o.id=l.order_id " + - "WHERE ip.tenant_id=#{tenantId} AND p.id IN " + + "WHERE ip.customer_id=#{customerId} AND p.id IN " + "#{id} " + "GROUP BY p.plan_id,p.id,p.warehouse_pallet_id,p.pallet_no,p.destination_warehouse_id," + "p.destination_warehouse_code,p.destination_warehouse_name,p.allocated_volume_cbm " + "ORDER BY p.id FOR UPDATE" + "") - List lockSelected(@Param("tenantId") String tenantId, + List lockSelected(@Param("customerId") String customerId, @Param("planPalletIds") List planPalletIds); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchItemMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchItemMapper.java index 4757bc8..2dff348 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchItemMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchItemMapper.java @@ -11,15 +11,15 @@ import java.util.List; @Mapper public interface DispatchItemMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_item WHERE tenant_id=#{tenantId} AND dispatch_order_id=#{dispatchOrderId} ORDER BY id") - List findByDispatchOrder(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_dispatch_item WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} ORDER BY id") + List findByDispatchOrder(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); @Update("UPDATE ll_dispatch_item i JOIN ll_inbound_plan_pallet p ON p.id=i.plan_pallet_id " + - "JOIN ll_inbound_plan ip ON ip.id=p.plan_id AND ip.tenant_id=i.tenant_id " + + "JOIN ll_inbound_plan ip ON ip.id=p.plan_id AND ip.customer_id=i.customer_id " + "SET i.warehouse_pallet_id=p.warehouse_pallet_id,i.source_stage='RECEIVED' " + - "WHERE i.tenant_id=#{tenantId} AND i.dispatch_order_id=#{dispatchOrderId} " + + "WHERE i.customer_id=#{customerId} AND i.dispatch_order_id=#{dispatchOrderId} " + "AND i.warehouse_pallet_id IS NULL AND p.warehouse_pallet_id IS NOT NULL") - int bindReceivedPallets(@Param("tenantId") String tenantId, + int bindReceivedPallets(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchOrderMapper.java index df46d02..2a46cf2 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchOrderMapper.java @@ -8,9 +8,9 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface DispatchOrderMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_order WHERE tenant_id=#{tenantId} AND dispatch_no=#{dispatchNo} LIMIT 1") - DispatchOrder findByDispatchNo(@Param("tenantId") String tenantId, @Param("dispatchNo") String dispatchNo); + @Select("SELECT * FROM ll_dispatch_order WHERE customer_id=#{customerId} AND dispatch_no=#{dispatchNo} LIMIT 1") + DispatchOrder findByDispatchNo(@Param("customerId") String customerId, @Param("dispatchNo") String dispatchNo); - @Select("SELECT * FROM ll_dispatch_order WHERE tenant_id=#{tenantId} AND id=#{id} LIMIT 1 FOR UPDATE") - DispatchOrder lockById(@Param("tenantId") String tenantId, @Param("id") Long id); + @Select("SELECT * FROM ll_dispatch_order WHERE customer_id=#{customerId} AND id=#{id} LIMIT 1 FOR UPDATE") + DispatchOrder lockById(@Param("customerId") String customerId, @Param("id") Long id); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchPalletLockMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchPalletLockMapper.java index 3df6a12..ba5a4e5 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchPalletLockMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/mapper/DispatchPalletLockMapper.java @@ -8,8 +8,8 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface DispatchPalletLockMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_pallet_lock WHERE tenant_id=#{tenantId} AND source_pallet_key=#{sourcePalletKey} " + + @Select("SELECT * FROM ll_dispatch_pallet_lock WHERE customer_id=#{customerId} AND source_pallet_key=#{sourcePalletKey} " + "AND status='ACTIVE' LIMIT 1 FOR UPDATE") - DispatchPalletLock lockActive(@Param("tenantId") String tenantId, + DispatchPalletLock lockActive(@Param("customerId") String customerId, @Param("sourcePalletKey") String sourcePalletKey); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/controller/DispatchPdaController.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/controller/DispatchPdaController.java index 85f5f80..7c4ace5 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/controller/DispatchPdaController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/controller/DispatchPdaController.java @@ -20,38 +20,38 @@ public class DispatchPdaController { @PostMapping("/push") public ApiResponse push(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody DispatchPdaRequests.Push request) { - return ApiResponse.success(service.push(scope(user, tenantId), dispatchNo, request)); + return ApiResponse.success(service.push(scope(user, customerId), dispatchNo, request)); } @GetMapping public ApiResponse task(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo, - @RequestParam(required = false) String tenantId) { - return ApiResponse.success(service.task(scope(user, tenantId), dispatchNo)); + @RequestParam(required = false) String customerId) { + return ApiResponse.success(service.task(scope(user, customerId), dispatchNo)); } @PostMapping("/items/{itemId}/pick") public ApiResponse pick(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo, @PathVariable long itemId, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody DispatchPdaRequests.Pick request) { - return ApiResponse.success(service.pick(scope(user, tenantId), dispatchNo, itemId, operator(user), request)); + return ApiResponse.success(service.pick(scope(user, customerId), dispatchNo, itemId, operator(user), request)); } @PostMapping("/complete") public ApiResponse completePicking( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody DispatchPdaRequests.CompletePicking request) { - return ApiResponse.success(service.completePicking(scope(user, tenantId), dispatchNo, request)); + return ApiResponse.success(service.completePicking(scope(user, customerId), dispatchNo, request)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant.trim(); + return requestedCustomer.trim(); } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaOperation.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaOperation.java index fd57cbb..94637d9 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaOperation.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaOperation.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @TableName("ll_dispatch_pda_operation") public class DispatchPdaOperation { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long taskId; private String operationType; private String idempotencyKey; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPick.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPick.java index b6e6453..fe47e73 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPick.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPick.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @TableName("ll_dispatch_pda_pick") public class DispatchPdaPick { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long taskId; private Long dispatchItemId; private Long inventoryPositionId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPickOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPickOrder.java index b53b582..4f87b9f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPickOrder.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaPickOrder.java @@ -12,7 +12,7 @@ import java.time.LocalDateTime; @TableName("ll_dispatch_pda_pick_order") public class DispatchPdaPickOrder { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long pickId; private Long receiptId; private Long orderId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaTask.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaTask.java index cd7d1ad..81ccee6 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaTask.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/entity/DispatchPdaTask.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @TableName("ll_dispatch_pda_task") public class DispatchPdaTask { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long dispatchOrderId; private String taskNo; private String status; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaOperationMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaOperationMapper.java index 2245925..a946136 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaOperationMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaOperationMapper.java @@ -6,6 +6,6 @@ import org.apache.ibatis.annotations.*; @Mapper public interface DispatchPdaOperationMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_pda_operation WHERE tenant_id=#{tenantId} AND idempotency_key=#{key} LIMIT 1") - DispatchPdaOperation findKey(@Param("tenantId") String tenantId, @Param("key") String key); + @Select("SELECT * FROM ll_dispatch_pda_operation WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + DispatchPdaOperation findKey(@Param("customerId") String customerId, @Param("key") String key); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickMapper.java index a88e9f8..2b8c0f3 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickMapper.java @@ -7,8 +7,8 @@ import java.util.List; @Mapper public interface DispatchPdaPickMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_pda_pick WHERE tenant_id=#{tenantId} AND task_id=#{taskId} ORDER BY id") - List listTask(@Param("tenantId") String tenantId, @Param("taskId") Long taskId); - @Select("SELECT COALESCE(SUM(packages),0) FROM ll_dispatch_pda_pick WHERE tenant_id=#{tenantId} AND task_id=#{taskId} AND dispatch_item_id=#{itemId}") - long totalItem(@Param("tenantId") String tenantId, @Param("taskId") Long taskId, @Param("itemId") Long itemId); + @Select("SELECT * FROM ll_dispatch_pda_pick WHERE customer_id=#{customerId} AND task_id=#{taskId} ORDER BY id") + List listTask(@Param("customerId") String customerId, @Param("taskId") Long taskId); + @Select("SELECT COALESCE(SUM(packages),0) FROM ll_dispatch_pda_pick WHERE customer_id=#{customerId} AND task_id=#{taskId} AND dispatch_item_id=#{itemId}") + long totalItem(@Param("customerId") String customerId, @Param("taskId") Long taskId, @Param("itemId") Long itemId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickOrderMapper.java index 3e3368d..c14b0f8 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaPickOrderMapper.java @@ -12,20 +12,20 @@ import java.util.List; @Mapper public interface DispatchPdaPickOrderMapper extends BaseMapper { @Select("SELECT r.* FROM ll_inbound_stock_receipt r " + - "WHERE r.tenant_id=#{tenantId} AND r.inventory_position_id=#{positionId} " + + "WHERE r.customer_id=#{customerId} AND r.inventory_position_id=#{positionId} " + "AND r.received_packages > (SELECT COALESCE(SUM(a.packages),0) FROM ll_dispatch_pda_pick_order a " + - "WHERE a.tenant_id=r.tenant_id AND a.receipt_id=r.id) ORDER BY r.received_at,r.id") - List availableReceipts(@Param("tenantId") String tenantId, + "WHERE a.customer_id=r.customer_id AND a.receipt_id=r.id) ORDER BY r.received_at,r.id") + List availableReceipts(@Param("customerId") String customerId, @Param("positionId") Long positionId); @Select("SELECT COALESCE(SUM(packages),0) FROM ll_dispatch_pda_pick_order " + - "WHERE tenant_id=#{tenantId} AND receipt_id=#{receiptId}") - long attributedPackages(@Param("tenantId") String tenantId, @Param("receiptId") Long receiptId); + "WHERE customer_id=#{customerId} AND receipt_id=#{receiptId}") + long attributedPackages(@Param("customerId") String customerId, @Param("receiptId") Long receiptId); @Select("SELECT COALESCE(SUM(product_quantity),0) FROM ll_dispatch_pda_pick_order " + - "WHERE tenant_id=#{tenantId} AND receipt_id=#{receiptId}") - long attributedQuantity(@Param("tenantId") String tenantId, @Param("receiptId") Long receiptId); + "WHERE customer_id=#{customerId} AND receipt_id=#{receiptId}") + long attributedQuantity(@Param("customerId") String customerId, @Param("receiptId") Long receiptId); - @Select("SELECT * FROM ll_dispatch_pda_pick_order WHERE tenant_id=#{tenantId} AND pick_id=#{pickId} ORDER BY id") - List listPick(@Param("tenantId") String tenantId, @Param("pickId") Long pickId); + @Select("SELECT * FROM ll_dispatch_pda_pick_order WHERE customer_id=#{customerId} AND pick_id=#{pickId} ORDER BY id") + List listPick(@Param("customerId") String customerId, @Param("pickId") Long pickId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaTaskMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaTaskMapper.java index 6122060..6c54869 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaTaskMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/mapper/DispatchPdaTaskMapper.java @@ -6,9 +6,9 @@ import org.apache.ibatis.annotations.*; @Mapper public interface DispatchPdaTaskMapper extends BaseMapper { - @Select("SELECT * FROM ll_dispatch_pda_task WHERE tenant_id=#{tenantId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1 FOR UPDATE") - DispatchPdaTask lockByDispatch(@Param("tenantId") String tenantId, @Param("dispatchOrderId") Long dispatchOrderId); + @Select("SELECT * FROM ll_dispatch_pda_task WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1 FOR UPDATE") + DispatchPdaTask lockByDispatch(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); - @Select("SELECT * FROM ll_dispatch_pda_task WHERE tenant_id=#{tenantId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1") - DispatchPdaTask findByDispatch(@Param("tenantId") String tenantId, @Param("dispatchOrderId") Long dispatchOrderId); + @Select("SELECT * FROM ll_dispatch_pda_task WHERE customer_id=#{customerId} AND dispatch_order_id=#{dispatchOrderId} LIMIT 1") + DispatchPdaTask findByDispatch(@Param("customerId") String customerId, @Param("dispatchOrderId") Long dispatchOrderId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java index 3ecf8b6..97d3871 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowService.java @@ -45,25 +45,25 @@ public class DispatchPdaWorkflowService { @Resource private ObjectMapper objectMapper; @Transactional - public DispatchPdaResponses.Task push(String tenantId, String dispatchNo, DispatchPdaRequests.Push request) { - DispatchOrder order = requireOrder(tenantId, dispatchNo); + public DispatchPdaResponses.Task push(String customerId, String dispatchNo, DispatchPdaRequests.Push request) { + DispatchOrder order = requireOrder(customerId, dispatchNo); requirePdaStatus(order); requireVehicle(order); String warehouseId = warehouse(request.warehouseId()); requireDispatchWarehouse(order, warehouseId); - DispatchPdaOperation replay = replay(tenantId, "PUSH", request.idempotencyKey(), request); + DispatchPdaOperation replay = replay(customerId, "PUSH", request.idempotencyKey(), request); if (replay != null) return read(replay, DispatchPdaResponses.Task.class); - items.bindReceivedPallets(tenantId, order.getId()); - List dispatchItems = items.findByDispatchOrder(tenantId, order.getId()); + items.bindReceivedPallets(customerId, order.getId()); + List dispatchItems = items.findByDispatchOrder(customerId, order.getId()); if (dispatchItems.isEmpty()) throw conflict("Dispatch has no pallets to push"); if (dispatchItems.stream().anyMatch(item -> item.getWarehousePalletId() == null)) { throw conflict("All pre-arranged pallets must be received before PDA push"); } - DispatchPdaTask task = tasks.lockByDispatch(tenantId, order.getId()); + DispatchPdaTask task = tasks.lockByDispatch(customerId, order.getId()); LocalDateTime now = LocalDateTime.now(); if (task == null) { task = new DispatchPdaTask(); - task.setId(IdWorker.getId()); task.setTenantId(tenantId); task.setDispatchOrderId(order.getId()); + task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setDispatchOrderId(order.getId()); task.setTaskNo("DPDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setWarehouseId(warehouseId); task.setOperatorId(trim(request.operatorId())); task.setOperatorName(trim(request.operatorName())); @@ -71,91 +71,91 @@ public class DispatchPdaWorkflowService { } if ("PENDING_DEPARTURE".equals(order.getStatus())) { order.setStatus("PICKING"); order.setUpdatedAt(now); orders.updateById(order); } if (!warehouseId.equals(task.getWarehouseId())) throw conflict("PDA task warehouse does not match selected warehouse"); - DispatchPdaResponses.Task result = task(tenantId, order, task, dispatchItems); - save(tenantId, task.getId(), "PUSH", request.idempotencyKey(), request, result); + DispatchPdaResponses.Task result = task(customerId, order, task, dispatchItems); + save(customerId, task.getId(), "PUSH", request.idempotencyKey(), request, result); return result; } @Transactional(readOnly = true) - public DispatchPdaResponses.Task task(String tenantId, String dispatchNo) { - DispatchOrder order = requireOrder(tenantId, dispatchNo); - DispatchPdaTask task = tasks.findByDispatch(tenantId, order.getId()); + public DispatchPdaResponses.Task task(String customerId, String dispatchNo) { + DispatchOrder order = requireOrder(customerId, dispatchNo); + DispatchPdaTask task = tasks.findByDispatch(customerId, order.getId()); if (task == null) throw notFound("Dispatch PDA task not found"); - return task(tenantId, order, task, items.findByDispatchOrder(tenantId, order.getId())); + return task(customerId, order, task, items.findByDispatchOrder(customerId, order.getId())); } @Transactional - public DispatchPdaResponses.Picked pick(String tenantId, String dispatchNo, long itemId, + public DispatchPdaResponses.Picked pick(String customerId, String dispatchNo, long itemId, String operatorId, DispatchPdaRequests.Pick request) { - DispatchOrder order = requireOrder(tenantId, dispatchNo); + DispatchOrder order = requireOrder(customerId, dispatchNo); requirePdaStatus(order); requireVehicle(order); String warehouseId = warehouse(request.warehouseId()); requireDispatchWarehouse(order, warehouseId); - DispatchPdaTask task = tasks.lockByDispatch(tenantId, order.getId()); + DispatchPdaTask task = tasks.lockByDispatch(customerId, order.getId()); if (task == null || !("PDA_PENDING".equals(task.getStatus()) || "PICKING".equals(task.getStatus()))) { throw conflict("Dispatch PDA task is not available for picking"); } if (!warehouseId.equals(task.getWarehouseId())) throw conflict("PDA pick warehouse does not match task warehouse"); DispatchItem item = items.selectById(itemId); - if (item == null || !tenantId.equals(item.getTenantId()) || !order.getId().equals(item.getDispatchOrderId())) { + if (item == null || !customerId.equals(item.getCustomerId()) || !order.getId().equals(item.getDispatchOrderId())) { throw notFound("Dispatch pallet item not found"); } if (!warehouseId.equals(item.getOverseasWarehouseId())) throw conflict("Pallet does not belong to selected warehouse"); - DispatchPdaOperation replay = replay(tenantId, "PICK", request.idempotencyKey(), request); + DispatchPdaOperation replay = replay(customerId, "PICK", request.idempotencyKey(), request); if (replay != null) return read(replay, DispatchPdaResponses.Picked.class); InventoryPosition position = positions.selectById(request.positionId()); - if (position == null || !tenantId.equals(position.getTenantId()) + if (position == null || !customerId.equals(position.getCustomerId()) || item.getWarehousePalletId() == null || !item.getWarehousePalletId().equals(position.getPalletId())) { throw conflict("Inventory position does not belong to this dispatch pallet"); } - long picked = picks.totalItem(tenantId, task.getId(), itemId); + long picked = picks.totalItem(customerId, task.getId(), itemId); if (picked + request.packages() > item.getPackageCount()) throw conflict("Picked packages exceed dispatch pallet packages"); String keyBase = "DISPATCH:" + order.getDispatchNo() + ":" + itemId + ":" + request.idempotencyKey(); - InventoryCommandRequests.Move move = new InventoryCommandRequests.Move(tenantId, request.positionId(), + InventoryCommandRequests.Move move = new InventoryCommandRequests.Move(customerId, request.positionId(), request.packages(), order.getDispatchNo(), order.getDispatchNo(), null, keyBase + ":ALLOCATE", request.remark()); - inventory.allocate(tenantId, operatorId, move); - inventory.pick(tenantId, operatorId, new InventoryCommandRequests.Move(tenantId, request.positionId(), + inventory.allocate(customerId, operatorId, move); + inventory.pick(customerId, operatorId, new InventoryCommandRequests.Move(customerId, request.positionId(), request.packages(), order.getDispatchNo(), order.getDispatchNo(), null, keyBase + ":PICK", request.remark())); DispatchPdaPick pick = new DispatchPdaPick(); - pick.setId(IdWorker.getId()); pick.setTenantId(tenantId); pick.setTaskId(task.getId()); + pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setDispatchItemId(itemId); pick.setInventoryPositionId(request.positionId()); pick.setPackages(request.packages()); pick.setProductQuantity(request.productQuantity()); pick.setIdempotencyKey(request.idempotencyKey()); pick.setPickedAt(LocalDateTime.now()); pick.setCreatedAt(pick.getPickedAt()); picks.insert(pick); - pickAttribution.attribute(tenantId, pick); + pickAttribution.attribute(customerId, pick); long total = picked + request.packages(); order.setPickedPackageCount((int) (n(order.getPickedPackageCount()) + request.packages())); task.setStatus("PICKING"); task.setUpdatedAt(LocalDateTime.now()); - if (allPicked(tenantId, task.getId(), items.findByDispatchOrder(tenantId, order.getId()))) { + if (allPicked(customerId, task.getId(), items.findByDispatchOrder(customerId, order.getId()))) { task.setStatus("COMPLETED"); task.setCompletedAt(LocalDateTime.now()); order.setStatus("READY_TO_DEPART"); } order.setUpdatedAt(LocalDateTime.now()); orders.updateById(order); tasks.updateById(task); DispatchPdaResponses.Picked result = new DispatchPdaResponses.Picked(itemId, request.positionId(), request.packages(), total, false, false); - save(tenantId, task.getId(), "PICK", request.idempotencyKey(), request, result); + save(customerId, task.getId(), "PICK", request.idempotencyKey(), request, result); return result; } @Transactional - public DispatchPdaResponses.PickingCompleted completePicking(String tenantId, String dispatchNo, + public DispatchPdaResponses.PickingCompleted completePicking(String customerId, String dispatchNo, DispatchPdaRequests.CompletePicking request) { - DispatchOrder found = requireOrder(tenantId, dispatchNo); - DispatchOrder order = orders.lockById(tenantId, found.getId()); + DispatchOrder found = requireOrder(customerId, dispatchNo); + DispatchOrder order = orders.lockById(customerId, found.getId()); if (order == null) throw notFound("Dispatch order not found"); - DispatchPdaOperation replay = replay(tenantId, "COMPLETE_PICKING", request.idempotencyKey(), request); + DispatchPdaOperation replay = replay(customerId, "COMPLETE_PICKING", request.idempotencyKey(), request); if (replay != null) return read(replay, DispatchPdaResponses.PickingCompleted.class).withReplay(); requireVehicle(order); - DispatchPdaTask task = tasks.lockByDispatch(tenantId, order.getId()); + DispatchPdaTask task = tasks.lockByDispatch(customerId, order.getId()); if (task == null || !("PDA_PENDING".equals(task.getStatus()) || "PICKING".equals(task.getStatus()))) { throw conflict("Dispatch PDA task is not available for completion"); } - List source = items.findByDispatchOrder(tenantId, order.getId()); + List source = items.findByDispatchOrder(customerId, order.getId()); long planned = source.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull) .mapToLong(Integer::longValue).sum(); - long picked = picks.listTask(tenantId, task.getId()).stream().mapToLong(p -> n(p.getPackages())).sum(); + long picked = picks.listTask(customerId, task.getId()).stream().mapToLong(p -> n(p.getPackages())).sum(); if (picked <= 0 || picked > planned) throw conflict("PDA completion requires a valid picked package count"); boolean partial = picked < planned; LocalDateTime now = LocalDateTime.now(); @@ -166,47 +166,47 @@ public class DispatchPdaWorkflowService { order.setUpdatedAt(now); orders.updateById(order); DispatchPdaResponses.PickingCompleted result = new DispatchPdaResponses.PickingCompleted(order.getDispatchNo(), order.getStatus(), planned, picked, planned - picked, partial, false); - save(tenantId, task.getId(), "COMPLETE_PICKING", request.idempotencyKey(), request, result); + save(customerId, task.getId(), "COMPLETE_PICKING", request.idempotencyKey(), request, result); return result; } /** Final warehouse exit: only fully PDA-picked dispatches can consume picked inventory. */ @Transactional - public DispatchPdaResponses.Departed depart(String tenantId, String dispatchNo, String operatorId, + public DispatchPdaResponses.Departed depart(String customerId, String dispatchNo, String operatorId, DispatchPdaRequests.Depart request) { - DispatchOrder found = requireOrder(tenantId, dispatchNo); - DispatchOrder order = orders.lockById(tenantId, found.getId()); + DispatchOrder found = requireOrder(customerId, dispatchNo); + DispatchOrder order = orders.lockById(customerId, found.getId()); if (order == null) throw notFound("Dispatch order not found"); - DispatchPdaOperation replay = replay(tenantId, "DEPART", request.idempotencyKey(), request); + DispatchPdaOperation replay = replay(customerId, "DEPART", request.idempotencyKey(), request); if (replay != null) return read(replay, DispatchPdaResponses.Departed.class).withReplay(); requireVehicle(order); - OutboundOrder outbound = outboundOrders.lockByDispatch(tenantId, order.getId()); + OutboundOrder outbound = outboundOrders.lockByDispatch(customerId, order.getId()); if (outbound == null || !"WAIT_OUTBOUND".equals(outbound.getStatus())) { throw conflict("Dispatch must be transferred to a pending outbound order before departure"); } - DispatchPdaTask task = tasks.lockByDispatch(tenantId, order.getId()); + DispatchPdaTask task = tasks.lockByDispatch(customerId, order.getId()); if (task == null || !"COMPLETED".equals(task.getStatus()) || !("READY_TO_DEPART".equals(order.getStatus()) || "PARTIAL_READY_TO_DEPART".equals(order.getStatus()))) { throw conflict("Dispatch must complete PDA picking before departure"); } - List dispatchItems = items.findByDispatchOrder(tenantId, order.getId()); + List dispatchItems = items.findByDispatchOrder(customerId, order.getId()); boolean partial = "PARTIAL_READY_TO_DEPART".equals(order.getStatus()); - if (!partial && !allPicked(tenantId, task.getId(), dispatchItems)) { + if (!partial && !allPicked(customerId, task.getId(), dispatchItems)) { throw conflict("Every dispatch pallet must be fully picked before departure"); } - List dispatchPicks = picks.listTask(tenantId, task.getId()); + List dispatchPicks = picks.listTask(customerId, task.getId()); if (dispatchPicks.isEmpty()) throw conflict("Dispatch has no PDA picks to ship"); long shipped = 0; for (DispatchPdaPick pick : dispatchPicks) { String key = "DISPATCH:" + order.getDispatchNo() + ":PICK:" + pick.getId() + ":SHIP"; - inventory.ship(tenantId, operatorId, new InventoryCommandRequests.Move(tenantId, + inventory.ship(customerId, operatorId, new InventoryCommandRequests.Move(customerId, pick.getInventoryPositionId(), n(pick.getPackages()), order.getDispatchNo(), order.getDispatchNo(), null, key, request.remark())); shipped += n(pick.getPackages()); } LocalDateTime now = LocalDateTime.now(); - pickAttribution.depart(tenantId, outbound.getOutboundNo(), order.getDispatchNo(), dispatchPicks, now); + pickAttribution.depart(customerId, outbound.getOutboundNo(), order.getDispatchNo(), dispatchPicks, now); outbound.setStatus("OUTBOUNDED"); outbound.setUpdatedAt(now); outboundOrders.updateById(outbound); @@ -214,33 +214,33 @@ public class DispatchPdaWorkflowService { order.setStatus(partial ? "PARTIAL_DEPARTED" : "DEPARTED"); order.setUpdatedAt(now); orders.updateById(order); - releasePalletLocks(tenantId, order.getId(), now); + releasePalletLocks(customerId, order.getId(), now); DispatchPdaResponses.Departed result = new DispatchPdaResponses.Departed(order.getDispatchNo(), order.getStatus(), shipped, false); - save(tenantId, task.getId(), "DEPART", request.idempotencyKey(), request, result); + save(customerId, task.getId(), "DEPART", request.idempotencyKey(), request, result); return result; } - private DispatchPdaResponses.Task task(String tenantId, DispatchOrder order, DispatchPdaTask task, List source) { + private DispatchPdaResponses.Task task(String customerId, DispatchOrder order, DispatchPdaTask task, List source) { List values = new ArrayList<>(); for (DispatchItem item : source) { List palletPositions = item.getWarehousePalletId() == null ? List.of() - : positions.listPallet(tenantId, item.getWarehousePalletId()); + : positions.listPallet(customerId, item.getWarehousePalletId()); values.add(new DispatchPdaResponses.Item(item.getId(), item.getPalletNo(), item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(), item.getFbaWarehouseCode(), item.getPackageCount(), - picks.totalItem(tenantId, task.getId(), item.getId()), palletPositions.stream().map(p -> + picks.totalItem(customerId, task.getId(), item.getId()), palletPositions.stream().map(p -> new DispatchPdaResponses.Position(p.getId(), p.getBalanceId(), n(p.getAvailablePackages()), n(p.getAllocatedPackages()), n(p.getPickedPackages()))).toList())); } return new DispatchPdaResponses.Task(task.getId(), task.getTaskNo(), order.getDispatchNo(), order.getVehiclePlate(), task.getWarehouseId(), task.getStatus(), values); } - private boolean allPicked(String tenantId, Long taskId, List source) { - return source.stream().allMatch(item -> picks.totalItem(tenantId, taskId, item.getId()) == item.getPackageCount()); + private boolean allPicked(String customerId, Long taskId, List source) { + return source.stream().allMatch(item -> picks.totalItem(customerId, taskId, item.getId()) == item.getPackageCount()); } - private void releasePalletLocks(String tenantId, Long dispatchOrderId, LocalDateTime now) { + private void releasePalletLocks(String customerId, Long dispatchOrderId, LocalDateTime now) { List activeLocks = palletLocks.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(DispatchPalletLock::getTenantId, tenantId) + .eq(DispatchPalletLock::getCustomerId, customerId) .eq(DispatchPalletLock::getDispatchOrderId, dispatchOrderId) .eq(DispatchPalletLock::getStatus, "ACTIVE")); for (DispatchPalletLock lock : activeLocks) { @@ -251,7 +251,7 @@ public class DispatchPdaWorkflowService { palletLocks.updateById(lock); } } - private DispatchOrder requireOrder(String tenantId, String dispatchNo) { DispatchOrder order = orders.findByDispatchNo(tenantId, dispatchNo); if (order == null) throw notFound("Dispatch order not found"); return order; } + private DispatchOrder requireOrder(String customerId, String dispatchNo) { DispatchOrder order = orders.findByDispatchNo(customerId, dispatchNo); if (order == null) throw notFound("Dispatch order not found"); return order; } private void requirePdaStatus(DispatchOrder order) { if (!("PENDING_DEPARTURE".equals(order.getStatus()) || "PICKING".equals(order.getStatus()) || "READY_TO_DEPART".equals(order.getStatus()))) throw conflict("Dispatch has already departed and cannot be changed by PDA"); @@ -263,8 +263,8 @@ public class DispatchPdaWorkflowService { throw conflict("Selected warehouse does not match dispatch warehouse"); } } - private DispatchPdaOperation replay(String tenantId, String type, String key, Object request) { DispatchPdaOperation op = operations.findKey(tenantId, required(key, "Idempotency key is required")); if (op == null) return null; if (!type.equals(op.getOperationType()) || !hash(request).equals(op.getRequestHash())) throw conflict("Idempotency key was reused with a different request"); if (!"COMPLETED".equals(op.getStatus())) throw conflict("PDA operation is still processing"); return op; } - private void save(String tenantId, Long taskId, String type, String key, Object request, Object result) { DispatchPdaOperation op = new DispatchPdaOperation(); op.setId(IdWorker.getId()); op.setTenantId(tenantId); op.setTaskId(taskId); op.setOperationType(type); op.setIdempotencyKey(required(key,"Idempotency key is required")); op.setRequestHash(hash(request)); op.setResultJson(json(result)); op.setStatus("COMPLETED"); op.setCreatedAt(LocalDateTime.now()); op.setUpdatedAt(op.getCreatedAt()); operations.insert(op); } + private DispatchPdaOperation replay(String customerId, String type, String key, Object request) { DispatchPdaOperation op = operations.findKey(customerId, required(key, "Idempotency key is required")); if (op == null) return null; if (!type.equals(op.getOperationType()) || !hash(request).equals(op.getRequestHash())) throw conflict("Idempotency key was reused with a different request"); if (!"COMPLETED".equals(op.getStatus())) throw conflict("PDA operation is still processing"); return op; } + private void save(String customerId, Long taskId, String type, String key, Object request, Object result) { DispatchPdaOperation op = new DispatchPdaOperation(); op.setId(IdWorker.getId()); op.setCustomerId(customerId); op.setTaskId(taskId); op.setOperationType(type); op.setIdempotencyKey(required(key,"Idempotency key is required")); op.setRequestHash(hash(request)); op.setResultJson(json(result)); op.setStatus("COMPLETED"); op.setCreatedAt(LocalDateTime.now()); op.setUpdatedAt(op.getCreatedAt()); operations.insert(op); } private T read(DispatchPdaOperation op, Class type) { try { return objectMapper.readValue(op.getResultJson(), type); } catch (Exception e) { throw new IllegalStateException("Cannot read PDA replay", e); } } private String hash(Object value) { try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(json(value).getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { throw new IllegalStateException(e); } } private String json(Object value) { try { return objectMapper.writeValueAsString(value); } catch (Exception e) { throw new IllegalStateException(e); } } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java index 2d50f88..441d30d 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPickOrderAttributionService.java @@ -26,18 +26,18 @@ public class DispatchPickOrderAttributionService { @Resource private LastlegOrderStatusHistoryMapper history; /** Call after the inventory position has been locked/moved and the PDA pick row has been inserted. */ - public List attribute(String tenantId, DispatchPdaPick pick) { + public List attribute(String customerId, DispatchPdaPick pick) { long remainingPackages = value(pick.getPackages()); long totalPackages = remainingPackages; long totalQuantity = value(pick.getProductQuantity()); long allocatedPackages = 0; long allocatedQuantity = 0; Map pickedByOrder = new LinkedHashMap<>(); - for (InboundStockReceipt receipt : attributions.availableReceipts(tenantId, + for (InboundStockReceipt receipt : attributions.availableReceipts(customerId, pick.getInventoryPositionId())) { if (remainingPackages == 0) break; long receiptPackages = value(receipt.getReceivedPackages()) - - attributions.attributedPackages(tenantId, receipt.getId()); + - attributions.attributedPackages(customerId, receipt.getId()); if (receiptPackages <= 0) continue; long packages = Math.min(remainingPackages, receiptPackages); long nextPackages = allocatedPackages + packages; @@ -45,7 +45,7 @@ public class DispatchPickOrderAttributionService { long quantity = nextQuantity - allocatedQuantity; DispatchPdaPickOrder row = new DispatchPdaPickOrder(); row.setId(IdWorker.getId()); - row.setTenantId(tenantId); + row.setCustomerId(customerId); row.setPickId(pick.getId()); row.setReceiptId(receipt.getId()); row.setOrderId(receipt.getOrderId()); @@ -60,7 +60,7 @@ public class DispatchPickOrderAttributionService { } if (remainingPackages != 0) throw conflict("PDA pick cannot be attributed to inbound receipt orders"); for (Map.Entry entry : pickedByOrder.entrySet()) { - if (orders.increasePicked(tenantId, entry.getKey(), entry.getValue()) != 1) { + if (orders.increasePicked(customerId, entry.getKey(), entry.getValue()) != 1) { throw conflict("Picked packages exceed tail-leg order remaining packages"); } } @@ -68,17 +68,17 @@ public class DispatchPickOrderAttributionService { } /** Call once for each departing PDA pick; repeated dispatch departure is protected by its outer idempotency key. */ - public List depart(String tenantId, String outboundNo, String dispatchNo, List picks, + public List depart(String customerId, String outboundNo, String dispatchNo, List picks, LocalDateTime departedAt) { Map shippedByOrder = new LinkedHashMap<>(); for (DispatchPdaPick pick : picks) { - for (DispatchPdaPickOrder row : attributions.listPick(tenantId, pick.getId())) { + for (DispatchPdaPickOrder row : attributions.listPick(customerId, pick.getId())) { shippedByOrder.merge(row.getOrderId(), value(row.getPackages()), Long::sum); } } for (Map.Entry entry : shippedByOrder.entrySet()) { - LastlegOrder before = orders.lockById(tenantId, entry.getKey()); - if (before == null || orders.transferPickedToShipped(tenantId, entry.getKey(), entry.getValue(), outboundNo) != 1) { + LastlegOrder before = orders.lockById(customerId, entry.getKey()); + if (before == null || orders.transferPickedToShipped(customerId, entry.getKey(), entry.getValue(), outboundNo) != 1) { throw conflict("Tail-leg order picked packages are inconsistent"); } long shipped = value(before.getShippedPackageCount()) + entry.getValue(); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java index 3932361..42abf0c 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/dispatch/service/DispatchService.java @@ -43,21 +43,21 @@ public class DispatchService { @Resource private DispatchPalletLockMapper locks; @Resource private DispatchCandidateMapper candidates; - public List candidates(String tenantId, String orderNo, + public List candidates(String customerId, String orderNo, String fbaWarehouseCode, String sourceStage) { if (StringUtils.hasText(sourceStage) && !Set.of("PLAN", "RECEIVED").contains(sourceStage)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "sourceStage must be PLAN or RECEIVED"); } - return candidates.available(tenantId, trimToNull(orderNo), trimToNull(fbaWarehouseCode), trimToNull(sourceStage)); + return candidates.available(customerId, trimToNull(orderNo), trimToNull(fbaWarehouseCode), trimToNull(sourceStage)); } @Transactional - public DispatchResponse preArrange(String tenantId, String operatorId, DispatchRequests.PreArrange request) { + public DispatchResponse preArrange(String customerId, String operatorId, DispatchRequests.PreArrange request) { List palletIds = request.planPalletIds().stream().filter(Objects::nonNull).distinct().sorted().toList(); if (palletIds.isEmpty() || palletIds.size() != request.planPalletIds().size()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Plan pallet IDs must be unique and non-empty"); } - List selected = candidates.lockSelected(tenantId, palletIds); + List selected = candidates.lockSelected(customerId, palletIds); if (selected.size() != palletIds.size()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "One or more selected pallets are not in this tenant"); } @@ -71,9 +71,9 @@ public class DispatchService { } for (DispatchPalletCandidate candidate : selected) { boolean planLocked = candidate.planPalletId() != null - && locks.lockActive(tenantId, "PLAN:" + candidate.planPalletId()) != null; + && locks.lockActive(customerId, "PLAN:" + candidate.planPalletId()) != null; boolean receivedLocked = candidate.warehousePalletId() != null - && locks.lockActive(tenantId, "RECEIVED:" + candidate.warehousePalletId()) != null; + && locks.lockActive(customerId, "RECEIVED:" + candidate.warehousePalletId()) != null; if (planLocked || receivedLocked) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Pallet is already locked by another active dispatch: " + candidate.palletNo()); @@ -83,7 +83,7 @@ public class DispatchService { LocalDateTime now = LocalDateTime.now(); DispatchOrder dispatch = new DispatchOrder(); dispatch.setId(IdWorker.getId()); - dispatch.setTenantId(tenantId); + dispatch.setCustomerId(customerId); dispatch.setDispatchNo("PC" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + dispatch.getId()); dispatch.setOverseasWarehouseId(selected.get(0).overseasWarehouseId()); dispatch.setOverseasWarehouseName(selected.get(0).overseasWarehouseName()); @@ -106,7 +106,7 @@ public class DispatchService { for (DispatchPalletCandidate candidate : selected) { DispatchPalletLock lock = new DispatchPalletLock(); lock.setId(IdWorker.getId()); - lock.setTenantId(tenantId); + lock.setCustomerId(customerId); lock.setSourcePalletKey(candidate.sourcePalletKey()); lock.setActiveKey(candidate.sourcePalletKey()); lock.setPlanId(candidate.planId()); @@ -126,7 +126,7 @@ public class DispatchService { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); - item.setTenantId(tenantId); + item.setCustomerId(customerId); item.setPlanId(candidate.planId()); item.setPlanPalletId(candidate.planPalletId()); item.setWarehousePalletId(candidate.warehousePalletId()); @@ -147,12 +147,12 @@ public class DispatchService { item.setCreatedAt(now); items.insert(item); } - return response(dispatch, items.findByDispatchOrder(tenantId, dispatch.getId())); + return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId())); } - public PageResult page(String tenantId, DispatchRequests.Query query) { + public PageResult page(String customerId, DispatchRequests.Query query) { Page page = orders.selectPage(new Page<>(query.page(), query.size()), - new LambdaQueryWrapper().eq(DispatchOrder::getTenantId, tenantId) + new LambdaQueryWrapper().eq(DispatchOrder::getCustomerId, customerId) .like(StringUtils.hasText(query.dispatchNo()), DispatchOrder::getDispatchNo, query.dispatchNo()) .like(StringUtils.hasText(query.vehiclePlate()), DispatchOrder::getVehiclePlate, query.vehiclePlate()) .eq(StringUtils.hasText(query.status()), DispatchOrder::getStatus, query.status()) @@ -160,18 +160,18 @@ public class DispatchService { query.fbaAppointmentNo()) .orderByDesc(DispatchOrder::getCreatedAt)); List records = page.getRecords().stream() - .map(order -> response(order, items.findByDispatchOrder(tenantId, order.getId()))).toList(); + .map(order -> response(order, items.findByDispatchOrder(customerId, order.getId()))).toList(); return new PageResult<>(records, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); } - public DispatchResponse detail(String tenantId, String dispatchNo) { - DispatchOrder dispatch = find(tenantId, dispatchNo); - return response(dispatch, items.findByDispatchOrder(tenantId, dispatch.getId())); + public DispatchResponse detail(String customerId, String dispatchNo) { + DispatchOrder dispatch = find(customerId, dispatchNo); + return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId())); } @Transactional - public DispatchResponse update(String tenantId, String dispatchNo, DispatchRequests.Update request) { - DispatchOrder dispatch = orders.lockById(tenantId, find(tenantId, dispatchNo).getId()); + public DispatchResponse update(String customerId, String dispatchNo, DispatchRequests.Update request) { + DispatchOrder dispatch = orders.lockById(customerId, find(customerId, dispatchNo).getId()); if (dispatch == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Dispatch order not found"); // Vehicle plate is intentionally editable through all dispatch states. dispatch.setVehiclePlate(trimToNull(request.vehiclePlate())); @@ -193,12 +193,12 @@ public class DispatchService { } dispatch.setUpdatedAt(LocalDateTime.now()); orders.updateById(dispatch); - return response(dispatch, items.findByDispatchOrder(tenantId, dispatch.getId())); + return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId())); } @Transactional - public void cancel(String tenantId, String dispatchNo) { - DispatchOrder dispatch = orders.lockById(tenantId, find(tenantId, dispatchNo).getId()); + public void cancel(String customerId, String dispatchNo) { + DispatchOrder dispatch = orders.lockById(customerId, find(customerId, dispatchNo).getId()); if (dispatch == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Dispatch order not found"); if (!(PENDING_DEPARTURE.equals(dispatch.getStatus()))) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Dispatched order cannot be cancelled"); @@ -208,7 +208,7 @@ public class DispatchService { dispatch.setUpdatedAt(now); orders.updateById(dispatch); List activeLocks = locks.selectList(new LambdaQueryWrapper() - .eq(DispatchPalletLock::getTenantId, tenantId) + .eq(DispatchPalletLock::getCustomerId, customerId) .eq(DispatchPalletLock::getDispatchOrderId, dispatch.getId()) .eq(DispatchPalletLock::getStatus, "ACTIVE")); for (DispatchPalletLock lock : activeLocks) { @@ -220,8 +220,8 @@ public class DispatchService { } } - private DispatchOrder find(String tenantId, String dispatchNo) { - DispatchOrder dispatch = orders.findByDispatchNo(tenantId, dispatchNo); + private DispatchOrder find(String customerId, String dispatchNo) { + DispatchOrder dispatch = orders.findByDispatchNo(customerId, dispatchNo); if (dispatch == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Dispatch order not found"); return dispatch; } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/controller/DropshipOrderController.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/controller/DropshipOrderController.java new file mode 100644 index 0000000..c529147 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/controller/DropshipOrderController.java @@ -0,0 +1,23 @@ +package com.baiyue.lastleg.dropship.controller; + +import com.baiyue.common.core.api.ApiResponse; +import com.baiyue.lastleg.dropship.dto.DropshipRequests; +import com.baiyue.lastleg.dropship.service.DropshipOrderService; +import com.baiyue.lastleg.security.InternalServiceTokenGuard; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/internal/v1/lastleg/dropship") +public class DropshipOrderController { + @Resource private DropshipOrderService service; + @Resource private InternalServiceTokenGuard tokenGuard; + + @PostMapping("/receive") + public ApiResponse receive(@RequestHeader(value = "X-Internal-Service-Token", required = false) String token, + @Valid @RequestBody DropshipRequests.Receive request) { + tokenGuard.verify(token); + return ApiResponse.success(service.receive(request)); + } +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/dto/DropshipRequests.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/dto/DropshipRequests.java new file mode 100644 index 0000000..1f16c8c --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/dto/DropshipRequests.java @@ -0,0 +1,15 @@ +package com.baiyue.lastleg.dropship.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.*; +import java.util.List; + +public final class DropshipRequests { + private DropshipRequests() {} + public record Receive(@NotBlank String customerId, @NotNull Long orderId, @NotNull Long warehouseId, + @NotBlank String idempotencyKey, @NotEmpty List<@Valid Pallet> pallets) {} + public record Pallet(@NotBlank String palletNo, @NotEmpty List<@Valid SkuPackages> skuPackages) {} + public record SkuPackages(@NotBlank String skuCode, String productName, @Positive long packageCount, + @Positive long productQuantity, + @NotEmpty List<@NotBlank String> cartonMarks) {} +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCarton.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCarton.java new file mode 100644 index 0000000..782f59b --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCarton.java @@ -0,0 +1,23 @@ +package com.baiyue.lastleg.dropship.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data @TableName("ll_dropship_carton") +public class DropshipCarton { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long orderId; + private Long profileId; + private Long warehousePalletId; + private String palletNo; + private String skuCode; + private String cartonMark; + private Integer cartonSequence; + private String status; + private String outboundOrderNo; + private Long version; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCartonLedger.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCartonLedger.java new file mode 100644 index 0000000..d96b408 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipCartonLedger.java @@ -0,0 +1,24 @@ +package com.baiyue.lastleg.dropship.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data @TableName("ll_dropship_carton_ledger") +public class DropshipCartonLedger { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long orderId; + private Long cartonId; + private String cartonMark; + private String skuCode; + private String operationType; + private Long beforePackages; + private Long deltaPackages; + private Long afterPackages; + private String businessOrderNo; + private String idempotencyKey; + private String remark; + private LocalDateTime occurredAt; + private LocalDateTime createdAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipOrderProfile.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipOrderProfile.java new file mode 100644 index 0000000..15bc504 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipOrderProfile.java @@ -0,0 +1,22 @@ +package com.baiyue.lastleg.dropship.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data @TableName("ll_dropship_order_profile") +public class DropshipOrderProfile { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long orderId; + private Long warehousePalletId; + private String palletNo; + /** Immutable pallet number snapshot supplied by the first-leg handover event. */ + private String sourcePalletNo; + private Long inventoryPositionId; + private String status; + private String idempotencyKey; + private Long version; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipSkuInventory.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipSkuInventory.java new file mode 100644 index 0000000..ee7ba18 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/entity/DropshipSkuInventory.java @@ -0,0 +1,19 @@ +package com.baiyue.lastleg.dropship.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data @TableName("ll_dropship_sku_inventory") +public class DropshipSkuInventory { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long orderId; + private Long profileId; + private String skuCode; + private String productName; + private Long packageCount; + private Long productQuantity; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonLedgerMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonLedgerMapper.java new file mode 100644 index 0000000..8db4093 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonLedgerMapper.java @@ -0,0 +1,8 @@ +package com.baiyue.lastleg.dropship.mapper; +import com.baiyue.lastleg.dropship.entity.DropshipCartonLedger; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.*; +public interface DropshipCartonLedgerMapper extends BaseMapper { + @Select("SELECT * FROM ll_dropship_carton_ledger WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + DropshipCartonLedger findIdempotency(@Param("customerId") String customerId, @Param("key") String key); +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java new file mode 100644 index 0000000..d29fc4d --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java @@ -0,0 +1,8 @@ +package com.baiyue.lastleg.dropship.mapper; +import com.baiyue.lastleg.dropship.entity.DropshipCarton; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.*; +public interface DropshipCartonMapper extends BaseMapper { + @Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND order_id=#{orderId} AND carton_mark=#{cartonMark} LIMIT 1") + DropshipCarton find(@Param("customerId") String customerId, @Param("orderId") Long orderId, @Param("cartonMark") String cartonMark); +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipOrderProfileMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipOrderProfileMapper.java new file mode 100644 index 0000000..01a7d1d --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipOrderProfileMapper.java @@ -0,0 +1,9 @@ +package com.baiyue.lastleg.dropship.mapper; +import com.baiyue.lastleg.dropship.entity.DropshipOrderProfile; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.*; +import java.util.List; +public interface DropshipOrderProfileMapper extends BaseMapper { + @Select("SELECT * FROM ll_dropship_order_profile WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY source_pallet_no") + List listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId); +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java new file mode 100644 index 0000000..224fe5b --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java @@ -0,0 +1,6 @@ +package com.baiyue.lastleg.dropship.mapper; + +import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface DropshipSkuInventoryMapper extends BaseMapper {} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/dropship/service/DropshipOrderService.java b/lastleg/src/main/java/com/baiyue/lastleg/dropship/service/DropshipOrderService.java new file mode 100644 index 0000000..85012f0 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/dropship/service/DropshipOrderService.java @@ -0,0 +1,130 @@ +package com.baiyue.lastleg.dropship.service; + +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baiyue.lastleg.dropship.dto.DropshipRequests; +import com.baiyue.lastleg.dropship.entity.*; +import com.baiyue.lastleg.dropship.mapper.*; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import jakarta.annotation.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Service +public class DropshipOrderService { + @Resource private LastlegOrderMapper orders; + @Resource private DropshipOrderProfileMapper profiles; + @Resource private DropshipSkuInventoryMapper skuInventory; + @Resource private DropshipCartonMapper cartons; + @Resource private DropshipCartonLedgerMapper ledger; + @Resource private WarehouseResourceGateway warehouse; + + @Transactional + public int receive(DropshipRequests.Receive request) { + LastlegOrder order = orders.selectById(request.orderId()); + if (order == null || !request.customerId().equals(order.getCustomerId())) throw conflict("Tail order not found"); + if (!"ONE_CLICK_DROPSHIP".equals(order.getOrderType())) throw conflict("Order is not a one-click dropship order"); + long totalPackages = request.pallets().stream().flatMap(value -> value.skuPackages().stream()) + .mapToLong(DropshipRequests.SkuPackages::packageCount).sum(); + long totalQuantity = request.pallets().stream().flatMap(value -> value.skuPackages().stream()) + .mapToLong(DropshipRequests.SkuPackages::productQuantity).sum(); + if (order.getPackageCount() != null && order.getPackageCount().longValue() != totalPackages) { + throw conflict("SKU package total does not equal order package count"); + } + if (order.getProductQuantity() != null && order.getProductQuantity().longValue() != totalQuantity) { + throw conflict("SKU product quantity total does not equal order product quantity"); + } + List existing = profiles.listOrder(request.customerId(), request.orderId()); + if (!existing.isEmpty()) { + Set existingPallets = existing.stream().map(DropshipOrderProfile::getSourcePalletNo) + .collect(java.util.stream.Collectors.toSet()); + Set requestedPallets = request.pallets().stream().map(value -> value.palletNo().trim()) + .collect(java.util.stream.Collectors.toSet()); + if (!existingPallets.equals(requestedPallets)) throw conflict("Dropship order pallet snapshot conflicts with existing data"); + return Math.toIntExact(totalPackages); + } + + validateMarks(request); + LocalDateTime now = LocalDateTime.now(); + for (DropshipRequests.Pallet inputPallet : request.pallets()) { + WarehouseResourceGateway.WarehousePalletReference pallet = warehouse.ensureDropshipPallet( + request.customerId(), request.warehouseId(), request.orderId() + ":" + inputPallet.palletNo().trim()); + DropshipOrderProfile profile = profile(request, order, inputPallet, pallet, now); + profiles.insert(profile); + int sequence = 0; + for (DropshipRequests.SkuPackages input : inputPallet.skuPackages()) { + DropshipSkuInventory inventory = new DropshipSkuInventory(); + inventory.setId(IdWorker.getId()); inventory.setCustomerId(request.customerId()); inventory.setOrderId(order.getId()); + inventory.setProfileId(profile.getId()); inventory.setSkuCode(input.skuCode()); inventory.setProductName(input.productName()); + inventory.setPackageCount(input.packageCount()); inventory.setProductQuantity(input.productQuantity()); + inventory.setCreatedAt(now); inventory.setUpdatedAt(now); + skuInventory.insert(inventory); + for (String mark : input.cartonMarks()) { + DropshipCarton carton = new DropshipCarton(); + carton.setId(IdWorker.getId()); carton.setCustomerId(request.customerId()); carton.setOrderId(order.getId()); + carton.setProfileId(profile.getId()); carton.setWarehousePalletId(pallet.palletId()); carton.setPalletNo(pallet.palletNo()); + carton.setSkuCode(input.skuCode()); carton.setCartonMark(mark.trim()); carton.setCartonSequence(++sequence); + carton.setStatus("AVAILABLE"); carton.setVersion(0L); carton.setCreatedAt(now); carton.setUpdatedAt(now); + cartons.insert(carton); + ledger.insert(stockInFact(request, order, carton, now)); + } + } + } + return Math.toIntExact(totalPackages); + } + + private void validateMarks(DropshipRequests.Receive request) { + Set marks = new HashSet<>(); + Set palletNos = new HashSet<>(); + for (DropshipRequests.Pallet pallet : request.pallets()) { + if (!palletNos.add(pallet.palletNo().trim())) throw conflict("Duplicate pallet number: " + pallet.palletNo()); + Set skus = new HashSet<>(); + for (DropshipRequests.SkuPackages item : pallet.skuPackages()) { + if (item.packageCount() != item.cartonMarks().size()) { + throw conflict("SKU packageCount must equal cartonMarks count: " + item.skuCode()); + } + if (!skus.add(item.skuCode().trim())) throw conflict("Duplicate SKU on pallet: " + item.skuCode()); + for (String mark : item.cartonMarks()) { + if (!marks.add(mark.trim())) throw conflict("Duplicate carton mark: " + mark); + } + } + } + } + + private DropshipOrderProfile profile(DropshipRequests.Receive request, LastlegOrder order, + DropshipRequests.Pallet input, + WarehouseResourceGateway.WarehousePalletReference pallet, LocalDateTime now) { + DropshipOrderProfile profile = new DropshipOrderProfile(); + profile.setId(IdWorker.getId()); profile.setCustomerId(request.customerId()); profile.setOrderId(order.getId()); + profile.setWarehousePalletId(pallet.palletId()); profile.setPalletNo(pallet.palletNo()); + profile.setSourcePalletNo(input.palletNo().trim()); + profile.setStatus("RECEIVED"); profile.setVersion(0L); + profile.setIdempotencyKey(request.idempotencyKey() + ":" + input.palletNo().trim()); + profile.setCreatedAt(now); profile.setUpdatedAt(now); + return profile; + } + + private DropshipCartonLedger stockInFact(DropshipRequests.Receive request, LastlegOrder order, + DropshipCarton carton, LocalDateTime now) { + DropshipCartonLedger fact = new DropshipCartonLedger(); + fact.setId(IdWorker.getId()); fact.setCustomerId(request.customerId()); fact.setOrderId(order.getId()); + fact.setCartonId(carton.getId()); fact.setCartonMark(carton.getCartonMark()); fact.setSkuCode(carton.getSkuCode()); + fact.setOperationType("STOCK_IN"); fact.setBeforePackages(0L); fact.setDeltaPackages(1L); fact.setAfterPackages(1L); + fact.setBusinessOrderNo(order.getOrderNo()); + fact.setIdempotencyKey(request.idempotencyKey() + ":" + carton.getCartonMark()); + fact.setOccurredAt(now); fact.setCreatedAt(now); + return fact; + } + + private static ResponseStatusException conflict(String message) { + return new ResponseStatusException(HttpStatus.CONFLICT, message); + } +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegEventInbox.java b/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegEventInbox.java index 199bbca..fac02ba 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegEventInbox.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegEventInbox.java @@ -1,3 +1,3 @@ package com.baiyue.lastleg.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; -@Data @TableName("ll_loading_event_inbox") public class LastlegEventInbox { @TableId private String eventId; private String tenantId; private String eventType; private Integer eventVersion; private String payloadHash; private String payloadJson; private String status; private LocalDateTime receivedAt; private LocalDateTime processedAt; private String lastError; } +@Data @TableName("ll_loading_event_inbox") public class LastlegEventInbox { @TableId private String eventId; private String customerId; private String eventType; private Integer eventVersion; private String payloadHash; private String payloadJson; private String status; private LocalDateTime receivedAt; private LocalDateTime processedAt; private String lastError; } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegOrder.java index 6ecacb6..7a87035 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegOrder.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/entity/LastlegOrder.java @@ -13,7 +13,7 @@ public class LastlegOrder { @TableId(type = IdType.ASSIGN_ID) private Long id; private String orderNo; - private String tenantId; + private String customerId; private String orderType; private String inboundCode; private String sourceRegion; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/controller/InboundPlanController.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/controller/InboundPlanController.java index 4f102e6..a360ddd 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/controller/InboundPlanController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/controller/InboundPlanController.java @@ -28,15 +28,15 @@ public class InboundPlanController { @Operation(summary = "Convert the entire container to one inbound plan") public ApiResponse convert(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody ConvertInboundPlanRequest request) { - return ApiResponse.success(service.convert(scope(user, request.tenantId()), request.orderId())); + return ApiResponse.success(service.convert(scope(user, request.customerId()), request.orderId())); } @GetMapping("/{planNo}") @Operation(summary = "Get an inbound plan with lines, pallets and allocations") public ApiResponse detail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, - @RequestParam(required = false) String tenantId) { - return ApiResponse.success(service.detailByPlanNo(scope(user, tenantId), planNo)); + @RequestParam(required = false) String customerId) { + return ApiResponse.success(service.detailByPlanNo(scope(user, customerId), planNo)); } @PostMapping("/fba-appointments/batch") @@ -44,9 +44,9 @@ public class InboundPlanController { public ApiResponse batchFbaAppointment( @AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody BatchFbaAppointmentRequest request) { - String tenantId = scope(user, request.tenantId()); + String customerId = scope(user, request.customerId()); String operatorId = user.userId() == null ? user.username() : String.valueOf(user.userId()); - return ApiResponse.success(appointmentService.batchCreate(tenantId, operatorId, request)); + return ApiResponse.success(appointmentService.batchCreate(customerId, operatorId, request)); } @GetMapping("/fba-appointments/{appointmentNo}") @@ -54,20 +54,20 @@ public class InboundPlanController { public ApiResponse fbaAppointmentDetail( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String appointmentNo, - @RequestParam(required = false) String tenantId) { - return ApiResponse.success(appointmentService.detail(scope(user, tenantId), appointmentNo)); + @RequestParam(required = false) String customerId) { + return ApiResponse.success(appointmentService.detail(scope(user, customerId), appointmentNo)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/BatchFbaAppointmentRequest.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/BatchFbaAppointmentRequest.java index 50f1bdd..1572cb5 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/BatchFbaAppointmentRequest.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/BatchFbaAppointmentRequest.java @@ -9,7 +9,7 @@ import java.time.LocalDateTime; import java.util.List; public record BatchFbaAppointmentRequest( - String tenantId, + String customerId, @NotEmpty @Size(max = 500) List<@NotNull Long> lineIds, @NotBlank @Size(max = 64) String appointmentNo, @NotNull LocalDateTime appointmentAt, diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/ConvertInboundPlanRequest.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/ConvertInboundPlanRequest.java index f78813a..8d4a6a2 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/ConvertInboundPlanRequest.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/ConvertInboundPlanRequest.java @@ -4,6 +4,6 @@ import jakarta.validation.constraints.NotNull; public record ConvertInboundPlanRequest( @NotNull Long orderId, - String tenantId + String customerId ) { } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/FbaAppointmentResponse.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/FbaAppointmentResponse.java index 7a25616..acf3263 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/FbaAppointmentResponse.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/FbaAppointmentResponse.java @@ -5,7 +5,7 @@ import java.util.List; public record FbaAppointmentResponse( Long id, - String tenantId, + String customerId, String appointmentNo, LocalDateTime appointmentAt, String appointmentStatus, diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/InboundPlanResponse.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/InboundPlanResponse.java index 9f4fb13..f50f695 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/InboundPlanResponse.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/dto/InboundPlanResponse.java @@ -7,7 +7,7 @@ import java.util.List; public record InboundPlanResponse( Long id, String planNo, - String tenantId, + String customerId, String containerNo, String status, int lineCount, diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundFbaAppointment.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundFbaAppointment.java index f0b306f..20f3480 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundFbaAppointment.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundFbaAppointment.java @@ -12,7 +12,7 @@ import java.time.LocalDateTime; public class InboundFbaAppointment { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private String appointmentNo; private LocalDateTime appointmentAt; private String appointmentStatus; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlan.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlan.java index ce95e1c..a796f86 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlan.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlan.java @@ -14,7 +14,7 @@ public class InboundPlan { @TableId(type = IdType.INPUT) private Long id; private String planNo; - private String tenantId; + private String customerId; private String containerNo; private String status; private Integer lineCount; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java index 690ba19..e2972ec 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundFbaAppointmentQueryMapper.java @@ -13,10 +13,10 @@ public interface InboundFbaAppointmentQueryMapper { "SELECT l.id AS lineId,l.plan_id AS planId,l.order_id AS orderId,l.inbound_code AS inboundCode," + "l.fba_warehouse_code AS fbaWarehouseCode " + "FROM ll_inbound_plan_line l JOIN ll_inbound_plan p ON p.id=l.plan_id " + - "WHERE p.tenant_id=#{tenantId} AND l.id IN " + + "WHERE p.customer_id=#{customerId} AND l.id IN " + "#{id} " + "ORDER BY l.id FOR UPDATE" + "") - List lockLines(@Param("tenantId") String tenantId, + List lockLines(@Param("customerId") String customerId, @Param("lineIds") List lineIds); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanMapper.java index fc668aa..893df05 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanMapper.java @@ -8,6 +8,6 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface InboundPlanMapper extends BaseMapper { - @Select("SELECT * FROM ll_inbound_plan WHERE tenant_id=#{tenantId} AND container_no=#{containerNo} LIMIT 1") - InboundPlan findByContainer(@Param("tenantId") String tenantId, @Param("containerNo") String containerNo); + @Select("SELECT * FROM ll_inbound_plan WHERE customer_id=#{customerId} AND container_no=#{containerNo} LIMIT 1") + InboundPlan findByContainer(@Param("customerId") String customerId, @Param("containerNo") String containerNo); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java index 0a9ffa9..ed66ee4 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java @@ -12,10 +12,10 @@ import java.util.List; @Mapper public interface InboundPlanOrderMapper { - @Select("SELECT container_no FROM ll_order WHERE tenant_id=#{tenantId} AND id=#{orderId} LIMIT 1") - String findContainerNo(@Param("tenantId") String tenantId, @Param("orderId") Long orderId); + @Select("SELECT container_no FROM ll_order WHERE customer_id=#{customerId} AND id=#{orderId} LIMIT 1") + String findContainerNo(@Param("customerId") String customerId, @Param("orderId") Long orderId); - @Select("SELECT o.id AS orderId,o.tenant_id AS tenantId,o.container_no AS containerNo," + + @Select("SELECT o.id AS orderId,o.customer_id AS customerId,o.container_no AS containerNo," + "o.inbound_code AS inboundCode,o.current_status AS currentStatus,o.inbound_order_no AS inboundOrderNo," + "COALESCE(o.volume_cbm,0) AS volumeCbm,COALESCE(o.package_count,0) AS packageCount," + "NULLIF(o.fba_warehouse_code,'') AS fbaWarehouseCode," + @@ -29,17 +29,17 @@ public interface InboundPlanOrderMapper { "<> COALESCE(NULLIF(o.transfer_warehouse_id,''),NULLIF(o.transfer_warehouse_code,'')) THEN 1 ELSE 0 END) " + "AS destinationMismatchCount " + "FROM ll_order o LEFT JOIN ll_order_detail d ON d.order_id=o.id " + - "WHERE o.tenant_id=#{tenantId} AND o.container_no=#{containerNo} " + - "GROUP BY o.id,o.tenant_id,o.container_no,o.inbound_code,o.current_status,o.inbound_order_no,o.volume_cbm,o.package_count,o.fba_warehouse_code," + + "WHERE o.customer_id=#{customerId} AND o.container_no=#{containerNo} " + + "GROUP BY o.id,o.customer_id,o.container_no,o.inbound_code,o.current_status,o.inbound_order_no,o.volume_cbm,o.package_count,o.fba_warehouse_code," + "o.transfer_warehouse_id,o.transfer_warehouse_code,o.transfer_warehouse_name " + "ORDER BY o.id FOR UPDATE") - List lockContainerOrders(@Param("tenantId") String tenantId, + List lockContainerOrders(@Param("customerId") String customerId, @Param("containerNo") String containerNo); @Update("UPDATE ll_order SET inbound_order_no=#{planNo},current_status='INBOUND_PLANNED'," + "pallet_merge_flag='MERGED',pallet_count=#{palletCount},updated_at=#{updatedAt} " + - "WHERE tenant_id=#{tenantId} AND id=#{orderId}") - int markPlanned(@Param("tenantId") String tenantId, @Param("orderId") Long orderId, + "WHERE customer_id=#{customerId} AND id=#{orderId}") + int markPlanned(@Param("customerId") String customerId, @Param("orderId") Long orderId, @Param("planNo") String planNo, @Param("palletCount") int palletCount, @Param("updatedAt") LocalDateTime updatedAt); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/model/SourceOrderSnapshot.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/model/SourceOrderSnapshot.java index 0907a67..b4d7d58 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/model/SourceOrderSnapshot.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/model/SourceOrderSnapshot.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; public record SourceOrderSnapshot( Long orderId, - String tenantId, + String customerId, String containerNo, String inboundCode, String currentStatus, @@ -18,12 +18,12 @@ public record SourceOrderSnapshot( int destinationCount, int destinationMismatchCount ) { - public SourceOrderSnapshot(Long orderId, String tenantId, String containerNo, String inboundCode, + public SourceOrderSnapshot(Long orderId, String customerId, String containerNo, String inboundCode, String currentStatus, String inboundOrderNo, BigDecimal volumeCbm, int packageCount, String destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, int destinationCount, int destinationMismatchCount) { - this(orderId, tenantId, containerNo, inboundCode, currentStatus, inboundOrderNo, volumeCbm, + this(orderId, customerId, containerNo, inboundCode, currentStatus, inboundOrderNo, volumeCbm, packageCount, null, destinationWarehouseId, destinationWarehouseCode, destinationWarehouseName, destinationCount, destinationMismatchCount); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPalletAssignmentController.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPalletAssignmentController.java index 5f8d0e7..f024f8e 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPalletAssignmentController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPalletAssignmentController.java @@ -24,19 +24,19 @@ public class InboundPalletAssignmentController { @PostMapping("/{planPalletId}/assign") public ApiResponse assign(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, @PathVariable long planPalletId, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody PdaRequests.Assign request) { - service.assign(scope(user, tenantId), planNo, planPalletId, request); + service.assign(scope(user, customerId), planNo, planPalletId, request); return ApiResponse.success(null); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPdaController.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPdaController.java index 11b188c..d6e75f0 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPdaController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPdaController.java @@ -28,66 +28,66 @@ public class InboundPdaController { @PostMapping("/push") public ApiResponse push(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody PdaRequests.Push request) { - return ApiResponse.success(service.push(scope(user, tenantId), planNo, request)); + return ApiResponse.success(service.push(scope(user, customerId), planNo, request)); } @GetMapping public ApiResponse task(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, - @RequestParam(required = false) String tenantId) { - return ApiResponse.success(service.task(scope(user, tenantId), planNo)); + @RequestParam(required = false) String customerId) { + return ApiResponse.success(service.task(scope(user, customerId), planNo)); } @PostMapping("/allocations/{allocationId}/count") public ApiResponse count(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, @PathVariable long allocationId, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody PdaRequests.PackageOperation request) { - return ApiResponse.success(service.count(scope(user, tenantId), planNo, allocationId, request)); + return ApiResponse.success(service.count(scope(user, customerId), planNo, allocationId, request)); } @PostMapping("/allocations/{allocationId}/pick") public ApiResponse pick(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, @PathVariable long allocationId, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody PdaRequests.PackageOperation request) { - return ApiResponse.success(service.pick(scope(user, tenantId), planNo, allocationId, request)); + return ApiResponse.success(service.pick(scope(user, customerId), planNo, allocationId, request)); } @GetMapping("/allocations/{allocationId}/merge-candidates") public ApiResponse> candidates( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, - @PathVariable long allocationId, @RequestParam(required = false) String tenantId) { - return ApiResponse.success(service.mergeCandidates(scope(user, tenantId), planNo, allocationId)); + @PathVariable long allocationId, @RequestParam(required = false) String customerId) { + return ApiResponse.success(service.mergeCandidates(scope(user, customerId), planNo, allocationId)); } @PostMapping("/allocations/{allocationId}/merge") public ApiResponse merge(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, @PathVariable long allocationId, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid @RequestBody PdaRequests.ConfirmMerge request) { - return ApiResponse.success(service.merge(scope(user, tenantId), planNo, allocationId, request)); + return ApiResponse.success(service.merge(scope(user, customerId), planNo, allocationId, request)); } @PostMapping("/complete") public ApiResponse complete(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String planNo, - @RequestParam(required = false) String tenantId) { - return ApiResponse.success(service.complete(scope(user, tenantId), planNo)); + @RequestParam(required = false) String customerId) { + return ApiResponse.success(service.complete(scope(user, customerId), planNo)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPalletMerge.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPalletMerge.java index 834a377..0e70e49 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPalletMerge.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPalletMerge.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InboundPalletMerge { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long taskId; private String mergeGroupNo; private Long sourcePalletId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaOperation.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaOperation.java index 84e6c7e..2a95ef7 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaOperation.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaOperation.java @@ -12,7 +12,7 @@ import java.time.LocalDateTime; public class InboundPdaOperation { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long taskId; private Long allocationId; private String action; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaTask.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaTask.java index 2cf1fb8..753cd2f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaTask.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundPdaTask.java @@ -13,7 +13,7 @@ public class InboundPdaTask { @TableId(type = IdType.INPUT) private Long id; private Long planId; - private String tenantId; + private String customerId; private String taskNo; private String status; private String operatorId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundStockReceipt.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundStockReceipt.java index 04f62f8..8149678 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundStockReceipt.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/entity/InboundStockReceipt.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InboundStockReceipt { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private Long planId; private Long taskId; private Long allocationId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java index ddb824b..1a5ebcf 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java @@ -27,12 +27,24 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { } @Override - public long reserve(String tenantId, long warehouseId, long palletId, long locationId, + public WarehousePalletReference ensureDropshipPallet(String customerId, long warehouseId, String businessId) { + WarehousePalletResponse response = client.post().uri("/internal/v1/warehouse-pallets/ensure-dropship") + .header("X-Internal-Service-Token", internalToken) + .body(Map.of("customerId", Long.parseLong(customerId), "warehouseId", warehouseId, "businessId", businessId)) + .retrieve().onStatus(HttpStatusCode::isError, (request, result) -> { + throw new IllegalStateException("Warehouse rejected dropship pallet: HTTP " + result.getStatusCode()); + }).body(WarehousePalletResponse.class); + if (response == null || response.data() == null) throw new IllegalStateException("Warehouse returned an empty pallet response"); + return new WarehousePalletReference(response.data().id(), response.data().palletCode(), response.data().locationId()); + } + + @Override + public long reserve(String customerId, long warehouseId, long palletId, long locationId, String businessType, String businessId, String reservationKey) { ReserveEnvelope response = client.post() .uri("/internal/v1/warehouse-resource-locks") .header("X-Internal-Service-Token", internalToken) - .body(Map.of("tenantId", Long.parseLong(tenantId), "warehouseId", warehouseId, + .body(Map.of("customerId", Long.parseLong(customerId), "warehouseId", warehouseId, "palletId", palletId, "locationId", locationId, "businessType", businessType, "businessId", businessId, "reservationKey", reservationKey)) .retrieve() @@ -45,11 +57,11 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { } @Override - public void scrap(String tenantId, long resourceLockId) { + public void scrap(String customerId, long resourceLockId) { client.post() .uri("/internal/v1/warehouse-resource-locks/{lockId}/scrap", resourceLockId) .header("X-Internal-Service-Token", internalToken) - .body(Map.of("tenantId", Long.parseLong(tenantId))) + .body(Map.of("customerId", Long.parseLong(customerId))) .retrieve() .onStatus(HttpStatusCode::isError, (request, response) -> { throw new IllegalStateException("Warehouse rejected pallet scrap: HTTP " + response.getStatusCode()); @@ -58,11 +70,11 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { } @Override - public void scrapPallet(String tenantId, long palletId) { + public void scrapPallet(String customerId, long palletId) { client.post() .uri("/internal/v1/warehouse-resource-locks/pallets/{palletId}/scrap", palletId) .header("X-Internal-Service-Token", internalToken) - .body(Map.of("tenantId", Long.parseLong(tenantId))) + .body(Map.of("customerId", Long.parseLong(customerId))) .retrieve() .onStatus(HttpStatusCode::isError, (request, response) -> { throw new IllegalStateException("Warehouse rejected pallet scrap: HTTP " + response.getStatusCode()); @@ -70,6 +82,8 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { .toBodilessEntity(); } + private record WarehousePalletResponse(WarehousePalletData data) {} + private record WarehousePalletData(long id, String palletCode, long locationId) {} private record ReserveEnvelope(ReserveData data) {} private record ReserveData(long lockId) {} } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/WarehouseResourceGateway.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/WarehouseResourceGateway.java index 4554845..ada468e 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/WarehouseResourceGateway.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/WarehouseResourceGateway.java @@ -1,8 +1,11 @@ package com.baiyue.lastleg.inboundplan.pda.gateway; public interface WarehouseResourceGateway { - long reserve(String tenantId, long warehouseId, long palletId, long locationId, + WarehousePalletReference ensureDropshipPallet(String customerId, long warehouseId, String businessId); + long reserve(String customerId, long warehouseId, long palletId, long locationId, String businessType, String businessId, String reservationKey); - void scrap(String tenantId, long resourceLockId); - void scrapPallet(String tenantId, long palletId); + void scrap(String customerId, long resourceLockId); + void scrapPallet(String customerId, long palletId); + + record WarehousePalletReference(long palletId, String palletNo, long locationId) {} } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaOperationMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaOperationMapper.java index 719b206..ac27ae9 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaOperationMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaOperationMapper.java @@ -8,6 +8,6 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface InboundPdaOperationMapper extends BaseMapper { - @Select("SELECT * FROM ll_inbound_pda_operation WHERE tenant_id=#{tenantId} AND idempotency_key=#{key} LIMIT 1") - InboundPdaOperation findByKey(@Param("tenantId") String tenantId, @Param("key") String key); + @Select("SELECT * FROM ll_inbound_pda_operation WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + InboundPdaOperation findByKey(@Param("customerId") String customerId, @Param("key") String key); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaTaskMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaTaskMapper.java index 4547945..b144e7d 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaTaskMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundPdaTaskMapper.java @@ -8,9 +8,9 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface InboundPdaTaskMapper extends BaseMapper { - @Select("SELECT * FROM ll_inbound_pda_task WHERE tenant_id=#{tenantId} AND plan_id=#{planId} LIMIT 1 FOR UPDATE") - InboundPdaTask lockByPlan(@Param("tenantId") String tenantId, @Param("planId") Long planId); + @Select("SELECT * FROM ll_inbound_pda_task WHERE customer_id=#{customerId} AND plan_id=#{planId} LIMIT 1 FOR UPDATE") + InboundPdaTask lockByPlan(@Param("customerId") String customerId, @Param("planId") Long planId); - @Select("SELECT * FROM ll_inbound_pda_task WHERE tenant_id=#{tenantId} AND plan_id=#{planId} LIMIT 1") - InboundPdaTask findByPlan(@Param("tenantId") String tenantId, @Param("planId") Long planId); + @Select("SELECT * FROM ll_inbound_pda_task WHERE customer_id=#{customerId} AND plan_id=#{planId} LIMIT 1") + InboundPdaTask findByPlan(@Param("customerId") String customerId, @Param("planId") Long planId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundStockReceiptMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundStockReceiptMapper.java index 8f8c109..bcde3f9 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundStockReceiptMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/mapper/InboundStockReceiptMapper.java @@ -8,7 +8,7 @@ import org.apache.ibatis.annotations.Select; import java.util.List; public interface InboundStockReceiptMapper extends BaseMapper { - @Select("SELECT * FROM ll_inbound_stock_receipt WHERE tenant_id=#{tenantId} AND task_id=#{taskId} " + + @Select("SELECT * FROM ll_inbound_stock_receipt WHERE customer_id=#{customerId} AND task_id=#{taskId} " + "ORDER BY allocation_id") - List listTask(@Param("tenantId") String tenantId, @Param("taskId") Long taskId); + List listTask(@Param("customerId") String customerId, @Param("taskId") Long taskId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/repository/PdaWorkflowRepository.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/repository/PdaWorkflowRepository.java index f77e285..e39b826 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/repository/PdaWorkflowRepository.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/repository/PdaWorkflowRepository.java @@ -30,14 +30,14 @@ public class PdaWorkflowRepository { @Resource private InboundPdaOperationMapper operations; @Resource private InboundPalletMergeMapper merges; - public InboundPlan lockPlan(String tenantId, String planNo) { + public InboundPlan lockPlan(String customerId, String planNo) { return plans.selectOne(new LambdaQueryWrapper() - .eq(InboundPlan::getTenantId, tenantId).eq(InboundPlan::getPlanNo, planNo).last("FOR UPDATE")); + .eq(InboundPlan::getCustomerId, customerId).eq(InboundPlan::getPlanNo, planNo).last("FOR UPDATE")); } - public InboundPdaTask lockTask(String tenantId, Long planId) { return tasks.lockByPlan(tenantId, planId); } - public InboundPdaTask task(String tenantId, Long planId) { return tasks.findByPlan(tenantId, planId); } - public InboundPdaOperation operation(String tenantId, String key) { return operations.findByKey(tenantId, key); } + public InboundPdaTask lockTask(String customerId, Long planId) { return tasks.lockByPlan(customerId, planId); } + public InboundPdaTask task(String customerId, Long planId) { return tasks.findByPlan(customerId, planId); } + public InboundPdaOperation operation(String customerId, String key) { return operations.findByKey(customerId, key); } public InboundPlanPalletAllocation lockAllocation(Long planId, Long allocationId) { return allocations.selectOne(new LambdaQueryWrapper() diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java index dd2d6ce..680e43d 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java @@ -55,22 +55,22 @@ public class InboundPdaWorkflowService { } @Transactional - public PdaResponses.Task push(String tenantId, String planNo, PdaRequests.Push request) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaOperation replay = replay(tenantId, "PUSH", request.idempotencyKey(), request); + public PdaResponses.Task push(String customerId, String planNo, PdaRequests.Push request) { + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaOperation replay = replay(customerId, "PUSH", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Task.class); List pallets = repository.pallets(plan.getId()); if (pallets.isEmpty() || pallets.stream().anyMatch(this::isNotAssigned)) { throw conflict("Every planned pallet must have an assigned and locked warehouse pallet/location"); } - InboundPdaTask task = repository.lockTask(tenantId, plan.getId()); + InboundPdaTask task = repository.lockTask(customerId, plan.getId()); LocalDateTime now = LocalDateTime.now(); if (task == null) { task = new InboundPdaTask(); task.setId(IdWorker.getId()); task.setPlanId(plan.getId()); - task.setTenantId(tenantId); + task.setCustomerId(customerId); task.setTaskNo("PDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setOperatorId(request.operatorId()); @@ -85,13 +85,13 @@ public class InboundPdaWorkflowService { plan.setUpdatedAt(now); repository.update(plan); PdaResponses.Task result = task(task, planNo); - saveOperation(tenantId, task.getId(), null, "PUSH", request.idempotencyKey(), request, result); + saveOperation(customerId, task.getId(), null, "PUSH", request.idempotencyKey(), request, result); return result; } @Transactional - public void assign(String tenantId, String planNo, long planPalletId, PdaRequests.Assign request) { - InboundPlan plan = requirePlan(tenantId, planNo); + public void assign(String customerId, String planNo, long planPalletId, PdaRequests.Assign request) { + InboundPlan plan = requirePlan(customerId, planNo); InboundPlanPallet pallet = requirePallet(plan.getId(), planPalletId); validateDestinationWarehouse(pallet, request.warehouseId()); if (pallet.getWarehouseResourceLockId() != null) { @@ -100,7 +100,7 @@ public class InboundPdaWorkflowService { throw conflict("Planned pallet is already assigned to another warehouse resource"); } String businessId = planNo + ":" + planPalletId; - long lockId = warehouse.reserve(tenantId, request.warehouseId(), request.palletId(), request.locationId(), + long lockId = warehouse.reserve(customerId, request.warehouseId(), request.palletId(), request.locationId(), "INBOUND_PLAN_PALLET", businessId, request.idempotencyKey()); pallet.setWarehousePalletId(request.palletId()); pallet.setWarehouseLocationId(request.locationId()); @@ -117,20 +117,20 @@ public class InboundPdaWorkflowService { } @Transactional - public PdaResponses.Task task(String tenantId, String planNo) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaTask task = repository.task(tenantId, plan.getId()); + public PdaResponses.Task task(String customerId, String planNo) { + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaTask task = repository.task(customerId, plan.getId()); if (task == null) throw notFound("PDA task not found"); return task(task, planNo); } @Transactional - public PdaResponses.Operation count(String tenantId, String planNo, long allocationId, + public PdaResponses.Operation count(String customerId, String planNo, long allocationId, PdaRequests.PackageOperation request) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaOperation replay = replay(tenantId, "COUNT", request.idempotencyKey(), request); + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaOperation replay = replay(customerId, "COUNT", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Operation.class); - InboundPdaTask task = requireActiveTask(tenantId, plan); + InboundPdaTask task = requireActiveTask(customerId, plan); InboundPlanPalletAllocation allocation = requireAllocation(plan.getId(), allocationId); int picked = value(allocation.getPickedPackageCount()); if (request.packageCount() < picked) { @@ -147,17 +147,17 @@ public class InboundPdaWorkflowService { startPicking(task, plan, request.operatorId(), request.operatorName()); InboundPlanLine line = requireLine(plan.getId(), allocation.getLineId()); PdaResponses.Operation result = operation("COUNTED", allocation, line, List.of(), null, null, null); - saveOperation(tenantId, task.getId(), allocationId, "COUNT", request.idempotencyKey(), request, result); + saveOperation(customerId, task.getId(), allocationId, "COUNT", request.idempotencyKey(), request, result); return result; } @Transactional - public PdaResponses.Operation pick(String tenantId, String planNo, long allocationId, + public PdaResponses.Operation pick(String customerId, String planNo, long allocationId, PdaRequests.PackageOperation request) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaOperation replay = replay(tenantId, "PICK", request.idempotencyKey(), request); + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaOperation replay = replay(customerId, "PICK", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Operation.class); - InboundPdaTask task = requireActiveTask(tenantId, plan); + InboundPdaTask task = requireActiveTask(customerId, plan); InboundPlanPalletAllocation allocation = requireAllocation(plan.getId(), allocationId); InboundPlanLine line = requireLine(plan.getId(), allocation.getLineId()); int picked = value(allocation.getPickedPackageCount()); @@ -169,7 +169,7 @@ public class InboundPdaWorkflowService { List candidates = candidates(plan, allocation, mergeCount); PdaResponses.Operation result = operation("MERGE_CONFIRMATION_REQUIRED", allocation, line, candidates, plannedRemaining, request.packageCount(), mergeCount); - saveOperation(tenantId, task.getId(), allocationId, "PICK", request.idempotencyKey(), request, result); + saveOperation(customerId, task.getId(), allocationId, "PICK", request.idempotencyKey(), request, result); return result; } if (request.packageCount() > countedRemaining) { @@ -189,25 +189,25 @@ public class InboundPdaWorkflowService { repository.update(pallet); startPicking(task, plan, request.operatorId(), request.operatorName()); PdaResponses.Operation result = operation("PICKED", allocation, line, List.of(), null, null, null); - saveOperation(tenantId, task.getId(), allocationId, "PICK", request.idempotencyKey(), request, result); + saveOperation(customerId, task.getId(), allocationId, "PICK", request.idempotencyKey(), request, result); return result; } @Transactional - public List mergeCandidates(String tenantId, String planNo, + public List mergeCandidates(String customerId, String planNo, long allocationId) { - InboundPlan plan = requirePlan(tenantId, planNo); + InboundPlan plan = requirePlan(customerId, planNo); InboundPlanPalletAllocation allocation = requireAllocation(plan.getId(), allocationId); return candidates(plan, allocation, 1); } @Transactional - public PdaResponses.Merge merge(String tenantId, String planNo, long targetAllocationId, + public PdaResponses.Merge merge(String customerId, String planNo, long targetAllocationId, PdaRequests.ConfirmMerge request) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaOperation replay = replay(tenantId, "MERGE", request.idempotencyKey(), request); + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaOperation replay = replay(customerId, "MERGE", request.idempotencyKey(), request); if (replay != null) return read(replay, PdaResponses.Merge.class); - InboundPdaTask task = requireActiveTask(tenantId, plan); + InboundPdaTask task = requireActiveTask(customerId, plan); if (request.sourceAllocationId() == targetAllocationId) throw conflict("Source and target allocations differ"); InboundPlanPalletAllocation target = requireAllocation(plan.getId(), targetAllocationId); InboundPlanPalletAllocation source = requireAllocation(plan.getId(), request.sourceAllocationId()); @@ -240,7 +240,7 @@ public class InboundPdaWorkflowService { if (sourcePallet.getWarehouseResourceLockId() == null) { throw conflict("Source pallet has no warehouse resource lock"); } - warehouse.scrap(tenantId, sourcePallet.getWarehouseResourceLockId()); + warehouse.scrap(customerId, sourcePallet.getWarehouseResourceLockId()); sourcePallet.setAssignmentStatus("SCRAPPED"); sourcePallet.setWarehouseLocationId(null); sourcePallet.setWarehouseResourceLockId(null); @@ -251,7 +251,7 @@ public class InboundPdaWorkflowService { String groupNo = "PMG" + IdWorker.getId(); InboundPalletMerge merge = new InboundPalletMerge(); merge.setId(IdWorker.getId()); - merge.setTenantId(tenantId); + merge.setCustomerId(customerId); merge.setTaskId(task.getId()); merge.setMergeGroupNo(groupNo); merge.setSourcePalletId(sourcePallet.getId()); @@ -270,14 +270,14 @@ public class InboundPdaWorkflowService { repository.insert(merge); PdaResponses.Merge result = new PdaResponses.Merge("MERGED", groupNo, sourceLine.getOrderId(), targetLine.getOrderId(), request.packageCount(), movedVolume, scrapped); - saveOperation(tenantId, task.getId(), targetAllocationId, "MERGE", request.idempotencyKey(), request, result); + saveOperation(customerId, task.getId(), targetAllocationId, "MERGE", request.idempotencyKey(), request, result); return result; } @Transactional - public PdaResponses.Task complete(String tenantId, String planNo) { - InboundPlan plan = requirePlan(tenantId, planNo); - InboundPdaTask task = repository.lockTask(tenantId, plan.getId()); + public PdaResponses.Task complete(String customerId, String planNo) { + InboundPlan plan = requirePlan(customerId, planNo); + InboundPdaTask task = repository.lockTask(customerId, plan.getId()); if (task == null) throw conflict("Push the inbound plan to PDA before operating"); if ("COMPLETED".equals(task.getStatus())) return task(task, planNo); Map pickedByLine = new HashMap<>(); @@ -293,7 +293,7 @@ public class InboundPdaWorkflowService { } } LocalDateTime now = LocalDateTime.now(); - stockReceipts.stockIn(tenantId, plan, task, lines, allocations, repository.pallets(plan.getId()), now); + stockReceipts.stockIn(customerId, plan, task, lines, allocations, repository.pallets(plan.getId()), now); task.setStatus("COMPLETED"); task.setCompletedAt(now); task.setUpdatedAt(now); @@ -419,14 +419,14 @@ public class InboundPdaWorkflowService { || !("ASSIGNED".equals(pallet.getAssignmentStatus()) || "LOCKED".equals(pallet.getAssignmentStatus())); } - private InboundPlan requirePlan(String tenantId, String planNo) { - InboundPlan plan = repository.lockPlan(tenantId, planNo); + private InboundPlan requirePlan(String customerId, String planNo) { + InboundPlan plan = repository.lockPlan(customerId, planNo); if (plan == null) throw notFound("Inbound plan not found"); return plan; } - private InboundPdaTask requireActiveTask(String tenantId, InboundPlan plan) { - InboundPdaTask task = repository.lockTask(tenantId, plan.getId()); + private InboundPdaTask requireActiveTask(String customerId, InboundPlan plan) { + InboundPdaTask task = repository.lockTask(customerId, plan.getId()); if (task == null) throw conflict("Push the inbound plan to PDA before operating"); if ("COMPLETED".equals(task.getStatus())) throw conflict("PDA task is already completed"); return task; @@ -470,8 +470,8 @@ public class InboundPdaWorkflowService { return volume(line.getVolumeCbm()).divide(BigDecimal.valueOf(required), 8, RoundingMode.HALF_UP); } - private InboundPdaOperation replay(String tenantId, String action, String key, Object request) { - InboundPdaOperation operation = repository.operation(tenantId, key); + private InboundPdaOperation replay(String customerId, String action, String key, Object request) { + InboundPdaOperation operation = repository.operation(customerId, key); if (operation == null) return null; if (!action.equals(operation.getAction()) || !hash(request).equals(operation.getRequestHash())) { throw conflict("Idempotency key was already used with a different request"); @@ -479,11 +479,11 @@ public class InboundPdaWorkflowService { return operation; } - private void saveOperation(String tenantId, Long taskId, Long allocationId, String action, + private void saveOperation(String customerId, Long taskId, Long allocationId, String action, String key, Object request, Object result) { InboundPdaOperation operation = new InboundPdaOperation(); operation.setId(IdWorker.getId()); - operation.setTenantId(tenantId); + operation.setCustomerId(customerId); operation.setTaskId(taskId); operation.setAllocationId(allocationId); operation.setAction(action); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java index 5c13d74..617033a 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptService.java @@ -32,14 +32,14 @@ public class InboundStockReceiptService { @Resource private InboundStockReceiptMapper receipts; @Resource private InventoryCommandService inventory; - public void stockIn(String tenantId, InboundPlan plan, InboundPdaTask task, + public void stockIn(String customerId, InboundPlan plan, InboundPdaTask task, List lines, List allocations, List pallets, LocalDateTime receivedAt) { - if (!receipts.listTask(tenantId, task.getId()).isEmpty()) return; + if (!receipts.listTask(customerId, task.getId()).isEmpty()) return; Map orderById = orders.selectBatchIds(lines.stream() .map(InboundPlanLine::getOrderId).collect(Collectors.toSet())).stream() - .filter(order -> tenantId.equals(order.getTenantId())) + .filter(order -> customerId.equals(order.getCustomerId())) .collect(Collectors.toMap(LastlegOrder::getId, order -> order)); Map palletById = pallets.stream() .collect(Collectors.toMap(InboundPlanPallet::getId, pallet -> pallet)); @@ -59,13 +59,13 @@ public class InboundStockReceiptService { throw conflict("PDA picked packages must equal the inbound order package total"); } for (InboundPlanPalletAllocation allocation : lineAllocations) { - persist(tenantId, plan, task, line, allocation, + persist(customerId, plan, task, line, allocation, requirePallet(palletById, allocation), order, receivedAt); } } } - private void persist(String tenantId, InboundPlan plan, InboundPdaTask task, InboundPlanLine line, + private void persist(String customerId, InboundPlan plan, InboundPdaTask task, InboundPlanLine line, InboundPlanPalletAllocation allocation, InboundPlanPallet pallet, LastlegOrder order, LocalDateTime receivedAt) { long packages = value(allocation.getPickedPackageCount()); @@ -77,7 +77,7 @@ public class InboundStockReceiptService { String operationKey = "PDA_STOCK_IN:" + task.getId() + ":" + allocation.getId(); String overseasWarehouseId = required(order.getOverseasWarehouseId(), "Actual overseas warehouse is required for stock-in"); - InventoryCommandResponse result = inventory.stockIn(tenantId, task.getOperatorId(), + InventoryCommandResponse result = inventory.stockIn(customerId, task.getOperatorId(), new InventoryCommandRequests.StockIn(null, order.getCustomerName(), overseasWarehouseId, null, order.getOverseasWarehouseName(), line.getDestinationWarehouseId(), destinationCode, line.getDestinationWarehouseName(), order.getId(), plan.getPlanNo(), line.getInboundCode(), @@ -88,7 +88,7 @@ public class InboundStockReceiptService { InboundStockReceipt receipt = new InboundStockReceipt(); receipt.setId(IdWorker.getId()); - receipt.setTenantId(tenantId); + receipt.setCustomerId(customerId); receipt.setPlanId(plan.getId()); receipt.setTaskId(task.getId()); receipt.setAllocationId(allocation.getId()); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java index 525418f..6045f4a 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentService.java @@ -32,7 +32,7 @@ public class InboundFbaAppointmentService { @Resource private InboundFbaAppointmentQueryMapper queryMapper; @Transactional - public FbaAppointmentResponse batchCreate(String tenantId, String operatorId, + public FbaAppointmentResponse batchCreate(String customerId, String operatorId, BatchFbaAppointmentRequest request) { List lineIds = List.copyOf(request.lineIds()); if (new HashSet<>(lineIds).size() != lineIds.size()) { @@ -42,12 +42,12 @@ public class InboundFbaAppointmentService { String status = normalizeStatus(request.appointmentStatus()); String remark = trimToNull(request.appointmentRemark()); - InboundFbaAppointment existing = find(tenantId, appointmentNo); + InboundFbaAppointment existing = find(customerId, appointmentNo); if (existing != null) { return verifyReplay(existing, lineIds, request.appointmentAt(), status, remark); } - List lines = queryMapper.lockLines(tenantId, lineIds); + List lines = queryMapper.lockLines(customerId, lineIds); if (lines.size() != lineIds.size()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "One or more selected inbound lines do not exist in this tenant"); @@ -55,7 +55,7 @@ public class InboundFbaAppointmentService { String fbaWarehouseCode = commonFbaWarehouse(lines); // The row locks serialize concurrent submissions selecting the same inbound lines. - existing = find(tenantId, appointmentNo); + existing = find(customerId, appointmentNo); if (existing != null) { return verifyReplay(existing, lineIds, request.appointmentAt(), status, remark); } @@ -63,7 +63,7 @@ public class InboundFbaAppointmentService { LocalDateTime now = LocalDateTime.now(); InboundFbaAppointment appointment = new InboundFbaAppointment(); appointment.setId(IdWorker.getId()); - appointment.setTenantId(tenantId); + appointment.setCustomerId(customerId); appointment.setAppointmentNo(appointmentNo); appointment.setAppointmentAt(request.appointmentAt()); appointment.setAppointmentStatus(status); @@ -76,7 +76,7 @@ public class InboundFbaAppointmentService { try { appointmentMapper.insert(appointment); } catch (DuplicateKeyException exception) { - InboundFbaAppointment concurrent = find(tenantId, appointmentNo); + InboundFbaAppointment concurrent = find(customerId, appointmentNo); if (concurrent != null) { return verifyReplay(concurrent, lineIds, request.appointmentAt(), status, remark); } @@ -99,8 +99,8 @@ public class InboundFbaAppointmentService { } @Transactional(readOnly = true) - public FbaAppointmentResponse detail(String tenantId, String appointmentNo) { - InboundFbaAppointment appointment = find(tenantId, + public FbaAppointmentResponse detail(String customerId, String appointmentNo) { + InboundFbaAppointment appointment = find(customerId, requiredTrimmed(appointmentNo, "Appointment number is required")); if (appointment == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "FBA appointment not found"); @@ -134,9 +134,9 @@ public class InboundFbaAppointmentService { return warehouses.get(0); } - private InboundFbaAppointment find(String tenantId, String appointmentNo) { + private InboundFbaAppointment find(String customerId, String appointmentNo) { return appointmentMapper.selectOne(new LambdaQueryWrapper() - .eq(InboundFbaAppointment::getTenantId, tenantId) + .eq(InboundFbaAppointment::getCustomerId, customerId) .eq(InboundFbaAppointment::getAppointmentNo, appointmentNo)); } @@ -148,7 +148,7 @@ public class InboundFbaAppointmentService { private FbaAppointmentResponse response(InboundFbaAppointment appointment, List items) { - return new FbaAppointmentResponse(appointment.getId(), appointment.getTenantId(), + return new FbaAppointmentResponse(appointment.getId(), appointment.getCustomerId(), appointment.getAppointmentNo(), appointment.getAppointmentAt(), appointment.getAppointmentStatus(), appointment.getAppointmentRemark(), appointment.getFbaWarehouseCode(), appointment.getSelectedCount(), appointment.getCreatedBy(), appointment.getCreatedAt(), appointment.getUpdatedAt(), diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java index 60efded..42607c5 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java @@ -36,20 +36,20 @@ public class InboundPlanService { @Resource private InboundPlanOrderMapper orderMapper; @Transactional - public InboundPlanResponse convert(String tenantId, Long selectedOrderId) { - String containerNo = trimToNull(orderMapper.findContainerNo(tenantId, selectedOrderId)); + public InboundPlanResponse convert(String customerId, Long selectedOrderId) { + String containerNo = trimToNull(orderMapper.findContainerNo(customerId, selectedOrderId)); if (containerNo == null) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Order does not exist in this tenant or container number is empty"); } - List orders = orderMapper.lockContainerOrders(tenantId, containerNo); + List orders = orderMapper.lockContainerOrders(customerId, containerNo); if (orders.isEmpty()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Container orders not found"); } - InboundPlan existing = planMapper.findByContainer(tenantId, containerNo); + InboundPlan existing = planMapper.findByContainer(customerId, containerNo); if (existing != null) { - return detail(tenantId, existing); + return detail(customerId, existing); } validateOrders(orders); @@ -57,7 +57,7 @@ public class InboundPlanService { InboundPlan plan = new InboundPlan(); plan.setId(IdWorker.getId()); plan.setPlanNo("LIP" + plan.getId()); - plan.setTenantId(tenantId); + plan.setCustomerId(customerId); plan.setContainerNo(containerNo); plan.setStatus("PENDING_ASSIGNMENT"); plan.setLineCount(orders.size()); @@ -88,23 +88,23 @@ public class InboundPlanService { InboundPlanLine line = lineByOrder.get(order.orderId()); line.setPlannedPalletCount(count); lineMapper.updateById(line); - if (orderMapper.markPlanned(tenantId, order.orderId(), plan.getPlanNo(), count, now) != 1) { + if (orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now) != 1) { throw new IllegalStateException("Failed to update order " + order.orderId()); } orderMapper.insertHistory(order.orderId(), order.currentStatus(), "Container converted to inbound plan " + plan.getPlanNo(), now); } - return detail(tenantId, plan); + return detail(customerId, plan); } @Transactional(readOnly = true) - public InboundPlanResponse detailByPlanNo(String tenantId, String planNo) { + public InboundPlanResponse detailByPlanNo(String customerId, String planNo) { InboundPlan plan = planMapper.selectOne(new LambdaQueryWrapper() - .eq(InboundPlan::getTenantId, tenantId).eq(InboundPlan::getPlanNo, planNo)); + .eq(InboundPlan::getCustomerId, customerId).eq(InboundPlan::getPlanNo, planNo)); if (plan == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inbound plan not found"); } - return detail(tenantId, plan); + return detail(customerId, plan); } private Map createLines(InboundPlan plan, List orders) { @@ -195,8 +195,8 @@ public class InboundPlanService { } } - private InboundPlanResponse detail(String tenantId, InboundPlan plan) { - if (!tenantId.equals(plan.getTenantId())) { + private InboundPlanResponse detail(String customerId, InboundPlan plan) { + if (!customerId.equals(plan.getCustomerId())) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inbound plan not found"); } List lines = lineMapper.selectList(new LambdaQueryWrapper() @@ -207,7 +207,7 @@ public class InboundPlanService { new LambdaQueryWrapper() .eq(InboundPlanPalletAllocation::getPlanId, plan.getId()) .orderByAsc(InboundPlanPalletAllocation::getId)); - return new InboundPlanResponse(plan.getId(), plan.getPlanNo(), plan.getTenantId(), plan.getContainerNo(), + return new InboundPlanResponse(plan.getId(), plan.getPlanNo(), plan.getCustomerId(), plan.getContainerNo(), plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(), lines.stream().map(line -> new InboundPlanResponse.Line(line.getId(), line.getOrderId(), line.getInboundCode(), line.getFbaWarehouseCode(), line.getDestinationWarehouseId(), line.getDestinationWarehouseCode(), line.getDestinationWarehouseName(), diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/controller/InventoryCommandController.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/controller/InventoryCommandController.java index d9b16e1..d064060 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/controller/InventoryCommandController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/controller/InventoryCommandController.java @@ -27,66 +27,66 @@ public class InventoryCommandController { @Operation(summary = "Put inbound-order packages into an overseas warehouse pallet") public ApiResponse stockIn(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.StockIn request) { - return ApiResponse.success(service.stockIn(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.stockIn(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/hold") @Operation(summary = "Move available inbound-order packages to hold") public ApiResponse hold(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.hold(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.hold(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/release") @Operation(summary = "Release held inbound-order packages") public ApiResponse release(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.release(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.release(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/allocate") @Operation(summary = "Allocate available inbound-order packages to an outbound order") public ApiResponse allocate(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.allocate(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.allocate(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/unallocate") @Operation(summary = "Cancel an outbound allocation") public ApiResponse unallocate(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.unallocate(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.unallocate(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/pick") @Operation(summary = "Confirm picked inbound-order packages") public ApiResponse pick(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.pick(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.pick(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/ship") @Operation(summary = "Ship picked inbound-order packages and report whether the pallet is empty") public ApiResponse ship(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Move request) { - return ApiResponse.success(service.ship(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.ship(scope(user, request.customerId()), operator(user), request)); } @PostMapping("/adjust") @Operation(summary = "Adjust inbound-order package inventory after a physical count") public ApiResponse adjust(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody InventoryCommandRequests.Adjustment request) { - return ApiResponse.success(service.adjust(scope(user, request.tenantId()), operator(user), request)); + return ApiResponse.success(service.adjust(scope(user, request.customerId()), operator(user), request)); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); } - return requestedTenant.trim(); + return requestedCustomer.trim(); } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java index 629a7b6..46c9e6c 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/dto/InventoryCommandRequests.java @@ -10,7 +10,7 @@ public final class InventoryCommandRequests { } public record StockIn( - String tenantId, + String customerId, String customerName, @NotBlank String overseasWarehouseId, String overseasWarehouseCode, @@ -35,7 +35,7 @@ public final class InventoryCommandRequests { } public record Move( - String tenantId, + String customerId, @NotNull Long positionId, @Positive long packages, String businessOrderNo, @@ -47,7 +47,7 @@ public final class InventoryCommandRequests { } public record Adjustment( - String tenantId, + String customerId, @NotNull Long positionId, long packageDelta, @NotBlank String reason, diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java index 83586e2..97b33ae 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/mapper/InventoryCommandMapper.java @@ -8,15 +8,15 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface InventoryCommandMapper { - @Select("SELECT * FROM ll_inventory_position WHERE tenant_id=#{tenantId} AND id=#{positionId} LIMIT 1 FOR UPDATE") - InventoryPosition lockPosition(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_inventory_position WHERE customer_id=#{customerId} AND id=#{positionId} LIMIT 1 FOR UPDATE") + InventoryPosition lockPosition(@Param("customerId") String customerId, @Param("positionId") Long positionId); - @Select("SELECT * FROM ll_inventory_balance WHERE tenant_id=#{tenantId} AND id=#{balanceId} LIMIT 1 FOR UPDATE") - InventoryBalance lockBalance(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} AND id=#{balanceId} LIMIT 1 FOR UPDATE") + InventoryBalance lockBalance(@Param("customerId") String customerId, @Param("balanceId") Long balanceId); @Select("SELECT COALESCE(SUM(available_packages+hold_packages+allocated_packages+picked_packages+receiving_packages),0) " + - "FROM ll_inventory_position WHERE tenant_id=#{tenantId} AND pallet_id=#{palletId}") - long palletPackages(@Param("tenantId") String tenantId, @Param("palletId") Long palletId); + "FROM ll_inventory_position WHERE customer_id=#{customerId} AND pallet_id=#{palletId}") + long palletPackages(@Param("customerId") String customerId, @Param("palletId") Long palletId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java index 7ab02c6..5f61d71 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/command/service/InventoryCommandService.java @@ -39,11 +39,11 @@ public class InventoryCommandService { @Resource private ObjectMapper objectMapper; @Transactional - public InventoryCommandResponse stockIn(String tenantId, String operatorId, + public InventoryCommandResponse stockIn(String customerId, String operatorId, InventoryCommandRequests.StockIn request) { - return execute(tenantId, operatorId, "STOCK_IN", request.idempotencyKey(), request, operation -> { - InventoryBalance balance = balance(tenantId, request); - InventoryPosition position = position(tenantId, balance, request); + return execute(customerId, operatorId, "STOCK_IN", request.idempotencyKey(), request, operation -> { + InventoryBalance balance = balance(customerId, request); + InventoryPosition position = position(customerId, balance, request); long before = value(position.getAvailablePackages()); position.setAvailablePackages(before + request.packages()); position.setStatus("OCCUPIED"); @@ -54,7 +54,7 @@ public class InventoryCommandService { balance.setAvailablePackages(value(balance.getAvailablePackages()) + request.packages()); touch(balance); balances.updateById(balance); - ledger(tenantId, operatorId, operation.getId(), balance, position, "STOCK_IN", + ledger(customerId, operatorId, operation.getId(), balance, position, "STOCK_IN", "OUTSIDE", "AVAILABLE", before, request.packages(), before + request.packages(), request.businessOrderNo(), request.inboundOrderNo(), null, request.sourceEventId(), request.idempotencyKey(), request.remark()); @@ -63,34 +63,34 @@ public class InventoryCommandService { } @Transactional - public InventoryCommandResponse hold(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return move(tenantId, operatorId, "HOLD", Bucket.AVAILABLE, Bucket.HOLD, request); + public InventoryCommandResponse hold(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return move(customerId, operatorId, "HOLD", Bucket.AVAILABLE, Bucket.HOLD, request); } @Transactional - public InventoryCommandResponse release(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return move(tenantId, operatorId, "RELEASE", Bucket.HOLD, Bucket.AVAILABLE, request); + public InventoryCommandResponse release(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return move(customerId, operatorId, "RELEASE", Bucket.HOLD, Bucket.AVAILABLE, request); } @Transactional - public InventoryCommandResponse allocate(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return move(tenantId, operatorId, "ALLOCATE", Bucket.AVAILABLE, Bucket.ALLOCATED, request); + public InventoryCommandResponse allocate(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return move(customerId, operatorId, "ALLOCATE", Bucket.AVAILABLE, Bucket.ALLOCATED, request); } @Transactional - public InventoryCommandResponse unallocate(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return move(tenantId, operatorId, "UNALLOCATE", Bucket.ALLOCATED, Bucket.AVAILABLE, request); + public InventoryCommandResponse unallocate(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return move(customerId, operatorId, "UNALLOCATE", Bucket.ALLOCATED, Bucket.AVAILABLE, request); } @Transactional - public InventoryCommandResponse pick(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return move(tenantId, operatorId, "PICK", Bucket.ALLOCATED, Bucket.PICKED, request); + public InventoryCommandResponse pick(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return move(customerId, operatorId, "PICK", Bucket.ALLOCATED, Bucket.PICKED, request); } @Transactional - public InventoryCommandResponse ship(String tenantId, String operatorId, InventoryCommandRequests.Move request) { - return execute(tenantId, operatorId, "SHIP", request.idempotencyKey(), request, operation -> { - Locked locked = locked(tenantId, request.positionId()); + public InventoryCommandResponse ship(String customerId, String operatorId, InventoryCommandRequests.Move request) { + return execute(customerId, operatorId, "SHIP", request.idempotencyKey(), request, operation -> { + Locked locked = locked(customerId, request.positionId()); InventoryPosition position = locked.position(); InventoryBalance balance = locked.balance(); requireAtLeast(value(position.getPickedPackages()), request.packages(), "picked"); @@ -105,9 +105,9 @@ public class InventoryCommandService { balance.setPickedPackages(value(balance.getPickedPackages()) - request.packages()); touch(balance); balances.updateById(balance); - boolean palletEmpty = commands.palletPackages(tenantId, position.getPalletId()) == 0; - if (palletEmpty) warehouseGateway.scrapPallet(tenantId, position.getPalletId()); - ledger(tenantId, operatorId, operation.getId(), balance, position, "SHIP", + boolean palletEmpty = commands.palletPackages(customerId, position.getPalletId()) == 0; + if (palletEmpty) warehouseGateway.scrapPallet(customerId, position.getPalletId()); + ledger(customerId, operatorId, operation.getId(), balance, position, "SHIP", "PICKED", "SHIPPED", before, -request.packages(), before - request.packages(), request.businessOrderNo(), null, request.outboundOrderNo(), request.sourceEventId(), request.idempotencyKey(), request.remark()); @@ -116,13 +116,13 @@ public class InventoryCommandService { } @Transactional - public InventoryCommandResponse adjust(String tenantId, String operatorId, + public InventoryCommandResponse adjust(String customerId, String operatorId, InventoryCommandRequests.Adjustment request) { if (request.packageDelta() == 0) { throw badRequest("Adjustment package delta cannot be zero"); } - return execute(tenantId, operatorId, "ADJUST", request.idempotencyKey(), request, operation -> { - Locked locked = locked(tenantId, request.positionId()); + return execute(customerId, operatorId, "ADJUST", request.idempotencyKey(), request, operation -> { + Locked locked = locked(customerId, request.positionId()); InventoryPosition position = locked.position(); InventoryBalance balance = locked.balance(); long before = value(position.getAvailablePackages()); @@ -139,7 +139,7 @@ public class InventoryCommandService { balance.setAvailablePackages(balanceAfter); touch(balance); balances.updateById(balance); - ledger(tenantId, operatorId, operation.getId(), balance, position, "ADJUST", + ledger(customerId, operatorId, operation.getId(), balance, position, "ADJUST", "AVAILABLE", "AVAILABLE", before, request.packageDelta(), after, request.businessOrderNo(), null, null, null, request.idempotencyKey(), request.reason() + remarkSuffix(request.remark())); @@ -147,10 +147,10 @@ public class InventoryCommandService { }); } - private InventoryCommandResponse move(String tenantId, String operatorId, String operationType, + private InventoryCommandResponse move(String customerId, String operatorId, String operationType, Bucket from, Bucket to, InventoryCommandRequests.Move request) { - return execute(tenantId, operatorId, operationType, request.idempotencyKey(), request, operation -> { - Locked locked = locked(tenantId, request.positionId()); + return execute(customerId, operatorId, operationType, request.idempotencyKey(), request, operation -> { + Locked locked = locked(customerId, request.positionId()); InventoryPosition position = locked.position(); InventoryBalance balance = locked.balance(); requireAtLeast(packages(position, from), request.packages(), from.name().toLowerCase()); @@ -166,7 +166,7 @@ public class InventoryCommandService { packages(balance, to, packages(balance, to) + request.packages()); touch(balance); balances.updateById(balance); - ledger(tenantId, operatorId, operation.getId(), balance, position, operationType, + ledger(customerId, operatorId, operation.getId(), balance, position, operationType, from.name(), to.name(), beforeTarget, request.packages(), beforeTarget + request.packages(), request.businessOrderNo(), null, request.outboundOrderNo(), request.sourceEventId(), request.idempotencyKey(), request.remark()); @@ -174,13 +174,13 @@ public class InventoryCommandService { }); } - private InventoryBalance balance(String tenantId, InventoryCommandRequests.StockIn request) { - InventoryBalance balance = balances.lockDimension(tenantId, request.overseasWarehouseId(), + private InventoryBalance balance(String customerId, InventoryCommandRequests.StockIn request) { + InventoryBalance balance = balances.lockDimension(customerId, request.overseasWarehouseId(), request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode()); if (balance != null) return balance; balance = new InventoryBalance(); balance.setId(IdWorker.getId()); - balance.setTenantId(tenantId); + balance.setCustomerId(customerId); balance.setCustomerName(trim(request.customerName())); balance.setOverseasWarehouseId(request.overseasWarehouseId().trim()); balance.setOverseasWarehouseCode(trim(request.overseasWarehouseCode())); @@ -199,21 +199,21 @@ public class InventoryCommandService { balances.insert(balance); return balance; } catch (DuplicateKeyException exception) { - InventoryBalance concurrent = balances.lockDimension(tenantId, request.overseasWarehouseId(), + InventoryBalance concurrent = balances.lockDimension(customerId, request.overseasWarehouseId(), request.destinationWarehouseCode(), request.inboundOrderId(), request.inboundCode()); if (concurrent != null) return concurrent; throw exception; } } - private InventoryPosition position(String tenantId, InventoryBalance balance, + private InventoryPosition position(String customerId, InventoryBalance balance, InventoryCommandRequests.StockIn request) { String batchNo = trim(request.batchNo()) == null ? "DEFAULT" : request.batchNo().trim(); - InventoryPosition position = positions.lockPosition(tenantId, balance.getId(), request.palletId(), batchNo); + InventoryPosition position = positions.lockPosition(customerId, balance.getId(), request.palletId(), batchNo); if (position != null) return position; position = new InventoryPosition(); position.setId(IdWorker.getId()); - position.setTenantId(tenantId); + position.setCustomerId(customerId); position.setBalanceId(balance.getId()); position.setOverseasWarehouseId(balance.getOverseasWarehouseId()); position.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); @@ -237,30 +237,30 @@ public class InventoryCommandService { positions.insert(position); return position; } catch (DuplicateKeyException exception) { - InventoryPosition concurrent = positions.lockPosition(tenantId, balance.getId(), request.palletId(), batchNo); + InventoryPosition concurrent = positions.lockPosition(customerId, balance.getId(), request.palletId(), batchNo); if (concurrent != null) return concurrent; throw exception; } } - private Locked locked(String tenantId, Long positionId) { - InventoryPosition position = commands.lockPosition(tenantId, positionId); + private Locked locked(String customerId, Long positionId) { + InventoryPosition position = commands.lockPosition(customerId, positionId); if (position == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inventory position not found"); - InventoryBalance balance = commands.lockBalance(tenantId, position.getBalanceId()); + InventoryBalance balance = commands.lockBalance(customerId, position.getBalanceId()); if (balance == null) throw new IllegalStateException("Inventory position balance is missing"); return new Locked(balance, position); } - private InventoryCommandResponse execute(String tenantId, String operatorId, String operationType, + private InventoryCommandResponse execute(String customerId, String operatorId, String operationType, String key, Object request, Work work) { String normalizedKey = required(key, "Idempotency key is required"); String requestHash = hash(request); - InventoryOperation existing = operations.findIdempotency(tenantId, normalizedKey); + InventoryOperation existing = operations.findIdempotency(customerId, normalizedKey); if (existing != null) return replay(existing, operationType, requestHash); InventoryOperation operation = new InventoryOperation(); operation.setId(IdWorker.getId()); - operation.setTenantId(tenantId); + operation.setCustomerId(customerId); operation.setIdempotencyKey(normalizedKey); operation.setOperationType(operationType); operation.setRequestHash(requestHash); @@ -270,7 +270,7 @@ public class InventoryCommandService { try { operations.insert(operation); } catch (DuplicateKeyException exception) { - InventoryOperation concurrent = operations.findIdempotency(tenantId, normalizedKey); + InventoryOperation concurrent = operations.findIdempotency(customerId, normalizedKey); if (concurrent != null) return replay(concurrent, operationType, requestHash); throw exception; } @@ -297,14 +297,14 @@ public class InventoryCommandService { } } - private void ledger(String tenantId, String operatorId, Long operationId, InventoryBalance balance, + private void ledger(String customerId, String operatorId, Long operationId, InventoryBalance balance, InventoryPosition position, String businessType, String from, String to, long beforePackages, long deltaPackages, long afterPackages, String businessOrderNo, String inboundOrderNo, String outboundOrderNo, String sourceEventId, String idempotencyKey, String remark) { InventoryLedger ledger = new InventoryLedger(); ledger.setId(IdWorker.getId()); - ledger.setTenantId(tenantId); + ledger.setCustomerId(customerId); ledger.setBalanceId(balance.getId()); ledger.setPositionId(position.getId()); ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId()); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryBalance.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryBalance.java index 5d1d794..c7e62a2 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryBalance.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryBalance.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InventoryBalance { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private String customerName; private String overseasWarehouseId; private String overseasWarehouseCode; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryLedger.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryLedger.java index 86af2a5..b828686 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryLedger.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryLedger.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InventoryLedger { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private Long balanceId; private Long positionId; private String overseasWarehouseId; @@ -22,6 +22,9 @@ public class InventoryLedger { private Long palletId; private String palletNo; private String batchNo; + private String skuCode; + private String cartonMark; + private Long cartonPackages; private String warehouseLocationCode; private String businessType; private Long businessOrderId; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryOperation.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryOperation.java index 66e2f7a..4331768 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryOperation.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryOperation.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InventoryOperation { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private String idempotencyKey; private String operationType; private String requestHash; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryPosition.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryPosition.java index dd8408d..3e5b276 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryPosition.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/entity/InventoryPosition.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; public class InventoryPosition { @TableId(type = IdType.ASSIGN_ID) private Long id; - private String tenantId; + private String customerId; private Long balanceId; private String overseasWarehouseId; private String destinationWarehouseCode; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryBalanceMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryBalanceMapper.java index b37e2cb..437a9dc 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryBalanceMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryBalanceMapper.java @@ -9,32 +9,32 @@ import org.apache.ibatis.annotations.Select; import java.util.List; public interface InventoryBalanceMapper extends BaseMapper { - @Select("SELECT * FROM ll_inventory_balance WHERE tenant_id=#{tenantId} " + + @Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} " + "AND overseas_warehouse_id=#{overseasWarehouseId} " + "AND destination_warehouse_code=#{destinationWarehouseCode} " + "AND inbound_order_id=#{inboundOrderId} AND inbound_code=#{inboundCode} LIMIT 1") - InventoryBalance findDimension(@Param("tenantId") String tenantId, + InventoryBalance findDimension(@Param("customerId") String customerId, @Param("overseasWarehouseId") String overseasWarehouseId, @Param("destinationWarehouseCode") String destinationWarehouseCode, @Param("inboundOrderId") Long inboundOrderId, @Param("inboundCode") String inboundCode); - @Select("SELECT * FROM ll_inventory_balance WHERE tenant_id=#{tenantId} " + + @Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} " + "AND overseas_warehouse_id=#{overseasWarehouseId} " + "AND destination_warehouse_code=#{destinationWarehouseCode} " + "AND inbound_order_id=#{inboundOrderId} AND inbound_code=#{inboundCode} LIMIT 1 FOR UPDATE") - InventoryBalance lockDimension(@Param("tenantId") String tenantId, + InventoryBalance lockDimension(@Param("customerId") String customerId, @Param("overseasWarehouseId") String overseasWarehouseId, @Param("destinationWarehouseCode") String destinationWarehouseCode, @Param("inboundOrderId") Long inboundOrderId, @Param("inboundCode") String inboundCode); - @Select("") - List listTenant(@Param("tenantId") String tenantId, + List listTenant(@Param("customerId") String customerId, @Param("overseasWarehouseId") String overseasWarehouseId, @Param("destinationWarehouseCode") String destinationWarehouseCode, @Param("inboundCode") String inboundCode, @@ -45,8 +45,8 @@ public interface InventoryBalanceMapper extends BaseMapper { "COALESCE(SUM(available_packages),0) available_packages,COALESCE(SUM(hold_packages),0) hold_packages," + "COALESCE(SUM(allocated_packages),0) allocated_packages,COALESCE(SUM(picked_packages),0) picked_packages," + "COALESCE(SUM(in_transit_packages),0) in_transit_packages,COALESCE(SUM(receiving_packages),0) receiving_packages," + - "COALESCE(SUM(procurement_packages),0) procurement_packages FROM ll_inventory_balance WHERE tenant_id=#{tenantId}" + + "COALESCE(SUM(procurement_packages),0) procurement_packages FROM ll_inventory_balance WHERE customer_id=#{customerId}" + " AND overseas_warehouse_id=#{overseasWarehouseId}") - InventorySummary summarize(@Param("tenantId") String tenantId, + InventorySummary summarize(@Param("customerId") String customerId, @Param("overseasWarehouseId") String overseasWarehouseId); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryLedgerMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryLedgerMapper.java index 0720cbd..f95b777 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryLedgerMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryLedgerMapper.java @@ -9,11 +9,11 @@ import java.time.LocalDateTime; import java.util.List; public interface InventoryLedgerMapper extends BaseMapper { - @Select("SELECT * FROM ll_inventory_ledger WHERE tenant_id=#{tenantId} AND idempotency_key=#{idempotencyKey} LIMIT 1") - InventoryLedger findIdempotency(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_inventory_ledger WHERE customer_id=#{customerId} AND idempotency_key=#{idempotencyKey} LIMIT 1") + InventoryLedger findIdempotency(@Param("customerId") String customerId, @Param("idempotencyKey") String idempotencyKey); - @Select("") - List listTenant(@Param("tenantId") String tenantId, + List listTenant(@Param("customerId") String customerId, @Param("overseasWarehouseId") String overseasWarehouseId, @Param("inboundCode") String inboundCode, @Param("businessType") String businessType, diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryOperationMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryOperationMapper.java index 718d3b9..3b6a162 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryOperationMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryOperationMapper.java @@ -6,11 +6,11 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface InventoryOperationMapper extends BaseMapper { - @Select("SELECT * FROM ll_inventory_operation WHERE tenant_id=#{tenantId} AND idempotency_key=#{idempotencyKey} LIMIT 1") - InventoryOperation findIdempotency(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_inventory_operation WHERE customer_id=#{customerId} AND idempotency_key=#{idempotencyKey} LIMIT 1") + InventoryOperation findIdempotency(@Param("customerId") String customerId, @Param("idempotencyKey") String idempotencyKey); - @Select("SELECT * FROM ll_inventory_operation WHERE tenant_id=#{tenantId} AND idempotency_key=#{idempotencyKey} LIMIT 1 FOR UPDATE") - InventoryOperation lockIdempotency(@Param("tenantId") String tenantId, + @Select("SELECT * FROM ll_inventory_operation WHERE customer_id=#{customerId} AND idempotency_key=#{idempotencyKey} LIMIT 1 FOR UPDATE") + InventoryOperation lockIdempotency(@Param("customerId") String customerId, @Param("idempotencyKey") String idempotencyKey); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryPositionMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryPositionMapper.java index b94ceb0..59b4386 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryPositionMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/mapper/InventoryPositionMapper.java @@ -9,18 +9,18 @@ import java.util.List; public interface InventoryPositionMapper extends BaseMapper { @Select("SELECT p.* FROM ll_inventory_position p JOIN ll_inventory_balance b ON b.id=p.balance_id " + - "WHERE b.tenant_id=#{tenantId} AND p.balance_id=#{balanceId} ORDER BY p.pallet_no,p.batch_no") - List listBalance(@Param("tenantId") String tenantId, + "WHERE b.customer_id=#{customerId} AND p.balance_id=#{balanceId} ORDER BY p.pallet_no,p.batch_no") + List listBalance(@Param("customerId") String customerId, @Param("balanceId") Long balanceId); - @Select("SELECT * FROM ll_inventory_position WHERE tenant_id=#{tenantId} AND pallet_id=#{palletId} " + + @Select("SELECT * FROM ll_inventory_position WHERE customer_id=#{customerId} AND pallet_id=#{palletId} " + "ORDER BY balance_id,batch_no") - List listPallet(@Param("tenantId") String tenantId, + List listPallet(@Param("customerId") String customerId, @Param("palletId") Long palletId); - @Select("SELECT * FROM ll_inventory_position WHERE tenant_id=#{tenantId} AND balance_id=#{balanceId} " + + @Select("SELECT * FROM ll_inventory_position WHERE customer_id=#{customerId} AND balance_id=#{balanceId} " + "AND pallet_id=#{palletId} AND batch_no=#{batchNo} LIMIT 1 FOR UPDATE") - InventoryPosition lockPosition(@Param("tenantId") String tenantId, + InventoryPosition lockPosition(@Param("customerId") String customerId, @Param("balanceId") Long balanceId, @Param("palletId") Long palletId, @Param("batchNo") String batchNo); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryController.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryController.java index c7496b5..f9b9f75 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryController.java @@ -38,55 +38,55 @@ public class InventoryQueryController { @Operation(summary = "Get inbound-order package inventory totals") public ApiResponse summary( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid InventoryQueryRequests.BalanceQuery query) { - return ApiResponse.success(service.dashboard(scope(user, tenantId), query)); + return ApiResponse.success(service.dashboard(scope(user, customerId), query)); } @GetMapping(BASE_PATH + "/balances") @Operation(summary = "Page inventory by customer, warehouse and inbound order") public ApiResponse> balances( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid InventoryQueryRequests.BalanceQuery query) { - return ApiResponse.success(service.balances(scope(user, tenantId), query)); + return ApiResponse.success(service.balances(scope(user, customerId), query)); } @GetMapping(BASE_PATH + "/balances/{balanceId}/positions") @Operation(summary = "List pallet and batch positions for one inbound-order balance") public ApiResponse> positions( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @PathVariable long balanceId, @Valid InventoryQueryRequests.PositionQuery query) { - return ApiResponse.success(service.positions(scope(user, tenantId), balanceId, query)); + return ApiResponse.success(service.positions(scope(user, customerId), balanceId, query)); } @GetMapping(BASE_PATH + "/ledger") @Operation(summary = "Page append-only inventory movements") public ApiResponse> ledgers( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid InventoryQueryRequests.LedgerQuery query) { - return ApiResponse.success(service.ledgers(scope(user, tenantId), query)); + return ApiResponse.success(service.ledgers(scope(user, customerId), query)); } @GetMapping(value = BASE_PATH + "/balances/export", produces = "text/csv") @Operation(summary = "Export filtered inbound-order inventory as UTF-8 CSV") public ResponseEntity exportBalances( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid InventoryQueryRequests.BalanceQuery query) { - return csv(service.exportBalances(scope(user, tenantId), query), "inventory-balances.csv"); + return csv(service.exportBalances(scope(user, customerId), query), "inventory-balances.csv"); } @GetMapping(value = BASE_PATH + "/ledger/export", produces = "text/csv") @Operation(summary = "Export filtered inventory movements as UTF-8 CSV") public ResponseEntity exportLedgers( @AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String customerId, @Valid InventoryQueryRequests.LedgerQuery query) { - return csv(service.exportLedgers(scope(user, tenantId), query), "inventory-ledger.csv"); + return csv(service.exportLedgers(scope(user, customerId), query), "inventory-ledger.csv"); } private static ResponseEntity csv(byte[] body, String filename) { @@ -96,16 +96,16 @@ public class InventoryQueryController { .body(body); } - private String scope(AuthenticatedUser user, String requestedTenant) { + private String scope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { - if (requestedTenant == null || requestedTenant.isBlank() || "0".equals(requestedTenant)) { + if (requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Platform queries must specify a customer tenant"); + "Platform queries must specify a customer"); } - return requestedTenant; + return requestedCustomer; } return String.valueOf(user.tenantId()); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/mapper/InventoryQueryMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/mapper/InventoryQueryMapper.java index bc06cd4..ab7344b 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/mapper/InventoryQueryMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/mapper/InventoryQueryMapper.java @@ -11,14 +11,14 @@ import org.apache.ibatis.annotations.Select; import java.util.List; public interface InventoryQueryMapper { - String BALANCE_FILTER = " WHERE tenant_id=#{tenantId}" + + String BALANCE_FILTER = " WHERE customer_id=#{customerId}" + " AND customer_name LIKE CONCAT('%',#{q.customerName},'%')" + " AND overseas_warehouse_id=#{q.overseasWarehouseId}" + " AND overseas_warehouse_code=#{q.overseasWarehouseCode}" + " AND destination_warehouse_code=#{q.destinationWarehouseCode}" + " AND inbound_order_no LIKE CONCAT('%',#{q.inboundOrderNo},'%')" + " AND inbound_code LIKE CONCAT('%',#{q.inboundCode},'%')" + - " AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=ll_inventory_balance.id AND l.tenant_id=#{tenantId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))" + + " AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=ll_inventory_balance.id AND l.customer_id=#{customerId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))" + "" + " AND available_packages > 0" + " AND hold_packages > 0" + @@ -29,14 +29,14 @@ public interface InventoryQueryMapper { " AND procurement_packages > 0" + ""; - String JOINED_BALANCE_FILTER = " WHERE b.tenant_id=#{tenantId}" + + String JOINED_BALANCE_FILTER = " WHERE b.customer_id=#{customerId}" + " AND b.customer_name LIKE CONCAT('%',#{q.customerName},'%')" + " AND b.overseas_warehouse_id=#{q.overseasWarehouseId}" + " AND b.overseas_warehouse_code=#{q.overseasWarehouseCode}" + " AND b.destination_warehouse_code=#{q.destinationWarehouseCode}" + " AND b.inbound_order_no LIKE CONCAT('%',#{q.inboundOrderNo},'%')" + " AND b.inbound_code LIKE CONCAT('%',#{q.inboundCode},'%')" + - " AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=b.id AND l.tenant_id=#{tenantId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))" + + " AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=b.id AND l.customer_id=#{customerId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))" + "" + " AND b.available_packages > 0" + " AND b.hold_packages > 0" + @@ -49,13 +49,13 @@ public interface InventoryQueryMapper { @Select("") - List balances(@Param("tenantId") String tenantId, + List balances(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.BalanceQuery query, @Param("offset") long offset, @Param("limit") long limit); @Select("") - long countBalances(@Param("tenantId") String tenantId, + long countBalances(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.BalanceQuery query); @Select("") - InventoryAggregateProjection aggregate(@Param("tenantId") String tenantId, + InventoryAggregateProjection aggregate(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.BalanceQuery query); @Select("") - long countPallets(@Param("tenantId") String tenantId, + long countPallets(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.BalanceQuery query); - @Select("") - List positions(@Param("tenantId") String tenantId, + List positions(@Param("customerId") String customerId, @Param("balanceId") long balanceId, @Param("q") InventoryQueryRequests.PositionQuery query); - @Select("SELECT * FROM ll_inventory_balance WHERE tenant_id=#{tenantId} AND id=#{balanceId} LIMIT 1") - InventoryBalance ownedBalance(@Param("tenantId") String tenantId, @Param("balanceId") long balanceId); + @Select("SELECT * FROM ll_inventory_balance WHERE customer_id=#{customerId} AND id=#{balanceId} LIMIT 1") + InventoryBalance ownedBalance(@Param("customerId") String customerId, @Param("balanceId") long balanceId); - String LEDGER_FILTER = " WHERE tenant_id=#{tenantId}" + + String LEDGER_FILTER = " WHERE customer_id=#{customerId}" + " AND overseas_warehouse_id=#{q.overseasWarehouseId}" + " AND destination_warehouse_code=#{q.destinationWarehouseCode}" + " AND business_type=#{q.businessType}" + @@ -108,12 +108,12 @@ public interface InventoryQueryMapper { @Select("") - List ledgers(@Param("tenantId") String tenantId, + List ledgers(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.LedgerQuery query, @Param("offset") long offset, @Param("limit") long limit); @Select("") - long countLedgers(@Param("tenantId") String tenantId, + long countLedgers(@Param("customerId") String customerId, @Param("q") InventoryQueryRequests.LedgerQuery query); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java index 426904d..7b1b680 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryService.java @@ -31,9 +31,9 @@ public class InventoryQueryService { @Resource private InventoryQueryMapper mapper; - public InventoryDashboardResponse dashboard(String tenantId, InventoryQueryRequests.BalanceQuery query) { + public InventoryDashboardResponse dashboard(String customerId, InventoryQueryRequests.BalanceQuery query) { validateStatus(query.inventoryStatus(), BALANCE_STATUSES); - InventoryAggregateProjection value = mapper.aggregate(tenantId, query); + InventoryAggregateProjection value = mapper.aggregate(customerId, query); long availablePackages = number(value.getAvailablePackages()); long holdPackages = number(value.getHoldPackages()); long allocatedPackages = number(value.getAllocatedPackages()); @@ -41,46 +41,46 @@ public class InventoryQueryService { long inTransitPackages = number(value.getInTransitPackages()); long receivingPackages = number(value.getReceivingPackages()); long procurementPackages = number(value.getProcurementPackages()); - return new InventoryDashboardResponse(number(value.getDimensionCount()), mapper.countPallets(tenantId, query), + return new InventoryDashboardResponse(number(value.getDimensionCount()), mapper.countPallets(customerId, query), availablePackages + holdPackages + allocatedPackages + pickedPackages + inTransitPackages + receivingPackages + procurementPackages, availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages, receivingPackages, procurementPackages); } - public PageResult balances(String tenantId, InventoryQueryRequests.BalanceQuery query) { + public PageResult balances(String customerId, InventoryQueryRequests.BalanceQuery query) { validateStatus(query.inventoryStatus(), BALANCE_STATUSES); - long total = mapper.countBalances(tenantId, query); + long total = mapper.countBalances(customerId, query); List records = total == 0 ? List.of() : mapper.balances( - tenantId, query, offset(query.page(), query.size()), query.size()).stream().map(this::balanceView).toList(); + customerId, query, offset(query.page(), query.size()), query.size()).stream().map(this::balanceView).toList(); return page(records, query.page(), query.size(), total); } - public List positions(String tenantId, long balanceId, + public List positions(String customerId, long balanceId, InventoryQueryRequests.PositionQuery query) { validateStatus(query.inventoryStatus(), POSITION_STATUSES); - InventoryBalance balance = mapper.ownedBalance(tenantId, balanceId); + InventoryBalance balance = mapper.ownedBalance(customerId, balanceId); if (balance == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inventory balance not found"); } - return mapper.positions(tenantId, balanceId, query).stream().map(value -> positionView(value, balance)).toList(); + return mapper.positions(customerId, balanceId, query).stream().map(value -> positionView(value, balance)).toList(); } - public PageResult ledgers(String tenantId, InventoryQueryRequests.LedgerQuery query) { + public PageResult ledgers(String customerId, InventoryQueryRequests.LedgerQuery query) { validateDateRange(query); - long total = mapper.countLedgers(tenantId, query); + long total = mapper.countLedgers(customerId, query); List records = total == 0 ? List.of() : mapper.ledgers( - tenantId, query, offset(query.page(), query.size()), query.size()).stream().map(this::ledgerView).toList(); + customerId, query, offset(query.page(), query.size()), query.size()).stream().map(this::ledgerView).toList(); return page(records, query.page(), query.size(), total); } - public byte[] exportBalances(String tenantId, InventoryQueryRequests.BalanceQuery query) { + public byte[] exportBalances(String customerId, InventoryQueryRequests.BalanceQuery query) { validateStatus(query.inventoryStatus(), BALANCE_STATUSES); List rows = new ArrayList<>(); rows.add("customerId,customerName,overseasWarehouseCode,overseasWarehouseName,destinationWarehouseCode," + "destinationWarehouseName,inboundOrderId,inboundOrderNo,inboundCode,totalPackages,availablePackages," + "holdPackages,allocatedPackages,pickedPackages,inTransitPackages,receivingPackages,procurementPackages"); - mapper.balances(tenantId, query, 0, EXPORT_LIMIT).stream().map(this::balanceView).forEach(v -> rows.add( + mapper.balances(customerId, query, 0, EXPORT_LIMIT).stream().map(this::balanceView).forEach(v -> rows.add( String.join(",", csv(v.customerId()), csv(v.customerName()), csv(v.overseasWarehouseCode()), csv(v.overseasWarehouseName()), csv(v.destinationWarehouseCode()), csv(v.destinationWarehouseName()), csv(v.inboundOrderId()), csv(v.inboundOrderNo()), csv(v.inboundCode()), @@ -90,13 +90,13 @@ public class InventoryQueryService { return ("\uFEFF" + String.join("\r\n", rows)).getBytes(StandardCharsets.UTF_8); } - public byte[] exportLedgers(String tenantId, InventoryQueryRequests.LedgerQuery query) { + public byte[] exportLedgers(String customerId, InventoryQueryRequests.LedgerQuery query) { validateDateRange(query); List rows = new ArrayList<>(); rows.add("occurredAt,customerId,overseasWarehouseId,destinationWarehouseCode,inboundOrderId,inboundOrderNo," + "inboundCode,businessType,businessOrderNo,outboundOrderNo,palletNo,batchNo,statusFrom,statusTo," + "beforePackages,deltaPackages,afterPackages,operatorId,remark"); - mapper.ledgers(tenantId, query, 0, EXPORT_LIMIT).stream().map(this::ledgerView).forEach(v -> rows.add( + mapper.ledgers(customerId, query, 0, EXPORT_LIMIT).stream().map(this::ledgerView).forEach(v -> rows.add( String.join(",", csv(v.occurredAt()), csv(v.customerId()), csv(v.overseasWarehouseId()), csv(v.destinationWarehouseCode()), csv(v.inboundOrderId()), csv(v.inboundOrderNo()), csv(v.inboundCode()), csv(v.businessType()), csv(v.businessOrderNo()), csv(v.outboundOrderNo()), csv(v.palletNo()), csv(v.batchNo()), @@ -113,7 +113,7 @@ public class InventoryQueryService { long inTransitPackages = number(value.getInTransitPackages()); long receivingPackages = number(value.getReceivingPackages()); long procurementPackages = number(value.getProcurementPackages()); - return new InventoryBalanceView(value.getId(), value.getTenantId(), value.getCustomerName(), value.getOverseasWarehouseId(), + return new InventoryBalanceView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(), value.getOverseasWarehouseCode(), value.getOverseasWarehouseName(), value.getDestinationWarehouseId(), value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(), value.getInboundOrderId(), value.getInboundOrderNo(), value.getInboundCode(), availablePackages + holdPackages + allocatedPackages + pickedPackages @@ -128,7 +128,7 @@ public class InventoryQueryService { long allocated = number(value.getAllocatedPackages()); long picked = number(value.getPickedPackages()); long receiving = number(value.getReceivingPackages()); - return new InventoryPositionView(value.getId(), value.getBalanceId(), value.getTenantId(), + return new InventoryPositionView(value.getId(), value.getBalanceId(), value.getCustomerId(), value.getOverseasWarehouseId(), value.getDestinationWarehouseCode(), balance.getInboundOrderId(), balance.getInboundOrderNo(), balance.getInboundCode(), value.getPalletId(), value.getPalletNo(), value.getBatchNo(), value.getWarehouseLocationId(), @@ -138,7 +138,7 @@ public class InventoryQueryService { } private InventoryLedgerView ledgerView(InventoryLedger value) { - return new InventoryLedgerView(value.getId(), value.getTenantId(), value.getOverseasWarehouseId(), + return new InventoryLedgerView(value.getId(), value.getCustomerId(), value.getOverseasWarehouseId(), value.getDestinationWarehouseCode(), value.getInboundOrderId(), value.getInboundOrderNo(), value.getInboundCode(), value.getPalletNo(), value.getBatchNo(), value.getWarehouseLocationCode(), value.getBusinessType(), value.getBusinessOrderNo(), diff --git a/lastleg/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java index f950f5f..638b2ac 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/mapper/LastlegOrderMapper.java @@ -9,30 +9,30 @@ import org.apache.ibatis.annotations.Update; @Mapper public interface LastlegOrderMapper extends BaseMapper { - @Select("SELECT * FROM ll_order WHERE tenant_id=#{tenantId} AND source_region=#{sourceRegion} " + + @Select("SELECT * FROM ll_order WHERE customer_id=#{customerId} AND source_region=#{sourceRegion} " + "AND source_inbound_id=#{sourceInboundId} LIMIT 1") - LastlegOrder findSource(@Param("tenantId") String tenantId, + LastlegOrder findSource(@Param("customerId") String customerId, @Param("sourceRegion") String sourceRegion, @Param("sourceInboundId") String sourceInboundId); - @Select("SELECT COUNT(*) FROM ll_order WHERE tenant_id=#{tenantId} AND container_no=#{containerNo}") - long countContainer(@Param("tenantId") String tenantId, @Param("containerNo") String containerNo); + @Select("SELECT COUNT(*) FROM ll_order WHERE customer_id=#{customerId} AND container_no=#{containerNo}") + long countContainer(@Param("customerId") String customerId, @Param("containerNo") String containerNo); - @Select("SELECT * FROM ll_order WHERE tenant_id=#{tenantId} AND id=#{id} FOR UPDATE") - LastlegOrder lockById(@Param("tenantId") String tenantId, @Param("id") Long id); + @Select("SELECT * FROM ll_order WHERE customer_id=#{customerId} AND id=#{id} FOR UPDATE") + LastlegOrder lockById(@Param("customerId") String customerId, @Param("id") Long id); @Update("UPDATE ll_order SET picked_package_count=picked_package_count+#{packages},updated_at=NOW() " + - "WHERE tenant_id=#{tenantId} AND id=#{id} " + + "WHERE customer_id=#{customerId} AND id=#{id} " + "AND package_count-shipped_package_count-picked_package_count >= #{packages}") - int increasePicked(@Param("tenantId") String tenantId, @Param("id") Long id, + int increasePicked(@Param("customerId") String customerId, @Param("id") Long id, @Param("packages") long packages); @Update("UPDATE ll_order SET picked_package_count=picked_package_count-#{packages}," + "shipped_package_count=shipped_package_count+#{packages},outbound_order_no=#{outboundNo}," + "current_status=CASE WHEN shipped_package_count+#{packages}=package_count " + "THEN 'COMPLETED' ELSE 'PARTIALLY_SHIPPED' END,updated_at=NOW() " + - "WHERE tenant_id=#{tenantId} AND id=#{id} AND picked_package_count >= #{packages} " + + "WHERE customer_id=#{customerId} AND id=#{id} AND picked_package_count >= #{packages} " + "AND shipped_package_count+#{packages} <= package_count") - int transferPickedToShipped(@Param("tenantId") String tenantId, @Param("id") Long id, + int transferPickedToShipped(@Param("customerId") String customerId, @Param("id") Long id, @Param("packages") long packages, @Param("outboundNo") String outboundNo); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/controller/RelabelApplicationController.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/controller/RelabelApplicationController.java index 4eb2700..349c89f 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/controller/RelabelApplicationController.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/controller/RelabelApplicationController.java @@ -20,24 +20,24 @@ public class RelabelApplicationController { @PostMapping public ApiResponse create(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, @Valid @RequestBody RelabelRequests.Create request) { - return ApiResponse.success(service.create(scope(user, tenantId), operator(user), request)); + @RequestParam(required = false) String customerId, @Valid @RequestBody RelabelRequests.Create request) { + return ApiResponse.success(service.create(scope(user, customerId), operator(user), request)); } @GetMapping public ApiResponse> page(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, @Valid RelabelRequests.Query query) { - return ApiResponse.success(service.page(scope(user, tenantId), query)); + @RequestParam(required = false) String customerId, @Valid RelabelRequests.Query query) { + return ApiResponse.success(service.page(scope(user, customerId), query)); } @GetMapping("/{relabelNo}") public ApiResponse detail(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, @PathVariable String relabelNo) { - return ApiResponse.success(service.detail(scope(user, tenantId), relabelNo)); + @RequestParam(required = false) String customerId, @PathVariable String relabelNo) { + return ApiResponse.success(service.detail(scope(user, customerId), relabelNo)); } @PutMapping("/{relabelNo}/status") public ApiResponse status(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam(required = false) String tenantId, @PathVariable String relabelNo, + @RequestParam(required = false) String customerId, @PathVariable String relabelNo, @Valid @RequestBody RelabelRequests.ChangeStatus request) { - return ApiResponse.success(service.changeStatus(scope(user, tenantId), relabelNo, operator(user), request)); + return ApiResponse.success(service.changeStatus(scope(user, customerId), relabelNo, operator(user), request)); } private String scope(AuthenticatedUser user, String requested) { if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelApplication.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelApplication.java index 3deb066..607c2f2 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelApplication.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelApplication.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @TableName("ll_relabel_application") public class RelabelApplication { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private String relabelNo; private Long lastlegOrderId; private String customerOrderNo; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java index 3f6ec61..5dde51a 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelItem.java @@ -12,7 +12,7 @@ import java.time.LocalDateTime; public class RelabelItem { @TableId(type = IdType.INPUT) private Long id; private Long applicationId; - private String tenantId; + private String customerId; private String skuCode; private String productImageFileId; private String productImageFileName; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelStatusHistory.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelStatusHistory.java index 89ee442..d68d2dd 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelStatusHistory.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/entity/RelabelStatusHistory.java @@ -11,7 +11,7 @@ import java.time.LocalDateTime; @TableName("ll_relabel_status_history") public class RelabelStatusHistory { @TableId(type = IdType.INPUT) private Long id; - private String tenantId; + private String customerId; private Long applicationId; private String fromStatus; private String toStatus; diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/mapper/RelabelApplicationMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/mapper/RelabelApplicationMapper.java index aa718dc..692276a 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/mapper/RelabelApplicationMapper.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/mapper/RelabelApplicationMapper.java @@ -8,12 +8,12 @@ import org.apache.ibatis.annotations.Select; @Mapper public interface RelabelApplicationMapper extends BaseMapper { - @Select("SELECT * FROM ll_relabel_application WHERE tenant_id=#{tenantId} AND relabel_no=#{relabelNo} LIMIT 1") - RelabelApplication findNo(@Param("tenantId") String tenantId, @Param("relabelNo") String relabelNo); + @Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} LIMIT 1") + RelabelApplication findNo(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo); - @Select("SELECT * FROM ll_relabel_application WHERE tenant_id=#{tenantId} AND idempotency_key=#{key} LIMIT 1") - RelabelApplication findKey(@Param("tenantId") String tenantId, @Param("key") String key); + @Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + RelabelApplication findKey(@Param("customerId") String customerId, @Param("key") String key); - @Select("SELECT * FROM ll_relabel_application WHERE tenant_id=#{tenantId} AND relabel_no=#{relabelNo} LIMIT 1 FOR UPDATE") - RelabelApplication lockNo(@Param("tenantId") String tenantId, @Param("relabelNo") String relabelNo); + @Select("SELECT * FROM ll_relabel_application WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} LIMIT 1 FOR UPDATE") + RelabelApplication lockNo(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java b/lastleg/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java index 0203c73..db47704 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java @@ -49,7 +49,7 @@ public class RelabelApplicationService { @Resource private ObjectMapper objectMapper; @Transactional - public RelabelResponses.Detail create(String tenantId, String operatorId, RelabelRequests.Create request) { + public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) { int total = 0; int notified = 0; for (RelabelRequests.Item item : request.items()) { @@ -60,10 +60,10 @@ public class RelabelApplicationService { notified = Math.addExact(notified, item.notifiedPackageCount()); } String requestHash = hash(request); - RelabelApplication replay = applications.findKey(tenantId, request.idempotencyKey().trim()); + RelabelApplication replay = applications.findKey(customerId, request.idempotencyKey().trim()); if (replay != null) { if (!requestHash.equals(replay.getRequestHash())) throw conflict("Idempotency key was reused with different relabel data"); - return detail(tenantId, replay.getRelabelNo()); + return detail(customerId, replay.getRelabelNo()); } LocalDateTime now = LocalDateTime.now(); long applicationId = IdWorker.getId(); @@ -71,7 +71,7 @@ public class RelabelApplicationService { LastlegOrder order = new LastlegOrder(); order.setId(IdWorker.getId()); order.setOrderNo(orderNoGenerator.next()); - order.setTenantId(tenantId); + order.setCustomerId(customerId); order.setOrderType("RELABEL_APPLICATION"); order.setInboundCode(relabelNo); order.setSourceRegion("CUSTOMER"); @@ -95,7 +95,7 @@ public class RelabelApplicationService { RelabelApplication application = new RelabelApplication(); application.setId(applicationId); - application.setTenantId(tenantId); + application.setCustomerId(customerId); application.setRelabelNo(relabelNo); application.setLastlegOrderId(order.getId()); application.setCustomerOrderNo(request.customerOrderNo().trim()); @@ -115,19 +115,19 @@ public class RelabelApplicationService { application.setUpdatedAt(now); applications.insert(application); - for (RelabelRequests.Item source : request.items()) insertItem(tenantId, applicationId, source, now); - insertHistory(tenantId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now); + for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now); + insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now); insertOrderHistory(order.getId(), null, PENDING_PROCESSING, "Relabel application submitted", now); - return detail(tenantId, relabelNo); + return detail(customerId, relabelNo); } @Transactional(readOnly = true) - public PageResult page(String tenantId, RelabelRequests.Query query) { + public PageResult page(String customerId, RelabelRequests.Query query) { if (query.createdFrom() != null && query.createdTo() != null && query.createdFrom().isAfter(query.createdTo())) { throw badRequest("createdFrom must not be after createdTo"); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(RelabelApplication::getTenantId, tenantId) + .eq(RelabelApplication::getCustomerId, customerId) .like(StringUtils.hasText(query.relabelNo()), RelabelApplication::getRelabelNo, query.relabelNo()) .like(StringUtils.hasText(query.customerOrderNo()), RelabelApplication::getCustomerOrderNo, query.customerOrderNo()) .eq(StringUtils.hasText(query.status()), RelabelApplication::getStatus, query.status()) @@ -135,7 +135,7 @@ public class RelabelApplicationService { .ge(query.createdFrom() != null, RelabelApplication::getCreatedAt, query.createdFrom()) .le(query.createdTo() != null, RelabelApplication::getCreatedAt, query.createdTo()) .apply(StringUtils.hasText(query.skuCode()), - "EXISTS (SELECT 1 FROM ll_relabel_item ri WHERE ri.application_id=ll_relabel_application.id AND ri.tenant_id=ll_relabel_application.tenant_id AND ri.sku_code LIKE CONCAT('%',{0},'%'))", + "EXISTS (SELECT 1 FROM ll_relabel_item ri WHERE ri.application_id=ll_relabel_application.id AND ri.customer_id=ll_relabel_application.customer_id AND ri.sku_code LIKE CONCAT('%',{0},'%'))", query.skuCode()) .orderByDesc(RelabelApplication::getCreatedAt); Page page = applications.selectPage(new Page<>(query.page(), query.size()), wrapper); @@ -144,11 +144,11 @@ public class RelabelApplicationService { } @Transactional(readOnly = true) - public RelabelResponses.Detail detail(String tenantId, String relabelNo) { - RelabelApplication application = require(tenantId, relabelNo); + public RelabelResponses.Detail detail(String customerId, String relabelNo) { + RelabelApplication application = require(customerId, relabelNo); List detailItems = itemList(application.getId()); List statusHistory = histories.selectList(new LambdaQueryWrapper() - .eq(RelabelStatusHistory::getTenantId, tenantId) + .eq(RelabelStatusHistory::getCustomerId, customerId) .eq(RelabelStatusHistory::getApplicationId, application.getId()) .orderByAsc(RelabelStatusHistory::getCreatedAt)); return new RelabelResponses.Detail(summary(application, detailItems), application.getRemark(), @@ -158,13 +158,13 @@ public class RelabelApplicationService { } @Transactional - public RelabelResponses.Detail changeStatus(String tenantId, String relabelNo, String operatorId, + public RelabelResponses.Detail changeStatus(String customerId, String relabelNo, String operatorId, RelabelRequests.ChangeStatus request) { - RelabelApplication application = applications.lockNo(tenantId, relabelNo); + RelabelApplication application = applications.lockNo(customerId, relabelNo); if (application == null) throw notFound("Relabel application not found"); String next = request.status().trim().toUpperCase(); if (!STATUSES.contains(next)) throw badRequest("Unsupported relabel status"); - if (next.equals(application.getStatus())) return detail(tenantId, relabelNo); + if (next.equals(application.getStatus())) return detail(customerId, relabelNo); validateTransition(application.getStatus(), next); String before = application.getStatus(); LocalDateTime now = LocalDateTime.now(); @@ -172,20 +172,20 @@ public class RelabelApplicationService { application.setUpdatedAt(now); applications.updateById(application); LastlegOrder order = orders.selectById(application.getLastlegOrderId()); - if (order == null || !tenantId.equals(order.getTenantId())) throw conflict("Linked tail-leg order is missing"); + if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Linked tail-leg order is missing"); String orderBefore = order.getCurrentStatus(); order.setCurrentStatus(next); order.setRelabelStatus(next); order.setUpdatedAt(now); orders.updateById(order); - insertHistory(tenantId, application.getId(), before, next, operatorId, request.remark(), now); + insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now); insertOrderHistory(order.getId(), orderBefore, next, request.remark(), now); - return detail(tenantId, relabelNo); + return detail(customerId, relabelNo); } - private void insertItem(String tenantId, long applicationId, RelabelRequests.Item source, LocalDateTime now) { + private void insertItem(String customerId, long applicationId, RelabelRequests.Item source, LocalDateTime now) { RelabelItem item = new RelabelItem(); - item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setTenantId(tenantId); + item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setCustomerId(customerId); item.setSkuCode(source.skuCode().trim()); item.setProductImageFileId(trim(source.productImageFileId())); item.setProductImageFileName(trim(source.productImageFileName())); item.setProductImageUrl(trim(source.productImageUrl())); item.setProductNameZh(source.productNameZh().trim()); item.setTotalPackageCount(source.totalPackageCount()); @@ -219,14 +219,14 @@ public class RelabelApplicationService { return items.selectList(new LambdaQueryWrapper().eq(RelabelItem::getApplicationId, applicationId) .orderByAsc(RelabelItem::getId)); } - private RelabelApplication require(String tenantId, String relabelNo) { - RelabelApplication value = applications.findNo(tenantId, relabelNo); + private RelabelApplication require(String customerId, String relabelNo) { + RelabelApplication value = applications.findNo(customerId, relabelNo); if (value == null) throw notFound("Relabel application not found"); return value; } - private void insertHistory(String tenantId, Long id, String from, String to, String operator, + private void insertHistory(String customerId, Long id, String from, String to, String operator, String remark, LocalDateTime now) { - RelabelStatusHistory value = new RelabelStatusHistory(); value.setId(IdWorker.getId()); value.setTenantId(tenantId); + RelabelStatusHistory value = new RelabelStatusHistory(); value.setId(IdWorker.getId()); value.setCustomerId(customerId); value.setApplicationId(id); value.setFromStatus(from); value.setToStatus(to); value.setOperatorId(trim(operator)); value.setRemark(trim(remark)); value.setCreatedAt(now); histories.insert(value); } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java b/lastleg/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java index ee8ccf2..d3b69fe 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java @@ -35,15 +35,15 @@ public class LastlegOrderService { @Resource private LastlegOrderDetailMapper detailMapper; @Resource private LastlegOrderStatusHistoryMapper historyMapper; - public PageResult page(String tenantId, LastlegOrderRequests.Query query) { + public PageResult page(String customerId, LastlegOrderRequests.Query query) { Page page = orderMapper.selectPage( - new Page<>(query.page(), query.size()), filter(tenantId, query).orderByDesc(LastlegOrder::getCreatedAt)); + new Page<>(query.page(), query.size()), filter(customerId, query).orderByDesc(LastlegOrder::getCreatedAt)); return new PageResult<>(page.getRecords().stream().map(this::summary).toList(), page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); } - public LastlegOrderDetailResponse detail(String tenantId, long orderId) { - LastlegOrder order = findOwned(tenantId, orderId); + public LastlegOrderDetailResponse detail(String customerId, long orderId) { + LastlegOrder order = findOwned(customerId, orderId); List cargo = detailMapper.selectList(new LambdaQueryWrapper() .eq(LastlegOrderDetail::getOrderId, orderId).orderByAsc(LastlegOrderDetail::getId)); List history = historyMapper.selectList( @@ -54,13 +54,13 @@ public class LastlegOrderService { } @Transactional - public int transferDestination(String tenantId, LastlegOrderRequests.TransferDestination request) { + public int transferDestination(String customerId, LastlegOrderRequests.TransferDestination request) { List ids = request.orderIds().stream().filter(Objects::nonNull).distinct().toList(); if (ids.isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least one order is required"); } List orders = orderMapper.selectList(new LambdaQueryWrapper() - .eq(LastlegOrder::getTenantId, tenantId).in(LastlegOrder::getId, ids).last("FOR UPDATE")); + .eq(LastlegOrder::getCustomerId, customerId).in(LastlegOrder::getId, ids).last("FOR UPDATE")); if (orders.size() != ids.size()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "One or more tail-leg orders were not found"); } @@ -96,8 +96,8 @@ public class LastlegOrderService { return orders.size(); } - public byte[] exportCsv(String tenantId, LastlegOrderRequests.Query query) { - List orders = orderMapper.selectList(filter(tenantId, query) + public byte[] exportCsv(String customerId, LastlegOrderRequests.Query query) { + List orders = orderMapper.selectList(filter(customerId, query) .orderByDesc(LastlegOrder::getCreatedAt).last("LIMIT 10000")); List lines = new ArrayList<>(); lines.add("ID,尾程单号,订单类型,入仓码,头程单号,货主,海外仓,转仓,转仓编码,FBA仓库,集装箱号,货柜状态,是否异常,是否暂扣,托数,箱数,已拣箱数,已出库箱数,剩余箱数,体积(m3),数量,体积重量,预计到港时间,实际到港时间,预计到仓,入库单号,出库单号,派送单号,换标单号,换标状态,合托标记,当前状态,备注(仅自己),备注(客户),备注(仓库)"); @@ -120,12 +120,12 @@ public class LastlegOrderService { return ("\uFEFF" + String.join("\r\n", lines)).getBytes(StandardCharsets.UTF_8); } - private LambdaQueryWrapper filter(String tenantId, LastlegOrderRequests.Query query) { + private LambdaQueryWrapper filter(String customerId, LastlegOrderRequests.Query query) { if (query.createdFrom() != null && query.createdTo() != null && query.createdFrom().isAfter(query.createdTo())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "createdFrom must not be after createdTo"); } return new LambdaQueryWrapper() - .eq(LastlegOrder::getTenantId, tenantId) + .eq(LastlegOrder::getCustomerId, customerId) .like(StringUtils.hasText(query.orderNo()), LastlegOrder::getOrderNo, query.orderNo()) .like(StringUtils.hasText(query.inboundCode()), LastlegOrder::getInboundCode, query.inboundCode()) .like(StringUtils.hasText(query.sourceOrderNo()), LastlegOrder::getSourceOrderNo, query.sourceOrderNo()) @@ -139,9 +139,9 @@ public class LastlegOrderService { .le(query.createdTo() != null, LastlegOrder::getCreatedAt, query.createdTo()); } - private LastlegOrder findOwned(String tenantId, long orderId) { + private LastlegOrder findOwned(String customerId, long orderId) { LastlegOrder order = orderMapper.selectOne(new LambdaQueryWrapper() - .eq(LastlegOrder::getTenantId, tenantId).eq(LastlegOrder::getId, orderId)); + .eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getId, orderId)); if (order == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tail-leg order not found"); return order; } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java b/lastleg/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java index a583d26..2cdd2fb 100644 --- a/lastleg/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java +++ b/lastleg/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java @@ -3,7 +3,10 @@ package com.baiyue.lastleg.service; import com.baiyue.common.event.*; import com.baiyue.lastleg.entity.*; import com.baiyue.lastleg.mapper.*; +import com.baiyue.lastleg.dropship.dto.DropshipRequests; +import com.baiyue.lastleg.dropship.service.DropshipOrderService; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.DeserializationFeature; import jakarta.annotation.Resource; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; @@ -12,6 +15,9 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Objects; +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; @Service public class LoadingCompletedConsumer { @@ -21,6 +27,7 @@ public class LoadingCompletedConsumer { @Resource private LastlegOrderStatusHistoryMapper historyMapper; @Resource private ObjectMapper objectMapper; @Resource private LastlegOrderNoGenerator orderNoGenerator; + @Resource private DropshipOrderService dropshipOrders; @Transactional public int consume(EventEnvelope envelope) { @@ -42,7 +49,7 @@ public class LoadingCompletedConsumer { } LastlegEventInbox inbox=new LastlegEventInbox(); - inbox.setEventId(envelope.eventId()); inbox.setTenantId(envelope.tenantId()); inbox.setEventType(envelope.eventType()); + inbox.setEventId(envelope.eventId()); inbox.setCustomerId(envelope.tenantId()); inbox.setEventType(envelope.eventType()); inbox.setEventVersion(envelope.eventVersion()); inbox.setPayloadHash(envelope.payloadHash()); inbox.setPayloadJson(envelope.payload().toString()); inbox.setStatus("RECEIVED"); inbox.setReceivedAt(LocalDateTime.now()); try { inboxMapper.insert(inbox); } catch(DuplicateKeyException exception) { @@ -50,13 +57,22 @@ public class LoadingCompletedConsumer { } String orderNo = orderNoGenerator.forContainerNo(payload.containerNo()); - for(var source:payload.orders()) createOrder(envelope,payload,source,orderNo); + JsonNode rawOrders = envelope.payload().path("orders"); + for (int index = 0; index < payload.orders().size(); index++) { + var source = payload.orders().get(index); + JsonNode rawSource = rawOrders.isArray() && index < rawOrders.size() ? rawOrders.get(index) : objectMapper.createObjectNode(); + createOrder(envelope, payload, source, orderNo, rawSource); + } inbox.setStatus("PROCESSED"); inbox.setProcessedAt(LocalDateTime.now()); inboxMapper.updateById(inbox); return payload.orders().size(); } private FirstLegLoadingPlanCompletedV1 parsePayload(EventEnvelope envelope) { - try { return objectMapper.treeToValue(envelope.payload(),FirstLegLoadingPlanCompletedV1.class); } + try { + ObjectMapper compatibleMapper = objectMapper.copy() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return compatibleMapper.treeToValue(envelope.payload(), FirstLegLoadingPlanCompletedV1.class); + } catch(Exception exception) { throw new IllegalArgumentException("尾程事件负载无效",exception); } } @@ -65,22 +81,33 @@ public class LoadingCompletedConsumer { } private void createOrder(EventEnvelope event,FirstLegLoadingPlanCompletedV1 payload, - FirstLegLoadingPlanCompletedV1.Order source,String orderNo) { - String orderHash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(objectMapper.valueToTree(source))); + FirstLegLoadingPlanCompletedV1.Order source,String orderNo, JsonNode rawSource) { + String orderHash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(rawSource)); LastlegOrder existing=orderMapper.findSource(event.tenantId(),event.sourceRegion().name(),source.sourceInboundId()); if(existing!=null) { if(!Objects.equals(existing.getSourcePayloadHash(),orderHash)) throw new IllegalStateException("来源入仓单快照冲突: "+source.sourceInboundId()); return; } LastlegOrder order=new LastlegOrder(); - order.setId(IdWorker.getId()); order.setOrderNo(orderNo); order.setTenantId(event.tenantId()); order.setOrderType("FIRST_LEG"); + String orderType = rawSource.path("orderType").asText(event.payload().path("orderType").asText("FIRST_LEG")); + String customerId = rawSource.path("customerId") + .asText(event.payload().path("customerId").asText(event.tenantId())); + if (!event.tenantId().equals(customerId)) { + throw new IllegalArgumentException("Order customerId does not match the trusted event customer"); + } + List dropshipPallets = "ONE_CLICK_DROPSHIP".equals(orderType) + ? pallets(rawSource) : List.of(); + if ("ONE_CLICK_DROPSHIP".equals(orderType) && dropshipPallets.isEmpty()) { + throw new IllegalArgumentException("One-click dropship event has no pallet snapshot"); + } + order.setId(IdWorker.getId()); order.setOrderNo(orderNo); order.setCustomerId(customerId); order.setOrderType(orderType); order.setInboundCode(source.inboundCode()); order.setSourceRegion(event.sourceRegion().name()); order.setSourceInboundId(source.sourceInboundId()); order.setRootInboundId(source.rootInboundId()); order.setSourceOrderId(source.sourceOrderId()); order.setSourceOrderNo(source.sourceOrderNo()); order.setRootOrderId(source.rootOrderId()); order.setOverseasWarehouseId(payload.warehouseId()); order.setOverseasWarehouseName(payload.warehouseName()); order.setFbaWarehouseCode(source.destinationWarehouseCode()); order.setContainerNo(payload.containerNo()); order.setContainerStatus("待转柜"); order.setAbnormal(false); order.setDetained(false); - order.setPackageCount(source.packageCount()); order.setPalletCount(Objects.requireNonNullElse(source.palletCount(),0)); + order.setPackageCount(source.packageCount()); order.setPalletCount(dropshipPallets.size()); order.setProductQuantity(source.productQuantity()); order.setVolumeCbm(source.volumeCbm()); order.setVolumeWeightKg(source.items().stream().map(FirstLegLoadingPlanCompletedV1.Item::dimensionalWeightKg) .filter(Objects::nonNull).reduce(BigDecimal.ZERO,BigDecimal::add)); @@ -110,5 +137,57 @@ public class LoadingCompletedConsumer { LastlegOrderStatusHistory history=new LastlegOrderStatusHistory(); history.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId()); history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history); + if ("ONE_CLICK_DROPSHIP".equals(orderType)) { + long warehouseId; + try { warehouseId = Long.parseLong(payload.warehouseId()); } + catch (Exception exception) { throw new IllegalArgumentException("One-click dropship warehouseId is invalid", exception); } + dropshipOrders.receive(new DropshipRequests.Receive(customerId, order.getId(), warehouseId, + event.eventId() + ":DROPSHIP", dropshipPallets)); + } + } + + private List pallets(JsonNode source) { + List result = new ArrayList<>(); + JsonNode nodes = source.path("pallets"); + if (nodes.isArray()) { + for (JsonNode pallet : nodes) { + String palletNo = pallet.path("palletNo").asText(pallet.path("sourcePalletNo").asText(null)); + List skuRows = skuPackages(pallet); + if (palletNo != null && !palletNo.isBlank() && !skuRows.isEmpty()) { + result.add(new DropshipRequests.Pallet(palletNo, skuRows)); + } + } + return result; + } + String palletNo = source.path("palletNo").asText(source.path("sourcePalletNo").asText(null)); + List skuRows = skuPackages(source); + if (palletNo != null && !palletNo.isBlank() && !skuRows.isEmpty()) { + result.add(new DropshipRequests.Pallet(palletNo, skuRows)); + } + return result; + } + + private List skuPackages(JsonNode source) { + List result = new ArrayList<>(); + JsonNode nodes = source.path("skuPackages"); + if (!nodes.isArray()) nodes = source.path("items"); + if (!nodes.isArray()) return result; + for (JsonNode item : nodes) { + String sku = item.path("skuCode").asText(null); + long packageCount = item.path("packageCount").asLong(0); + long productQuantity = item.path("productQuantity").asLong(0); + JsonNode markNodes = item.path("cartonMarks"); + if (!markNodes.isArray()) markNodes = item.path("boxMarks"); + if (!markNodes.isArray()) markNodes = item.path("cartonNos"); + List marks = new ArrayList<>(); + if (markNodes.isArray()) { + for (JsonNode mark : markNodes) if (!mark.asText().isBlank()) marks.add(mark.asText()); + } + if (sku != null && !sku.isBlank() && packageCount > 0 && productQuantity > 0 && !marks.isEmpty()) { + String productName = item.path("productName").asText(item.path("productNameZh").asText(null)); + result.add(new DropshipRequests.SkuPackages(sku, productName, packageCount, productQuantity, marks)); + } + } + return result; } } diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/TransferMapperConfiguration.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/TransferMapperConfiguration.java new file mode 100644 index 0000000..17bc09b --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/TransferMapperConfiguration.java @@ -0,0 +1,4 @@ +package com.baiyue.lastleg.transfer; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; +@Configuration @MapperScan("com.baiyue.lastleg.transfer.mapper") public class TransferMapperConfiguration {} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/controller/WarehouseTransferController.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/controller/WarehouseTransferController.java new file mode 100644 index 0000000..a47dc82 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/controller/WarehouseTransferController.java @@ -0,0 +1,46 @@ +package com.baiyue.lastleg.transfer.controller; + +import com.baiyue.common.core.api.ApiResponse; +import com.baiyue.common.security.AuthenticatedUser; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferResponses; +import com.baiyue.lastleg.transfer.service.WarehouseTransferService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@RequestMapping("/api/platform/v1/last-leg/warehouse-transfers") +public class WarehouseTransferController { + @Resource private WarehouseTransferService service; + + @PostMapping + public ApiResponse create(@AuthenticationPrincipal AuthenticatedUser user, + @RequestParam(required = false) String customerId, @Valid @RequestBody WarehouseTransferRequests.Create request) { + return ApiResponse.success(service.create(scope(user, customerId), operator(user), request)); + } + + @GetMapping("/{transferNo}") + public ApiResponse detail(@AuthenticationPrincipal AuthenticatedUser user, + @RequestParam(required = false) String customerId, @PathVariable String transferNo) { + return ApiResponse.success(service.detail(scope(user, customerId), transferNo)); + } + + private String scope(AuthenticatedUser user, String requested) { + if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) { + if (requested == null || requested.isBlank() || "0".equals(requested)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Platform request must specify target tenant"); + } + return requested.trim(); + } + return String.valueOf(user.tenantId()); + } + + private String operator(AuthenticatedUser user) { + return user.userId() == null ? user.username() : String.valueOf(user.userId()); + } +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferRequests.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferRequests.java new file mode 100644 index 0000000..ed94b6f --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferRequests.java @@ -0,0 +1,39 @@ +package com.baiyue.lastleg.transfer.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +public final class WarehouseTransferRequests { + private WarehouseTransferRequests() {} + + public record Create( + @NotBlank @Size(max = 128) String inboundCode, + @NotEmpty @Size(max = 200) List<@NotNull Long> sourceLastlegOrderIds, + @NotBlank @Size(max = 128) String operatingWarehouseId, + @NotBlank @Size(max = 128) String fbaWarehouseCode, + @NotBlank @Size(max = 1000) String deliveryAddress, + @Positive int packageCount, + @NotBlank @Size(max = 64) String deliveryMethod, + @NotNull LocalDateTime estimatedArrivalAt, + @NotBlank @Size(max = 128) String fbaShipmentNo, + @Size(max = 255) String trackingNo, + @Size(max = 2000) String fbaBoxNos, + @Size(max = 1000) String remark, + boolean needRelabel, + @NotBlank @Size(max = 255) String idempotencyKey, + @NotEmpty @Size(max = 500) List<@Valid Item> items + ) {} + + public record Item( + @NotBlank @Size(max = 128) String skuCode, + @NotBlank @Size(max = 255) String productNameZh, + @Positive int packageCount, + @Size(max = 255) String shippingMark + ) {} +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferResponses.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferResponses.java new file mode 100644 index 0000000..da5b30b --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/dto/WarehouseTransferResponses.java @@ -0,0 +1,17 @@ +package com.baiyue.lastleg.transfer.dto; + +import java.time.LocalDateTime; +import java.util.List; + +public final class WarehouseTransferResponses { + private WarehouseTransferResponses() {} + public record Item(String skuCode, String productNameZh, int packageCount, String shippingMark) {} + public record SourceOrder(Long lastlegOrderId, String lastlegOrderNo, Long inboundOrderId, String inboundCode) {} + public record Detail(Long id, String transferNo, Long inboundOrderId, String inboundCode, + Long lastlegOrderId, String lastlegOrderNo, String orderType, String orderStatus, + boolean needRelabel, Long relabelApplicationId, String relabelNo, String relabelStatus, + String operatingWarehouseId, String fbaWarehouseCode, String deliveryAddress, + int packageCount, String deliveryMethod, LocalDateTime estimatedArrivalAt, + String fbaShipmentNo, String trackingNo, String fbaBoxNos, String remark, + List sourceOrders, List items, LocalDateTime createdAt) {} +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferItem.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferItem.java new file mode 100644 index 0000000..6ca13d3 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferItem.java @@ -0,0 +1,20 @@ +package com.baiyue.lastleg.transfer.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("ll_warehouse_transfer_item") +public class WarehouseTransferItem { + @TableId(type = IdType.INPUT) private Long id; + private Long transferOrderId; + private String customerId; + private String skuCode; + private String productNameZh; + private Integer packageCount; + private String shippingMark; + private LocalDateTime createdAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferOrder.java new file mode 100644 index 0000000..47d91b0 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferOrder.java @@ -0,0 +1,38 @@ +package com.baiyue.lastleg.transfer.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("ll_warehouse_transfer_order") +public class WarehouseTransferOrder { + @TableId(type = IdType.INPUT) private Long id; + private String customerId; + private String transferNo; + private Long inboundOrderId; + private String inboundCode; + private Long lastlegOrderId; + private String lastlegOrderNo; + private String operatingWarehouseId; + private String fbaWarehouseCode; + private String deliveryAddress; + private Integer packageCount; + private String deliveryMethod; + private LocalDateTime estimatedArrivalAt; + private String fbaShipmentNo; + private String trackingNo; + private String fbaBoxNos; + private String remark; + private Boolean needRelabel; + private Long relabelApplicationId; + private String relabelNo; + private String status; + private String idempotencyKey; + private String requestHash; + private String createdBy; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferSourceOrder.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferSourceOrder.java new file mode 100644 index 0000000..c760c70 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/entity/WarehouseTransferSourceOrder.java @@ -0,0 +1,20 @@ +package com.baiyue.lastleg.transfer.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("ll_warehouse_transfer_source_order") +public class WarehouseTransferSourceOrder { + @TableId(type = IdType.INPUT) private Long id; + private Long transferOrderId; + private String customerId; + private Long sourceLastlegOrderId; + private String sourceLastlegOrderNo; + private Long inboundOrderId; + private String inboundCode; + private LocalDateTime createdAt; +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/InboundSnapshotMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/InboundSnapshotMapper.java new file mode 100644 index 0000000..59e1463 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/InboundSnapshotMapper.java @@ -0,0 +1,10 @@ +package com.baiyue.lastleg.transfer.mapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +@Mapper public interface InboundSnapshotMapper { + @Select("SELECT id FROM ll_inbound_order WHERE customer_id=#{customerId} AND (inbound_code=#{code} OR source_inbound_id=#{code}) LIMIT 1") + Long findId(@Param("customerId") String customerId, @Param("code") String code); + @Select("SELECT id FROM ll_inbound_order WHERE customer_id=#{customerId} AND source_inbound_id=#{sourceInboundId} LIMIT 1") + Long findBySourceInboundId(@Param("customerId") String customerId, @Param("sourceInboundId") String sourceInboundId); +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferItemMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferItemMapper.java new file mode 100644 index 0000000..7de29cb --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferItemMapper.java @@ -0,0 +1,5 @@ +package com.baiyue.lastleg.transfer.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferItem; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface WarehouseTransferItemMapper extends BaseMapper {} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferOrderMapper.java new file mode 100644 index 0000000..fdbc237 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferOrderMapper.java @@ -0,0 +1,12 @@ +package com.baiyue.lastleg.transfer.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +@Mapper public interface WarehouseTransferOrderMapper extends BaseMapper { + @Select("SELECT * FROM ll_warehouse_transfer_order WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") + WarehouseTransferOrder findKey(@Param("customerId") String customerId, @Param("key") String key); + @Select("SELECT * FROM ll_warehouse_transfer_order WHERE customer_id=#{customerId} AND transfer_no=#{transferNo} LIMIT 1") + WarehouseTransferOrder findNo(@Param("customerId") String customerId, @Param("transferNo") String transferNo); +} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferSourceOrderMapper.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferSourceOrderMapper.java new file mode 100644 index 0000000..a501a51 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/mapper/WarehouseTransferSourceOrderMapper.java @@ -0,0 +1,5 @@ +package com.baiyue.lastleg.transfer.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferSourceOrder; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface WarehouseTransferSourceOrderMapper extends BaseMapper {} diff --git a/lastleg/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java b/lastleg/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java new file mode 100644 index 0000000..70d5a16 --- /dev/null +++ b/lastleg/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java @@ -0,0 +1,250 @@ +package com.baiyue.lastleg.transfer.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.entity.LastlegOrderDetail; +import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; +import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.relabel.entity.RelabelApplication; +import com.baiyue.lastleg.relabel.entity.RelabelItem; +import com.baiyue.lastleg.relabel.entity.RelabelStatusHistory; +import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper; +import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper; +import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper; +import com.baiyue.lastleg.relabel.service.RelabelApplicationService; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferResponses; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferItem; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferSourceOrder; +import com.baiyue.lastleg.transfer.mapper.InboundSnapshotMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferItemMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferOrderMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferSourceOrderMapper; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ResponseStatusException; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HexFormat; +import java.util.List; + +@Service +public class WarehouseTransferService { + public static final String ORDER_TYPE = "WAREHOUSE_TRANSFER"; + public static final String PENDING_REVIEW = "PENDING_REVIEW"; + + @Resource private WarehouseTransferOrderMapper transfers; + @Resource private WarehouseTransferItemMapper transferItems; + @Resource private WarehouseTransferSourceOrderMapper sourceOrders; + @Resource private InboundSnapshotMapper inboundSnapshots; + @Resource private LastlegOrderMapper orders; + @Resource private LastlegOrderDetailMapper orderDetails; + @Resource private LastlegOrderStatusHistoryMapper orderHistories; + @Resource private RelabelApplicationMapper relabelApplications; + @Resource private RelabelItemMapper relabelItems; + @Resource private RelabelStatusHistoryMapper relabelHistories; + @Resource private LastlegOrderNoGenerator orderNoGenerator; + @Resource private ObjectMapper objectMapper; + + @Transactional + public WarehouseTransferResponses.Detail create(String customerId, String operatorId, + WarehouseTransferRequests.Create request) { + int itemPackages = request.items().stream().mapToInt(WarehouseTransferRequests.Item::packageCount).sum(); + if (itemPackages != request.packageCount()) { + throw badRequest("Cargo item package total must equal transfer package count"); + } + String key = request.idempotencyKey().trim(); + String requestHash = hash(request); + WarehouseTransferOrder replay = transfers.findKey(customerId, key); + if (replay != null) { + if (!requestHash.equals(replay.getRequestHash())) { + throw conflict("Idempotency key was reused with different warehouse transfer data"); + } + return detail(customerId, replay.getTransferNo()); + } + + String inboundCode = request.inboundCode().trim(); + Long inboundOrderId = inboundSnapshots.findId(customerId, inboundCode); + if (inboundOrderId == null) throw notFound("Inbound order was not found for inboundCode " + inboundCode); + List selectedIds = request.sourceLastlegOrderIds().stream().distinct().toList(); + List selectedOrders = orders.selectList(new LambdaQueryWrapper() + .eq(LastlegOrder::getCustomerId, customerId).in(LastlegOrder::getId, selectedIds)); + if (selectedOrders.size() != selectedIds.size()) { + throw notFound("One or more selected customer orders do not exist in the current tenant"); + } + + LocalDateTime now = LocalDateTime.now(); + long transferId = IdWorker.getId(); + String transferNo = "WTR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + transferId; + LastlegOrder order = createTailOrder(customerId, inboundOrderId, transferNo, request, requestHash, now); + + WarehouseTransferOrder transfer = new WarehouseTransferOrder(); + transfer.setId(transferId); transfer.setCustomerId(customerId); transfer.setTransferNo(transferNo); + transfer.setInboundOrderId(inboundOrderId); transfer.setInboundCode(inboundCode); + transfer.setLastlegOrderId(order.getId()); transfer.setLastlegOrderNo(order.getOrderNo()); + transfer.setOperatingWarehouseId(request.operatingWarehouseId().trim()); + transfer.setFbaWarehouseCode(request.fbaWarehouseCode().trim()); + transfer.setDeliveryAddress(request.deliveryAddress().trim()); transfer.setPackageCount(request.packageCount()); + transfer.setDeliveryMethod(request.deliveryMethod().trim()); transfer.setEstimatedArrivalAt(request.estimatedArrivalAt()); + transfer.setFbaShipmentNo(request.fbaShipmentNo().trim()); transfer.setTrackingNo(trim(request.trackingNo())); + transfer.setFbaBoxNos(trim(request.fbaBoxNos())); transfer.setRemark(trim(request.remark())); + transfer.setNeedRelabel(request.needRelabel()); transfer.setStatus(PENDING_REVIEW); + transfer.setIdempotencyKey(key); transfer.setRequestHash(requestHash); transfer.setCreatedBy(trim(operatorId)); + transfer.setCreatedAt(now); transfer.setUpdatedAt(now); + transfers.insert(transfer); + + for (LastlegOrder selectedOrder : selectedOrders) { + Long selectedInboundId = StringUtils.hasText(selectedOrder.getSourceInboundId()) + ? inboundSnapshots.findBySourceInboundId(customerId, selectedOrder.getSourceInboundId()) : null; + if (selectedInboundId == null) selectedInboundId = inboundSnapshots.findId(customerId, selectedOrder.getInboundCode()); + if (selectedInboundId == null) { + throw conflict("Selected order " + selectedOrder.getOrderNo() + " has no linked inbound order"); + } + WarehouseTransferSourceOrder link = new WarehouseTransferSourceOrder(); + link.setId(IdWorker.getId()); link.setTransferOrderId(transferId); link.setCustomerId(customerId); + link.setSourceLastlegOrderId(selectedOrder.getId()); link.setSourceLastlegOrderNo(selectedOrder.getOrderNo()); + link.setInboundOrderId(selectedInboundId); link.setInboundCode(selectedOrder.getInboundCode()); link.setCreatedAt(now); + sourceOrders.insert(link); + } + + for (int index = 0; index < request.items().size(); index++) { + WarehouseTransferRequests.Item source = request.items().get(index); + insertTransferItem(customerId, transferId, source, now); + insertOrderDetail(order.getId(), transferNo, index, source, request, now); + } + insertOrderHistory(order.getId(), PENDING_REVIEW, transferNo, now); + if (request.needRelabel()) createRelabel(customerId, operatorId, transfer, order, request, now); + return detail(customerId, transferNo); + } + + @Transactional(readOnly = true) + public WarehouseTransferResponses.Detail detail(String customerId, String transferNo) { + WarehouseTransferOrder transfer = transfers.findNo(customerId, transferNo); + if (transfer == null) throw notFound("Warehouse transfer order not found"); + List items = transferItems.selectList( + new LambdaQueryWrapper().eq(WarehouseTransferItem::getCustomerId, customerId) + .eq(WarehouseTransferItem::getTransferOrderId, transfer.getId()).orderByAsc(WarehouseTransferItem::getId)) + .stream().map(value -> new WarehouseTransferResponses.Item(value.getSkuCode(), value.getProductNameZh(), + value.getPackageCount(), value.getShippingMark())).toList(); + List sources = sourceOrders.selectList( + new LambdaQueryWrapper().eq(WarehouseTransferSourceOrder::getCustomerId, customerId) + .eq(WarehouseTransferSourceOrder::getTransferOrderId, transfer.getId()).orderByAsc(WarehouseTransferSourceOrder::getId)) + .stream().map(value -> new WarehouseTransferResponses.SourceOrder(value.getSourceLastlegOrderId(), + value.getSourceLastlegOrderNo(), value.getInboundOrderId(), value.getInboundCode())).toList(); + String relabelStatus = null; + if (transfer.getRelabelNo() != null) { + RelabelApplication relabel = relabelApplications.findNo(customerId, transfer.getRelabelNo()); + relabelStatus = relabel == null ? null : relabel.getStatus(); + } + return new WarehouseTransferResponses.Detail(transfer.getId(), transfer.getTransferNo(), transfer.getInboundOrderId(), + transfer.getInboundCode(), transfer.getLastlegOrderId(), transfer.getLastlegOrderNo(), ORDER_TYPE, PENDING_REVIEW, + Boolean.TRUE.equals(transfer.getNeedRelabel()), transfer.getRelabelApplicationId(), transfer.getRelabelNo(), + relabelStatus, + transfer.getOperatingWarehouseId(), transfer.getFbaWarehouseCode(), transfer.getDeliveryAddress(), + transfer.getPackageCount(), transfer.getDeliveryMethod(), transfer.getEstimatedArrivalAt(), transfer.getFbaShipmentNo(), + transfer.getTrackingNo(), transfer.getFbaBoxNos(), transfer.getRemark(), sources, items, transfer.getCreatedAt()); + } + + private LastlegOrder createTailOrder(String customerId, Long inboundOrderId, String transferNo, + WarehouseTransferRequests.Create request, String requestHash, LocalDateTime now) { + LastlegOrder order = new LastlegOrder(); + order.setId(IdWorker.getId()); order.setOrderNo(orderNoGenerator.next()); order.setCustomerId(customerId); + order.setOrderType(ORDER_TYPE); order.setInboundCode(request.inboundCode().trim()); order.setSourceRegion("CUSTOMER"); + order.setSourceInboundId("WAREHOUSE_TRANSFER:" + transferNo); order.setRootInboundId(String.valueOf(inboundOrderId)); + order.setSourceOrderNo(transferNo); order.setOverseasWarehouseId(request.operatingWarehouseId().trim()); + order.setFbaWarehouseCode(request.fbaWarehouseCode().trim()); order.setPackageCount(request.packageCount()); + order.setPalletCount(0); order.setPickedPackageCount(0); order.setShippedPackageCount(0); + order.setEstimatedWarehouseAt(request.estimatedArrivalAt()); order.setInboundOrderNo(request.inboundCode().trim()); + order.setCurrentStatus(PENDING_REVIEW); order.setCustomerRemark(trim(request.remark())); + order.setSourceEventId("WAREHOUSE_TRANSFER:" + transferNo); order.setSourceLoadingPlanId(ORDER_TYPE); + order.setSourcePayloadHash("sha256:" + requestHash); order.setCreatedAt(now); order.setUpdatedAt(now); + orders.insert(order); + return order; + } + + private void insertTransferItem(String customerId, long transferId, WarehouseTransferRequests.Item source, + LocalDateTime now) { + WarehouseTransferItem item = new WarehouseTransferItem(); + item.setId(IdWorker.getId()); item.setTransferOrderId(transferId); item.setCustomerId(customerId); + item.setSkuCode(source.skuCode().trim()); item.setProductNameZh(source.productNameZh().trim()); + item.setPackageCount(source.packageCount()); item.setShippingMark(trim(source.shippingMark())); item.setCreatedAt(now); + transferItems.insert(item); + } + + private void insertOrderDetail(long orderId, String transferNo, int index, WarehouseTransferRequests.Item source, + WarehouseTransferRequests.Create request, LocalDateTime now) { + LastlegOrderDetail detail = new LastlegOrderDetail(); + detail.setOrderId(orderId); detail.setSourceInboundDetailId(transferNo + ":" + index); + detail.setSkuCode(source.skuCode().trim()); detail.setProductNameZh(source.productNameZh().trim()); + detail.setPackageCount(source.packageCount()); detail.setDestinationWarehouseCode(request.fbaWarehouseCode().trim()); + detail.setFbaTrackingNo(trim(request.trackingNo())); detail.setShipmentNo(request.fbaShipmentNo().trim()); + detail.setShippingMark(trim(source.shippingMark())); orderDetails.insert(detail); + } + + private void createRelabel(String customerId, String operatorId, WarehouseTransferOrder transfer, LastlegOrder order, + WarehouseTransferRequests.Create request, LocalDateTime now) { + long applicationId = IdWorker.getId(); + String relabelNo = "FBR" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + applicationId; + RelabelApplication application = new RelabelApplication(); + application.setId(applicationId); application.setCustomerId(customerId); application.setRelabelNo(relabelNo); + application.setLastlegOrderId(order.getId()); application.setCustomerOrderNo(transfer.getTransferNo()); + application.setOverseasWarehouseId(request.operatingWarehouseId().trim()); application.setLabelDeadline(request.estimatedArrivalAt()); + application.setContactName(StringUtils.hasText(operatorId) ? operatorId.trim() : "SYSTEM"); + application.setContactPhone("N/A"); application.setRemark(trim(request.remark())); + application.setStatus(RelabelApplicationService.PENDING_PROCESSING); application.setItemCount(request.items().size()); + application.setTotalPackageCount(request.packageCount()); application.setNotifiedPackageCount(request.packageCount()); + application.setIdempotencyKey("WAREHOUSE_TRANSFER:" + request.idempotencyKey().trim()); + application.setRequestHash(hash(request)); application.setCreatedBy(trim(operatorId)); + application.setCreatedAt(now); application.setUpdatedAt(now); relabelApplications.insert(application); + + for (WarehouseTransferRequests.Item source : request.items()) { + RelabelItem item = new RelabelItem(); + item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setCustomerId(customerId); + item.setSkuCode(source.skuCode().trim()); item.setProductNameZh(source.productNameZh().trim()); + item.setTotalPackageCount(source.packageCount()); item.setNotifiedPackageCount(source.packageCount()); + item.setShipmentNo(request.fbaShipmentNo().trim()); item.setTrackingCode(trim(request.trackingNo())); + item.setNewSkuCode(source.skuCode().trim()); item.setLabelRequirement("WAREHOUSE_TRANSFER_RELABEL"); + item.setCreatedAt(now); relabelItems.insert(item); + } + RelabelStatusHistory history = new RelabelStatusHistory(); + history.setId(IdWorker.getId()); history.setCustomerId(customerId); history.setApplicationId(applicationId); + history.setToStatus(RelabelApplicationService.PENDING_PROCESSING); history.setOperatorId(trim(operatorId)); + history.setRemark("Created from warehouse transfer " + transfer.getTransferNo()); history.setCreatedAt(now); + relabelHistories.insert(history); + transfer.setRelabelApplicationId(applicationId); transfer.setRelabelNo(relabelNo); transfer.setUpdatedAt(now); + transfers.updateById(transfer); + order.setRelabelOrderNo(relabelNo); order.setRelabelStatus(RelabelApplicationService.PENDING_PROCESSING); + order.setUpdatedAt(now); orders.updateById(order); + } + + private void insertOrderHistory(long orderId, String status, String transferNo, LocalDateTime now) { + LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); + history.setOrderId(orderId); history.setToStatus(status); history.setEventId("WAREHOUSE_TRANSFER:" + transferNo); + history.setRemark("Warehouse transfer submitted for review"); history.setCreatedAt(now); orderHistories.insert(history); + } + + private String hash(Object value) { + try { + byte[] json = objectMapper.copy().findAndRegisterModules().writeValueAsString(value).getBytes(StandardCharsets.UTF_8); + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(json)); + } catch (Exception exception) { + throw new IllegalStateException(exception); + } + } + private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; } + private static ResponseStatusException badRequest(String message) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, message); } + private static ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); } + private static ResponseStatusException notFound(String message) { return new ResponseStatusException(HttpStatus.NOT_FOUND, message); } +} diff --git a/lastleg/src/main/resources/db/lastleg.sql b/lastleg/src/main/resources/db/lastleg.sql index 25ad54a..1f9b26a 100644 --- a/lastleg/src/main/resources/db/lastleg.sql +++ b/lastleg/src/main/resources/db/lastleg.sql @@ -3,7 +3,7 @@ USE baiyue_lastleg; CREATE TABLE IF NOT EXISTS ll_loading_event_inbox ( event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一标识', - tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', event_type VARCHAR(64) NOT NULL COMMENT '事件类型', event_version INT NOT NULL COMMENT '事件契约版本', payload_hash VARCHAR(71) NOT NULL COMMENT '规范化事件负载 SHA-256 摘要', @@ -13,13 +13,13 @@ CREATE TABLE IF NOT EXISTS ll_loading_event_inbox ( processed_at DATETIME(3) NULL COMMENT '处理完成时间', last_error VARCHAR(1000) NULL COMMENT '最近一次处理错误', PRIMARY KEY(event_id), - UNIQUE KEY uk_ll_loading_inbox_tenant_key (tenant_id,event_type,event_id) + UNIQUE KEY uk_ll_loading_inbox_tenant_key (customer_id,event_type,event_id) ) ENGINE=InnoDB COMMENT='头程装柜完成事件接收幂等箱'; CREATE TABLE IF NOT EXISTS ll_order ( id BIGINT NOT NULL COMMENT '雪花 ID', order_no VARCHAR(32) NOT NULL COMMENT '尾程单号', - tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', order_type VARCHAR(32) NOT NULL DEFAULT 'FIRST_LEG' COMMENT '订单类型', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓码', source_region VARCHAR(32) NOT NULL COMMENT '来源区域/系统', @@ -67,9 +67,10 @@ CREATE TABLE IF NOT EXISTS ll_order ( created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY(id), - UNIQUE KEY uk_ll_order_source (tenant_id,source_region,source_inbound_id), - KEY idx_ll_order_group(tenant_id,order_no), - KEY idx_ll_order_list(tenant_id,current_status,created_at) + UNIQUE KEY uk_ll_order_source (customer_id,source_region,source_inbound_id), + KEY idx_ll_order_group(customer_id,order_no), + KEY idx_ll_order_container(customer_id,container_no), + KEY idx_ll_order_list(customer_id,current_status,created_at) ) ENGINE=InnoDB COMMENT='尾程订单主表'; CREATE TABLE IF NOT EXISTS ll_order_detail ( @@ -111,24 +112,24 @@ CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( CREATE TABLE IF NOT EXISTS ll_event_conflict ( id BIGINT NOT NULL COMMENT '冲突记录主键', event_id VARCHAR(128) NOT NULL COMMENT '冲突事件 ID', - tenant_id BIGINT NOT NULL COMMENT '租户 ID', conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', + customer_id BIGINT NOT NULL COMMENT '租户 ID', conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', conflict_message VARCHAR(512) NOT NULL COMMENT '冲突说明', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY(id), KEY idx_ll_conflict_event(event_id,tenant_id) + PRIMARY KEY(id), KEY idx_ll_conflict_event(event_id,customer_id) ) ENGINE=InnoDB COMMENT='事件冲突记录'; CREATE TABLE IF NOT EXISTS ll_event_inbox ( id BIGINT NOT NULL COMMENT '事件接收记录主键', event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一 ID', - tenant_id BIGINT NOT NULL COMMENT '租户 ID', event_type VARCHAR(128) NOT NULL COMMENT '事件类型', + customer_id BIGINT NOT NULL COMMENT '租户 ID', event_type VARCHAR(128) NOT NULL COMMENT '事件类型', aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合 ID', aggregate_version BIGINT NOT NULL COMMENT '聚合版本', payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', status VARCHAR(32) NOT NULL COMMENT '消费状态', error_message VARCHAR(512) NULL COMMENT '错误信息', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY(id), UNIQUE KEY uk_ll_inbox_event(event_id), KEY idx_ll_inbox_source(tenant_id,aggregate_id) + PRIMARY KEY(id), UNIQUE KEY uk_ll_inbox_event(event_id), KEY idx_ll_inbox_source(customer_id,aggregate_id) ) ENGINE=InnoDB COMMENT='通用事件接收幂等箱'; CREATE TABLE IF NOT EXISTS ll_inbound_order ( - id BIGINT NOT NULL COMMENT '入仓单主键', tenant_id BIGINT NOT NULL COMMENT '租户 ID', source_region VARCHAR(32) NOT NULL COMMENT '来源区域', + id BIGINT NOT NULL COMMENT '入仓单主键', customer_id BIGINT NOT NULL COMMENT '租户 ID', source_region VARCHAR(32) NOT NULL COMMENT '来源区域', source_inbound_id VARCHAR(128) NOT NULL COMMENT '来源入仓分片 ID', root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', source_order_no VARCHAR(128) NULL COMMENT '来源订单号', root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', inbound_code VARCHAR(128) NULL COMMENT '入仓码', destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', @@ -139,21 +140,21 @@ CREATE TABLE IF NOT EXISTS ll_inbound_order ( source_loading_plan_version BIGINT NOT NULL COMMENT '装柜计划版本', handover_version BIGINT NOT NULL COMMENT '交接版本', payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', status VARCHAR(32) NOT NULL COMMENT '入仓单状态', exception_flag VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '异常标记', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY(id), UNIQUE KEY uk_ll_order_source(tenant_id,source_region,source_inbound_id), UNIQUE KEY uk_ll_order_event(source_event_id,source_inbound_id) + PRIMARY KEY(id), UNIQUE KEY uk_ll_order_source(customer_id,source_region,source_inbound_id), UNIQUE KEY uk_ll_order_event(source_event_id,source_inbound_id) ) ENGINE=InnoDB COMMENT='尾程入仓单快照'; CREATE TABLE IF NOT EXISTS ll_inbound_order_item ( - id BIGINT NOT NULL COMMENT '入仓明细主键', tenant_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '入仓单 ID', + id BIGINT NOT NULL COMMENT '入仓明细主键', customer_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '入仓单 ID', source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细 ID', package_count INT NOT NULL COMMENT '箱数', product_quantity INT NULL COMMENT '数量', pallet_count INT NULL COMMENT '托数', gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '体积重量(千克)', volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA跟踪号', shipment_no VARCHAR(128) NULL COMMENT '货件编号', shipping_mark VARCHAR(255) NULL COMMENT '唛头', sku_code VARCHAR(128) NULL COMMENT 'SKU 编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', product_name_en VARCHAR(255) NULL COMMENT '英文品名', - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_item_order(tenant_id,order_id) + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_item_order(customer_id,order_id) ) ENGINE=InnoDB COMMENT='尾程入仓商品明细快照'; CREATE TABLE IF NOT EXISTS ll_order_status_history ( - id BIGINT NOT NULL COMMENT '订单状态历史主键', tenant_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '订单 ID', + id BIGINT NOT NULL COMMENT '订单状态历史主键', customer_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '订单 ID', from_status VARCHAR(32) NULL COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', reason VARCHAR(255) NULL COMMENT '变更原因', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间', PRIMARY KEY(id) ) ENGINE=InnoDB COMMENT='尾程订单状态历史'; @@ -171,21 +172,21 @@ ALTER TABLE ll_order_status_history COMMENT='尾程订单状态历史'; ALTER TABLE ll_event_conflict MODIFY COLUMN id BIGINT NOT NULL COMMENT '冲突记录主键', MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '冲突事件 ID', - MODIFY COLUMN tenant_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', + MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', MODIFY COLUMN conflict_message VARCHAR(512) NOT NULL COMMENT '冲突说明', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'; ALTER TABLE ll_event_inbox - MODIFY COLUMN id BIGINT NOT NULL COMMENT '事件接收记录主键', MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一 ID', MODIFY COLUMN tenant_id BIGINT NOT NULL COMMENT '租户 ID', + MODIFY COLUMN id BIGINT NOT NULL COMMENT '事件接收记录主键', MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一 ID', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN event_type VARCHAR(128) NOT NULL COMMENT '事件类型', MODIFY COLUMN aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', MODIFY COLUMN aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合 ID', MODIFY COLUMN aggregate_version BIGINT NOT NULL COMMENT '聚合版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', MODIFY COLUMN status VARCHAR(32) NOT NULL COMMENT '消费状态', MODIFY COLUMN error_message VARCHAR(512) NULL COMMENT '错误信息', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', MODIFY COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'; ALTER TABLE ll_inbound_order - MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓单主键', MODIFY COLUMN tenant_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN source_region VARCHAR(32) NOT NULL COMMENT '来源区域', MODIFY COLUMN source_inbound_id VARCHAR(128) NOT NULL COMMENT '来源入仓分片 ID', MODIFY COLUMN root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', MODIFY COLUMN source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', MODIFY COLUMN source_order_no VARCHAR(128) NULL COMMENT '来源订单号', MODIFY COLUMN root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', MODIFY COLUMN inbound_code VARCHAR(128) NULL COMMENT '入仓码', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', MODIFY COLUMN source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '装柜计划 ID', MODIFY COLUMN source_loading_plan_version BIGINT NOT NULL COMMENT '装柜计划版本', MODIFY COLUMN handover_version BIGINT NOT NULL COMMENT '交接版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', MODIFY COLUMN status VARCHAR(32) NOT NULL COMMENT '入仓单状态', MODIFY COLUMN exception_flag VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '异常标记', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', MODIFY COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'; + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓单主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN source_region VARCHAR(32) NOT NULL COMMENT '来源区域', MODIFY COLUMN source_inbound_id VARCHAR(128) NOT NULL COMMENT '来源入仓分片 ID', MODIFY COLUMN root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', MODIFY COLUMN source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', MODIFY COLUMN source_order_no VARCHAR(128) NULL COMMENT '来源订单号', MODIFY COLUMN root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', MODIFY COLUMN inbound_code VARCHAR(128) NULL COMMENT '入仓码', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', MODIFY COLUMN source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '装柜计划 ID', MODIFY COLUMN source_loading_plan_version BIGINT NOT NULL COMMENT '装柜计划版本', MODIFY COLUMN handover_version BIGINT NOT NULL COMMENT '交接版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', MODIFY COLUMN status VARCHAR(32) NOT NULL COMMENT '入仓单状态', MODIFY COLUMN exception_flag VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '异常标记', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', MODIFY COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'; ALTER TABLE ll_inbound_order_item - MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓明细主键', MODIFY COLUMN tenant_id BIGINT NOT NULL COMMENT '租户ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '入仓单ID', MODIFY COLUMN source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细ID', MODIFY COLUMN package_count INT NOT NULL COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '体积重量(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA跟踪号', MODIFY COLUMN shipment_no VARCHAR(128) NULL COMMENT '货件编号', MODIFY COLUMN shipping_mark VARCHAR(255) NULL COMMENT '唛头', MODIFY COLUMN sku_code VARCHAR(128) NULL COMMENT 'SKU编码', MODIFY COLUMN product_name_zh VARCHAR(255) NULL COMMENT '中文品名', MODIFY COLUMN product_name_en VARCHAR(255) NULL COMMENT '英文品名', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'; + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓明细主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '入仓单ID', MODIFY COLUMN source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细ID', MODIFY COLUMN package_count INT NOT NULL COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '体积重量(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA跟踪号', MODIFY COLUMN shipment_no VARCHAR(128) NULL COMMENT '货件编号', MODIFY COLUMN shipping_mark VARCHAR(255) NULL COMMENT '唛头', MODIFY COLUMN sku_code VARCHAR(128) NULL COMMENT 'SKU编码', MODIFY COLUMN product_name_zh VARCHAR(255) NULL COMMENT '中文品名', MODIFY COLUMN product_name_en VARCHAR(255) NULL COMMENT '英文品名', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'; ALTER TABLE ll_order_status_history - MODIFY COLUMN id BIGINT NOT NULL COMMENT '订单状态历史主键', MODIFY COLUMN tenant_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '订单 ID', MODIFY COLUMN from_status VARCHAR(32) NULL COMMENT '变更前状态', MODIFY COLUMN to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', MODIFY COLUMN reason VARCHAR(255) NULL COMMENT '变更原因', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间'; + MODIFY COLUMN id BIGINT NOT NULL COMMENT '订单状态历史主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '订单 ID', MODIFY COLUMN from_status VARCHAR(32) NULL COMMENT '变更前状态', MODIFY COLUMN to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', MODIFY COLUMN reason VARCHAR(255) NULL COMMENT '变更原因', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间'; ALTER TABLE ll_loading_event_inbox MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一标识', - MODIFY COLUMN tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', MODIFY COLUMN event_type VARCHAR(64) NOT NULL COMMENT '事件类型', MODIFY COLUMN event_version INT NOT NULL COMMENT '事件契约版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '规范化事件负载 SHA-256 摘要', @@ -198,7 +199,7 @@ ALTER TABLE ll_loading_event_inbox ALTER TABLE ll_order MODIFY COLUMN id BIGINT NOT NULL COMMENT '雪花 ID', MODIFY COLUMN order_no VARCHAR(32) NOT NULL COMMENT '尾程单号', - MODIFY COLUMN tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', MODIFY COLUMN order_type VARCHAR(32) NOT NULL DEFAULT 'FIRST_LEG' COMMENT '订单类型', MODIFY COLUMN inbound_code VARCHAR(128) NOT NULL COMMENT '入仓码', MODIFY COLUMN source_region VARCHAR(32) NOT NULL COMMENT '来源区域/系统', @@ -298,7 +299,7 @@ DEALLOCATE PREPARE ll_drop_order_no_unique_stmt; SET @ll_add_order_group_index = ( SELECT IF(COUNT(*) = 0, - 'CREATE INDEX idx_ll_order_group ON ll_order(tenant_id,order_no)', + 'CREATE INDEX idx_ll_order_group ON ll_order(customer_id,order_no)', 'SELECT 1') FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'll_order' @@ -332,7 +333,7 @@ ALTER TABLE ll_order_detail CREATE TABLE IF NOT EXISTS ll_inbound_plan ( id BIGINT NOT NULL COMMENT '入库计划ID(雪花ID)', plan_no VARCHAR(32) NOT NULL COMMENT '入库单号', - tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', status VARCHAR(32) NOT NULL DEFAULT 'PLANNED' COMMENT '入库计划状态', line_count INT NOT NULL DEFAULT 0 COMMENT '入仓码明细数量', @@ -342,7 +343,7 @@ CREATE TABLE IF NOT EXISTS ll_inbound_plan ( updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY (id), UNIQUE KEY uk_ll_inbound_plan_no (plan_no), - UNIQUE KEY uk_ll_inbound_plan_container (tenant_id, container_no) + UNIQUE KEY uk_ll_inbound_plan_container (customer_id, container_no) ) ENGINE=InnoDB COMMENT='尾程入库计划表:每个租户的每个集装箱生成一张入库单'; CREATE TABLE IF NOT EXISTS ll_inbound_plan_line ( @@ -393,7 +394,7 @@ ALTER TABLE ll_inbound_plan COMMENT='尾程入库计划表:每个租户的每 ALTER TABLE ll_inbound_plan MODIFY COLUMN id BIGINT NOT NULL COMMENT '入库计划ID(雪花ID)', MODIFY COLUMN plan_no VARCHAR(32) NOT NULL COMMENT '入库单号', - MODIFY COLUMN tenant_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', MODIFY COLUMN container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', MODIFY COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PLANNED' COMMENT '入库计划状态', MODIFY COLUMN line_count INT NOT NULL DEFAULT 0 COMMENT '入仓码明细数量', @@ -466,27 +467,27 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; CREATE TABLE IF NOT EXISTS ll_inbound_pda_task ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, plan_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, plan_id BIGINT NOT NULL, task_no VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'PENDING', operator_id VARCHAR(128) NULL, operator_name VARCHAR(255) NULL, pushed_at DATETIME(3) NULL, started_at DATETIME(3) NULL, completed_at DATETIME(3) NULL, version INT NOT NULL DEFAULT 0, 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_ll_inbound_pda_task_plan (tenant_id, plan_id), + PRIMARY KEY (id), UNIQUE KEY uk_ll_inbound_pda_task_plan (customer_id, plan_id), UNIQUE KEY uk_ll_inbound_pda_task_no (task_no) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS ll_inbound_pda_operation ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, allocation_id BIGINT NULL, action VARCHAR(32) NOT NULL, idempotency_key VARCHAR(255) NOT NULL, request_hash VARCHAR(128) NOT NULL, request_json LONGTEXT NOT NULL, result_json LONGTEXT NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), UNIQUE KEY uk_ll_inbound_pda_operation_key (tenant_id, idempotency_key), + PRIMARY KEY (id), UNIQUE KEY uk_ll_inbound_pda_operation_key (customer_id, idempotency_key), KEY idx_ll_inbound_pda_operation_task (task_id), KEY idx_ll_inbound_pda_operation_allocation (allocation_id) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS ll_inbound_pallet_merge ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, merge_group_no VARCHAR(64) NOT NULL, source_pallet_id BIGINT NOT NULL, target_pallet_id BIGINT NOT NULL, source_allocation_id BIGINT NOT NULL, target_allocation_id BIGINT NOT NULL, source_line_id BIGINT NOT NULL, target_line_id BIGINT NOT NULL, @@ -494,7 +495,7 @@ CREATE TABLE IF NOT EXISTS ll_inbound_pallet_merge ( moved_package_count INT NOT NULL, moved_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0, source_pallet_scrapped TINYINT NOT NULL DEFAULT 0, status VARCHAR(32) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), - KEY idx_ll_inbound_merge_task (tenant_id, task_id), KEY idx_ll_inbound_merge_group (merge_group_no) + KEY idx_ll_inbound_merge_task (customer_id, task_id), KEY idx_ll_inbound_merge_group (merge_group_no) ) ENGINE=InnoDB; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='fba_warehouse_code'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN fba_warehouse_code VARCHAR(128) NULL AFTER inbound_code')); @@ -505,7 +506,7 @@ WHERE l.fba_warehouse_code IS NULL AND o.fba_warehouse_code IS NOT NULL; CREATE TABLE IF NOT EXISTS ll_inbound_fba_appointment ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, appointment_no VARCHAR(64) NOT NULL, appointment_at DATETIME(3) NOT NULL, appointment_status VARCHAR(32) NOT NULL DEFAULT 'PENDING', @@ -516,8 +517,8 @@ CREATE TABLE IF NOT EXISTS ll_inbound_fba_appointment ( 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_ll_fba_appointment_no (tenant_id, appointment_no), - KEY idx_ll_fba_appointment_list (tenant_id, appointment_status, fba_warehouse_code, created_at) + UNIQUE KEY uk_ll_fba_appointment_no (customer_id, appointment_no), + KEY idx_ll_fba_appointment_list (customer_id, appointment_status, fba_warehouse_code, created_at) ) ENGINE=InnoDB COMMENT='Batch FBA appointment'; CREATE TABLE IF NOT EXISTS ll_inbound_fba_appointment_item ( @@ -546,7 +547,7 @@ ALTER TABLE ll_loading_order_status_history CREATE TABLE IF NOT EXISTS ll_inventory_balance ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL COMMENT 'Customer tenant', customer_name VARCHAR(255) NULL COMMENT 'Customer name snapshot', + customer_id VARCHAR(128) NOT NULL COMMENT 'Customer tenant', customer_name VARCHAR(255) NULL COMMENT 'Customer name snapshot', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT 'Actual overseas warehouse ID', overseas_warehouse_code VARCHAR(128) NULL COMMENT 'Actual overseas warehouse code snapshot', overseas_warehouse_name VARCHAR(255) NULL COMMENT 'Actual overseas warehouse name snapshot', @@ -564,13 +565,13 @@ CREATE TABLE IF NOT EXISTS ll_inventory_balance ( 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_ll_inventory_dimension (tenant_id,overseas_warehouse_id,destination_warehouse_code,inbound_order_id,inbound_code), - KEY idx_ll_inventory_list (tenant_id,overseas_warehouse_id,updated_at), - KEY idx_ll_inventory_inbound (tenant_id,inbound_code) + UNIQUE KEY uk_ll_inventory_dimension (customer_id,overseas_warehouse_id,destination_warehouse_code,inbound_order_id,inbound_code), + KEY idx_ll_inventory_list (customer_id,overseas_warehouse_id,updated_at), + KEY idx_ll_inventory_inbound (customer_id,inbound_code) ) ENGINE=InnoDB COMMENT='Real-time inbound-order package inventory balance'; CREATE TABLE IF NOT EXISTS ll_inventory_position ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, balance_id BIGINT NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, balance_id BIGINT NOT NULL, overseas_warehouse_id VARCHAR(128) NOT NULL, destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '', pallet_id BIGINT NOT NULL, pallet_no VARCHAR(64) NOT NULL, batch_no VARCHAR(128) NOT NULL DEFAULT '', warehouse_location_id BIGINT NULL, @@ -582,16 +583,17 @@ CREATE TABLE IF NOT EXISTS ll_inventory_position ( 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_ll_inventory_position (balance_id,pallet_id,batch_no), - KEY idx_ll_inventory_position_pallet (tenant_id,pallet_id), - KEY idx_ll_inventory_position_location (tenant_id,overseas_warehouse_id,warehouse_location_code) + KEY idx_ll_inventory_position_pallet (customer_id,pallet_id), + KEY idx_ll_inventory_position_location (customer_id,overseas_warehouse_id,warehouse_location_code) ) ENGINE=InnoDB COMMENT='Inbound-order package stock by pallet, default location and batch'; CREATE TABLE IF NOT EXISTS ll_inventory_ledger ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, balance_id BIGINT NOT NULL, position_id BIGINT NULL, overseas_warehouse_id VARCHAR(128) NOT NULL, destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '', inbound_order_id BIGINT NOT NULL, pallet_id BIGINT NULL, pallet_no VARCHAR(64) NULL, batch_no VARCHAR(128) NULL, - warehouse_location_code VARCHAR(128) NULL, business_type VARCHAR(32) NOT NULL, + warehouse_location_code VARCHAR(128) NULL, sku_code VARCHAR(128) NULL, carton_mark VARCHAR(128) NULL, carton_packages BIGINT NULL, + business_type VARCHAR(32) NOT NULL, business_order_id BIGINT NULL, business_order_no VARCHAR(128) NULL, inbound_code VARCHAR(128) NULL, inbound_order_no VARCHAR(128) NULL, outbound_order_no VARCHAR(128) NULL, source_event_id VARCHAR(128) NULL, idempotency_key VARCHAR(255) NOT NULL, @@ -599,25 +601,25 @@ CREATE TABLE IF NOT EXISTS ll_inventory_ledger ( before_packages BIGINT NOT NULL, delta_packages BIGINT NOT NULL, after_packages BIGINT NOT NULL, operator_id VARCHAR(128) NULL, remark VARCHAR(1000) NULL, occurred_at DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), UNIQUE KEY uk_ll_inventory_ledger_idempotency (tenant_id,idempotency_key), - KEY idx_ll_inventory_ledger_query (tenant_id,overseas_warehouse_id,occurred_at), - KEY idx_ll_inventory_ledger_inbound (tenant_id,inbound_code,occurred_at), - KEY idx_ll_inventory_ledger_business (tenant_id,business_type,business_order_no) + PRIMARY KEY (id), UNIQUE KEY uk_ll_inventory_ledger_idempotency (customer_id,idempotency_key), + KEY idx_ll_inventory_ledger_query (customer_id,overseas_warehouse_id,occurred_at), + KEY idx_ll_inventory_ledger_inbound (customer_id,inbound_code,occurred_at), + KEY idx_ll_inventory_ledger_business (customer_id,business_type,business_order_no) ) ENGINE=InnoDB COMMENT='Immutable inbound-order package inventory ledger'; CREATE TABLE IF NOT EXISTS ll_inventory_operation ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, idempotency_key VARCHAR(255) NOT NULL, operation_type VARCHAR(32) NOT NULL, request_hash VARCHAR(128) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'PROCESSING', result_json LONGTEXT NULL, last_error VARCHAR(2000) NULL, 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_ll_inventory_operation_key (tenant_id,idempotency_key), - KEY idx_ll_inventory_operation_status (tenant_id,status,updated_at) + PRIMARY KEY (id), UNIQUE KEY uk_ll_inventory_operation_key (customer_id,idempotency_key), + KEY idx_ll_inventory_operation_status (customer_id,status,updated_at) ) ENGINE=InnoDB COMMENT='Inventory command idempotency record'; CREATE TABLE IF NOT EXISTS ll_inbound_stock_receipt ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, plan_id BIGINT NOT NULL, task_id BIGINT NOT NULL, allocation_id BIGINT NOT NULL, line_id BIGINT NOT NULL, order_id BIGINT NOT NULL, inbound_code VARCHAR(128) NOT NULL, warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(64) NOT NULL, @@ -626,13 +628,13 @@ CREATE TABLE IF NOT EXISTS ll_inbound_stock_receipt ( inventory_balance_id BIGINT NOT NULL, inventory_position_id BIGINT NOT NULL, inventory_operation_key VARCHAR(255) NOT NULL, received_at DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), UNIQUE KEY uk_ll_stock_receipt_task_allocation (tenant_id,task_id,allocation_id), - KEY idx_ll_stock_receipt_plan (tenant_id,plan_id,line_id), KEY idx_ll_stock_receipt_inventory (inventory_balance_id,inventory_position_id) + PRIMARY KEY (id), UNIQUE KEY uk_ll_stock_receipt_task_allocation (customer_id,task_id,allocation_id), + KEY idx_ll_stock_receipt_plan (customer_id,plan_id,line_id), KEY idx_ll_stock_receipt_inventory (inventory_balance_id,inventory_position_id) ) ENGINE=InnoDB COMMENT='Immutable inbound-order pallet package receipt snapshot'; CREATE TABLE IF NOT EXISTS ll_dispatch_order ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, dispatch_no VARCHAR(64) NOT NULL COMMENT 'Dispatch order number', overseas_warehouse_id VARCHAR(128) NULL COMMENT 'Overseas warehouse scope for PDA operations', overseas_warehouse_name VARCHAR(255) NULL, @@ -657,15 +659,15 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_order ( 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_ll_dispatch_order_no (tenant_id,dispatch_no), - KEY idx_ll_dispatch_order_list (tenant_id,status,depart_at), - KEY idx_ll_dispatch_order_vehicle (tenant_id,vehicle_plate) + UNIQUE KEY uk_ll_dispatch_order_no (customer_id,dispatch_no), + KEY idx_ll_dispatch_order_list (customer_id,status,depart_at), + KEY idx_ll_dispatch_order_vehicle (customer_id,vehicle_plate) ) ENGINE=InnoDB COMMENT='Last-leg pre-dispatch order'; CREATE TABLE IF NOT EXISTS ll_dispatch_item ( id BIGINT NOT NULL COMMENT 'Snowflake ID', dispatch_order_id BIGINT NOT NULL, - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, plan_id BIGINT NULL, plan_pallet_id BIGINT NULL, warehouse_pallet_id BIGINT NULL, @@ -686,14 +688,14 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item ( created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_item_source (dispatch_order_id,plan_pallet_id,warehouse_pallet_id), - KEY idx_ll_dispatch_item_order (tenant_id,order_id), - KEY idx_ll_dispatch_item_inbound (tenant_id,inbound_code), - KEY idx_ll_dispatch_item_pallet (tenant_id,plan_pallet_id,warehouse_pallet_id) + KEY idx_ll_dispatch_item_order (customer_id,order_id), + KEY idx_ll_dispatch_item_inbound (customer_id,inbound_code), + KEY idx_ll_dispatch_item_pallet (customer_id,plan_pallet_id,warehouse_pallet_id) ) ENGINE=InnoDB COMMENT='Frozen pallet and cargo snapshot for a dispatch order'; CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, source_pallet_key VARCHAR(160) NOT NULL COMMENT 'PLAN:{planPalletId} or RECEIVED:{warehousePalletId}', active_key VARCHAR(160) NULL COMMENT 'Equals source key only while lock is active', plan_id BIGINT NULL, @@ -706,90 +708,127 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock ( 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_ll_dispatch_active_pallet (tenant_id,active_key), - KEY idx_ll_dispatch_pallet_lock_order (tenant_id,dispatch_order_id,status), - KEY idx_ll_dispatch_pallet_lock_source (tenant_id,source_pallet_key,status) + UNIQUE KEY uk_ll_dispatch_active_pallet (customer_id,active_key), + KEY idx_ll_dispatch_pallet_lock_order (customer_id,dispatch_order_id,status), + KEY idx_ll_dispatch_pallet_lock_source (customer_id,source_pallet_key,status) ) ENGINE=InnoDB COMMENT='Active dispatch pallet exclusion lock'; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='order_refs'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN order_refs VARCHAR(2000) NULL AFTER order_no')); PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; CREATE TABLE IF NOT EXISTS ll_dispatch_pda_task ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, dispatch_order_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, dispatch_order_id BIGINT NOT NULL, task_no VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL, operator_id VARCHAR(128) NULL, warehouse_id VARCHAR(128) NULL COMMENT 'Selected overseas warehouse for PDA operation', operator_name VARCHAR(128) NULL, pushed_at DATETIME(3) NOT NULL, completed_at DATETIME(3) NULL, 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_ll_dispatch_pda_task_dispatch (tenant_id,dispatch_order_id), - UNIQUE KEY uk_ll_dispatch_pda_task_no (tenant_id,task_no) + PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_pda_task_dispatch (customer_id,dispatch_order_id), + UNIQUE KEY uk_ll_dispatch_pda_task_no (customer_id,task_no) ) ENGINE=InnoDB COMMENT='PDA task for a pre-arranged dispatch'; CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL, dispatch_item_id BIGINT NOT NULL, inventory_position_id BIGINT NOT NULL, packages BIGINT NOT NULL, product_quantity BIGINT NOT NULL DEFAULT 0, idempotency_key VARCHAR(255) NOT NULL, picked_at DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_pda_pick_key (tenant_id,idempotency_key), - KEY idx_ll_dispatch_pda_pick_item (tenant_id,task_id,dispatch_item_id) + PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_pda_pick_key (customer_id,idempotency_key), + KEY idx_ll_dispatch_pda_pick_item (customer_id,task_id,dispatch_item_id) ) ENGINE=InnoDB COMMENT='Immutable dispatch PDA pallet package picks'; CREATE TABLE IF NOT EXISTS ll_dispatch_pda_operation ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, task_id BIGINT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, task_id BIGINT NULL, operation_type VARCHAR(32) NOT NULL, idempotency_key VARCHAR(255) NOT NULL, request_hash CHAR(64) NOT NULL, result_json LONGTEXT NULL, status VARCHAR(32) NOT NULL, 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_ll_dispatch_pda_operation_key (tenant_id,idempotency_key) + PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_pda_operation_key (customer_id,idempotency_key) ) ENGINE=InnoDB COMMENT='Idempotent dispatch PDA operations'; CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick_order ( - id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, pick_id BIGINT NOT NULL, + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, pick_id BIGINT NOT NULL, receipt_id BIGINT NOT NULL, order_id BIGINT NOT NULL, packages BIGINT NOT NULL, product_quantity BIGINT NOT NULL DEFAULT 0, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), KEY idx_ll_dispatch_pick_order_pick (tenant_id,pick_id), - KEY idx_ll_dispatch_pick_order_receipt (tenant_id,receipt_id) + PRIMARY KEY (id), KEY idx_ll_dispatch_pick_order_pick (customer_id,pick_id), + KEY idx_ll_dispatch_pick_order_receipt (customer_id,receipt_id) ) ENGINE=InnoDB COMMENT='PDA pick attribution to frozen inbound receipt and tail order'; -- Transfer a dispatch to delivery: one outbound header, many pallet rows, one delivery header. CREATE TABLE IF NOT EXISTS ll_outbound_order ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, outbound_no VARCHAR(64) NOT NULL, dispatch_order_id BIGINT NOT NULL, dispatch_no VARCHAR(64) NOT NULL, overseas_warehouse_id VARCHAR(128) NULL, status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND', package_count INT NOT NULL DEFAULT 0, pallet_count INT NOT NULL DEFAULT 0, idempotency_key VARCHAR(255) NOT NULL, created_by VARCHAR(128) NULL, 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_ll_outbound_no (tenant_id,outbound_no), - UNIQUE KEY uk_ll_outbound_idempotency (tenant_id,idempotency_key), UNIQUE KEY uk_ll_outbound_dispatch (tenant_id,dispatch_order_id), - KEY idx_ll_outbound_list (tenant_id,status,created_at) + PRIMARY KEY (id), UNIQUE KEY uk_ll_outbound_no (customer_id,outbound_no), + UNIQUE KEY uk_ll_outbound_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_outbound_dispatch (customer_id,dispatch_order_id), + KEY idx_ll_outbound_list (customer_id,status,created_at) ) ENGINE=InnoDB COMMENT='Outbound document generated from a dispatch'; CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, outbound_order_id BIGINT NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, outbound_order_id BIGINT NOT NULL, warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(128) NOT NULL, destination_warehouse_id VARCHAR(128) NULL, destination_warehouse_code VARCHAR(128) NULL, destination_warehouse_name VARCHAR(255) NULL, package_count INT NOT NULL DEFAULT 0, order_refs VARCHAR(2000) NULL, inbound_code VARCHAR(128) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), - UNIQUE KEY uk_ll_outbound_pallet (tenant_id,outbound_order_id,warehouse_pallet_id), - KEY idx_ll_outbound_pallet_list (tenant_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (tenant_id,destination_warehouse_code) + UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id), + KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code) ) ENGINE=InnoDB COMMENT='Pallet detail snapshot for an outbound document'; CREATE TABLE IF NOT EXISTS ll_delivery_order ( - id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, delivery_no VARCHAR(64) NOT NULL, outbound_order_id BIGINT NOT NULL, outbound_no VARCHAR(64) NOT NULL, dispatch_order_id BIGINT NOT NULL, dispatch_no VARCHAR(64) NOT NULL, overseas_warehouse_id VARCHAR(128) NULL, status VARCHAR(32) NOT NULL DEFAULT 'WAIT_DELIVERY', package_count INT NOT NULL DEFAULT 0, pallet_count INT NOT NULL DEFAULT 0, idempotency_key VARCHAR(255) NOT NULL, created_by VARCHAR(128) NULL, 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_ll_delivery_no (tenant_id,delivery_no), - UNIQUE KEY uk_ll_delivery_idempotency (tenant_id,idempotency_key), UNIQUE KEY uk_ll_delivery_outbound (tenant_id,outbound_order_id), - KEY idx_ll_delivery_list (tenant_id,status,created_at) + PRIMARY KEY (id), UNIQUE KEY uk_ll_delivery_no (customer_id,delivery_no), + UNIQUE KEY uk_ll_delivery_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_delivery_outbound (customer_id,outbound_order_id), + KEY idx_ll_delivery_list (customer_id,status,created_at) ) ENGINE=InnoDB COMMENT='Delivery document linked to an outbound document'; +-- Customer warehouse transfer: generated tail order plus selected historical order/inbound bindings. +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_order ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', customer_id VARCHAR(128) NOT NULL, + transfer_no VARCHAR(64) NOT NULL, inbound_order_id BIGINT NOT NULL, inbound_code VARCHAR(128) NOT NULL, + lastleg_order_id BIGINT NOT NULL, lastleg_order_no VARCHAR(32) NOT NULL, + operating_warehouse_id VARCHAR(128) NOT NULL, fba_warehouse_code VARCHAR(128) NOT NULL, + delivery_address VARCHAR(1000) NOT NULL, package_count INT NOT NULL, delivery_method VARCHAR(64) NOT NULL, + estimated_arrival_at DATETIME(3) NOT NULL, fba_shipment_no VARCHAR(128) NOT NULL, + tracking_no VARCHAR(255) NULL, fba_box_nos VARCHAR(2000) NULL, remark VARCHAR(1000) NULL, + need_relabel TINYINT NOT NULL DEFAULT 0, relabel_application_id BIGINT NULL, relabel_no VARCHAR(64) NULL, + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW', idempotency_key VARCHAR(255) NOT NULL, + request_hash CHAR(64) NOT NULL, created_by VARCHAR(128) NULL, + 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_ll_wh_transfer_no (customer_id,transfer_no), + UNIQUE KEY uk_ll_wh_transfer_key (customer_id,idempotency_key), + UNIQUE KEY uk_ll_wh_transfer_tail_order (lastleg_order_id), + KEY idx_ll_wh_transfer_list (customer_id,status,created_at), KEY idx_ll_wh_transfer_inbound (customer_id,inbound_code) +) ENGINE=InnoDB COMMENT='Customer warehouse transfer order'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', transfer_order_id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, + source_lastleg_order_id BIGINT NOT NULL, source_lastleg_order_no VARCHAR(32) NOT NULL, + inbound_order_id BIGINT NOT NULL, inbound_code VARCHAR(128) NOT NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), + UNIQUE KEY uk_ll_wh_transfer_source (transfer_order_id,source_lastleg_order_id), + KEY idx_ll_wh_transfer_source_list (customer_id,transfer_order_id) +) ENGINE=InnoDB COMMENT='Selected historical customer orders and inbound bindings'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_item ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', transfer_order_id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, + sku_code VARCHAR(128) NOT NULL, product_name_zh VARCHAR(255) NOT NULL, + package_count INT NOT NULL, shipping_mark VARCHAR(255) NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), + KEY idx_ll_wh_transfer_item_list (customer_id,transfer_order_id), KEY idx_ll_wh_transfer_item_sku (customer_id,sku_code) +) ENGINE=InnoDB COMMENT='Frozen warehouse transfer cargo items'; + -- Customer relabel application: header, item snapshots, and immutable process history. CREATE TABLE IF NOT EXISTS ll_relabel_application ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, relabel_no VARCHAR(64) NOT NULL, lastleg_order_id BIGINT NOT NULL, customer_order_no VARCHAR(128) NOT NULL, @@ -808,18 +847,18 @@ CREATE TABLE IF NOT EXISTS ll_relabel_application ( 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_ll_relabel_application_no (tenant_id,relabel_no), - UNIQUE KEY uk_ll_relabel_application_idempotency (tenant_id,idempotency_key), + UNIQUE KEY uk_ll_relabel_application_no (customer_id,relabel_no), + UNIQUE KEY uk_ll_relabel_application_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_relabel_application_order (lastleg_order_id), - KEY idx_ll_relabel_application_list (tenant_id,status,created_at), - KEY idx_ll_relabel_application_customer (tenant_id,customer_order_no), - KEY idx_ll_relabel_application_warehouse (tenant_id,overseas_warehouse_id) + KEY idx_ll_relabel_application_list (customer_id,status,created_at), + KEY idx_ll_relabel_application_customer (customer_id,customer_order_no), + KEY idx_ll_relabel_application_warehouse (customer_id,overseas_warehouse_id) ) ENGINE=InnoDB COMMENT='Customer relabel application header'; CREATE TABLE IF NOT EXISTS ll_relabel_item ( id BIGINT NOT NULL COMMENT 'Snowflake ID', application_id BIGINT NOT NULL, - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, sku_code VARCHAR(128) NOT NULL, product_image_file_id VARCHAR(128) NULL, product_image_file_name VARCHAR(255) NULL, @@ -836,13 +875,13 @@ CREATE TABLE IF NOT EXISTS ll_relabel_item ( relabel_file_url VARCHAR(1000) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), - KEY idx_ll_relabel_item_application (tenant_id,application_id), - KEY idx_ll_relabel_item_sku (tenant_id,sku_code) + KEY idx_ll_relabel_item_application (customer_id,application_id), + KEY idx_ll_relabel_item_sku (customer_id,sku_code) ) ENGINE=InnoDB COMMENT='Frozen customer relabel cargo items'; CREATE TABLE IF NOT EXISTS ll_relabel_status_history ( id BIGINT NOT NULL COMMENT 'Snowflake ID', - tenant_id VARCHAR(128) NOT NULL, + customer_id VARCHAR(128) NOT NULL, application_id BIGINT NOT NULL, from_status VARCHAR(32) NULL, to_status VARCHAR(32) NOT NULL, @@ -850,5 +889,42 @@ CREATE TABLE IF NOT EXISTS ll_relabel_status_history ( remark VARCHAR(1000) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id), - KEY idx_ll_relabel_status_history_application (tenant_id,application_id,created_at) + KEY idx_ll_relabel_status_history_application (customer_id,application_id,created_at) ) ENGINE=InnoDB COMMENT='Immutable customer relabel process history'; + +CREATE TABLE IF NOT EXISTS ll_dropship_order_profile ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, + warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(128) NOT NULL, + source_pallet_no VARCHAR(128) NOT NULL COMMENT 'Pallet number snapshot supplied by first-leg handover', + inventory_position_id BIGINT NULL, status VARCHAR(32) NOT NULL DEFAULT 'RECEIVED', + idempotency_key VARCHAR(255) NOT NULL, version BIGINT NOT NULL DEFAULT 0, + 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_ll_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no), + UNIQUE KEY uk_ll_dropship_profile_key(customer_id,idempotency_key), KEY idx_ll_dropship_profile_order(customer_id,order_id) +) ENGINE=InnoDB COMMENT='One-click dropship pallet profile; one order may own multiple pallets'; + +CREATE TABLE IF NOT EXISTS ll_dropship_carton ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, profile_id BIGINT NOT NULL, + warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(128) NOT NULL, sku_code VARCHAR(128) NOT NULL, carton_mark VARCHAR(128) NOT NULL, + carton_sequence INT NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'AVAILABLE', outbound_order_no VARCHAR(128) NULL, + version BIGINT NOT NULL DEFAULT 0, 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_ll_dropship_carton(customer_id,order_id,carton_mark), + KEY idx_ll_dropship_carton_status(customer_id,status), KEY idx_ll_dropship_carton_sku(customer_id,sku_code,status) +) ENGINE=InnoDB COMMENT='One-click dropship carton-mark identity for one SKU box'; + +CREATE TABLE IF NOT EXISTS ll_dropship_sku_inventory ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, profile_id BIGINT NOT NULL, + sku_code VARCHAR(128) NOT NULL, product_name VARCHAR(255) NULL, package_count BIGINT NOT NULL, product_quantity BIGINT NOT NULL, + 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_ll_dropship_sku_inventory(profile_id,sku_code), + KEY idx_ll_dropship_sku_order(customer_id,order_id,sku_code) +) ENGINE=InnoDB COMMENT='One-click dropship pallet-level SKU package-count and product-quantity inventory summary'; + +CREATE TABLE IF NOT EXISTS ll_dropship_carton_ledger ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, carton_id BIGINT NOT NULL, + carton_mark VARCHAR(128) NOT NULL, sku_code VARCHAR(128) NOT NULL, operation_type VARCHAR(32) NOT NULL, + before_packages BIGINT NOT NULL, delta_packages BIGINT NOT NULL, after_packages BIGINT NOT NULL, + business_order_no VARCHAR(128) NULL, idempotency_key VARCHAR(255) NOT NULL, remark VARCHAR(1000) NULL, + occurred_at DATETIME(3) NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY(id), + UNIQUE KEY uk_ll_dropship_ledger_key(customer_id,idempotency_key), KEY idx_ll_dropship_ledger_sku(customer_id,sku_code,carton_mark) +) ENGINE=InnoDB COMMENT='Immutable dropship SKU/carton-mark package ledger'; diff --git a/lastleg/src/test/java/com/baiyue/lastleg/controller/LastlegOrderControllerTest.java b/lastleg/src/test/java/com/baiyue/lastleg/controller/LastlegOrderControllerTest.java index 490f83b..a00c65a 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/controller/LastlegOrderControllerTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/controller/LastlegOrderControllerTest.java @@ -40,8 +40,8 @@ class LastlegOrderControllerTest { verify(service).page(eq("200"), eq(query)); } - private static AuthenticatedUser user(long tenantId, String appCode) { - return new AuthenticatedUser(1L, tenantId, tenantId, 1L, "tester", appCode, "WEB", Set.of()); + private static AuthenticatedUser user(long customerId, String appCode) { + return new AuthenticatedUser(1L, customerId, customerId, 1L, "tester", appCode, "WEB", Set.of()); } private LastlegOrderController controller() { diff --git a/lastleg/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java index 7b8f71d..4dee1fd 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/delivery/DeliverySchemaContractTest.java @@ -23,17 +23,17 @@ class DeliverySchemaContractTest { assertTrue(schema.contains("create table if not exists ll_outbound_order")); assertTrue(schema.contains("status varchar(32) not null default 'wait_outbound'")); - assertTrue(schema.contains("unique key uk_ll_outbound_idempotency (tenant_id,idempotency_key)")); - assertTrue(schema.contains("unique key uk_ll_outbound_dispatch (tenant_id,dispatch_order_id)")); + assertTrue(schema.contains("unique key uk_ll_outbound_idempotency (customer_id,idempotency_key)")); + assertTrue(schema.contains("unique key uk_ll_outbound_dispatch (customer_id,dispatch_order_id)")); assertTrue(schema.contains("create table if not exists ll_outbound_pallet_item")); - assertTrue(schema.contains("unique key uk_ll_outbound_pallet (tenant_id,outbound_order_id,warehouse_pallet_id)")); + assertTrue(schema.contains("unique key uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id)")); assertTrue(schema.contains("destination_warehouse_code varchar(128) null")); assertTrue(schema.contains("order_refs varchar(2000) null")); assertTrue(schema.contains("inbound_code varchar(128) null")); assertTrue(schema.contains("create table if not exists ll_delivery_order")); assertTrue(schema.contains("status varchar(32) not null default 'wait_delivery'")); - assertTrue(schema.contains("unique key uk_ll_delivery_idempotency (tenant_id,idempotency_key)")); - assertTrue(schema.contains("unique key uk_ll_delivery_outbound (tenant_id,outbound_order_id)")); + assertTrue(schema.contains("unique key uk_ll_delivery_idempotency (customer_id,idempotency_key)")); + assertTrue(schema.contains("unique key uk_ll_delivery_outbound (customer_id,outbound_order_id)")); } @Test diff --git a/lastleg/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java index a998723..d02e94c 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java @@ -171,7 +171,7 @@ class DeliveryTransferServiceTest { DispatchItem source = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 12); source.setOrderId(901L); LastlegOrder tailOrder = new LastlegOrder(); - tailOrder.setId(901L); tailOrder.setTenantId("TENANT-A"); tailOrder.setCurrentStatus("PENDING_ASSIGNMENT"); + tailOrder.setId(901L); tailOrder.setCustomerId("TENANT-A"); tailOrder.setCurrentStatus("PENDING_ASSIGNMENT"); when(dispatchOrders.findByDispatchNo("TENANT-A", "DSP-101")).thenReturn(dispatch); when(dispatchOrders.lockById("TENANT-A", 101L)).thenReturn(dispatch); when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(source)); @@ -245,9 +245,9 @@ class DeliveryTransferServiceTest { verify(outboundPallets, never()).selectPage(any(), any()); } - private static DispatchOrder dispatch(Long id, String dispatchNo, String tenantId) { + private static DispatchOrder dispatch(Long id, String dispatchNo, String customerId) { DispatchOrder value = new DispatchOrder(); - value.setId(id); value.setTenantId(tenantId); value.setDispatchNo(dispatchNo); + value.setId(id); value.setCustomerId(customerId); value.setDispatchNo(dispatchNo); value.setOverseasWarehouseId("WH-US-1"); value.setStatus("READY_TO_DEPART"); return value; } @@ -266,7 +266,7 @@ class DeliveryTransferServiceTest { private static OutboundOrder outbound(Long id, Long dispatchId, String dispatchNo, String outboundNo, String idempotencyKey) { OutboundOrder value = new OutboundOrder(); - value.setId(id); value.setTenantId("TENANT-A"); value.setDispatchOrderId(dispatchId); + value.setId(id); value.setCustomerId("TENANT-A"); value.setDispatchOrderId(dispatchId); value.setDispatchNo(dispatchNo); value.setOutboundNo(outboundNo); value.setStatus("WAIT_OUTBOUND"); value.setOverseasWarehouseId("WH-US-1"); value.setPalletCount(2); value.setPackageCount(20); value.setIdempotencyKey(idempotencyKey); value.setCreatedAt(LocalDateTime.now()); @@ -275,7 +275,7 @@ class DeliveryTransferServiceTest { private static DeliveryOrder delivery(Long id, OutboundOrder outbound, String deliveryNo) { DeliveryOrder value = new DeliveryOrder(); - value.setId(id); value.setTenantId(outbound.getTenantId()); value.setDeliveryNo(deliveryNo); + value.setId(id); value.setCustomerId(outbound.getCustomerId()); value.setDeliveryNo(deliveryNo); value.setOutboundOrderId(outbound.getId()); value.setOutboundNo(outbound.getOutboundNo()); value.setDispatchOrderId(outbound.getDispatchOrderId()); value.setDispatchNo(outbound.getDispatchNo()); value.setOverseasWarehouseId(outbound.getOverseasWarehouseId()); value.setStatus("WAIT_DELIVERY"); @@ -287,7 +287,7 @@ class DeliveryTransferServiceTest { private static OutboundPalletItem pallet(Long warehousePalletId, String palletNo, String destinationCode, String destinationName, String inboundCode, int packages) { OutboundPalletItem value = new OutboundPalletItem(); - value.setId(warehousePalletId + 1000); value.setTenantId("TENANT-A"); value.setOutboundOrderId(301L); + value.setId(warehousePalletId + 1000); value.setCustomerId("TENANT-A"); value.setOutboundOrderId(301L); value.setWarehousePalletId(warehousePalletId); value.setPalletNo(palletNo); value.setDestinationWarehouseId("DEST-" + destinationCode); value.setDestinationWarehouseCode(destinationCode); value.setDestinationWarehouseName(destinationName); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java index ee68706..675bf5e 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/dispatch/pda/service/DispatchPdaWorkflowServiceTest.java @@ -30,7 +30,7 @@ class DispatchPdaWorkflowServiceTest { DispatchPdaWorkflowService service = new DispatchPdaWorkflowService(); ReflectionTestUtils.setField(service, "orders", orders); DispatchOrder order = new DispatchOrder(); - order.setId(1L); order.setTenantId("100"); order.setDispatchNo("PC1"); order.setVehiclePlate(" "); + order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setVehiclePlate(" "); when(orders.findByDispatchNo("100", "PC1")).thenReturn(order); ResponseStatusException exception = assertThrows(ResponseStatusException.class, @@ -44,7 +44,7 @@ class DispatchPdaWorkflowServiceTest { DispatchPdaWorkflowService service = new DispatchPdaWorkflowService(); ReflectionTestUtils.setField(service, "orders", orders); DispatchOrder order = new DispatchOrder(); - order.setId(1L); order.setTenantId("100"); order.setDispatchNo("PC1"); + order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setStatus("PENDING_DEPARTURE"); order.setVehiclePlate("CA-100"); order.setOverseasWarehouseId("WH-1"); when(orders.findByDispatchNo("100", "PC1")).thenReturn(order); @@ -61,7 +61,7 @@ class DispatchPdaWorkflowServiceTest { ReflectionTestUtils.setField(service, "orders", orders); ReflectionTestUtils.setField(service, "operations", operations); DispatchOrder order = new DispatchOrder(); - order.setId(1L); order.setTenantId("100"); order.setDispatchNo("PC1"); + order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setStatus("READY_TO_DEPART"); order.setVehiclePlate(" "); when(orders.findByDispatchNo("100", "PC1")).thenReturn(order); when(orders.lockById("100", 1L)).thenReturn(order); @@ -80,7 +80,7 @@ class DispatchPdaWorkflowServiceTest { ReflectionTestUtils.setField(service, "operations", operations); ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders); DispatchOrder order = new DispatchOrder(); - order.setId(1L); order.setTenantId("100"); order.setDispatchNo("PC1"); + order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setStatus("READY_TO_DEPART"); order.setVehiclePlate("CA-100"); when(orders.findByDispatchNo("100", "PC1")).thenReturn(order); when(orders.lockById("100", 1L)).thenReturn(order); @@ -101,7 +101,7 @@ class DispatchPdaWorkflowServiceTest { ReflectionTestUtils.setField(service, "operations", operations); ReflectionTestUtils.setField(service, "objectMapper", objectMapper); DispatchOrder order = new DispatchOrder(); - order.setId(1L); order.setTenantId("100"); order.setDispatchNo("PC1"); order.setStatus("DEPARTED"); + order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1"); order.setStatus("DEPARTED"); when(orders.findByDispatchNo("100", "PC1")).thenReturn(order); when(orders.lockById("100", 1L)).thenReturn(order); DispatchPdaOperation operation = new DispatchPdaOperation(); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/dropship/DropshipSchemaContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/dropship/DropshipSchemaContractTest.java new file mode 100644 index 0000000..4a09e12 --- /dev/null +++ b/lastleg/src/test/java/com/baiyue/lastleg/dropship/DropshipSchemaContractTest.java @@ -0,0 +1,30 @@ +package com.baiyue.lastleg.dropship; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +class DropshipSchemaContractTest { + @Test + void baselineAndIncrementalSqlKeepContainerAndMultiPalletIndexes() throws Exception { + String baseline = Files.readString( + Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8).toLowerCase(); + String migration = Files.readString( + Path.of("..", "scripts", "db", "incremental.sql"), StandardCharsets.UTF_8).toLowerCase(); + + assertThat(baseline).contains( + "key idx_ll_order_container(customer_id,container_no)", + "unique key uk_ll_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no)", + "unique key uk_ll_dropship_sku_inventory(profile_id,sku_code)", + "key idx_ll_dropship_profile_order(customer_id,order_id)"); + assertThat(migration).contains( + "add key idx_ll_order_container (customer_id,container_no)", + "add unique key uk_ll_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no)", + "add unique key uk_ll_dropship_sku_profile(profile_id,sku_code)", + "add key idx_ll_dropship_profile_order(customer_id,order_id)"); + } +} diff --git a/lastleg/src/test/java/com/baiyue/lastleg/dropship/service/DropshipOrderServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/dropship/service/DropshipOrderServiceTest.java new file mode 100644 index 0000000..75a264d --- /dev/null +++ b/lastleg/src/test/java/com/baiyue/lastleg/dropship/service/DropshipOrderServiceTest.java @@ -0,0 +1,76 @@ +package com.baiyue.lastleg.dropship.service; + +import com.baiyue.lastleg.dropship.dto.DropshipRequests; +import com.baiyue.lastleg.dropship.entity.DropshipOrderProfile; +import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory; +import com.baiyue.lastleg.dropship.entity.DropshipCarton; +import com.baiyue.lastleg.dropship.entity.DropshipCartonLedger; +import com.baiyue.lastleg.dropship.mapper.DropshipCartonLedgerMapper; +import com.baiyue.lastleg.dropship.mapper.DropshipCartonMapper; +import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper; +import com.baiyue.lastleg.dropship.mapper.DropshipSkuInventoryMapper; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class DropshipOrderServiceTest { + @Mock private LastlegOrderMapper orders; + @Mock private DropshipOrderProfileMapper profiles; + @Mock private DropshipSkuInventoryMapper skuInventory; + @Mock private DropshipCartonMapper cartons; + @Mock private DropshipCartonLedgerMapper ledger; + @Mock private WarehouseResourceGateway warehouse; + @InjectMocks private DropshipOrderService service; + + @Test + void receivesMultiplePalletsForOneInboundOrder() { + LastlegOrder order = new LastlegOrder(); + order.setId(10L); + order.setCustomerId("C-1"); + order.setOrderType("ONE_CLICK_DROPSHIP"); + order.setOrderNo("LL-10"); + order.setPackageCount(3); + order.setProductQuantity(30); + when(orders.selectById(10L)).thenReturn(order); + when(profiles.listOrder("C-1", 10L)).thenReturn(List.of()); + when(warehouse.ensureDropshipPallet(eq("C-1"), eq(20L), anyString())) + .thenReturn(new WarehouseResourceGateway.WarehousePalletReference(101L, "WH-P-1", 1L)) + .thenReturn(new WarehouseResourceGateway.WarehousePalletReference(102L, "WH-P-2", 1L)); + + DropshipRequests.Receive request = new DropshipRequests.Receive("C-1", 10L, 20L, "EVENT-1", + List.of( + new DropshipRequests.Pallet("SOURCE-P-1", List.of( + new DropshipRequests.SkuPackages("SKU-A", "Product A", 2, 20, + List.of("MARK-1", "MARK-2")))), + new DropshipRequests.Pallet("SOURCE-P-2", List.of( + new DropshipRequests.SkuPackages("SKU-A", "Product A", 1, 10, + List.of("MARK-3")))))); + + assertThat(service.receive(request)).isEqualTo(3); + verify(warehouse, times(2)).ensureDropshipPallet(eq("C-1"), eq(20L), anyString()); + verify(profiles, times(2)).insert(any(DropshipOrderProfile.class)); + verify(skuInventory, times(2)).insert(any(DropshipSkuInventory.class)); + verify(cartons, times(3)).insert(any(DropshipCarton.class)); + verify(ledger, times(3)).insert(any(DropshipCartonLedger.class)); + + ArgumentCaptor profilesCaptor = ArgumentCaptor.forClass(DropshipOrderProfile.class); + verify(profiles, times(2)).insert(profilesCaptor.capture()); + assertThat(profilesCaptor.getAllValues()).extracting(DropshipOrderProfile::getSourcePalletNo) + .containsExactly("SOURCE-P-1", "SOURCE-P-2"); + assertThat(profilesCaptor.getAllValues()).extracting(DropshipOrderProfile::getCustomerId) + .containsOnly("C-1"); + } +} diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/InboundPlanSchemaContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/InboundPlanSchemaContractTest.java index 466d96c..32b31ef 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/InboundPlanSchemaContractTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/InboundPlanSchemaContractTest.java @@ -14,7 +14,7 @@ class InboundPlanSchemaContractTest { try (var stream = getClass().getClassLoader().getResourceAsStream("db/lastleg.sql")) { assertTrue(stream != null, "lastleg schema must be available"); String schema = new String(stream.readAllBytes(), StandardCharsets.UTF_8).toLowerCase(); - assertTrue(schema.contains("unique key uk_ll_inbound_plan_container (tenant_id, container_no)")); + assertTrue(schema.contains("unique key uk_ll_inbound_plan_container (customer_id, container_no)")); assertTrue(schema.contains("default 'pending_assignment'")); assertTrue(schema.contains("required_package_count")); assertTrue(schema.contains("planned_package_count")); @@ -23,7 +23,7 @@ class InboundPlanSchemaContractTest { assertTrue(schema.contains("create table if not exists ll_inbound_pda_operation")); assertTrue(schema.contains("create table if not exists ll_inbound_pallet_merge")); assertTrue(schema.contains("create table if not exists ll_inbound_fba_appointment")); - assertTrue(schema.contains("unique key uk_ll_fba_appointment_no (tenant_id, appointment_no)")); + assertTrue(schema.contains("unique key uk_ll_fba_appointment_no (customer_id, appointment_no)")); assertTrue(schema.contains("create table if not exists ll_inbound_fba_appointment_item")); assertTrue(schema.contains("fba_warehouse_code")); assertFalse(schema.contains("cabinet_no")); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java index 20d86e9..1e2c79e 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java @@ -311,7 +311,7 @@ class InboundPdaWorkflowServiceTest { private InboundPlan plan() { InboundPlan value = new InboundPlan(); value.setId(1L); - value.setTenantId("100"); + value.setCustomerId("100"); value.setPlanNo("LIP1"); value.setStatus("ASSIGNED"); return value; @@ -321,7 +321,7 @@ class InboundPdaWorkflowServiceTest { InboundPdaTask value = new InboundPdaTask(); value.setId(2L); value.setPlanId(1L); - value.setTenantId("100"); + value.setCustomerId("100"); value.setTaskNo("PDA2"); value.setStatus("PDA_PENDING"); value.setVersion(0); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java index c114aea..ab3822a 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundStockReceiptServiceTest.java @@ -127,7 +127,7 @@ class InboundStockReceiptServiceTest { line.setInboundCode("IN-1"); line.setDestinationWarehouseId("WH-DEST"); line.setDestinationWarehouseCode("ONT8"); line.setDestinationWarehouseName("ONT8"); line.setRequiredPackageCount(required); line.setVolumeCbm(new BigDecimal("1.0")); - LastlegOrder order = new LastlegOrder(); order.setId(101L); order.setTenantId("100"); + LastlegOrder order = new LastlegOrder(); order.setId(101L); order.setCustomerId("100"); order.setOrderNo("LMO-1"); order.setCustomerName("Customer"); order.setOverseasWarehouseId("OW-1"); order.setOverseasWarehouseName("Warehouse"); order.setSourceEventId("evt-1"); order.setFbaWarehouseCode("ONT8"); LastlegOrderDetail detail = detail(201L, "SKU-1", required, quantity); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentServiceTest.java index 2890520..0430ca2 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundFbaAppointmentServiceTest.java @@ -125,7 +125,7 @@ class InboundFbaAppointmentServiceTest { private InboundFbaAppointment appointment(Long id, String number, String fba) { InboundFbaAppointment value = new InboundFbaAppointment(); value.setId(id); - value.setTenantId("100"); + value.setCustomerId("100"); value.setAppointmentNo(number); value.setAppointmentAt(appointmentAt); value.setAppointmentStatus("SUCCESS"); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java index e693e37..179612f 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java @@ -98,7 +98,7 @@ class InboundPlanServiceTest { InboundPlan existing = new InboundPlan(); existing.setId(9L); existing.setPlanNo("LIP9"); - existing.setTenantId("100"); + existing.setCustomerId("100"); existing.setContainerNo("CONT-1"); existing.setStatus("PLANNED"); existing.setLineCount(1); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inventory/InventorySchemaContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inventory/InventorySchemaContractTest.java index dfb926e..dc9c506 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inventory/InventorySchemaContractTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inventory/InventorySchemaContractTest.java @@ -28,7 +28,7 @@ class InventorySchemaContractTest { } assertTrue(schema.contains("create table if not exists ll_inventory_balance")); - assertTrue(schema.contains("unique key uk_ll_inventory_dimension (tenant_id,overseas_warehouse_id,destination_warehouse_code,inbound_order_id,inbound_code)")); + assertTrue(schema.contains("unique key uk_ll_inventory_dimension (customer_id,overseas_warehouse_id,destination_warehouse_code,inbound_order_id,inbound_code)")); assertTrue(schema.contains("inbound_order_id bigint not null")); assertTrue(schema.contains("inbound_code varchar(128) not null")); assertTrue(schema.contains("available_packages bigint not null default 0")); @@ -46,12 +46,13 @@ class InventorySchemaContractTest { assertTrue(schema.contains("before_packages bigint not null")); assertTrue(schema.contains("delta_packages bigint not null")); assertTrue(schema.contains("after_packages bigint not null")); - assertFalse(tableDefinition(schema, "ll_inventory_ledger").contains("sku_code")); + assertTrue(tableDefinition(schema, "ll_inventory_ledger").contains("sku_code")); + assertTrue(tableDefinition(schema, "ll_inventory_ledger").contains("carton_mark")); assertFalse(tableDefinition(schema, "ll_inventory_ledger").contains("before_quantity")); - assertTrue(schema.contains("unique key uk_ll_inventory_ledger_idempotency (tenant_id,idempotency_key)")); + assertTrue(schema.contains("unique key uk_ll_inventory_ledger_idempotency (customer_id,idempotency_key)")); assertTrue(schema.contains("create table if not exists ll_inventory_operation")); assertTrue(schema.contains("create table if not exists ll_inbound_stock_receipt")); - assertTrue(schema.contains("unique key uk_ll_stock_receipt_task_allocation (tenant_id,task_id,allocation_id)")); + assertTrue(schema.contains("unique key uk_ll_stock_receipt_task_allocation (customer_id,task_id,allocation_id)")); assertFalse(tableDefinition(schema, "ll_inventory_ledger").contains("updated_at")); } @@ -100,7 +101,8 @@ class InventorySchemaContractTest { assertThrowsNoField(InventoryBalance.class, "availableQuantity"); assertThrowsNoField(InventoryPosition.class, "skuCode"); assertThrowsNoField(InventoryPosition.class, "productQuantity"); - assertThrowsNoField(InventoryLedger.class, "skuCode"); + assertNotNull(InventoryLedger.class.getDeclaredField("skuCode")); + assertNotNull(InventoryLedger.class.getDeclaredField("cartonMark")); assertThrowsNoField(InventoryLedger.class, "deltaQuantity"); } diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java index 1621ff1..cfc7980 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inventory/command/InventoryCommandServiceTest.java @@ -133,7 +133,7 @@ class InventoryCommandServiceTest { private InventoryBalance balance(Long id, long available) { InventoryBalance value = new InventoryBalance(); - value.setId(id); value.setTenantId("100"); value.setOverseasWarehouseId("WH-1"); + value.setId(id); value.setCustomerId("100"); value.setOverseasWarehouseId("WH-1"); value.setDestinationWarehouseCode("ONT8"); value.setInboundOrderId(101L); value.setInboundOrderNo("IN-1"); value.setInboundCode("IN-CODE-1"); value.setAvailablePackages(available); @@ -144,7 +144,7 @@ class InventoryCommandServiceTest { private InventoryPosition position(Long id, Long balanceId, long available) { InventoryPosition value = new InventoryPosition(); - value.setId(id); value.setTenantId("100"); value.setBalanceId(balanceId); + value.setId(id); value.setCustomerId("100"); value.setBalanceId(balanceId); value.setPalletId(300L); value.setPalletNo("PLT-1"); value.setBatchNo("DEFAULT"); value.setAvailablePackages(available); value.setHoldPackages(0L); value.setAllocatedPackages(0L); value.setPickedPackages(0L); value.setReceivingPackages(0L); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryControllerTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryControllerTest.java index 971a0fa..4510325 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryControllerTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/controller/InventoryQueryControllerTest.java @@ -46,8 +46,8 @@ class InventoryQueryControllerTest { () -> controller.positions(null, null, 1L, positionQuery)); } - private static AuthenticatedUser user(long tenantId, String appCode) { - return new AuthenticatedUser(1L, tenantId, tenantId, 1L, "tester", appCode, "WEB", Set.of()); + private static AuthenticatedUser user(long customerId, String appCode) { + return new AuthenticatedUser(1L, customerId, customerId, 1L, "tester", appCode, "WEB", Set.of()); } private InventoryQueryController controller() { diff --git a/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryServiceTest.java index 193c59d..1bbfa8a 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/inventory/query/service/InventoryQueryServiceTest.java @@ -55,7 +55,7 @@ class InventoryQueryServiceTest { InventoryQueryRequests.BalanceQuery query = balanceQuery(null); InventoryBalance balance = new InventoryBalance(); balance.setId(1L); - balance.setTenantId("100"); + balance.setCustomerId("100"); when(mapper.countBalances("100", query)).thenReturn(1L); when(mapper.balances("100", query, 0, 20)).thenReturn(List.of(balance)); diff --git a/lastleg/src/test/java/com/baiyue/lastleg/relabel/RelabelContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/relabel/RelabelContractTest.java index 6fadddd..e603bb6 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/relabel/RelabelContractTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/relabel/RelabelContractTest.java @@ -32,7 +32,7 @@ class RelabelContractTest { assertTrue(schema.contains("ll_relabel_application")); assertTrue(schema.contains("ll_relabel_item")); assertTrue(schema.contains("ll_relabel_status_history")); - assertTrue(schema.contains("tenant_id")); + assertTrue(schema.contains("customer_id")); assertTrue(schema.contains("idempotency_key")); assertTrue(schema.contains("request_hash")); } diff --git a/lastleg/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java index 5830290..e655340 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/relabel/service/RelabelApplicationServiceTest.java @@ -184,11 +184,11 @@ class RelabelApplicationServiceTest { } private void stubDetail(RelabelApplication application) { - when(applications.findNo(application.getTenantId(), application.getRelabelNo())).thenReturn(application); + when(applications.findNo(application.getCustomerId(), application.getRelabelNo())).thenReturn(application); when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 10, 6))); when(histories.selectList(any())).thenReturn(List.of()); when(orders.selectById(application.getLastlegOrderId())).thenReturn(order(application.getLastlegOrderId(), - application.getTenantId(), "LMO-RELABEL-1", application.getStatus())); + application.getCustomerId(), "LMO-RELABEL-1", application.getStatus())); } private static RelabelRequests.Create request(String key, int total, int notified) { @@ -198,10 +198,10 @@ class RelabelApplicationServiceTest { "SKU-NEW", "Label on carton", "file-1", "relabel.pdf", "https://files/relabel.pdf"))); } - private static RelabelApplication application(long id, String no, String tenantId, String key, String requestHash, + private static RelabelApplication application(long id, String no, String customerId, String key, String requestHash, String status) { RelabelApplication value = new RelabelApplication(); - value.setId(id); value.setTenantId(tenantId); value.setRelabelNo(no); value.setIdempotencyKey(key); + value.setId(id); value.setCustomerId(customerId); value.setRelabelNo(no); value.setIdempotencyKey(key); value.setRequestHash(requestHash); value.setStatus(status); value.setCreatedAt(LocalDateTime.now()); value.setContactName("Li"); value.setContactPhone("13800000000"); value.setCustomerOrderNo("CUSTOMER-1"); value.setOverseasWarehouseId("WH-1"); value.setLabelDeadline(LocalDateTime.now().plusDays(1)); @@ -211,15 +211,15 @@ class RelabelApplicationServiceTest { private static RelabelItem item(long id, long applicationId, String sku, int total, int notified) { RelabelItem value = new RelabelItem(); - value.setId(id); value.setApplicationId(applicationId); value.setTenantId("TENANT-A"); value.setSkuCode(sku); + value.setId(id); value.setApplicationId(applicationId); value.setCustomerId("TENANT-A"); value.setSkuCode(sku); value.setProductNameZh("Chinese name"); value.setTotalPackageCount(total); value.setNotifiedPackageCount(notified); value.setShipmentNo("SHP-1"); value.setNewSkuCode("SKU-NEW"); return value; } - private static LastlegOrder order(long id, String tenantId, String orderNo, String status) { + private static LastlegOrder order(long id, String customerId, String orderNo, String status) { LastlegOrder value = new LastlegOrder(); - value.setId(id); value.setTenantId(tenantId); value.setOrderNo(orderNo); value.setCurrentStatus(status); + value.setId(id); value.setCustomerId(customerId); value.setOrderNo(orderNo); value.setCurrentStatus(status); return value; } diff --git a/lastleg/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java index a816875..bd08fac 100644 --- a/lastleg/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java +++ b/lastleg/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java @@ -75,7 +75,7 @@ class LastlegOrderServiceTest { } @Test - void batchRejectsMissingOrCrossTenantIdsBeforeWriting() { + void batchRejectsMissingOrCrossCustomerIdsBeforeWriting() { when(orders.selectList(any())).thenReturn(List.of(order(11L, "100", "PENDING_ASSIGNMENT", null))); var request = new LastlegOrderRequests.TransferDestination( List.of(11L, 22L), "wh-2", "LAX-02", "Los Angeles 02", null); @@ -86,10 +86,10 @@ class LastlegOrderServiceTest { verify(orders, never()).updateById(any(LastlegOrder.class)); } - private static LastlegOrder order(long id, String tenantId, String status, String inboundOrderNo) { + private static LastlegOrder order(long id, String customerId, String status, String inboundOrderNo) { LastlegOrder order = new LastlegOrder(); order.setId(id); - order.setTenantId(tenantId); + order.setCustomerId(customerId); order.setCurrentStatus(status); order.setInboundOrderNo(inboundOrderNo); return order; diff --git a/lastleg/src/test/java/com/baiyue/lastleg/transfer/WarehouseTransferSchemaContractTest.java b/lastleg/src/test/java/com/baiyue/lastleg/transfer/WarehouseTransferSchemaContractTest.java new file mode 100644 index 0000000..a761f4e --- /dev/null +++ b/lastleg/src/test/java/com/baiyue/lastleg/transfer/WarehouseTransferSchemaContractTest.java @@ -0,0 +1,21 @@ +package com.baiyue.lastleg.transfer; + +import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class WarehouseTransferSchemaContractTest { + @Test + void baselineAndIncrementalContainTransferAndHistoricalOrderBindingTables() throws Exception { + String baseline = Files.readString(Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8).toLowerCase(); + String migration = Files.readString(Path.of("..", "scripts", "db", "incremental.sql"), StandardCharsets.UTF_8).toLowerCase(); + for (String table : new String[]{"ll_warehouse_transfer_order", "ll_warehouse_transfer_source_order", "ll_warehouse_transfer_item"}) { + assertTrue(baseline.contains("create table if not exists " + table)); + assertTrue(migration.contains("create table if not exists " + table)); + } + assertTrue(migration.contains("information_schema.statistics")); + assertTrue(migration.contains("uk_ll_wh_transfer_source")); + } +} diff --git a/lastleg/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java b/lastleg/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java new file mode 100644 index 0000000..e519685 --- /dev/null +++ b/lastleg/src/test/java/com/baiyue/lastleg/transfer/service/WarehouseTransferServiceTest.java @@ -0,0 +1,154 @@ +package com.baiyue.lastleg.transfer.service; + +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.relabel.entity.RelabelApplication; +import com.baiyue.lastleg.relabel.entity.RelabelItem; +import com.baiyue.lastleg.relabel.entity.RelabelStatusHistory; +import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper; +import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper; +import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder; +import com.baiyue.lastleg.transfer.entity.WarehouseTransferSourceOrder; +import com.baiyue.lastleg.transfer.mapper.InboundSnapshotMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferItemMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferOrderMapper; +import com.baiyue.lastleg.transfer.mapper.WarehouseTransferSourceOrderMapper; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class WarehouseTransferServiceTest { + @Mock WarehouseTransferOrderMapper transfers; + @Mock WarehouseTransferItemMapper transferItems; + @Mock WarehouseTransferSourceOrderMapper sourceOrders; + @Mock InboundSnapshotMapper inboundSnapshots; + @Mock LastlegOrderMapper orders; + @Mock LastlegOrderDetailMapper orderDetails; + @Mock LastlegOrderStatusHistoryMapper orderHistories; + @Mock RelabelApplicationMapper relabelApplications; + @Mock RelabelItemMapper relabelItems; + @Mock RelabelStatusHistoryMapper relabelHistories; + @Mock LastlegOrderNoGenerator orderNoGenerator; + private WarehouseTransferService service; + + @BeforeEach + void setUp() { + service = new WarehouseTransferService(); + ReflectionTestUtils.setField(service, "transfers", transfers); + ReflectionTestUtils.setField(service, "transferItems", transferItems); + ReflectionTestUtils.setField(service, "sourceOrders", sourceOrders); + ReflectionTestUtils.setField(service, "inboundSnapshots", inboundSnapshots); + ReflectionTestUtils.setField(service, "orders", orders); + ReflectionTestUtils.setField(service, "orderDetails", orderDetails); + ReflectionTestUtils.setField(service, "orderHistories", orderHistories); + ReflectionTestUtils.setField(service, "relabelApplications", relabelApplications); + ReflectionTestUtils.setField(service, "relabelItems", relabelItems); + ReflectionTestUtils.setField(service, "relabelHistories", relabelHistories); + ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator); + ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules()); + lenient().when(orderNoGenerator.next()).thenReturn("LMO-TRANSFER-1"); + } + + @Test + void relabelTransferLinksHistoricalOrderAndInboundAndCreatesOnePendingReviewTailOrder() { + WarehouseTransferRequests.Create request = request(true); + LastlegOrder historical = historicalOrder(); + when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L); + when(orders.selectList(any())).thenReturn(List.of(historical)); + when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> persistedTransfer()); + when(transferItems.selectList(any())).thenReturn(List.of()); + when(sourceOrders.selectList(any())).thenReturn(List.of()); + + var result = service.create("TENANT-A", "USER-1", request); + + ArgumentCaptor tailOrder = ArgumentCaptor.forClass(LastlegOrder.class); + verify(orders).insert(tailOrder.capture()); + assertEquals(WarehouseTransferService.ORDER_TYPE, tailOrder.getValue().getOrderType()); + assertEquals(WarehouseTransferService.PENDING_REVIEW, tailOrder.getValue().getCurrentStatus()); + assertEquals("LMO-TRANSFER-1", tailOrder.getValue().getOrderNo()); + verify(sourceOrders).insert(any(WarehouseTransferSourceOrder.class)); + verify(relabelApplications).insert(any(RelabelApplication.class)); + verify(relabelItems).insert(any(RelabelItem.class)); + verify(relabelHistories).insert(any(RelabelStatusHistory.class)); + verify(orders).updateById(tailOrder.getValue()); + assertNotNull(tailOrder.getValue().getRelabelOrderNo()); + assertEquals("PENDING_PROCESSING", tailOrder.getValue().getRelabelStatus()); + assertEquals(WarehouseTransferService.PENDING_REVIEW, result.orderStatus()); + } + + @Test + void transferWithoutRelabelDoesNotCreateRelabelDocument() { + when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L); + when(orders.selectList(any())).thenReturn(List.of(historicalOrder())); + when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> persistedTransfer()); + when(transferItems.selectList(any())).thenReturn(List.of()); + when(sourceOrders.selectList(any())).thenReturn(List.of()); + + var result = service.create("TENANT-A", "USER-1", request(false)); + + verify(relabelApplications, never()).insert(any(RelabelApplication.class)); + verify(relabelItems, never()).insert(any(RelabelItem.class)); + verify(relabelHistories, never()).insert(any(RelabelStatusHistory.class)); + assertNull(result.relabelNo()); + } + + @Test + void selectedOrderMustBelongToTenantAndHaveInboundBinding() { + when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L); + when(orders.selectList(any())).thenReturn(List.of()); + + ResponseStatusException exception = assertThrows(ResponseStatusException.class, + () -> service.create("TENANT-A", "USER-1", request(false))); + + assertEquals(404, exception.getStatusCode().value()); + verify(transfers, never()).insert(any(WarehouseTransferOrder.class)); + verify(orders, never()).insert(any(LastlegOrder.class)); + } + + private static WarehouseTransferRequests.Create request(boolean needRelabel) { + return new WarehouseTransferRequests.Create("IN-1", List.of(11L), "WH-1", "ONT8", + "123 FBA Road", 5, "TRUCK", LocalDateTime.of(2026, 8, 20, 10, 0), "FBA-SHP-1", + "TRACK-1", "BOX-1-5", "remark", needRelabel, "key-1", + List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 5, "MARK-1"))); + } + + private static LastlegOrder historicalOrder() { + LastlegOrder order = new LastlegOrder(); + order.setId(11L); order.setCustomerId("TENANT-A"); order.setOrderNo("LMO-OLD-1"); order.setInboundCode("IN-1"); + return order; + } + + private static WarehouseTransferOrder persistedTransfer() { + WarehouseTransferOrder transfer = new WarehouseTransferOrder(); + transfer.setId(21L); transfer.setCustomerId("TENANT-A"); transfer.setTransferNo("WTR-1"); + transfer.setInboundOrderId(101L); transfer.setInboundCode("IN-1"); transfer.setLastlegOrderId(31L); + transfer.setLastlegOrderNo("LMO-TRANSFER-1"); transfer.setNeedRelabel(false); transfer.setPackageCount(5); + return transfer; + } +} diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index ab17948..abe1ec7 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -1148,3 +1148,159 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE migration_018 FROM @ddl; EXECUTE migration_018; DEALLOCATE PREPARE migration_018; -- No DROP COLUMN is included. Existing columns and business rows remain recoverable. + +-- ============================================================================ +-- 2026-08-17 / 019 +-- Warehouse transfer orders with historical customer-order/inbound bindings. +-- ============================================================================ +USE baiyue_lastleg; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_order ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', tenant_id VARCHAR(128) NOT NULL, + transfer_no VARCHAR(64) NOT NULL, inbound_order_id BIGINT NOT NULL, inbound_code VARCHAR(128) NOT NULL, + lastleg_order_id BIGINT NOT NULL, lastleg_order_no VARCHAR(32) NOT NULL, + operating_warehouse_id VARCHAR(128) NOT NULL, fba_warehouse_code VARCHAR(128) NOT NULL, + delivery_address VARCHAR(1000) NOT NULL, package_count INT NOT NULL, delivery_method VARCHAR(64) NOT NULL, + estimated_arrival_at DATETIME(3) NOT NULL, fba_shipment_no VARCHAR(128) NOT NULL, + tracking_no VARCHAR(255) NULL, fba_box_nos VARCHAR(2000) NULL, remark VARCHAR(1000) NULL, + need_relabel TINYINT NOT NULL DEFAULT 0, relabel_application_id BIGINT NULL, relabel_no VARCHAR(64) NULL, + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW', idempotency_key VARCHAR(255) NOT NULL, + request_hash CHAR(64) NOT NULL, created_by VARCHAR(128) NULL, + 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) +) ENGINE=InnoDB COMMENT='Customer warehouse transfer order'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', transfer_order_id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, + source_lastleg_order_id BIGINT NOT NULL, source_lastleg_order_no VARCHAR(32) NOT NULL, + inbound_order_id BIGINT NOT NULL, inbound_code VARCHAR(128) NOT NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id) +) ENGINE=InnoDB COMMENT='Selected historical customer orders and inbound bindings'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_item ( + id BIGINT NOT NULL COMMENT 'Snowflake ID', transfer_order_id BIGINT NOT NULL, tenant_id VARCHAR(128) NOT NULL, + sku_code VARCHAR(128) NOT NULL, product_name_zh VARCHAR(255) NOT NULL, + package_count INT NOT NULL, shipping_mark VARCHAR(255) NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY (id) +) ENGINE=InnoDB COMMENT='Frozen warehouse transfer cargo items'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND index_name='uk_ll_wh_transfer_no'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD UNIQUE KEY uk_ll_wh_transfer_no (tenant_id,transfer_no)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND index_name='uk_ll_wh_transfer_key'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD UNIQUE KEY uk_ll_wh_transfer_key (tenant_id,idempotency_key)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND index_name='uk_ll_wh_transfer_tail_order'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD UNIQUE KEY uk_ll_wh_transfer_tail_order (lastleg_order_id)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND index_name='idx_ll_wh_transfer_list'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD KEY idx_ll_wh_transfer_list (tenant_id,status,created_at)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND index_name='idx_ll_wh_transfer_inbound'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD KEY idx_ll_wh_transfer_inbound (tenant_id,inbound_code)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_source_order' AND index_name='uk_ll_wh_transfer_source'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_source_order ADD UNIQUE KEY uk_ll_wh_transfer_source (transfer_order_id,source_lastleg_order_id)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_source_order' AND index_name='idx_ll_wh_transfer_source_list'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_source_order ADD KEY idx_ll_wh_transfer_source_list (tenant_id,transfer_order_id)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_item' AND index_name='idx_ll_wh_transfer_item_list'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_item ADD KEY idx_ll_wh_transfer_item_list (tenant_id,transfer_order_id)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_item' AND index_name='idx_ll_wh_transfer_item_sku'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_item ADD KEY idx_ll_wh_transfer_item_sku (tenant_id,sku_code)')); +PREPARE transfer_019 FROM @ddl; EXECUTE transfer_019; DEALLOCATE PREPARE transfer_019; + +-- 2026-08-17 / customer naming and one-click dropship carton inventory. +-- Rename only existing columns; deployed data and index definitions remain intact. +DELIMITER // +CREATE PROCEDURE migrate_tenant_columns_to_customer() +BEGIN + DECLARE done INT DEFAULT FALSE; + DECLARE table_name_value VARCHAR(128); + DECLARE column_cursor CURSOR FOR + SELECT c.table_name FROM information_schema.columns c + WHERE c.table_schema=DATABASE() AND c.column_name='tenant_id' + AND NOT EXISTS (SELECT 1 FROM information_schema.columns c2 + WHERE c2.table_schema=c.table_schema AND c2.table_name=c.table_name AND c2.column_name='customer_id'); + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=TRUE; + OPEN column_cursor; + rename_loop: LOOP + FETCH column_cursor INTO table_name_value; + IF done THEN LEAVE rename_loop; END IF; + SET @rename_sql=CONCAT('ALTER TABLE `', table_name_value, '` RENAME COLUMN tenant_id TO customer_id'); + PREPARE rename_statement FROM @rename_sql; + EXECUTE rename_statement; + DEALLOCATE PREPARE rename_statement; + END LOOP; + CLOSE column_cursor; +END// +DELIMITER ; +CALL migrate_tenant_columns_to_customer(); +DROP PROCEDURE migrate_tenant_columns_to_customer; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_inventory_ledger ADD COLUMN sku_code VARCHAR(128) NULL AFTER batch_no')); +PREPARE inventory_sku_020 FROM @ddl; EXECUTE inventory_sku_020; DEALLOCATE PREPARE inventory_sku_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='carton_mark'), 'SELECT 1', IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='carton_no'), 'ALTER TABLE ll_inventory_ledger RENAME COLUMN carton_no TO carton_mark', 'ALTER TABLE ll_inventory_ledger ADD COLUMN carton_mark VARCHAR(128) NULL AFTER sku_code')))); +PREPARE inventory_carton_020 FROM @ddl; EXECUTE inventory_carton_020; DEALLOCATE PREPARE inventory_carton_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inventory_ledger' AND column_name='carton_packages'), 'SELECT 1', 'ALTER TABLE ll_inventory_ledger ADD COLUMN carton_packages BIGINT NULL AFTER carton_mark')); +PREPARE inventory_carton_qty_020 FROM @ddl; EXECUTE inventory_carton_qty_020; DEALLOCATE PREPARE inventory_carton_qty_020; + +CREATE TABLE IF NOT EXISTS ll_dropship_order_profile ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, + warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(128) NOT NULL, inventory_position_id BIGINT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'RECEIVED', idempotency_key VARCHAR(255) NOT NULL, version BIGINT NOT NULL DEFAULT 0, + 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_ll_dropship_profile_order(customer_id,order_id), UNIQUE KEY uk_ll_dropship_profile_key(customer_id,idempotency_key) +) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS ll_dropship_carton ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, profile_id BIGINT NOT NULL, + warehouse_pallet_id BIGINT NOT NULL, pallet_no VARCHAR(128) NOT NULL, sku_code VARCHAR(128) NOT NULL, carton_mark VARCHAR(128) NOT NULL, carton_sequence INT NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'AVAILABLE', outbound_order_no VARCHAR(128) NULL, version BIGINT NOT NULL DEFAULT 0, + 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_ll_dropship_carton(customer_id,order_id,carton_mark), KEY idx_ll_dropship_carton_status(customer_id,status), + KEY idx_ll_dropship_carton_sku(customer_id,sku_code,status) +) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS ll_dropship_sku_inventory ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, profile_id BIGINT NOT NULL, + sku_code VARCHAR(128) NOT NULL, product_name VARCHAR(255) NULL, package_count BIGINT NOT NULL, product_quantity BIGINT NOT NULL, + 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_ll_dropship_sku_inventory(customer_id,order_id,sku_code) +) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS ll_dropship_carton_ledger ( + id BIGINT NOT NULL, customer_id VARCHAR(128) NOT NULL, order_id BIGINT NOT NULL, carton_id BIGINT NOT NULL, + carton_mark VARCHAR(128) NOT NULL, sku_code VARCHAR(128) NOT NULL, operation_type VARCHAR(32) NOT NULL, + before_packages BIGINT NOT NULL, delta_packages BIGINT NOT NULL, after_packages BIGINT NOT NULL, + business_order_no VARCHAR(128) NULL, idempotency_key VARCHAR(255) NOT NULL, remark VARCHAR(1000) NULL, occurred_at DATETIME(3) NOT NULL, + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY(id), UNIQUE KEY uk_ll_dropship_ledger_key(customer_id,idempotency_key), + KEY idx_ll_dropship_ledger_sku(customer_id,sku_code,carton_mark) +) ENGINE=InnoDB; +-- Dropship profile stores the immutable first-leg pallet number snapshot. +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND column_name='source_pallet_no'), 'SELECT 1', 'ALTER TABLE ll_dropship_order_profile ADD COLUMN source_pallet_no VARCHAR(128) NULL COMMENT ''Pallet number snapshot supplied by first-leg handover'' AFTER pallet_no')); +PREPARE dropship_source_pallet_no_stmt FROM @ddl; EXECUTE dropship_source_pallet_no_stmt; DEALLOCATE PREPARE dropship_source_pallet_no_stmt; + +-- Explicit index upgrades for existing installations. CREATE TABLE IF NOT EXISTS +-- does not add indexes to tables that already exist. +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_order' AND index_name='idx_ll_order_container'), 'SELECT 1', 'ALTER TABLE ll_order ADD KEY idx_ll_order_container (customer_id,container_no)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND index_name='uk_ll_dropship_profile_key'), 'SELECT 1', 'ALTER TABLE ll_dropship_order_profile ADD UNIQUE KEY uk_ll_dropship_profile_key (customer_id,idempotency_key)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_carton' AND index_name='uk_ll_dropship_carton'), 'SELECT 1', 'ALTER TABLE ll_dropship_carton ADD UNIQUE KEY uk_ll_dropship_carton (customer_id,order_id,carton_mark)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_carton' AND index_name='idx_ll_dropship_carton_status'), 'SELECT 1', 'ALTER TABLE ll_dropship_carton ADD KEY idx_ll_dropship_carton_status (customer_id,status)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_carton' AND index_name='idx_ll_dropship_carton_sku'), 'SELECT 1', 'ALTER TABLE ll_dropship_carton ADD KEY idx_ll_dropship_carton_sku (customer_id,sku_code,status)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_carton_ledger' AND index_name='uk_ll_dropship_ledger_key'), 'SELECT 1', 'ALTER TABLE ll_dropship_carton_ledger ADD UNIQUE KEY uk_ll_dropship_ledger_key (customer_id,idempotency_key)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_carton_ledger' AND index_name='idx_ll_dropship_ledger_sku'), 'SELECT 1', 'ALTER TABLE ll_dropship_carton_ledger ADD KEY idx_ll_dropship_ledger_sku (customer_id,sku_code,carton_mark)')); +PREPARE lastleg_indexes_020 FROM @ddl; EXECUTE lastleg_indexes_020; DEALLOCATE PREPARE lastleg_indexes_020; + +-- ============================================================================ +-- 2026-08-18 / dropship multi-pallet cardinality +-- One one-click dropship inbound order may own multiple source pallets. +-- ============================================================================ +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND index_name='uk_ll_dropship_profile_order'), 'ALTER TABLE ll_dropship_order_profile DROP INDEX uk_ll_dropship_profile_order', 'SELECT 1')); +PREPARE dropship_drop_single_pallet_stmt FROM @ddl; EXECUTE dropship_drop_single_pallet_stmt; DEALLOCATE PREPARE dropship_drop_single_pallet_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND index_name='uk_ll_dropship_profile_order_pallet'), 'SELECT 1', 'ALTER TABLE ll_dropship_order_profile ADD UNIQUE KEY uk_ll_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no)')); +PREPARE dropship_add_order_pallet_stmt FROM @ddl; EXECUTE dropship_add_order_pallet_stmt; DEALLOCATE PREPARE dropship_add_order_pallet_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND index_name='idx_ll_dropship_profile_order'), 'SELECT 1', 'ALTER TABLE ll_dropship_order_profile ADD KEY idx_ll_dropship_profile_order(customer_id,order_id)')); +PREPARE dropship_add_order_idx_stmt FROM @ddl; EXECUTE dropship_add_order_idx_stmt; DEALLOCATE PREPARE dropship_add_order_idx_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_sku_inventory' AND index_name='uk_ll_dropship_sku_inventory'), 'ALTER TABLE ll_dropship_sku_inventory DROP INDEX uk_ll_dropship_sku_inventory', 'SELECT 1')); +PREPARE dropship_drop_order_sku_stmt FROM @ddl; EXECUTE dropship_drop_order_sku_stmt; DEALLOCATE PREPARE dropship_drop_order_sku_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_sku_inventory' AND index_name='uk_ll_dropship_sku_profile'), 'SELECT 1', 'ALTER TABLE ll_dropship_sku_inventory ADD UNIQUE KEY uk_ll_dropship_sku_profile(profile_id,sku_code)')); +PREPARE dropship_add_profile_sku_stmt FROM @ddl; EXECUTE dropship_add_profile_sku_stmt; DEALLOCATE PREPARE dropship_add_profile_sku_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_dropship_sku_inventory' AND index_name='idx_ll_dropship_sku_order'), 'SELECT 1', 'ALTER TABLE ll_dropship_sku_inventory ADD KEY idx_ll_dropship_sku_order(customer_id,order_id,sku_code)')); +PREPARE dropship_add_order_sku_idx_stmt FROM @ddl; EXECUTE dropship_add_order_sku_idx_stmt; DEALLOCATE PREPARE dropship_add_order_sku_idx_stmt; diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehousePalletController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehousePalletController.java new file mode 100644 index 0000000..5cceb91 --- /dev/null +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehousePalletController.java @@ -0,0 +1,25 @@ +package com.baiyue.warehouse.controller; + +import com.baiyue.common.core.api.ApiResponse; +import com.baiyue.warehouse.dto.DropshipPalletRequests; +import com.baiyue.warehouse.entity.WarehousePalletEntity; +import com.baiyue.warehouse.security.InternalServiceTokenGuard; +import com.baiyue.warehouse.service.WarehouseResourceService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/internal/v1/warehouse-pallets") +public class InternalWarehousePalletController { + @Resource private WarehouseResourceService service; + @Resource private InternalServiceTokenGuard tokenGuard; + + @PostMapping("/ensure-dropship") + public ApiResponse ensure( + @RequestHeader(value = "X-Internal-Service-Token", required = false) String token, + @Valid @RequestBody DropshipPalletRequests.Ensure request) { + tokenGuard.verify(token); + return ApiResponse.success(service.ensureDropshipPallet(request.customerId(), request.warehouseId(), request.businessId())); + } +} diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehouseResourceLockController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehouseResourceLockController.java index f685d7a..1e9c74f 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehouseResourceLockController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/InternalWarehouseResourceLockController.java @@ -38,7 +38,7 @@ public class InternalWarehouseResourceLockController { @PathVariable long lockId, @Valid @RequestBody WarehouseResourceLockRequests.CloseRequest request) { tokenGuard.verify(token); - return ApiResponse.success(service.release(request.tenantId(), lockId)); + return ApiResponse.success(service.release(request.customerId(), lockId)); } @PostMapping("/{lockId}/scrap") @@ -47,7 +47,7 @@ public class InternalWarehouseResourceLockController { @PathVariable long lockId, @Valid @RequestBody WarehouseResourceLockRequests.CloseRequest request) { tokenGuard.verify(token); - return ApiResponse.success(service.scrap(request.tenantId(), lockId)); + return ApiResponse.success(service.scrap(request.customerId(), lockId)); } @PostMapping("/pallets/{palletId}/scrap") @@ -56,7 +56,7 @@ public class InternalWarehouseResourceLockController { @PathVariable long palletId, @Valid @RequestBody WarehouseResourceLockRequests.CloseRequest request) { tokenGuard.verify(token); - return ApiResponse.success(service.scrapByPallet(request.tenantId(), palletId)); + return ApiResponse.success(service.scrapByPallet(request.customerId(), palletId)); } @PostMapping("/default-locations/ensure") @@ -65,6 +65,6 @@ public class InternalWarehouseResourceLockController { @Valid @RequestBody WarehouseResourceLockRequests.DefaultLocationRequest request) { tokenGuard.verify(token); return ApiResponse.success(resourceService.ensureDefaultVirtualLocation( - request.tenantId(), request.warehouseId())); + request.customerId(), request.warehouseId())); } } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseController.java index 5f98848..e8bcfb1 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseController.java @@ -56,7 +56,7 @@ public class PlatformWarehouseController { public ApiResponse create(@AuthenticationPrincipal AuthenticatedUser user, @Valid @RequestBody WarehouseRequests.SaveRequest request) { requirePlatform(user); - requireTenant(request.tenantId()); + requireTenant(request.customerId()); return ApiResponse.success(service.create(request)); } @@ -75,13 +75,13 @@ public class PlatformWarehouseController { public ApiResponse update( @AuthenticationPrincipal AuthenticatedUser user, @Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true) @PathVariable long id, @Valid @RequestBody WarehouseRequests.SaveRequest request) { requirePlatform(user); - requireTenant(tenantId); - service.update(tenantId, id, request); + requireTenant(customerId); + service.update(customerId, id, request); return ApiResponse.success(null); } @@ -98,11 +98,11 @@ public class PlatformWarehouseController { public ApiResponse> page( @AuthenticationPrincipal AuthenticatedUser user, @Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @Valid WarehouseRequests.QueryRequest request) { requirePlatform(user); - requireTenant(tenantId); - return ApiResponse.success(service.page(tenantId, request)); + requireTenant(customerId); + return ApiResponse.success(service.page(customerId, request)); } @Operation( @@ -118,12 +118,12 @@ public class PlatformWarehouseController { public ApiResponse detail( @AuthenticationPrincipal AuthenticatedUser user, @Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true) @PathVariable long id) { requirePlatform(user); - requireTenant(tenantId); - return ApiResponse.success(service.detail(tenantId, id)); + requireTenant(customerId); + return ApiResponse.success(service.detail(customerId, id)); } @Operation( @@ -140,14 +140,14 @@ public class PlatformWarehouseController { public ApiResponse status( @AuthenticationPrincipal AuthenticatedUser user, @Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true) @PathVariable long id, @Parameter(description = "Target status: ACTIVE or INACTIVE / 目标状态:ACTIVE 或 INACTIVE", example = "ACTIVE", required = true) @RequestParam String status) { requirePlatform(user); - requireTenant(tenantId); - service.changeStatus(tenantId, id, status); + requireTenant(customerId); + service.changeStatus(customerId, id, status); return ApiResponse.success(null); } @@ -157,8 +157,8 @@ public class PlatformWarehouseController { } } - private static void requireTenant(Long tenantId) { - if (tenantId == null || tenantId <= 0) { + private static void requireTenant(Long customerId) { + if (customerId == null || customerId <= 0) { throw new BizException(ResultCode.BAD_REQUEST, "Target tenant is required / 必须指定有效目标租户"); } } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseResourceController.java b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseResourceController.java index bf3e9ae..7e71424 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseResourceController.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformWarehouseResourceController.java @@ -33,98 +33,98 @@ public class PlatformWarehouseResourceController { @PostMapping("/locations") public ApiResponse createLocation(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @Valid @RequestBody LocationRequests.SaveRequest request) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.createLocation(tenantId, warehouseId, request)); + requirePlatform(user, customerId); + return ApiResponse.success(service.createLocation(customerId, warehouseId, request)); } @PutMapping("/locations/{id}") public ApiResponse updateLocation(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id, @Valid @RequestBody LocationRequests.SaveRequest request) { - requirePlatform(user, tenantId); - service.updateLocation(tenantId, warehouseId, id, request); + requirePlatform(user, customerId); + service.updateLocation(customerId, warehouseId, id, request); return ApiResponse.success(null); } @GetMapping("/locations") public ApiResponse> locations( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam @NotNull Long tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @Valid LocationRequests.QueryRequest query) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.locations(tenantId, warehouseId, query)); + requirePlatform(user, customerId); + return ApiResponse.success(service.locations(customerId, warehouseId, query)); } @GetMapping("/locations/{id}") public ApiResponse location(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.location(tenantId, warehouseId, id)); + requirePlatform(user, customerId); + return ApiResponse.success(service.location(customerId, warehouseId, id)); } @DeleteMapping("/locations/{id}") public ApiResponse deleteLocation(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id) { - requirePlatform(user, tenantId); - service.deleteLocation(tenantId, warehouseId, id); + requirePlatform(user, customerId); + service.deleteLocation(customerId, warehouseId, id); return ApiResponse.success(null); } @PostMapping("/pallets") public ApiResponse createPallet(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @Valid @RequestBody PalletRequests.SaveRequest request) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.createPallet(tenantId, warehouseId, request)); + requirePlatform(user, customerId); + return ApiResponse.success(service.createPallet(customerId, warehouseId, request)); } @PutMapping("/pallets/{id}") public ApiResponse updatePallet(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id, @Valid @RequestBody PalletRequests.SaveRequest request) { - requirePlatform(user, tenantId); - service.updatePallet(tenantId, warehouseId, id, request); + requirePlatform(user, customerId); + service.updatePallet(customerId, warehouseId, id, request); return ApiResponse.success(null); } @GetMapping("/pallets") public ApiResponse> pallets( - @AuthenticationPrincipal AuthenticatedUser user, @RequestParam @NotNull Long tenantId, + @AuthenticationPrincipal AuthenticatedUser user, @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @Valid PalletRequests.QueryRequest query) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.pallets(tenantId, warehouseId, query)); + requirePlatform(user, customerId); + return ApiResponse.success(service.pallets(customerId, warehouseId, query)); } @GetMapping("/pallets/{id}") public ApiResponse pallet(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id) { - requirePlatform(user, tenantId); - return ApiResponse.success(service.pallet(tenantId, warehouseId, id)); + requirePlatform(user, customerId); + return ApiResponse.success(service.pallet(customerId, warehouseId, id)); } @DeleteMapping("/pallets/{id}") public ApiResponse deletePallet(@AuthenticationPrincipal AuthenticatedUser user, - @RequestParam @NotNull Long tenantId, + @RequestParam @NotNull Long customerId, @PathVariable long warehouseId, @PathVariable long id) { - requirePlatform(user, tenantId); - service.deletePallet(tenantId, warehouseId, id); + requirePlatform(user, customerId); + service.deletePallet(customerId, warehouseId, id); return ApiResponse.success(null); } - private static void requirePlatform(AuthenticatedUser user, Long tenantId) { + private static void requirePlatform(AuthenticatedUser user, Long customerId) { if (user == null || !"PLATFORM".equals(user.appCode())) { throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问"); } - if (tenantId == null || tenantId <= 0) { + if (customerId == null || customerId <= 0) { throw new BizException(ResultCode.BAD_REQUEST, "必须指定有效目标租户"); } } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/DropshipPalletRequests.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/DropshipPalletRequests.java new file mode 100644 index 0000000..b5b99da --- /dev/null +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/DropshipPalletRequests.java @@ -0,0 +1,9 @@ +package com.baiyue.warehouse.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; + +public final class DropshipPalletRequests { + private DropshipPalletRequests() {} + public record Ensure(@Positive long customerId, @Positive long warehouseId, @NotBlank String businessId) {} +} diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseRequests.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseRequests.java index 079625e..034dde3 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseRequests.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseRequests.java @@ -24,7 +24,7 @@ public final class WarehouseRequests { @Schema(name = "WarehouseSaveRequest", description = "Warehouse create or update request / 仓库新增或更新请求") public record SaveRequest( @Schema(description = "Target tenant ID / 目标租户 ID", example = "10001", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull Long tenantId, + @NotNull Long customerId, @Schema(description = "Tenant-unique warehouse code / 租户内唯一仓库编码", example = "US-LAX-01", maxLength = 50, requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank @Size(max = 50) String warehouseCode, @Schema(description = "Warehouse name / 仓库名称", example = "Los Angeles Main Warehouse / 洛杉矶主仓", maxLength = 120, requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockRequests.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockRequests.java index 46b84eb..042ec6d 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockRequests.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockRequests.java @@ -7,7 +7,7 @@ public final class WarehouseResourceLockRequests { private WarehouseResourceLockRequests() {} public record ReserveRequest( - @Positive long tenantId, + @Positive long customerId, @Positive long warehouseId, @Positive long palletId, @Positive long locationId, @@ -16,10 +16,10 @@ public final class WarehouseResourceLockRequests { @NotBlank String reservationKey ) {} - public record CloseRequest(@Positive long tenantId) {} + public record CloseRequest(@Positive long customerId) {} public record DefaultLocationRequest( - @Positive long tenantId, + @Positive long customerId, @Positive long warehouseId ) {} } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockResponse.java b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockResponse.java index e848a03..332b92b 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockResponse.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseResourceLockResponse.java @@ -6,7 +6,7 @@ import java.time.LocalDateTime; public record WarehouseResourceLockResponse( long lockId, - long tenantId, + long customerId, long warehouseId, long palletId, long locationId, @@ -18,7 +18,7 @@ public record WarehouseResourceLockResponse( LocalDateTime releasedAt ) { public static WarehouseResourceLockResponse from(WarehouseResourceLockEntity entity) { - return new WarehouseResourceLockResponse(entity.getId(), entity.getTenantId(), entity.getWarehouseId(), + return new WarehouseResourceLockResponse(entity.getId(), entity.getCustomerId(), entity.getWarehouseId(), entity.getPalletId(), entity.getLocationId(), entity.getBusinessType(), entity.getBusinessId(), entity.getReservationKey(), entity.getStatus(), entity.getLockedAt(), entity.getReleasedAt()); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/ServiceCapabilityEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/ServiceCapabilityEntity.java index 3de6424..2ed0103 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/ServiceCapabilityEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/ServiceCapabilityEntity.java @@ -24,7 +24,7 @@ public class ServiceCapabilityEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; @Schema(description = "租户编号", example = "10001") - private Long tenantId; + private Long customerId; @Schema(description = "仓库编号", example = "190000000000000001") private Long warehouseId; @Schema(description = "能力编码", example = "STORAGE") diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseAddressEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseAddressEntity.java index e455725..e43afda 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseAddressEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseAddressEntity.java @@ -24,7 +24,7 @@ public class WarehouseAddressEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; @Schema(description = "租户编号", example = "10001") - private Long tenantId; + private Long customerId; @Schema(description = "仓库编号", example = "190000000000000001") private Long warehouseId; @Schema(description = "地址类型", example = "WAREHOUSE") diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseEntity.java index 19cddfc..2a8c435 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseEntity.java @@ -22,7 +22,7 @@ public class WarehouseEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; @Schema(description = "租户编号", example = "10001") - private Long tenantId; + private Long customerId; @Schema(description = "仓库编码", example = "US-LAX-01") private String warehouseCode; @Schema(description = "仓库名称", example = "洛杉矶主仓") diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseLocationEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseLocationEntity.java index 7d50708..9027321 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseLocationEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseLocationEntity.java @@ -15,7 +15,7 @@ import java.math.BigDecimal; public class WarehouseLocationEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; - private Long tenantId; + private Long customerId; private Long warehouseId; private String zoneCode; private String locationCode; diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletBindingEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletBindingEntity.java new file mode 100644 index 0000000..b456960 --- /dev/null +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletBindingEntity.java @@ -0,0 +1,19 @@ +package com.baiyue.warehouse.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("wh_pallet_business_binding") +public class WarehousePalletBindingEntity { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private Long customerId; + private Long warehouseId; + private String businessType; + private String businessId; + private Long palletId; + private LocalDateTime createdAt; +} diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletEntity.java index eb769a7..d6d3569 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehousePalletEntity.java @@ -15,7 +15,7 @@ import java.math.BigDecimal; public class WarehousePalletEntity extends BaseEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; - private Long tenantId; + private Long customerId; private Long warehouseId; private Long locationId; private String palletCode; diff --git a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseResourceLockEntity.java b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseResourceLockEntity.java index d2ebd64..e401318 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseResourceLockEntity.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/entity/WarehouseResourceLockEntity.java @@ -14,7 +14,7 @@ import java.time.LocalDateTime; public class WarehouseResourceLockEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; - private Long tenantId; + private Long customerId; private Long warehouseId; private Long palletId; private Long locationId; diff --git a/warehouse/src/main/java/com/baiyue/warehouse/mapper/ServiceCapabilityMapper.java b/warehouse/src/main/java/com/baiyue/warehouse/mapper/ServiceCapabilityMapper.java index 8f861d1..7f265bc 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/mapper/ServiceCapabilityMapper.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/mapper/ServiceCapabilityMapper.java @@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Param; */ public interface ServiceCapabilityMapper extends BaseMapper { - @Delete("DELETE FROM wh_service_capability WHERE tenant_id = #{tenantId} AND warehouse_id = #{warehouseId}") - int physicallyDeleteByWarehouse(@Param("tenantId") long tenantId, + @Delete("DELETE FROM wh_service_capability WHERE customer_id = #{customerId} AND warehouse_id = #{warehouseId}") + int physicallyDeleteByWarehouse(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseAddressMapper.java b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseAddressMapper.java index 90716b5..620f0e0 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseAddressMapper.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseAddressMapper.java @@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Param; */ public interface WarehouseAddressMapper extends BaseMapper { - @Delete("DELETE FROM wh_warehouse_address WHERE tenant_id = #{tenantId} AND warehouse_id = #{warehouseId}") - int physicallyDeleteByWarehouse(@Param("tenantId") long tenantId, + @Delete("DELETE FROM wh_warehouse_address WHERE customer_id = #{customerId} AND warehouse_id = #{warehouseId}") + int physicallyDeleteByWarehouse(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseLocationMapper.java b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseLocationMapper.java index f69c679..813b2a6 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseLocationMapper.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseLocationMapper.java @@ -11,10 +11,10 @@ public interface WarehouseLocationMapper extends BaseMapper { + @Select("SELECT * FROM wh_pallet_business_binding WHERE customer_id=#{customerId} AND warehouse_id=#{warehouseId} " + + "AND business_type=#{businessType} AND business_id=#{businessId} LIMIT 1") + WarehousePalletBindingEntity find(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId, + @Param("businessType") String businessType, @Param("businessId") String businessId); +} diff --git a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseResourceLockMapper.java b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseResourceLockMapper.java index aa2d346..962a339 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseResourceLockMapper.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/mapper/WarehouseResourceLockMapper.java @@ -9,51 +9,51 @@ import org.apache.ibatis.annotations.Update; public interface WarehouseResourceLockMapper extends BaseMapper { @Select(""" SELECT * FROM wh_resource_lock - WHERE tenant_id = #{tenantId} AND reservation_key = #{reservationKey} + WHERE customer_id = #{customerId} AND reservation_key = #{reservationKey} LIMIT 1 """) - WarehouseResourceLockEntity findByReservationKey(@Param("tenantId") long tenantId, + WarehouseResourceLockEntity findByReservationKey(@Param("customerId") long customerId, @Param("reservationKey") String reservationKey); - @Select("SELECT * FROM wh_resource_lock WHERE tenant_id=#{tenantId} AND pallet_id=#{palletId} " + + @Select("SELECT * FROM wh_resource_lock WHERE customer_id=#{customerId} AND pallet_id=#{palletId} " + "AND status='ACTIVE' ORDER BY locked_at DESC LIMIT 1") - WarehouseResourceLockEntity findActiveByPallet(@Param("tenantId") long tenantId, + WarehouseResourceLockEntity findActiveByPallet(@Param("customerId") long customerId, @Param("palletId") long palletId); @Update(""" UPDATE wh_pallet SET status = 'OCCUPIED', location_id = #{locationId}, updated_at = CURRENT_TIMESTAMP(3), version = version + 1 - WHERE id = #{palletId} AND tenant_id = #{tenantId} AND warehouse_id = #{warehouseId} + WHERE id = #{palletId} AND customer_id = #{customerId} AND warehouse_id = #{warehouseId} AND status = 'IDLE' AND deleted = 0 """) - int occupyPallet(@Param("tenantId") long tenantId, @Param("warehouseId") long warehouseId, + int occupyPallet(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId, @Param("palletId") long palletId, @Param("locationId") long locationId); @Update(""" UPDATE wh_location SET status = 'OCCUPIED', updated_at = CURRENT_TIMESTAMP(3), version = version + 1 - WHERE id = #{locationId} AND tenant_id = #{tenantId} AND warehouse_id = #{warehouseId} + WHERE id = #{locationId} AND customer_id = #{customerId} AND warehouse_id = #{warehouseId} AND status = 'IDLE' AND deleted = 0 """) - int occupyLocation(@Param("tenantId") long tenantId, @Param("warehouseId") long warehouseId, + int occupyLocation(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId, @Param("locationId") long locationId); @Update(""" UPDATE wh_pallet SET status = 'IDLE', location_id = NULL, updated_at = CURRENT_TIMESTAMP(3), version = version + 1 - WHERE id = #{palletId} AND tenant_id = #{tenantId} AND warehouse_id = #{warehouseId} + WHERE id = #{palletId} AND customer_id = #{customerId} AND warehouse_id = #{warehouseId} AND location_id = #{locationId} AND status = 'OCCUPIED' AND deleted = 0 """) - int releasePallet(@Param("tenantId") long tenantId, @Param("warehouseId") long warehouseId, + int releasePallet(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId, @Param("palletId") long palletId, @Param("locationId") long locationId); @Update(""" UPDATE wh_pallet SET status = 'IDLE', updated_at = CURRENT_TIMESTAMP(3), version = version + 1 - WHERE id = #{palletId} AND tenant_id = #{tenantId} AND warehouse_id = #{warehouseId} + WHERE id = #{palletId} AND customer_id = #{customerId} AND warehouse_id = #{warehouseId} AND location_id = #{locationId} AND status = 'OCCUPIED' AND deleted = 0 """) - int releasePalletAtVirtualLocation(@Param("tenantId") long tenantId, + int releasePalletAtVirtualLocation(@Param("customerId") long customerId, @Param("warehouseId") long warehouseId, @Param("palletId") long palletId, @Param("locationId") long locationId); @@ -61,35 +61,35 @@ public interface WarehouseResourceLockMapper extends BaseMapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getLocationCode, locationCode)) > 0; } @@ -47,22 +47,22 @@ public class WarehouseRepository { warehouses.updateById(warehouse); } - public WarehouseEntity findOwned(long tenantId, long id) { + public WarehouseEntity findOwned(long customerId, long id) { return warehouses.selectOne(new LambdaQueryWrapper() - .eq(WarehouseEntity::getTenantId, tenantId) + .eq(WarehouseEntity::getCustomerId, customerId) .eq(WarehouseEntity::getId, id)); } - public boolean existsCode(long tenantId, String code, Long excludedId) { + public boolean existsCode(long customerId, String code, Long excludedId) { return warehouses.selectCount(new LambdaQueryWrapper() - .eq(WarehouseEntity::getTenantId, tenantId) + .eq(WarehouseEntity::getCustomerId, customerId) .eq(WarehouseEntity::getWarehouseCode, code) .ne(excludedId != null, WarehouseEntity::getId, excludedId)) > 0; } - public PageResult page(long tenantId, WarehouseRequests.QueryRequest query) { + public PageResult page(long customerId, WarehouseRequests.QueryRequest query) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(WarehouseEntity::getTenantId, tenantId) + .eq(WarehouseEntity::getCustomerId, customerId) .eq(StringUtils.hasText(query.status()), WarehouseEntity::getStatus, query.status()) .eq(StringUtils.hasText(query.countryCode()), WarehouseEntity::getCountryCode, query.countryCode()) .orderByDesc(WarehouseEntity::getCreatedAt); @@ -70,7 +70,7 @@ public class WarehouseRepository { if (StringUtils.hasText(query.capabilityCode())) { List ids = capabilities.selectList(new LambdaQueryWrapper() .select(ServiceCapabilityEntity::getWarehouseId) - .eq(ServiceCapabilityEntity::getTenantId, tenantId) + .eq(ServiceCapabilityEntity::getCustomerId, customerId) .eq(ServiceCapabilityEntity::getCapabilityCode, query.capabilityCode()) .eq(ServiceCapabilityEntity::getEnabled, true)) .stream() @@ -85,21 +85,21 @@ public class WarehouseRepository { return PageResult.from(warehouses.selectPage(new Page<>(query.page(), query.size()), wrapper)); } - public List findAddresses(long tenantId, long warehouseId) { + public List findAddresses(long customerId, long warehouseId) { return addresses.selectList(new LambdaQueryWrapper() - .eq(WarehouseAddressEntity::getTenantId, tenantId) + .eq(WarehouseAddressEntity::getCustomerId, customerId) .eq(WarehouseAddressEntity::getWarehouseId, warehouseId)); } - public List findCapabilities(long tenantId, long warehouseId) { + public List findCapabilities(long customerId, long warehouseId) { return capabilities.selectList(new LambdaQueryWrapper() - .eq(ServiceCapabilityEntity::getTenantId, tenantId) + .eq(ServiceCapabilityEntity::getCustomerId, customerId) .eq(ServiceCapabilityEntity::getWarehouseId, warehouseId)); } - public void physicallyDeleteChildren(long tenantId, long warehouseId) { - addresses.physicallyDeleteByWarehouse(tenantId, warehouseId); - capabilities.physicallyDeleteByWarehouse(tenantId, warehouseId); + public void physicallyDeleteChildren(long customerId, long warehouseId) { + addresses.physicallyDeleteByWarehouse(customerId, warehouseId); + capabilities.physicallyDeleteByWarehouse(customerId, warehouseId); } public void insertAddress(WarehouseAddressEntity address) { diff --git a/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceLockRepository.java b/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceLockRepository.java index 65e1a27..03933ca 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceLockRepository.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceLockRepository.java @@ -16,74 +16,74 @@ public class WarehouseResourceLockRepository { @Resource private WarehousePalletMapper pallets; @Resource private WarehouseLocationMapper locations; - public WarehouseResourceLockEntity findByReservationKey(long tenantId, String reservationKey) { - return locks.findByReservationKey(tenantId, reservationKey); + public WarehouseResourceLockEntity findByReservationKey(long customerId, String reservationKey) { + return locks.findByReservationKey(customerId, reservationKey); } - public WarehouseResourceLockEntity find(long tenantId, long lockId) { + public WarehouseResourceLockEntity find(long customerId, long lockId) { return locks.selectOne(new LambdaQueryWrapper() - .eq(WarehouseResourceLockEntity::getTenantId, tenantId) + .eq(WarehouseResourceLockEntity::getCustomerId, customerId) .eq(WarehouseResourceLockEntity::getId, lockId)); } - public WarehouseResourceLockEntity findActiveByPallet(long tenantId, long palletId) { - return locks.findActiveByPallet(tenantId, palletId); + public WarehouseResourceLockEntity findActiveByPallet(long customerId, long palletId) { + return locks.findActiveByPallet(customerId, palletId); } - public WarehousePalletEntity findPallet(long tenantId, long palletId) { + public WarehousePalletEntity findPallet(long customerId, long palletId) { return pallets.selectOne(new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getId, palletId)); } - public WarehousePalletEntity findPallet(long tenantId, long warehouseId, long palletId) { + public WarehousePalletEntity findPallet(long customerId, long warehouseId, long palletId) { return pallets.selectOne(new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getWarehouseId, warehouseId) .eq(WarehousePalletEntity::getId, palletId)); } - public WarehouseLocationEntity findLocation(long tenantId, long warehouseId, long locationId) { + public WarehouseLocationEntity findLocation(long customerId, long warehouseId, long locationId) { return locations.selectOne(new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getId, locationId)); } - public int occupyPallet(long tenantId, long warehouseId, long palletId, long locationId) { - return locks.occupyPallet(tenantId, warehouseId, palletId, locationId); + public int occupyPallet(long customerId, long warehouseId, long palletId, long locationId) { + return locks.occupyPallet(customerId, warehouseId, palletId, locationId); } - public int occupyLocation(long tenantId, long warehouseId, long locationId) { - return locks.occupyLocation(tenantId, warehouseId, locationId); + public int occupyLocation(long customerId, long warehouseId, long locationId) { + return locks.occupyLocation(customerId, warehouseId, locationId); } public void insert(WarehouseResourceLockEntity lock) { locks.insert(lock); } public int releasePallet(WarehouseResourceLockEntity lock) { - return locks.releasePallet(lock.getTenantId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); + return locks.releasePallet(lock.getCustomerId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); } public int releasePalletAtVirtualLocation(WarehouseResourceLockEntity lock) { - return locks.releasePalletAtVirtualLocation(lock.getTenantId(), lock.getWarehouseId(), + return locks.releasePalletAtVirtualLocation(lock.getCustomerId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); } public int scrapPallet(WarehouseResourceLockEntity lock) { - return locks.scrapPallet(lock.getTenantId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); + return locks.scrapPallet(lock.getCustomerId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); } - public int scrapPalletWithoutLock(long tenantId, long palletId) { - return locks.scrapPalletWithoutLock(tenantId, palletId); + public int scrapPalletWithoutLock(long customerId, long palletId) { + return locks.scrapPalletWithoutLock(customerId, palletId); } public int scrapPalletAtVirtualLocation(WarehouseResourceLockEntity lock) { - return locks.scrapPalletAtVirtualLocation(lock.getTenantId(), lock.getWarehouseId(), + return locks.scrapPalletAtVirtualLocation(lock.getCustomerId(), lock.getWarehouseId(), lock.getPalletId(), lock.getLocationId()); } public int releaseLocation(WarehouseResourceLockEntity lock) { - return locks.releaseLocation(lock.getTenantId(), lock.getWarehouseId(), lock.getLocationId()); + return locks.releaseLocation(lock.getCustomerId(), lock.getWarehouseId(), lock.getLocationId()); } public int close(WarehouseResourceLockEntity lock, String status) { return locks.closeLock(lock.getId(), status); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceRepository.java b/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceRepository.java index cbdb40d..7c02c7d 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceRepository.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/repository/WarehouseResourceRepository.java @@ -11,6 +11,8 @@ import com.baiyue.warehouse.entity.WarehousePalletEntity; import com.baiyue.warehouse.mapper.WarehouseLocationMapper; import com.baiyue.warehouse.mapper.WarehouseMapper; import com.baiyue.warehouse.mapper.WarehousePalletMapper; +import com.baiyue.warehouse.mapper.WarehousePalletBindingMapper; +import com.baiyue.warehouse.entity.WarehousePalletBindingEntity; import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; import jakarta.annotation.Resource; @@ -20,22 +22,32 @@ public class WarehouseResourceRepository { @Resource private WarehouseMapper warehouses; @Resource private WarehouseLocationMapper locations; @Resource private WarehousePalletMapper pallets; + @Resource private WarehousePalletBindingMapper bindings; - public boolean warehouseOwned(long tenantId, long warehouseId) { + public WarehousePalletBindingEntity findPalletBinding(long customerId, long warehouseId, + String businessType, String businessId) { + return bindings.find(customerId, warehouseId, businessType, businessId); + } + + public void insertPalletBinding(WarehousePalletBindingEntity entity) { + bindings.insert(entity); + } + + public boolean warehouseOwned(long customerId, long warehouseId) { return warehouses.selectCount(new LambdaQueryWrapper() - .eq(WarehouseEntity::getTenantId, tenantId) + .eq(WarehouseEntity::getCustomerId, customerId) .eq(WarehouseEntity::getId, warehouseId)) > 0; } - public WarehouseEntity findWarehouse(long tenantId, long warehouseId) { + public WarehouseEntity findWarehouse(long customerId, long warehouseId) { return warehouses.selectOne(new LambdaQueryWrapper() - .eq(WarehouseEntity::getTenantId, tenantId) + .eq(WarehouseEntity::getCustomerId, customerId) .eq(WarehouseEntity::getId, warehouseId)); } - public WarehouseLocationEntity findDefaultVirtualLocation(long tenantId, long warehouseId) { + public WarehouseLocationEntity findDefaultVirtualLocation(long customerId, long warehouseId) { return locations.selectOne(new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getLocationType, "VIRTUAL") .eq(WarehouseLocationEntity::getStatus, "ACTIVE") @@ -43,9 +55,9 @@ public class WarehouseResourceRepository { .last("LIMIT 1")); } - public WarehouseLocationEntity findLocationByCode(long tenantId, long warehouseId, String locationCode) { + public WarehouseLocationEntity findLocationByCode(long customerId, long warehouseId, String locationCode) { return locations.selectOne(new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getLocationCode, locationCode)); } @@ -62,25 +74,25 @@ public class WarehouseResourceRepository { locations.updateById(entity); } - public WarehouseLocationEntity findLocation(long tenantId, long warehouseId, long id) { + public WarehouseLocationEntity findLocation(long customerId, long warehouseId, long id) { return locations.selectOne(new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getId, id)); } - public boolean locationCodeExists(long tenantId, long warehouseId, String code, Long excludedId) { + public boolean locationCodeExists(long customerId, long warehouseId, String code, Long excludedId) { return locations.selectCount(new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(WarehouseLocationEntity::getLocationCode, code) .ne(excludedId != null, WarehouseLocationEntity::getId, excludedId)) > 0; } - public PageResult pageLocations(long tenantId, long warehouseId, + public PageResult pageLocations(long customerId, long warehouseId, LocationRequests.QueryRequest query) { var wrapper = new LambdaQueryWrapper() - .eq(WarehouseLocationEntity::getTenantId, tenantId) + .eq(WarehouseLocationEntity::getCustomerId, customerId) .eq(WarehouseLocationEntity::getWarehouseId, warehouseId) .eq(StringUtils.hasText(query.zoneCode()), WarehouseLocationEntity::getZoneCode, query.zoneCode()) .eq(StringUtils.hasText(query.locationType()), WarehouseLocationEntity::getLocationType, query.locationType()) @@ -97,9 +109,9 @@ public class WarehouseResourceRepository { locations.deleteById(id); } - public boolean locationHasPallets(long tenantId, long warehouseId, long locationId) { + public boolean locationHasPallets(long customerId, long warehouseId, long locationId) { return pallets.selectCount(new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getWarehouseId, warehouseId) .eq(WarehousePalletEntity::getLocationId, locationId)) > 0; } @@ -112,25 +124,25 @@ public class WarehouseResourceRepository { pallets.updateById(entity); } - public WarehousePalletEntity findPallet(long tenantId, long warehouseId, long id) { + public WarehousePalletEntity findPallet(long customerId, long warehouseId, long id) { return pallets.selectOne(new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getWarehouseId, warehouseId) .eq(WarehousePalletEntity::getId, id)); } - public boolean palletCodeExists(long tenantId, long warehouseId, String code, Long excludedId) { + public boolean palletCodeExists(long customerId, long warehouseId, String code, Long excludedId) { return pallets.selectCount(new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getWarehouseId, warehouseId) .eq(WarehousePalletEntity::getPalletCode, code) .ne(excludedId != null, WarehousePalletEntity::getId, excludedId)) > 0; } - public PageResult pagePallets(long tenantId, long warehouseId, + public PageResult pagePallets(long customerId, long warehouseId, PalletRequests.QueryRequest query) { var wrapper = new LambdaQueryWrapper() - .eq(WarehousePalletEntity::getTenantId, tenantId) + .eq(WarehousePalletEntity::getCustomerId, customerId) .eq(WarehousePalletEntity::getWarehouseId, warehouseId) .eq(StringUtils.hasText(query.palletType()), WarehousePalletEntity::getPalletType, query.palletType()) .eq(StringUtils.hasText(query.status()), WarehousePalletEntity::getStatus, query.status()) diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseApplicationService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseApplicationService.java index 4cb1649..a10c136 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseApplicationService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseApplicationService.java @@ -36,21 +36,21 @@ public class WarehouseApplicationService { captureBefore = false ) public Long create(WarehouseRequests.SaveRequest request) { - ensureCodeUnique(request.tenantId(), request.warehouseCode(), null); + ensureCodeUnique(request.customerId(), request.warehouseCode(), null); WarehouseEntity warehouse = toEntity(request); repository.insertWarehouse(warehouse); ensureDefaultVirtualLocation(warehouse); - insertChildren(warehouse.getTenantId(), warehouse.getId(), request.address(), request.capabilities()); + insertChildren(warehouse.getCustomerId(), warehouse.getId(), request.address(), request.capabilities()); return warehouse.getId(); } private void ensureDefaultVirtualLocation(WarehouseEntity warehouse) { String locationCode = "LOC-" + warehouse.getWarehouseCode() + "-DEFAULT"; - if (repository.existsLocationCode(warehouse.getTenantId(), warehouse.getId(), locationCode)) { + if (repository.existsLocationCode(warehouse.getCustomerId(), warehouse.getId(), locationCode)) { return; } WarehouseLocationEntity location = new WarehouseLocationEntity(); - location.setTenantId(warehouse.getTenantId()); + location.setCustomerId(warehouse.getCustomerId()); location.setWarehouseId(warehouse.getId()); location.setZoneCode("DEFAULT"); location.setLocationCode(locationCode); @@ -68,29 +68,29 @@ public class WarehouseApplicationService { resourceId = "#id", description = "更新仓库" ) - public void update(long tenantId, long id, WarehouseRequests.SaveRequest request) { - if (request.tenantId() == null || tenantId != request.tenantId()) { + public void update(long customerId, long id, WarehouseRequests.SaveRequest request) { + if (request.customerId() == null || customerId != request.customerId()) { throw new BizException(ResultCode.FORBIDDEN, "租户不匹配"); } - WarehouseEntity old = owned(tenantId, id); - ensureCodeUnique(tenantId, request.warehouseCode(), id); + WarehouseEntity old = owned(customerId, id); + ensureCodeUnique(customerId, request.warehouseCode(), id); WarehouseEntity warehouse = toEntity(request); warehouse.setId(old.getId()); repository.saveWarehouse(warehouse); - repository.physicallyDeleteChildren(tenantId, id); - insertChildren(tenantId, id, request.address(), request.capabilities()); + repository.physicallyDeleteChildren(customerId, id); + insertChildren(customerId, id, request.address(), request.capabilities()); } - public PageResult page(long tenantId, WarehouseRequests.QueryRequest query) { - return repository.page(tenantId, query); + public PageResult page(long customerId, WarehouseRequests.QueryRequest query) { + return repository.page(customerId, query); } - public WarehouseDetailResponse detail(long tenantId, long id) { - WarehouseEntity warehouse = owned(tenantId, id); - List addresses = repository.findAddresses(tenantId, id); - List capabilities = repository.findCapabilities(tenantId, id); + public WarehouseDetailResponse detail(long customerId, long id) { + WarehouseEntity warehouse = owned(customerId, id); + List addresses = repository.findAddresses(customerId, id); + List capabilities = repository.findCapabilities(customerId, id); return new WarehouseDetailResponse(warehouse, addresses, capabilities); } @@ -101,32 +101,32 @@ public class WarehouseApplicationService { resourceId = "#id", description = "启用或停用仓库" ) - public void changeStatus(long tenantId, long id, String status) { + public void changeStatus(long customerId, long id, String status) { if (!"ACTIVE".equals(status) && !"INACTIVE".equals(status)) { throw new BizException("无效状态"); } - WarehouseEntity warehouse = owned(tenantId, id); + WarehouseEntity warehouse = owned(customerId, id); warehouse.setStatus(status); repository.saveWarehouse(warehouse); } - private WarehouseEntity owned(long tenantId, long id) { - WarehouseEntity warehouse = repository.findOwned(tenantId, id); + private WarehouseEntity owned(long customerId, long id) { + WarehouseEntity warehouse = repository.findOwned(customerId, id); if (warehouse == null) { throw new BizException(ResultCode.NOT_FOUND, "仓库不存在"); } return warehouse; } - private void ensureCodeUnique(long tenantId, String code, Long excludedId) { - if (repository.existsCode(tenantId, code, excludedId)) { + private void ensureCodeUnique(long customerId, String code, Long excludedId) { + if (repository.existsCode(customerId, code, excludedId)) { throw new BizException(ResultCode.CONFLICT, "仓库编码已存在"); } } private WarehouseEntity toEntity(WarehouseRequests.SaveRequest request) { WarehouseEntity warehouse = new WarehouseEntity(); - warehouse.setTenantId(request.tenantId()); + warehouse.setCustomerId(request.customerId()); warehouse.setWarehouseCode(request.warehouseCode()); warehouse.setWarehouseName(request.warehouseName()); warehouse.setWarehouseType(request.warehouseType()); @@ -140,11 +140,11 @@ public class WarehouseApplicationService { return warehouse; } - private void insertChildren(long tenantId, long warehouseId, + private void insertChildren(long customerId, long warehouseId, WarehouseRequests.AddressRequest address, List requestedCapabilities) { WarehouseAddressEntity addressEntity = new WarehouseAddressEntity(); - addressEntity.setTenantId(tenantId); + addressEntity.setCustomerId(customerId); addressEntity.setWarehouseId(warehouseId); addressEntity.setAddressType(address.addressType()); addressEntity.setCountryCode(address.countryCode()); @@ -163,7 +163,7 @@ public class WarehouseApplicationService { } for (WarehouseRequests.CapabilityRequest capability : requestedCapabilities) { ServiceCapabilityEntity entity = new ServiceCapabilityEntity(); - entity.setTenantId(tenantId); + entity.setCustomerId(customerId); entity.setWarehouseId(warehouseId); entity.setCapabilityCode(capability.capabilityCode()); entity.setCapabilityName(capability.capabilityName()); diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceLockService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceLockService.java index a12ffd5..b3ef444 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceLockService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceLockService.java @@ -23,14 +23,14 @@ public class WarehouseResourceLockService { @Transactional(isolation = Isolation.READ_COMMITTED) public WarehouseResourceLockResponse reserve(WarehouseResourceLockRequests.ReserveRequest request) { - WarehouseResourceLockEntity existing = repository.findByReservationKey(request.tenantId(), request.reservationKey()); + WarehouseResourceLockEntity existing = repository.findByReservationKey(request.customerId(), request.reservationKey()); if (existing != null) return idempotentResult(existing, request); - WarehousePalletEntity pallet = repository.findPallet(request.tenantId(), request.warehouseId(), request.palletId()); - WarehouseLocationEntity location = repository.findLocation(request.tenantId(), request.warehouseId(), request.locationId()); + WarehousePalletEntity pallet = repository.findPallet(request.customerId(), request.warehouseId(), request.palletId()); + WarehouseLocationEntity location = repository.findLocation(request.customerId(), request.warehouseId(), request.locationId()); if (pallet == null || location == null) { WarehouseResourceLockEntity concurrent = - repository.findByReservationKey(request.tenantId(), request.reservationKey()); + repository.findByReservationKey(request.customerId(), request.reservationKey()); if (concurrent != null) return idempotentResult(concurrent, request); throw new BizException(ResultCode.NOT_FOUND, "Pallet or location does not belong to the tenant warehouse"); } @@ -38,19 +38,19 @@ public class WarehouseResourceLockService { boolean locationAvailable = virtualLocation ? "ACTIVE".equals(location.getStatus()) : "IDLE".equals(location.getStatus()); if (!"IDLE".equals(pallet.getStatus()) || !locationAvailable) { WarehouseResourceLockEntity concurrent = - repository.findByReservationKey(request.tenantId(), request.reservationKey()); + repository.findByReservationKey(request.customerId(), request.reservationKey()); if (concurrent != null) return idempotentResult(concurrent, request); throw new BizException(ResultCode.CONFLICT, "Only IDLE pallet and location can be reserved"); } - if (repository.occupyPallet(request.tenantId(), request.warehouseId(), request.palletId(), request.locationId()) != 1) { + if (repository.occupyPallet(request.customerId(), request.warehouseId(), request.palletId(), request.locationId()) != 1) { return concurrentIdempotentOrConflict(request); } - if (!virtualLocation && repository.occupyLocation(request.tenantId(), request.warehouseId(), request.locationId()) != 1) { + if (!virtualLocation && repository.occupyLocation(request.customerId(), request.warehouseId(), request.locationId()) != 1) { throw new BizException(ResultCode.CONFLICT, "Location is no longer IDLE"); } WarehouseResourceLockEntity lock = new WarehouseResourceLockEntity(); - lock.setTenantId(request.tenantId()); + lock.setCustomerId(request.customerId()); lock.setWarehouseId(request.warehouseId()); lock.setPalletId(request.palletId()); lock.setLocationId(request.locationId()); @@ -64,8 +64,8 @@ public class WarehouseResourceLockService { } @Transactional - public WarehouseResourceLockResponse release(long tenantId, long lockId) { - WarehouseResourceLockEntity lock = requireLock(tenantId, lockId); + public WarehouseResourceLockResponse release(long customerId, long lockId) { + WarehouseResourceLockEntity lock = requireLock(customerId, lockId); if ("RELEASED".equals(lock.getStatus())) return WarehouseResourceLockResponse.from(lock); requireActive(lock); boolean virtualLocation = isVirtualLockLocation(lock); @@ -80,8 +80,8 @@ public class WarehouseResourceLockService { } @Transactional - public WarehouseResourceLockResponse scrap(long tenantId, long lockId) { - WarehouseResourceLockEntity lock = requireLock(tenantId, lockId); + public WarehouseResourceLockResponse scrap(long customerId, long lockId) { + WarehouseResourceLockEntity lock = requireLock(customerId, lockId); if ("SCRAPPED".equals(lock.getStatus())) return WarehouseResourceLockResponse.from(lock); requireActive(lock); boolean virtualLocation = isVirtualLockLocation(lock); @@ -96,8 +96,8 @@ public class WarehouseResourceLockService { } @Transactional - public WarehousePalletScrapResponse scrapByPallet(long tenantId, long palletId) { - WarehousePalletEntity pallet = repository.findPallet(tenantId, palletId); + public WarehousePalletScrapResponse scrapByPallet(long customerId, long palletId) { + WarehousePalletEntity pallet = repository.findPallet(customerId, palletId); if (pallet == null) throw new BizException(ResultCode.NOT_FOUND, "Pallet not found"); if ("SCRAPPED".equals(pallet.getStatus())) { return new WarehousePalletScrapResponse(palletId, pallet.getWarehouseId(), pallet.getLocationId(), "SCRAPPED"); @@ -105,10 +105,10 @@ public class WarehouseResourceLockService { if (!"OCCUPIED".equals(pallet.getStatus())) { throw new BizException(ResultCode.CONFLICT, "Only an OCCUPIED pallet can be scrapped after shipment"); } - WarehouseResourceLockEntity lock = repository.findActiveByPallet(tenantId, palletId); + WarehouseResourceLockEntity lock = repository.findActiveByPallet(customerId, palletId); if (lock != null) { - scrap(tenantId, lock.getId()); - } else if (repository.scrapPalletWithoutLock(tenantId, palletId) != 1) { + scrap(customerId, lock.getId()); + } else if (repository.scrapPalletWithoutLock(customerId, palletId) != 1) { throw new BizException(ResultCode.CONFLICT, "Pallet state changed concurrently"); } return new WarehousePalletScrapResponse(palletId, pallet.getWarehouseId(), pallet.getLocationId(), "SCRAPPED"); @@ -116,7 +116,7 @@ public class WarehouseResourceLockService { private WarehouseResourceLockResponse concurrentIdempotentOrConflict( WarehouseResourceLockRequests.ReserveRequest request) { - WarehouseResourceLockEntity existing = repository.findByReservationKey(request.tenantId(), request.reservationKey()); + WarehouseResourceLockEntity existing = repository.findByReservationKey(request.customerId(), request.reservationKey()); if (existing != null) return idempotentResult(existing, request); throw new BizException(ResultCode.CONFLICT, "Pallet is no longer IDLE"); } @@ -133,8 +133,8 @@ public class WarehouseResourceLockService { return WarehouseResourceLockResponse.from(existing); } - private WarehouseResourceLockEntity requireLock(long tenantId, long lockId) { - WarehouseResourceLockEntity lock = repository.find(tenantId, lockId); + private WarehouseResourceLockEntity requireLock(long customerId, long lockId) { + WarehouseResourceLockEntity lock = repository.find(customerId, lockId); if (lock == null) throw new BizException(ResultCode.NOT_FOUND, "Resource lock not found"); return lock; } @@ -147,7 +147,7 @@ public class WarehouseResourceLockService { private boolean isVirtualLockLocation(WarehouseResourceLockEntity lock) { - WarehouseLocationEntity location = repository.findLocation(lock.getTenantId(), lock.getWarehouseId(), lock.getLocationId()); + WarehouseLocationEntity location = repository.findLocation(lock.getCustomerId(), lock.getWarehouseId(), lock.getLocationId()); if (location == null) throw new BizException(ResultCode.NOT_FOUND, "Resource lock location not found"); return isVirtual(location); } diff --git a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceService.java b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceService.java index c75c15f..b713456 100644 --- a/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceService.java +++ b/warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseResourceService.java @@ -8,6 +8,7 @@ import com.baiyue.warehouse.dto.PalletRequests; import com.baiyue.warehouse.entity.WarehouseLocationEntity; import com.baiyue.warehouse.entity.WarehousePalletEntity; import com.baiyue.warehouse.entity.WarehouseEntity; +import com.baiyue.warehouse.entity.WarehousePalletBindingEntity; import com.baiyue.warehouse.repository.WarehouseResourceRepository; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import org.springframework.stereotype.Service; @@ -27,78 +28,99 @@ public class WarehouseResourceService { @Resource private WarehouseResourceRepository repository; @Transactional - public Long createLocation(long tenantId, long warehouseId, LocationRequests.SaveRequest request) { - requireWarehouse(tenantId, warehouseId); - validateLocation(request, tenantId, warehouseId, null); - WarehouseLocationEntity entity = locationEntity(tenantId, warehouseId, request); + public Long createLocation(long customerId, long warehouseId, LocationRequests.SaveRequest request) { + requireWarehouse(customerId, warehouseId); + validateLocation(request, customerId, warehouseId, null); + WarehouseLocationEntity entity = locationEntity(customerId, warehouseId, request); repository.insertLocation(entity); return entity.getId(); } @Transactional - public void updateLocation(long tenantId, long warehouseId, long id, LocationRequests.SaveRequest request) { - WarehouseLocationEntity existing = requireLocation(tenantId, warehouseId, id); + public void updateLocation(long customerId, long warehouseId, long id, LocationRequests.SaveRequest request) { + WarehouseLocationEntity existing = requireLocation(customerId, warehouseId, id); if ("VIRTUAL".equals(existing.getLocationType()) && (!"VIRTUAL".equals(request.locationType()) || !"ACTIVE".equals(request.status()) || !existing.getLocationCode().equals(request.locationCode()))) { throw new BizException(ResultCode.CONFLICT, "Default virtual location must remain VIRTUAL and ACTIVE"); } - validateLocation(request, tenantId, warehouseId, id); - WarehouseLocationEntity entity = locationEntity(tenantId, warehouseId, request); + validateLocation(request, customerId, warehouseId, id); + WarehouseLocationEntity entity = locationEntity(customerId, warehouseId, request); entity.setId(existing.getId()); repository.updateLocation(entity); } - public WarehouseLocationEntity location(long tenantId, long warehouseId, long id) { - requireWarehouse(tenantId, warehouseId); - return requireLocation(tenantId, warehouseId, id); + public WarehouseLocationEntity location(long customerId, long warehouseId, long id) { + requireWarehouse(customerId, warehouseId); + return requireLocation(customerId, warehouseId, id); } - public PageResult locations(long tenantId, long warehouseId, + public PageResult locations(long customerId, long warehouseId, LocationRequests.QueryRequest query) { - requireWarehouse(tenantId, warehouseId); - return repository.pageLocations(tenantId, warehouseId, query); + requireWarehouse(customerId, warehouseId); + return repository.pageLocations(customerId, warehouseId, query); } @Transactional - public void deleteLocation(long tenantId, long warehouseId, long id) { - WarehouseLocationEntity location = requireLocation(tenantId, warehouseId, id); + public void deleteLocation(long customerId, long warehouseId, long id) { + WarehouseLocationEntity location = requireLocation(customerId, warehouseId, id); if ("VIRTUAL".equals(location.getLocationType())) { throw new BizException(ResultCode.CONFLICT, "Default virtual location cannot be deleted"); } - if (repository.locationHasPallets(tenantId, warehouseId, id)) { + if (repository.locationHasPallets(customerId, warehouseId, id)) { throw new BizException(ResultCode.CONFLICT, "库位仍绑定托盘,不能删除"); } repository.deleteLocation(id); } @Transactional - public Long createPallet(long tenantId, long warehouseId, PalletRequests.SaveRequest request) { - WarehouseEntity warehouse = requireWarehouseEntity(tenantId, warehouseId); + public Long createPallet(long customerId, long warehouseId, PalletRequests.SaveRequest request) { + WarehouseEntity warehouse = requireWarehouseEntity(customerId, warehouseId); validatePalletStatus(request.status()); Long palletId = IdWorker.getId(); String palletCode = StringUtils.hasText(request.palletCode()) ? request.palletCode().trim() : generatedPalletCode(warehouse.getWarehouseCode(), palletId); Long locationId = request.locationId(); if (locationId == null) { - locationId = ensureDefaultVirtualLocation(tenantId, warehouseId).getId(); + locationId = ensureDefaultVirtualLocation(customerId, warehouseId).getId(); } - validatePallet(request, palletCode, locationId, tenantId, warehouseId, null); - WarehousePalletEntity entity = palletEntity(tenantId, warehouseId, request, palletCode, locationId); + validatePallet(request, palletCode, locationId, customerId, warehouseId, null); + WarehousePalletEntity entity = palletEntity(customerId, warehouseId, request, palletCode, locationId); entity.setId(palletId); repository.insertPallet(entity); return entity.getId(); } + @Transactional + public WarehousePalletEntity ensureDropshipPallet(long customerId, long warehouseId, String businessId) { + WarehousePalletBindingEntity existing = repository.findPalletBinding(customerId, warehouseId, "DROPSHIP", businessId); + if (existing != null) return requirePallet(customerId, warehouseId, existing.getPalletId()); + Long palletId = createPallet(customerId, warehouseId, + new PalletRequests.SaveRequest(null, "DROPSHIP", null, java.math.BigDecimal.ZERO, "IDLE", null, + "Default dropship pallet for " + businessId)); + WarehousePalletBindingEntity binding = new WarehousePalletBindingEntity(); + binding.setId(IdWorker.getId()); binding.setCustomerId(customerId); binding.setWarehouseId(warehouseId); + binding.setBusinessType("DROPSHIP"); binding.setBusinessId(businessId); binding.setPalletId(palletId); + binding.setCreatedAt(java.time.LocalDateTime.now()); + try { + repository.insertPalletBinding(binding); + return requirePallet(customerId, warehouseId, palletId); + } catch (org.springframework.dao.DuplicateKeyException duplicate) { + WarehousePalletBindingEntity winner = repository.findPalletBinding(customerId, warehouseId, "DROPSHIP", businessId); + if (winner == null) throw duplicate; + return requirePallet(customerId, warehouseId, winner.getPalletId()); + } + } + /** * Returns the warehouse-scoped virtual location used only as a pallet/inventory reference. * It is deliberately never made IDLE/OCCUPIED by resource locking. */ @Transactional - public WarehouseLocationEntity ensureDefaultVirtualLocation(long tenantId, long warehouseId) { - WarehouseEntity warehouse = requireWarehouseEntity(tenantId, warehouseId); + public WarehouseLocationEntity ensureDefaultVirtualLocation(long customerId, long warehouseId) { + WarehouseEntity warehouse = requireWarehouseEntity(customerId, warehouseId); String locationCode = defaultLocationCode(warehouse.getWarehouseCode()); - WarehouseLocationEntity existing = repository.findLocationByCode(tenantId, warehouseId, locationCode); + WarehouseLocationEntity existing = repository.findLocationByCode(customerId, warehouseId, locationCode); if (existing != null) { if (!"VIRTUAL".equals(existing.getLocationType()) || !"ACTIVE".equals(existing.getStatus())) { throw new BizException(ResultCode.CONFLICT, @@ -109,7 +131,7 @@ public class WarehouseResourceService { WarehouseLocationEntity location = new WarehouseLocationEntity(); location.setId(IdWorker.getId()); - location.setTenantId(tenantId); + location.setCustomerId(customerId); location.setWarehouseId(warehouseId); location.setZoneCode("DEFAULT"); location.setLocationCode(locationCode); @@ -119,7 +141,7 @@ public class WarehouseResourceService { location.setRemark("System default virtual location"); repository.insertDefaultVirtualLocationIgnore(location); - WarehouseLocationEntity resolved = repository.findLocationByCode(tenantId, warehouseId, locationCode); + WarehouseLocationEntity resolved = repository.findLocationByCode(customerId, warehouseId, locationCode); if (resolved == null) { throw new BizException(ResultCode.CONFLICT, "Default virtual location could not be created"); } @@ -127,34 +149,34 @@ public class WarehouseResourceService { } @Transactional - public void updatePallet(long tenantId, long warehouseId, long id, PalletRequests.SaveRequest request) { - WarehousePalletEntity existing = requirePallet(tenantId, warehouseId, id); + public void updatePallet(long customerId, long warehouseId, long id, PalletRequests.SaveRequest request) { + WarehousePalletEntity existing = requirePallet(customerId, warehouseId, id); if ("SCRAPPED".equals(existing.getStatus()) && !"SCRAPPED".equals(request.status())) { throw new BizException(ResultCode.CONFLICT, "SCRAPPED pallet cannot be reused"); } String palletCode = StringUtils.hasText(request.palletCode()) ? request.palletCode().trim() : existing.getPalletCode(); Long locationId = request.locationId() == null ? existing.getLocationId() : request.locationId(); - validatePallet(request, palletCode, locationId, tenantId, warehouseId, id); - WarehousePalletEntity entity = palletEntity(tenantId, warehouseId, request, palletCode, locationId); + validatePallet(request, palletCode, locationId, customerId, warehouseId, id); + WarehousePalletEntity entity = palletEntity(customerId, warehouseId, request, palletCode, locationId); entity.setId(existing.getId()); repository.updatePallet(entity); } - public WarehousePalletEntity pallet(long tenantId, long warehouseId, long id) { - requireWarehouse(tenantId, warehouseId); - return requirePallet(tenantId, warehouseId, id); + public WarehousePalletEntity pallet(long customerId, long warehouseId, long id) { + requireWarehouse(customerId, warehouseId); + return requirePallet(customerId, warehouseId, id); } - public PageResult pallets(long tenantId, long warehouseId, + public PageResult pallets(long customerId, long warehouseId, PalletRequests.QueryRequest query) { - requireWarehouse(tenantId, warehouseId); - return repository.pagePallets(tenantId, warehouseId, query); + requireWarehouse(customerId, warehouseId); + return repository.pagePallets(customerId, warehouseId, query); } @Transactional - public void deletePallet(long tenantId, long warehouseId, long id) { - WarehousePalletEntity pallet = requirePallet(tenantId, warehouseId, id); + public void deletePallet(long customerId, long warehouseId, long id) { + WarehousePalletEntity pallet = requirePallet(customerId, warehouseId, id); if ("OCCUPIED".equals(pallet.getStatus())) { throw new BizException(ResultCode.CONFLICT, "占用中的托盘不能删除"); } @@ -164,52 +186,52 @@ public class WarehouseResourceService { repository.deletePallet(id); } - private void requireWarehouse(long tenantId, long warehouseId) { - if (!repository.warehouseOwned(tenantId, warehouseId)) { + private void requireWarehouse(long customerId, long warehouseId) { + if (!repository.warehouseOwned(customerId, warehouseId)) { throw new BizException(ResultCode.NOT_FOUND, "当前租户下仓库不存在"); } } - private WarehouseEntity requireWarehouseEntity(long tenantId, long warehouseId) { - WarehouseEntity warehouse = repository.findWarehouse(tenantId, warehouseId); + private WarehouseEntity requireWarehouseEntity(long customerId, long warehouseId) { + WarehouseEntity warehouse = repository.findWarehouse(customerId, warehouseId); if (warehouse == null) { throw new BizException(ResultCode.NOT_FOUND, "Tenant warehouse not found"); } return warehouse; } - private WarehouseLocationEntity requireLocation(long tenantId, long warehouseId, long id) { - requireWarehouse(tenantId, warehouseId); - WarehouseLocationEntity entity = repository.findLocation(tenantId, warehouseId, id); + private WarehouseLocationEntity requireLocation(long customerId, long warehouseId, long id) { + requireWarehouse(customerId, warehouseId); + WarehouseLocationEntity entity = repository.findLocation(customerId, warehouseId, id); if (entity == null) throw new BizException(ResultCode.NOT_FOUND, "当前仓库下库位不存在"); return entity; } - private WarehousePalletEntity requirePallet(long tenantId, long warehouseId, long id) { - requireWarehouse(tenantId, warehouseId); - WarehousePalletEntity entity = repository.findPallet(tenantId, warehouseId, id); + private WarehousePalletEntity requirePallet(long customerId, long warehouseId, long id) { + requireWarehouse(customerId, warehouseId); + WarehousePalletEntity entity = repository.findPallet(customerId, warehouseId, id); if (entity == null) throw new BizException(ResultCode.NOT_FOUND, "当前仓库下托盘不存在"); return entity; } - private void validateLocation(LocationRequests.SaveRequest request, long tenantId, + private void validateLocation(LocationRequests.SaveRequest request, long customerId, long warehouseId, Long excludedId) { if (!LOCATION_STATUSES.contains(request.status())) { throw new BizException("库位状态仅支持 ACTIVE、IDLE、OCCUPIED、INACTIVE"); } - if (repository.locationCodeExists(tenantId, warehouseId, request.locationCode(), excludedId)) { + if (repository.locationCodeExists(customerId, warehouseId, request.locationCode(), excludedId)) { throw new BizException(ResultCode.CONFLICT, "当前仓库下库位编码已存在"); } } private void validatePallet(PalletRequests.SaveRequest request, String palletCode, Long locationId, - long tenantId, long warehouseId, Long excludedId) { + long customerId, long warehouseId, Long excludedId) { validatePalletStatus(request.status()); - if (repository.palletCodeExists(tenantId, warehouseId, palletCode, excludedId)) { + if (repository.palletCodeExists(customerId, warehouseId, palletCode, excludedId)) { throw new BizException(ResultCode.CONFLICT, "当前仓库下托盘编码已存在"); } if (locationId != null) { - requireLocation(tenantId, warehouseId, locationId); + requireLocation(customerId, warehouseId, locationId); } } @@ -219,10 +241,10 @@ public class WarehouseResourceService { } } - private WarehouseLocationEntity locationEntity(long tenantId, long warehouseId, + private WarehouseLocationEntity locationEntity(long customerId, long warehouseId, LocationRequests.SaveRequest request) { WarehouseLocationEntity entity = new WarehouseLocationEntity(); - entity.setTenantId(tenantId); + entity.setCustomerId(customerId); entity.setWarehouseId(warehouseId); entity.setZoneCode(request.zoneCode()); entity.setLocationCode(request.locationCode()); @@ -233,11 +255,11 @@ public class WarehouseResourceService { return entity; } - private WarehousePalletEntity palletEntity(long tenantId, long warehouseId, + private WarehousePalletEntity palletEntity(long customerId, long warehouseId, PalletRequests.SaveRequest request, String palletCode, Long locationId) { WarehousePalletEntity entity = new WarehousePalletEntity(); - entity.setTenantId(tenantId); + entity.setCustomerId(customerId); entity.setWarehouseId(warehouseId); entity.setLocationId(locationId); entity.setPalletCode(palletCode); diff --git a/warehouse/src/main/resources/db/warehouse.sql b/warehouse/src/main/resources/db/warehouse.sql index 7b3d28b..10a96d7 100644 --- a/warehouse/src/main/resources/db/warehouse.sql +++ b/warehouse/src/main/resources/db/warehouse.sql @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS wh_location ( id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', zone_code VARCHAR(50) NOT NULL COMMENT '仓库分区编码', location_code VARCHAR(64) NOT NULL COMMENT '仓库内唯一库位编码', @@ -20,13 +20,13 @@ CREATE TABLE IF NOT EXISTS wh_location ( deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id), - UNIQUE KEY uk_location_code (tenant_id, warehouse_id, location_code, deleted), - KEY idx_location_query (tenant_id, warehouse_id, zone_code, status, deleted) + UNIQUE KEY uk_location_code (customer_id, warehouse_id, location_code, deleted), + KEY idx_location_query (customer_id, warehouse_id, zone_code, status, deleted) ) ENGINE=InnoDB COMMENT='仓库库位表:按租户和仓库维护库位及承重信息'; CREATE TABLE IF NOT EXISTS wh_pallet ( id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', location_id BIGINT UNSIGNED DEFAULT NULL COMMENT '同仓库内绑定的库位ID,可为空', pallet_code VARCHAR(128) NOT NULL COMMENT '仓库内唯一托盘编码', @@ -42,14 +42,14 @@ CREATE TABLE IF NOT EXISTS wh_pallet ( deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id), - UNIQUE KEY uk_pallet_code (tenant_id, warehouse_id, pallet_code, deleted), - KEY idx_pallet_query (tenant_id, warehouse_id, status, deleted), - KEY idx_pallet_location (tenant_id, warehouse_id, location_id, deleted) + UNIQUE KEY uk_pallet_code (customer_id, warehouse_id, pallet_code, deleted), + KEY idx_pallet_query (customer_id, warehouse_id, status, deleted), + KEY idx_pallet_location (customer_id, warehouse_id, location_id, deleted) ) ENGINE=InnoDB COMMENT='仓库托盘表:按租户和仓库维护托盘、容量、单价及状态'; CREATE TABLE IF NOT EXISTS wh_resource_lock ( id BIGINT UNSIGNED NOT NULL COMMENT 'Snowflake resource lock ID', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT 'Owning tenant ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT 'Owning tenant ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT 'Owning warehouse ID', pallet_id BIGINT UNSIGNED NOT NULL COMMENT 'Reserved pallet ID', location_id BIGINT UNSIGNED NOT NULL COMMENT 'Reserved location ID', @@ -62,15 +62,15 @@ CREATE TABLE IF NOT EXISTS wh_resource_lock ( 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_resource_lock_reservation (tenant_id, reservation_key), - KEY idx_resource_lock_pallet (tenant_id, warehouse_id, pallet_id, status), - KEY idx_resource_lock_location (tenant_id, warehouse_id, location_id, status), - KEY idx_resource_lock_business (tenant_id, business_type, business_id) + UNIQUE KEY uk_resource_lock_reservation (customer_id, reservation_key), + KEY idx_resource_lock_pallet (customer_id, warehouse_id, pallet_id, status), + KEY idx_resource_lock_location (customer_id, warehouse_id, location_id, status), + KEY idx_resource_lock_business (customer_id, business_type, business_id) ) ENGINE=InnoDB COMMENT='Atomic pallet and location reservation records'; CREATE TABLE IF NOT EXISTS wh_warehouse ( id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID(雪花ID)', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID', warehouse_code VARCHAR(50) NOT NULL COMMENT '租户内唯一仓库编码', warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称', warehouse_type VARCHAR(32) NOT NULL DEFAULT 'OVERSEAS' COMMENT '仓库类型', @@ -88,14 +88,14 @@ CREATE TABLE IF NOT EXISTS wh_warehouse ( deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id), - UNIQUE KEY uk_wh_tenant_code (tenant_id, warehouse_code, deleted), - KEY idx_wh_tenant_status (tenant_id, status, deleted), - KEY idx_wh_country (tenant_id, country_code, deleted) + UNIQUE KEY uk_wh_tenant_code (customer_id, warehouse_code, deleted), + KEY idx_wh_tenant_status (customer_id, status, deleted), + KEY idx_wh_country (customer_id, country_code, deleted) ) ENGINE=InnoDB COMMENT='仓库主数据表:以租户为边界维护仓库编码、名称、国家、时区和启停状态'; CREATE TABLE IF NOT EXISTS wh_warehouse_address ( id BIGINT UNSIGNED NOT NULL COMMENT '仓库地址ID', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', address_type VARCHAR(20) NOT NULL DEFAULT 'OPERATING' COMMENT '地址类型,例如OPERATING或RETURN', country_code CHAR(2) NOT NULL COMMENT '国家二字码', @@ -114,13 +114,13 @@ CREATE TABLE IF NOT EXISTS wh_warehouse_address ( deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id), - UNIQUE KEY uk_address_type (tenant_id, warehouse_id, address_type, deleted), - KEY idx_address_warehouse (tenant_id, warehouse_id, deleted) + UNIQUE KEY uk_address_type (customer_id, warehouse_id, address_type, deleted), + KEY idx_address_warehouse (customer_id, warehouse_id, deleted) ) ENGINE=InnoDB COMMENT='仓库地址表:保存运营、退货等不同用途的地址及可选经纬度'; CREATE TABLE IF NOT EXISTS wh_service_capability ( id BIGINT UNSIGNED NOT NULL COMMENT '服务能力ID', - tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', capability_code VARCHAR(50) NOT NULL COMMENT '能力编码:RECEIVING、STORAGE、PICK_PACK、RETURN等', capability_name VARCHAR(100) NOT NULL COMMENT '能力名称', @@ -134,17 +134,17 @@ CREATE TABLE IF NOT EXISTS wh_service_capability ( deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id), - UNIQUE KEY uk_capability (tenant_id, warehouse_id, capability_code, deleted), - KEY idx_capability_query (tenant_id, warehouse_id, enabled, deleted) + UNIQUE KEY uk_capability (customer_id, warehouse_id, capability_code, deleted), + KEY idx_capability_query (customer_id, warehouse_id, enabled, deleted) ) ENGINE=InnoDB COMMENT='仓库服务能力表:声明仓库可提供的收货、仓储、拣包、退货等能力及截单时间'; --- 不使用跨服务数据库外键;仓库聚合内的归属由服务代码通过 tenant_id 与 warehouse_id 共同校验。 +-- 不使用跨服务数据库外键;仓库聚合内的归属由服务代码通过 customer_id 与 warehouse_id 共同校验。 -- 修复既有数据库中因历史脚本编码错误或英文初始化产生的表与字段注释。 ALTER TABLE wh_location COMMENT='仓库库位表:按租户和仓库维护库位及承重信息'; ALTER TABLE wh_location MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)', - MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', MODIFY COLUMN zone_code VARCHAR(50) NOT NULL COMMENT '仓库分区编码', MODIFY COLUMN location_code VARCHAR(64) NOT NULL COMMENT '仓库内唯一库位编码', @@ -159,22 +159,30 @@ ALTER TABLE wh_location MODIFY COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', MODIFY COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'; +CREATE TABLE IF NOT EXISTS wh_pallet_business_binding ( + id BIGINT NOT NULL, customer_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, + business_type VARCHAR(32) NOT NULL, business_id VARCHAR(128) NOT NULL, + pallet_id BIGINT NOT NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (id), UNIQUE KEY uk_wh_pallet_binding (customer_id,warehouse_id,business_type,business_id), + UNIQUE KEY uk_wh_pallet_binding_pallet (pallet_id) +) ENGINE=InnoDB COMMENT='Customer business binding to real warehouse pallet'; + -- Every warehouse has one non-occupiable virtual location used for inventory history. -INSERT INTO wh_location (id, tenant_id, warehouse_id, zone_code, location_code, location_type, +INSERT INTO wh_location (id, customer_id, warehouse_id, zone_code, location_code, location_type, max_load_kg, status, remark) -SELECT UUID_SHORT(), w.tenant_id, w.id, 'DEFAULT', CONCAT('LOC-', w.warehouse_code, '-DEFAULT'), +SELECT UUID_SHORT(), w.customer_id, w.id, 'DEFAULT', CONCAT('LOC-', w.warehouse_code, '-DEFAULT'), 'VIRTUAL', 999999999.000, 'ACTIVE', 'System default virtual location' FROM wh_warehouse w WHERE NOT EXISTS ( SELECT 1 FROM wh_location l - WHERE l.tenant_id = w.tenant_id AND l.warehouse_id = w.id + WHERE l.customer_id = w.customer_id AND l.warehouse_id = w.id AND l.location_code = CONCAT('LOC-', w.warehouse_code, '-DEFAULT') AND l.deleted = 0 ); ALTER TABLE wh_pallet COMMENT='仓库托盘表:按租户和仓库维护托盘、容量、单价及状态'; ALTER TABLE wh_pallet MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)', - MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', MODIFY COLUMN location_id BIGINT UNSIGNED NULL COMMENT '同仓库内绑定的库位ID,可为空', MODIFY COLUMN pallet_code VARCHAR(128) NOT NULL COMMENT '仓库内唯一托盘编码', @@ -193,7 +201,7 @@ ALTER TABLE wh_pallet ALTER TABLE wh_warehouse COMMENT='仓库主数据表:以租户为边界维护仓库编码、名称、国家、时区和启停状态'; ALTER TABLE wh_warehouse MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID(雪花ID)', - MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID', MODIFY COLUMN warehouse_code VARCHAR(50) NOT NULL COMMENT '租户内唯一仓库编码', MODIFY COLUMN warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称', MODIFY COLUMN warehouse_type VARCHAR(32) NOT NULL DEFAULT 'OVERSEAS' COMMENT '仓库类型', @@ -214,7 +222,7 @@ ALTER TABLE wh_warehouse ALTER TABLE wh_warehouse_address COMMENT='仓库地址表:保存运营、退货等不同用途的地址及可选经纬度'; ALTER TABLE wh_warehouse_address MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '仓库地址ID', - MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', MODIFY COLUMN address_type VARCHAR(20) NOT NULL DEFAULT 'OPERATING' COMMENT '地址类型:OPERATING运营、RETURN退货', MODIFY COLUMN country_code CHAR(2) NOT NULL COMMENT '国家二字码', @@ -236,7 +244,7 @@ ALTER TABLE wh_warehouse_address ALTER TABLE wh_service_capability COMMENT='仓库服务能力表:声明仓库可提供的收货、仓储、拣包、退货等能力及截单时间'; ALTER TABLE wh_service_capability MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '服务能力ID', - MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', MODIFY COLUMN capability_code VARCHAR(50) NOT NULL COMMENT '能力编码:RECEIVING收货、STORAGE仓储、PICK_PACK拣包、RETURN退货', MODIFY COLUMN capability_name VARCHAR(100) NOT NULL COMMENT '能力名称', diff --git a/warehouse/src/test/java/com/baiyue/warehouse/controller/WarehouseResourceControllerTest.java b/warehouse/src/test/java/com/baiyue/warehouse/controller/WarehouseResourceControllerTest.java index 58545db..4c1f680 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/controller/WarehouseResourceControllerTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/controller/WarehouseResourceControllerTest.java @@ -50,8 +50,8 @@ class WarehouseResourceControllerTest { verify(service).location(10002L, 20001L, 30001L); } - private static AuthenticatedUser user(long tenantId, String appCode) { - return new AuthenticatedUser(1L, tenantId, tenantId, 1L, + private static AuthenticatedUser user(long customerId, String appCode) { + return new AuthenticatedUser(1L, customerId, customerId, 1L, "tester", appCode, "WEB", Set.of()); } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/schema/WarehouseResourceLockSchemaTest.java b/warehouse/src/test/java/com/baiyue/warehouse/schema/WarehouseResourceLockSchemaTest.java index 035b049..639f8cf 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/schema/WarehouseResourceLockSchemaTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/schema/WarehouseResourceLockSchemaTest.java @@ -17,8 +17,8 @@ class WarehouseResourceLockSchemaTest { } assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_resource_lock"); - assertThat(sql).contains("UNIQUE KEY uk_resource_lock_reservation (tenant_id, reservation_key)"); - assertThat(sql).contains("KEY idx_resource_lock_pallet (tenant_id, warehouse_id, pallet_id, status)"); - assertThat(sql).contains("KEY idx_resource_lock_location (tenant_id, warehouse_id, location_id, status)"); + assertThat(sql).contains("UNIQUE KEY uk_resource_lock_reservation (customer_id, reservation_key)"); + assertThat(sql).contains("KEY idx_resource_lock_pallet (customer_id, warehouse_id, pallet_id, status)"); + assertThat(sql).contains("KEY idx_resource_lock_location (customer_id, warehouse_id, location_id, status)"); } } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseApplicationServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseApplicationServiceTest.java index 5391a71..8fce212 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseApplicationServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseApplicationServiceTest.java @@ -41,7 +41,7 @@ class WarehouseApplicationServiceTest { ArgumentCaptor location = ArgumentCaptor.forClass(WarehouseLocationEntity.class); verify(repository).insertLocation(location.capture()); - assertThat(location.getValue().getTenantId()).isEqualTo(10001L); + assertThat(location.getValue().getCustomerId()).isEqualTo(10001L); assertThat(location.getValue().getWarehouseId()).isEqualTo(20001L); assertThat(location.getValue().getLocationCode()).isEqualTo("LOC-US-LAX-01-DEFAULT"); assertThat(location.getValue().getLocationType()).isEqualTo("VIRTUAL"); diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceLockServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceLockServiceTest.java index 87bcc8b..a5420b4 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceLockServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceLockServiceTest.java @@ -250,7 +250,7 @@ class WarehouseResourceLockServiceTest { private static WarehouseResourceLockEntity activeLock() { WarehouseResourceLockEntity lock = new WarehouseResourceLockEntity(); lock.setId(501L); - lock.setTenantId(TENANT); + lock.setCustomerId(TENANT); lock.setWarehouseId(WAREHOUSE); lock.setPalletId(PALLET); lock.setLocationId(LOCATION); diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceServiceTest.java index 33be3a5..778edcd 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/WarehouseResourceServiceTest.java @@ -63,7 +63,7 @@ class WarehouseResourceServiceTest { assertThat(id).isEqualTo(2089000000000000001L); ArgumentCaptor captor = ArgumentCaptor.forClass(WarehouseLocationEntity.class); verify(repository).insertLocation(captor.capture()); - assertThat(captor.getValue().getTenantId()).isEqualTo(TENANT); + assertThat(captor.getValue().getCustomerId()).isEqualTo(TENANT); assertThat(captor.getValue().getWarehouseId()).isEqualTo(WAREHOUSE); } @@ -253,7 +253,7 @@ class WarehouseResourceServiceTest { private static WarehouseEntity warehouse() { WarehouseEntity warehouse = new WarehouseEntity(); warehouse.setId(WAREHOUSE); - warehouse.setTenantId(TENANT); + warehouse.setCustomerId(TENANT); warehouse.setWarehouseCode("US-LAX-01"); return warehouse; }