From 394c690601789f7cebf08a4634e7d15cb335cbca Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 26 Aug 2026 13:51:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/sql/头程数据库统一增量-MySQL8.sql | 6 ++ .../DeliveryTransferController.java | 14 ++- .../delivery/dto/DeliveryRequests.java | 37 ++++++- .../delivery/dto/DeliveryResponses.java | 30 +++++- .../lastleg/delivery/entity/DeliveryFee.java | 24 +++++ .../delivery/entity/DeliveryOperation.java | 21 ++++ .../delivery/entity/DeliveryOrder.java | 22 ++++ .../delivery/mapper/DeliveryFeeMapper.java | 16 +++ .../mapper/DeliveryOperationMapper.java | 16 +++ .../mapper/OutboundPdaPickMapper.java | 3 + .../service/DeliveryTransferService.java | 101 ++++++++++++++++-- .../dropship/mapper/DropshipCartonMapper.java | 2 + .../mapper/DropshipSkuInventoryMapper.java | 2 + .../src/main/resources/db/lastleg.sql | 22 ++++ .../DeliveryLongIdJsonContractTest.java | 71 ++++++++++++ .../service/DeliveryTransferServiceTest.java | 9 ++ .../service/LastlegOrderServiceTest.java | 4 +- scripts/db/incremental.sql | 6 ++ 18 files changed, 390 insertions(+), 16 deletions(-) create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryFee.java create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOperation.java create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryFeeMapper.java create mode 100644 lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOperationMapper.java create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryLongIdJsonContractTest.java diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index e71adf7d2..982373ef2 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -16563,6 +16563,12 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE 'ALTER TABLE ll_local_looseboard_order ADD UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id)')); PREPARE lastleg_looseboard_tail_stmt FROM @ddl; EXECUTE lastleg_looseboard_tail_stmt; DEALLOCATE PREPARE lastleg_looseboard_tail_stmt; +-- 派送单列表与详情快照、转派送费用及操作记录(当前上下文为尾程库)。 +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='overseas_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name VARCHAR(255) NULL COMMENT ''海外仓名称快照'', ADD COLUMN customer_name VARCHAR(255) NULL COMMENT ''客户名称快照'', ADD COLUMN waybill_no VARCHAR(128) NULL COMMENT ''承运运单号'', ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式'', ADD COLUMN carrier_name VARCHAR(255) NULL COMMENT ''派送承运商'', ADD COLUMN pickup_method VARCHAR(64) NULL COMMENT ''提货方式'', ADD COLUMN loading_type VARCHAR(64) NULL COMMENT ''装车类型'', ADD COLUMN vehicle_type VARCHAR(64) NULL COMMENT ''车辆类型'', ADD COLUMN vehicle_size VARCHAR(64) NULL COMMENT ''车辆大小'', ADD COLUMN vehicle_plate VARCHAR(64) NULL COMMENT ''车牌号'', ADD COLUMN driver_name VARCHAR(128) NULL COMMENT ''司机或车队名称'', ADD COLUMN driver_phone VARCHAR(64) NULL COMMENT ''车队联系电话'', ADD COLUMN appointment_account VARCHAR(128) NULL COMMENT ''预约账号'', ADD COLUMN seller_name VARCHAR(128) NULL COMMENT ''卖家姓名'', ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注''')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +CREATE TABLE IF NOT EXISTS ll_delivery_fee (id BIGINT NOT NULL COMMENT '派送费用主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', fee_direction VARCHAR(16) NOT NULL DEFAULT 'PAYABLE' COMMENT '应收应付方向', fee_type VARCHAR(64) NOT NULL COMMENT '费用类型', currency VARCHAR(8) NOT NULL COMMENT '币种', amount DECIMAL(18,2) NOT NULL COMMENT '费用金额', remark VARCHAR(500) NULL COMMENT '费用备注', created_by VARCHAR(128) NULL COMMENT '创建人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_delivery_fee_order(customer_id,delivery_order_id,id)) ENGINE=InnoDB COMMENT='派送单费用明细'; +CREATE TABLE IF NOT EXISTS ll_delivery_operation (id BIGINT NOT NULL COMMENT '派送操作主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', operation_node VARCHAR(64) NOT NULL COMMENT '操作节点', operator_id VARCHAR(128) NULL COMMENT '操作人ID', operator_name VARCHAR(128) NULL COMMENT '操作人名称', result VARCHAR(500) NOT NULL COMMENT '处理结果', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', PRIMARY KEY(id), KEY idx_ll_delivery_operation_order(customer_id,delivery_order_id,created_at,id)) ENGINE=InnoDB COMMENT='派送单操作记录'; + CALL baiyue_firstleg.assert_unified_final_schema(); DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema; SELECT 'PASS' AS migration_status, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java index 960f108f1..24d02b3d3 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/controller/DeliveryTransferController.java @@ -54,15 +54,25 @@ public class DeliveryTransferController { return ApiResponse.success(service.outboundDetail(scope(user, customerId), outboundNo, query)); } + @PostMapping("/outbound-orders/{outboundNo}/cancel") + @Operation(summary = "Cancel outbound and linked delivery, restoring inventory") + public ApiResponse cancelOutbound(@AuthenticationPrincipal AuthenticatedUser user, + @RequestParam(required = false) String customerId, + @PathVariable String outboundNo, + @Valid @RequestBody DeliveryRequests.Cancel request) { + service.cancelOutbound(scope(user, customerId), operator(user), outboundNo, request); + return ApiResponse.success(null); + } + @GetMapping("/delivery-orders") public ApiResponse> deliveryPage( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid DeliveryRequests.Query query) { - return ApiResponse.success(service.deliveryPage(scope(user, customerId), query)); + return ApiResponse.success(service.deliveryPage(listScope(user, customerId), query)); } @GetMapping("/delivery-orders/{deliveryNo}") - public ApiResponse deliveryDetail( + public ApiResponse deliveryDetail( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @PathVariable String deliveryNo, @Valid DeliveryRequests.PalletQuery query) { return ApiResponse.success(service.deliveryDetail(scope(user, customerId), deliveryNo, query)); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java index 49cd60a3f..672d64a26 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java @@ -7,16 +7,48 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import jakarta.validation.Valid; import java.util.List; +import java.math.BigDecimal; +import java.time.LocalDateTime; public final class DeliveryRequests { private DeliveryRequests() {} public record Transfer(@NotBlank @Size(max = 255) String idempotencyKey, - @Size(max = 1000) String remark) {} + @NotNull LocalDateTime plannedDeliveryAt, + @NotBlank @Size(max = 32) String deliveryMethod, + @Size(max = 128) String waybillNo, + @Size(max = 255) String carrierName, + @Size(max = 64) String pickupMethod, + @Size(max = 64) String loadingType, + @Size(max = 64) String vehicleType, + @Size(max = 64) String vehicleSize, + @Size(max = 128) String driverName, + @Size(max = 64) String driverPhone, + @Size(max = 64) String vehiclePlate, + @Size(max = 128) String appointmentAccount, + @Size(max = 128) String sellerName, + @Size(max = 1000) String remark, + @Valid @Size(max = 20) List fees) { + public Transfer(String idempotencyKey, String remark) { + this(idempotencyKey, LocalDateTime.now(), "SELF_DELIVERY", null, null, null, null, + null, null, null, null, null, null, null, remark, List.of()); + } + } + + public record FeeItem(@NotBlank @Size(max = 64) String feeType, + @NotBlank @Size(max = 8) String currency, + @NotNull @Min(0) BigDecimal amount, + @Size(max = 500) String remark) {} public record Query(@Min(1) long page, @Min(1) @Max(200) long size, - String documentNo, String warehouseId, String status) { + String documentNo, String warehouseId, String status, + String dispatchNo, String waybillNo, String vehiclePlate, + String driverName, String deliveryMethod, + LocalDateTime plannedFrom, LocalDateTime plannedTo) { public Query { if (page == 0) page = 1; if (size == 0) size = 20; } + public Query(long page, long size, String documentNo, String warehouseId, String status) { + this(page, size, documentNo, warehouseId, status, null, null, null, null, null, null, null); + } } public record PalletQuery(@Min(1) long page, @Min(1) @Max(200) long size, @@ -32,4 +64,5 @@ public final class DeliveryRequests { @NotBlank @Size(max=128) String sku) {} public record PdaPick(@NotNull Long outboundPalletItemId, @NotNull Long inventoryPositionId, @NotNull @Min(1) Long packages, @NotBlank @Size(max=255) String idempotencyKey) {} + public record Cancel(@NotBlank @Size(max=1000) String reason) {} } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java index 5a0e3e0d0..116cbd2ad 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryResponses.java @@ -1,6 +1,8 @@ package com.baiyue.lastleg.delivery.dto; import java.time.LocalDateTime; +import java.math.BigDecimal; +import java.util.List; public final class DeliveryResponses { private DeliveryResponses() {} @@ -12,9 +14,31 @@ public final class DeliveryResponses { public record OutboundRow(Long id, String outboundNo, String dispatchNo, String overseasWarehouseId, String status, int palletCount, int packageCount, LocalDateTime createdAt) {} - public record DeliveryRow(Long id, String deliveryNo, String outboundNo, String dispatchNo, - String overseasWarehouseId, String status, int palletCount, - int packageCount, LocalDateTime createdAt) {} + public record DeliveryRow(Long id, String customerId, String customerName, String deliveryNo, + String outboundNo, String dispatchNo, String overseasWarehouseId, + String overseasWarehouseName, String status, String waybillNo, + String deliveryMethod, String carrierName, String pickupMethod, + String loadingType, String vehicleType, String vehicleSize, + String vehiclePlate, String driverName, String driverPhone, + String appointmentAccount, String sellerName, int palletCount, + int packageCount, BigDecimal totalWeightKg, BigDecimal totalVolumeCbm, + LocalDateTime plannedDeliveryAt, LocalDateTime dispatchedAt, + LocalDateTime expectedArrivalAt, LocalDateTime signedAt, + String remark, LocalDateTime createdAt) {} + + public record DeliverySku(String inboundCode, String customerName, String destinationWarehouseCode, + String receivingAddress, String shipmentNo, String fbaTrackingNo, + String skuCode, String productName, int packageCount, + BigDecimal weightKg, BigDecimal volumeCbm) {} + + public record DeliveryFeeRow(Long id, String feeDirection, String feeType, String currency, + BigDecimal amount, String remark) {} + + public record DeliveryOperationRow(Long id, LocalDateTime operatedAt, String operationNode, + String operatorName, String result) {} + + public record DeliveryDetail(DeliveryRow order, List skus, + List fees, List operations) {} public record OutboundDetail(OutboundRow outbound, DeliveryRow delivery) {} public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryFee.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryFee.java new file mode 100644 index 000000000..adcf4cc51 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryFee.java @@ -0,0 +1,24 @@ +package com.baiyue.lastleg.delivery.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@TableName("ll_delivery_fee") +public class DeliveryFee { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long deliveryOrderId; + private String feeDirection; + private String feeType; + private String currency; + private BigDecimal amount; + private String remark; + private String createdBy; + private LocalDateTime createdAt; +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOperation.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOperation.java new file mode 100644 index 000000000..8cd075215 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOperation.java @@ -0,0 +1,21 @@ +package com.baiyue.lastleg.delivery.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_delivery_operation") +public class DeliveryOperation { + @TableId(type = IdType.ASSIGN_ID) private Long id; + private String customerId; + private Long deliveryOrderId; + private String operationNode; + private String operatorId; + private String operatorName; + private String result; + private LocalDateTime createdAt; +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java index ee1b4f809..93dbf8173 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/entity/DeliveryOrder.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import java.math.BigDecimal; import java.time.LocalDateTime; /** Delivery document linked one-to-one to its outbound document and dispatch. */ @@ -20,9 +21,30 @@ public class DeliveryOrder { private Long dispatchOrderId; private String dispatchNo; private String overseasWarehouseId; + private String overseasWarehouseName; + private String customerName; private String status; + private String waybillNo; + private String deliveryMethod; + private String carrierName; + private String pickupMethod; + private String loadingType; + private String vehicleType; + private String vehicleSize; + private String vehiclePlate; + private String driverName; + private String driverPhone; + private String appointmentAccount; + private String sellerName; private Integer packageCount; private Integer palletCount; + private BigDecimal totalWeightKg; + private BigDecimal totalVolumeCbm; + private LocalDateTime plannedDeliveryAt; + private LocalDateTime dispatchedAt; + private LocalDateTime expectedArrivalAt; + private LocalDateTime signedAt; + private String remark; private String idempotencyKey; private String createdBy; private LocalDateTime createdAt; diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryFeeMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryFeeMapper.java new file mode 100644 index 000000000..372d62e69 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryFeeMapper.java @@ -0,0 +1,16 @@ +package com.baiyue.lastleg.delivery.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.delivery.entity.DeliveryFee; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface DeliveryFeeMapper extends BaseMapper { + @Select("SELECT * FROM ll_delivery_fee WHERE customer_id=#{customerId} AND delivery_order_id=#{deliveryOrderId} ORDER BY id") + List findByDelivery(@Param("customerId") String customerId, + @Param("deliveryOrderId") Long deliveryOrderId); +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOperationMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOperationMapper.java new file mode 100644 index 000000000..1b34e5973 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/DeliveryOperationMapper.java @@ -0,0 +1,16 @@ +package com.baiyue.lastleg.delivery.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.delivery.entity.DeliveryOperation; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface DeliveryOperationMapper extends BaseMapper { + @Select("SELECT * FROM ll_delivery_operation WHERE customer_id=#{customerId} AND delivery_order_id=#{deliveryOrderId} ORDER BY created_at,id") + List findByDelivery(@Param("customerId") String customerId, + @Param("deliveryOrderId") Long deliveryOrderId); +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaPickMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaPickMapper.java index 6f19262c1..c4d30223b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaPickMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/mapper/OutboundPdaPickMapper.java @@ -3,9 +3,12 @@ package com.baiyue.lastleg.delivery.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.lastleg.delivery.entity.OutboundPdaPick; import org.apache.ibatis.annotations.*; +import java.util.List; @Mapper public interface OutboundPdaPickMapper extends BaseMapper { @Select("SELECT * FROM ll_outbound_pda_pick WHERE customer_id=#{customerId} AND idempotency_key=#{key} LIMIT 1") OutboundPdaPick findByKey(@Param("customerId") String customerId, @Param("key") String key); + @Select("SELECT * FROM ll_outbound_pda_pick WHERE customer_id=#{customerId} AND task_id IN (SELECT id FROM ll_outbound_pda_task WHERE customer_id=#{customerId} AND outbound_order_id=#{outboundId})") + List findByOutbound(@Param("customerId") String customerId, @Param("outboundId") Long outboundId); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java index a0b1a210b..0c9d26c6b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java @@ -7,9 +7,13 @@ import com.baiyue.common.core.api.PageResult; import com.baiyue.lastleg.delivery.dto.DeliveryRequests; import com.baiyue.lastleg.delivery.dto.DeliveryResponses; import com.baiyue.lastleg.delivery.entity.DeliveryOrder; +import com.baiyue.lastleg.delivery.entity.DeliveryFee; +import com.baiyue.lastleg.delivery.entity.DeliveryOperation; import com.baiyue.lastleg.delivery.entity.OutboundOrder; import com.baiyue.lastleg.delivery.entity.OutboundPalletItem; import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper; +import com.baiyue.lastleg.delivery.mapper.DeliveryFeeMapper; +import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper; import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper; import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper; import com.baiyue.lastleg.dispatch.entity.DispatchItem; @@ -18,6 +22,7 @@ import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper; import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper; import com.baiyue.lastleg.entity.LastlegOrder; import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; +import com.baiyue.lastleg.entity.LastlegOrderDetail; import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine; import com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanLineMapper; @@ -37,6 +42,7 @@ import com.baiyue.lastleg.dropship.mapper.DropshipCartonMapper; import com.baiyue.lastleg.dropship.mapper.DropshipSkuInventoryMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import jakarta.annotation.Resource; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.HttpStatus; @@ -47,6 +53,7 @@ import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.math.BigDecimal; import java.util.*; @Service @@ -56,6 +63,8 @@ public class DeliveryTransferService { @Resource private OutboundOrderMapper outboundOrders; @Resource private OutboundPalletItemMapper outboundPallets; @Resource private DeliveryOrderMapper deliveryOrders; + @Resource private DeliveryFeeMapper deliveryFees; + @Resource private DeliveryOperationMapper deliveryOperations; @Resource private OutboundPdaTaskMapper pdaTasks; @Resource private OutboundPdaPickMapper pdaPicks; @Resource private InventoryPositionMapper inventoryPositions; @@ -67,6 +76,7 @@ public class DeliveryTransferService { @Resource private InboundPlanLineMapper planLines; @Resource private LastlegOrderMapper tailOrders; @Resource private LastlegOrderStatusHistoryMapper histories; + @Resource private LastlegOrderDetailMapper tailOrderDetails; @Transactional public DeliveryResponses.TransferResult transfer(String customerId, String dispatchNo, String operatorId, @@ -133,10 +143,27 @@ public class DeliveryTransferService { 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()); - delivery.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); delivery.setStatus("WAIT_DELIVERY"); + delivery.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); delivery.setOverseasWarehouseName(dispatch.getOverseasWarehouseName()); + LastlegOrder firstOrder = firstTailOrder(customerId, items); + delivery.setCustomerName(firstOrder == null ? null : firstOrder.getCustomerName()); delivery.setStatus("WAIT_DELIVERY"); + delivery.setWaybillNo(trim(request.waybillNo())); delivery.setDeliveryMethod(request.deliveryMethod().trim()); + delivery.setCarrierName(firstText(request.carrierName(), dispatch.getCarrierName())); delivery.setPickupMethod(trim(request.pickupMethod())); + delivery.setLoadingType(trim(request.loadingType())); delivery.setVehicleType(trim(request.vehicleType())); delivery.setVehicleSize(trim(request.vehicleSize())); + delivery.setVehiclePlate(firstText(request.vehiclePlate(), dispatch.getVehiclePlate())); delivery.setDriverName(firstText(request.driverName(), dispatch.getDriverName())); + delivery.setDriverPhone(firstText(request.driverPhone(), dispatch.getDriverPhone())); delivery.setAppointmentAccount(trim(request.appointmentAccount())); + delivery.setSellerName(trim(request.sellerName())); delivery.setPlannedDeliveryAt(request.plannedDeliveryAt()); delivery.setPackageCount(outbound.getPackageCount()); delivery.setPalletCount(outbound.getPalletCount()); + delivery.setTotalVolumeCbm(items.stream().map(DispatchItem::getVolumeCbm).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); + delivery.setTotalWeightKg(totalWeight(customerId, items)); delivery.setExpectedArrivalAt(dispatch.getEstimatedArrivalAt()); delivery.setRemark(trim(request.remark())); delivery.setIdempotencyKey(request.idempotencyKey().trim()); delivery.setCreatedBy(operatorId); delivery.setCreatedAt(now); delivery.setUpdatedAt(now); deliveryOrders.insert(delivery); + List feeInputs = request.fees() == null ? List.of() : request.fees(); + for (DeliveryRequests.FeeItem input : feeInputs) { + DeliveryFee fee = new DeliveryFee(); fee.setCustomerId(customerId); fee.setDeliveryOrderId(delivery.getId()); + fee.setFeeDirection("PAYABLE"); fee.setFeeType(input.feeType().trim()); fee.setCurrency(input.currency().trim().toUpperCase(Locale.ROOT)); + fee.setAmount(input.amount()); fee.setRemark(trim(input.remark())); fee.setCreatedBy(operatorId); fee.setCreatedAt(now); deliveryFees.insert(fee); + } + recordOperation(customerId, delivery.getId(), operatorId, "CREATE_DELIVERY", "CREATED", now); bindTailOrders(customerId, items, outbound.getOutboundNo(), delivery.getDeliveryNo(), request.remark(), now); return result(outbound, delivery, false); } @@ -194,6 +221,10 @@ public class DeliveryTransferService { for (DropshipCarton carton : cartons) { carton.setStatus("SHIPPED"); carton.setOutboundOrderNo(outbound.getOutboundNo()); carton.setVersion((carton.getVersion() == null ? 0 : carton.getVersion()) + 1); carton.setUpdatedAt(LocalDateTime.now()); dropshipCartons.updateById(carton); } OutboundPdaPick pick = new OutboundPdaPick(); pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId()); pick.setInventoryPositionId(request.inventoryPositionId()); pick.setPackages(request.packages()); pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick); item.setPickedPackageCount(value(item.getPickedPackageCount()) + request.packages().intValue()); item.setStatus(item.getPickedPackageCount() >= item.getPackageCount() ? "PICKED" : "PARTIAL_PICKED"); outboundPallets.updateById(item); + List pickedItems = outboundPallets.findByOutbound(customerId, outbound.getId()); + if (!pickedItems.isEmpty() && pickedItems.stream().allMatch(i -> "PICKED".equals(i.getStatus()))) { + outbound.setStatus("PICKED"); outbound.setUpdatedAt(LocalDateTime.now()); outboundOrders.updateById(outbound); + } task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task); return taskResult(outbound, task); } @@ -207,11 +238,45 @@ public class DeliveryTransferService { task.setStatus("COMPLETED"); task.setCompletedAt(LocalDateTime.now()); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task); outbound.setStatus("OUTBOUNDED"); outbound.setUpdatedAt(LocalDateTime.now()); outboundOrders.updateById(outbound); return taskResult(outbound, task); } + @Transactional + public void cancelOutbound(String customerId, String operatorId, String outboundNo, DeliveryRequests.Cancel request) { + OutboundOrder outbound = outboundOrders.lockByNo(customerId, outboundNo); + if (outbound == null) throw notFound("Outbound order not found"); + if ("CANCELLED".equals(outbound.getStatus())) return; + List outboundItems = outboundPallets.findByOutbound(customerId, outbound.getId()); + if (outboundItems.isEmpty() || outboundItems.stream().anyMatch(i -> !"PICKED".equals(i.getStatus()))) + throw conflict("Outbound order can only be cancelled after picking is completed"); + DeliveryOrder existingDelivery = deliveryOrders.lockByOutbound(customerId, outbound.getId()); + if (existingDelivery != null && !"WAIT_DELIVERY".equals(existingDelivery.getStatus())) + throw conflict("Outbound order cannot be cancelled while delivery is in progress"); + LocalDateTime now = LocalDateTime.now(); + for (OutboundPdaPick pick : pdaPicks.findByOutbound(customerId, outbound.getId())) { + inventoryCommands.adjust(customerId, operatorId, + new InventoryCommandRequests.Adjustment(pick.getInventoryPositionId(), pick.getPackages(), "OUTBOUND_CANCEL", + outbound.getOutboundNo(), outbound.getOutboundNo() + ":CANCEL:" + pick.getId(), request.reason())); + OutboundPalletItem pickedItem = outboundItems.stream().filter(i -> Objects.equals(i.getId(), pick.getOutboundPalletItemId())).findFirst().orElse(null); + DropshipOrderProfile profile = dropshipProfiles.findPosition(customerId, pick.getInventoryPositionId()); + if (profile != null && pickedItem != null) dropshipSkuInventory.restore(customerId, profile.getId(), pickedItem.getSkuCode(), pick.getPackages()); + } + dropshipCartons.restoreOutbound(customerId, outbound.getOutboundNo()); + DeliveryOrder delivery = existingDelivery; + if (delivery != null) { delivery.setStatus("CANCELLED"); delivery.setUpdatedAt(now); deliveryOrders.updateById(delivery); } + outbound.setStatus("CANCELLED"); outbound.setUpdatedAt(now); outboundOrders.updateById(outbound); + for (OutboundPalletItem item : outboundItems) { item.setStatus("CANCELLED"); outboundPallets.updateById(item); } + if (delivery != null) { + for (LastlegOrder order : tailOrders.selectList(new LambdaQueryWrapper().eq(LastlegOrder::getCustomerId, customerId).eq(LastlegOrder::getOutboundOrderNo, outboundNo))) { + String from = order.getCurrentStatus(); order.setCurrentStatus("RECEIVED"); order.setSubOrderStatus(null); order.setOutboundOrderNo(null); order.setDeliveryOrderNo(null); order.setUpdatedAt(now); tailOrders.updateById(order); + LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId()); history.setFromStatus(from); history.setToStatus("RECEIVED"); history.setEventId("CANCEL_OUTBOUND:" + outboundNo); history.setRemark(request.reason().trim()); history.setCreatedAt(now); histories.insert(history); + } + } + } + public List pdaTasks(String customerId, String status) { return pdaTasks.list(customerId, StringUtils.hasText(status) ? status : null).stream().map(task -> taskResult(outboundOrders.selectById(task.getOutboundOrderId()), task)).toList(); } private DeliveryResponses.PdaTask taskResult(OutboundOrder outbound, OutboundPdaTask task) { return new DeliveryResponses.PdaTask(task.getId(), task.getTaskNo(), task.getStatus(), outbound.getOutboundNo(), task.getPushedAt(), task.getCompletedAt()); } private String outboundNo(Long id) { OutboundOrder value = outboundOrders.selectById(id); return value == null ? "" : value.getOutboundNo(); } private int value(Integer value) { return value == null ? 0 : value; } + private long value(Long value) { return value == null ? 0L : value; } public PageResult outboundPage(String customerId, DeliveryRequests.Query query) { Page page = outboundOrders.selectPage(new Page<>(query.page(), query.size()), @@ -233,21 +298,35 @@ public class DeliveryTransferService { public PageResult deliveryPage(String customerId, DeliveryRequests.Query query) { Page page = deliveryOrders.selectPage(new Page<>(query.page(), query.size()), - new LambdaQueryWrapper().eq(DeliveryOrder::getCustomerId, customerId) + new LambdaQueryWrapper().eq(StringUtils.hasText(customerId), 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()) + .like(StringUtils.hasText(query.dispatchNo()), DeliveryOrder::getDispatchNo, query.dispatchNo()) + .like(StringUtils.hasText(query.waybillNo()), DeliveryOrder::getWaybillNo, query.waybillNo()) + .like(StringUtils.hasText(query.vehiclePlate()), DeliveryOrder::getVehiclePlate, query.vehiclePlate()) + .like(StringUtils.hasText(query.driverName()), DeliveryOrder::getDriverName, query.driverName()) + .eq(StringUtils.hasText(query.deliveryMethod()), DeliveryOrder::getDeliveryMethod, query.deliveryMethod()) + .ge(query.plannedFrom() != null, DeliveryOrder::getPlannedDeliveryAt, query.plannedFrom()) + .le(query.plannedTo() != null, DeliveryOrder::getPlannedDeliveryAt, query.plannedTo()) .orderByDesc(DeliveryOrder::getCreatedAt)); return page(page, page.getRecords().stream().map(this::deliveryRow).toList()); } - public DeliveryResponses.OutboundDetail deliveryDetail(String customerId, String deliveryNo, + public DeliveryResponses.DeliveryDetail deliveryDetail(String customerId, String deliveryNo, DeliveryRequests.PalletQuery query) { DeliveryOrder delivery = deliveryOrders.findByNo(customerId, deliveryNo); if (delivery == null) throw notFound("Delivery order not found"); - OutboundOrder outbound = outboundOrders.selectById(delivery.getOutboundOrderId()); - if (outbound == null || !customerId.equals(outbound.getCustomerId())) throw notFound("Outbound order not found"); - return outboundDetail(customerId, outbound.getOutboundNo(), query); + List items = dispatchItems.findByDispatchOrder(customerId, delivery.getDispatchOrderId()); + List orderIds = items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList(); + Map ordersById = orderIds.isEmpty() ? Map.of() : tailOrders.selectBatchIds(orderIds).stream() + .filter(order -> customerId.equals(order.getCustomerId())).collect(java.util.stream.Collectors.toMap(LastlegOrder::getId, order -> order)); + List details = orderIds.isEmpty() ? List.of() : tailOrderDetails.selectList( + new LambdaQueryWrapper().eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, orderIds)); + List skus = details.stream().map(detail -> sku(detail, ordersById.get(detail.getOrderId()))).toList(); + return new DeliveryResponses.DeliveryDetail(deliveryRow(delivery), skus, + deliveryFees.findByDelivery(customerId, delivery.getId()).stream().map(this::feeRow).toList(), + deliveryOperations.findByDelivery(customerId, delivery.getId()).stream().map(this::operationRow).toList()); } private void bindTailOrders(String customerId, List items, String outboundNo, String deliveryNo, @@ -290,7 +369,15 @@ public class DeliveryTransferService { d.getDeliveryNo(), d.getStatus(), o.getPalletCount(), o.getPackageCount(), replayed); } private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getCreatedAt()); } - private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getStatus(), d.getPalletCount(), d.getPackageCount(), d.getCreatedAt()); } + private DeliveryResponses.DeliveryRow deliveryRow(DeliveryOrder d) { return new DeliveryResponses.DeliveryRow(d.getId(), d.getCustomerId(), d.getCustomerName(), d.getDeliveryNo(), d.getOutboundNo(), d.getDispatchNo(), d.getOverseasWarehouseId(), d.getOverseasWarehouseName(), d.getStatus(), d.getWaybillNo(), d.getDeliveryMethod(), d.getCarrierName(), d.getPickupMethod(), d.getLoadingType(), d.getVehicleType(), d.getVehicleSize(), d.getVehiclePlate(), d.getDriverName(), d.getDriverPhone(), d.getAppointmentAccount(), d.getSellerName(), value(d.getPalletCount()), value(d.getPackageCount()), d.getTotalWeightKg(), d.getTotalVolumeCbm(), d.getPlannedDeliveryAt(), d.getDispatchedAt(), d.getExpectedArrivalAt(), d.getSignedAt(), d.getRemark(), d.getCreatedAt()); } + private DeliveryResponses.DeliverySku sku(LastlegOrderDetail d, LastlegOrder order) { return new DeliveryResponses.DeliverySku(order == null ? null : order.getInboundCode(), order == null ? null : order.getCustomerName(), d.getDestinationWarehouseCode(), d.getDestinationWarehouseName(), d.getShipmentNo(), d.getFbaTrackingNo(), d.getSkuCode(), firstText(d.getProductNameZh(), d.getProductNameEn()), value(d.getPackageCount()), d.getGrossWeightKg(), d.getVolumeCbm()); } + private DeliveryResponses.DeliveryFeeRow feeRow(DeliveryFee f) { return new DeliveryResponses.DeliveryFeeRow(f.getId(), f.getFeeDirection(), f.getFeeType(), f.getCurrency(), f.getAmount(), f.getRemark()); } + private DeliveryResponses.DeliveryOperationRow operationRow(DeliveryOperation o) { return new DeliveryResponses.DeliveryOperationRow(o.getId(), o.getCreatedAt(), o.getOperationNode(), firstText(o.getOperatorName(), o.getOperatorId()), o.getResult()); } + private void recordOperation(String customerId, Long deliveryId, String operatorId, String node, String result, LocalDateTime now) { DeliveryOperation operation = new DeliveryOperation(); operation.setCustomerId(customerId); operation.setDeliveryOrderId(deliveryId); operation.setOperationNode(node); operation.setOperatorId(operatorId); operation.setOperatorName(operatorId); operation.setResult(result); operation.setCreatedAt(now); deliveryOperations.insert(operation); } + private LastlegOrder firstTailOrder(String customerId, List items) { return items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).map(tailOrders::selectById).filter(Objects::nonNull).filter(order -> customerId.equals(order.getCustomerId())).findFirst().orElse(null); } + private BigDecimal totalWeight(String customerId, List items) { List ids = items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList(); if (ids.isEmpty()) return BigDecimal.ZERO; return tailOrderDetails.selectList(new LambdaQueryWrapper().eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, ids)).stream().map(LastlegOrderDetail::getGrossWeightKg).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); } + private static String trim(String value) { return StringUtils.hasText(value) ? value.trim() : null; } + private static String firstText(String primary, String fallback) { return StringUtils.hasText(primary) ? primary.trim() : trim(fallback); } private static PageResult page(Page page, List records) { return new PageResult<>(records, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); } 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-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java index cb57e1de3..89ed7542a 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipCartonMapper.java @@ -14,4 +14,6 @@ public interface DropshipCartonMapper extends BaseMapper { List listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId); @Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND profile_id=#{profileId} AND sku_code=#{sku} AND status='AVAILABLE' ORDER BY carton_sequence LIMIT #{packages} FOR UPDATE") List lockAvailable(@Param("customerId") String customerId, @Param("profileId") Long profileId, @Param("sku") String sku, @Param("packages") long packages); + @Update("UPDATE ll_dropship_carton SET status='AVAILABLE', outbound_order_no=NULL, updated_at=NOW(3) WHERE customer_id=#{customerId} AND outbound_order_no=#{outboundNo} AND status='SHIPPED'") + int restoreOutbound(@Param("customerId") String customerId, @Param("outboundNo") String outboundNo); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java index 3c97736f1..1ea85d9ed 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dropship/mapper/DropshipSkuInventoryMapper.java @@ -13,4 +13,6 @@ public interface DropshipSkuInventoryMapper extends BaseMapper listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId); @Update("UPDATE ll_dropship_sku_inventory SET package_count=package_count-#{packages},updated_at=NOW(3) WHERE customer_id=#{customerId} AND profile_id=#{profileId} AND sku_code=#{sku} AND package_count>=#{packages}") int deduct(@Param("customerId") String customerId, @Param("profileId") Long profileId, @Param("sku") String sku, @Param("packages") long packages); + @Update("UPDATE ll_dropship_sku_inventory SET package_count=package_count+#{packages},updated_at=NOW(3) WHERE customer_id=#{customerId} AND profile_id=#{profileId} AND sku_code=#{sku}") + int restore(@Param("customerId") String customerId, @Param("profileId") Long profileId, @Param("sku") String sku, @Param("packages") long packages); } diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 26cd0f694..e85e97965 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -165,6 +165,28 @@ CREATE TABLE IF NOT EXISTS ll_order_operation_log ( KEY idx_ll_order_operation_customer_order(customer_id,order_id,created_at) ) ENGINE=InnoDB COMMENT='尾程订单操作记录'; +-- 派送单列表与详情快照、转派送费用及操作记录。 +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='overseas_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name VARCHAR(255) NULL COMMENT ''海外仓名称快照'' AFTER overseas_warehouse_id')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='customer_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN customer_name VARCHAR(255) NULL COMMENT ''客户名称快照'' AFTER overseas_warehouse_name')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='waybill_no'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN waybill_no VARCHAR(128) NULL COMMENT ''承运运单号'', ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式'', ADD COLUMN carrier_name VARCHAR(255) NULL COMMENT ''派送承运商'', ADD COLUMN pickup_method VARCHAR(64) NULL COMMENT ''提货方式'', ADD COLUMN loading_type VARCHAR(64) NULL COMMENT ''装车类型'', ADD COLUMN vehicle_type VARCHAR(64) NULL COMMENT ''车辆类型'', ADD COLUMN vehicle_size VARCHAR(64) NULL COMMENT ''车辆大小'', ADD COLUMN vehicle_plate VARCHAR(64) NULL COMMENT ''车牌号'', ADD COLUMN driver_name VARCHAR(128) NULL COMMENT ''司机或车队名称'', ADD COLUMN driver_phone VARCHAR(64) NULL COMMENT ''车队联系电话'', ADD COLUMN appointment_account VARCHAR(128) NULL COMMENT ''预约账号'', ADD COLUMN seller_name VARCHAR(128) NULL COMMENT ''卖家姓名'' AFTER status')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='total_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注'' AFTER pallet_count')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +CREATE TABLE IF NOT EXISTS ll_delivery_fee ( + id BIGINT NOT NULL COMMENT '派送费用主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', + fee_direction VARCHAR(16) NOT NULL DEFAULT 'PAYABLE' COMMENT '应收应付方向', fee_type VARCHAR(64) NOT NULL COMMENT '费用类型', currency VARCHAR(8) NOT NULL COMMENT '币种', + amount DECIMAL(18,2) NOT NULL COMMENT '费用金额', remark VARCHAR(500) NULL COMMENT '费用备注', created_by VARCHAR(128) NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_delivery_fee_order(customer_id,delivery_order_id,id) +) ENGINE=InnoDB COMMENT='派送单费用明细'; +CREATE TABLE IF NOT EXISTS ll_delivery_operation ( + id BIGINT NOT NULL COMMENT '派送操作主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', + operation_node VARCHAR(64) NOT NULL COMMENT '操作节点', operator_id VARCHAR(128) NULL COMMENT '操作人ID', operator_name VARCHAR(128) NULL COMMENT '操作人名称', + result VARCHAR(500) NOT NULL COMMENT '处理结果', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY(id), KEY idx_ll_delivery_operation_order(customer_id,delivery_order_id,created_at,id) +) ENGINE=InnoDB COMMENT='派送单操作记录'; + CREATE TABLE IF NOT EXISTS ll_event_conflict ( id BIGINT NOT NULL COMMENT '冲突记录主键', event_id VARCHAR(128) NOT NULL COMMENT '冲突事件 ID', customer_id BIGINT NOT NULL COMMENT '租户 ID', conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryLongIdJsonContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryLongIdJsonContractTest.java new file mode 100644 index 000000000..fd4e31675 --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/DeliveryLongIdJsonContractTest.java @@ -0,0 +1,71 @@ +package com.baiyue.lastleg.delivery; + +import com.baiyue.common.web.LongIdJacksonConfiguration; +import com.baiyue.lastleg.delivery.dto.DeliveryRequests; +import com.baiyue.lastleg.delivery.dto.DeliveryResponses; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("派送单长整型 ID JSON 契约") +class DeliveryLongIdJsonContractTest { + + private static final long DELIVERY_ID = 19_000_000_000_000_001L; + private static final long FEE_ID = 19_000_000_000_000_002L; + private static final long OPERATION_ID = 19_000_000_000_000_003L; + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + JacksonAutoConfiguration.class, LongIdJacksonConfiguration.class)); + + @Test + void deliveryResponseIdsShouldBeSerializedAsJsonStrings() { + contextRunner.run(context -> { + ObjectMapper objectMapper = context.getBean(ObjectMapper.class); + DeliveryResponses.DeliveryDetail detail = new DeliveryResponses.DeliveryDetail( + deliveryRow(), + List.of(), + List.of(new DeliveryResponses.DeliveryFeeRow( + FEE_ID, "RECEIVABLE", "DELIVERY_FEE", "USD", null, null)), + List.of(new DeliveryResponses.DeliveryOperationRow( + OPERATION_ID, null, "CREATED", "tester", "success"))); + + JsonNode json = objectMapper.readTree(objectMapper.writeValueAsString(detail)); + + assertThat(json.at("/order/id").isTextual()).isTrue(); + assertThat(json.at("/order/id").textValue()).isEqualTo(Long.toString(DELIVERY_ID)); + assertThat(json.at("/fees/0/id").textValue()).isEqualTo(Long.toString(FEE_ID)); + assertThat(json.at("/operations/0/id").textValue()).isEqualTo(Long.toString(OPERATION_ID)); + }); + } + + @Test + void quotedRequestIdShouldBeDeserializedToLong() { + contextRunner.run(context -> { + ObjectMapper objectMapper = context.getBean(ObjectMapper.class); + + DeliveryRequests.OneClickItem item = objectMapper.readValue( + "{\"inventoryPositionId\":\"19000000000000001\",\"packages\":\"2\",\"sku\":\"SKU-1\"}", + DeliveryRequests.OneClickItem.class); + + assertThat(item.inventoryPositionId()).isEqualTo(DELIVERY_ID); + assertThat(item.packages()).isEqualTo(2L); + }); + } + + private DeliveryResponses.DeliveryRow deliveryRow() { + return new DeliveryResponses.DeliveryRow( + DELIVERY_ID, + null, null, "PS-1", null, null, null, null, "CREATED", null, + null, null, null, null, null, null, null, null, null, null, null, + 0, 0, null, null, null, null, null, null, null, null); + } +} diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java index 29cbef8a2..7b4d63944 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java @@ -7,6 +7,8 @@ import com.baiyue.lastleg.delivery.entity.DeliveryOrder; import com.baiyue.lastleg.delivery.entity.OutboundOrder; import com.baiyue.lastleg.delivery.entity.OutboundPalletItem; import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper; +import com.baiyue.lastleg.delivery.mapper.DeliveryFeeMapper; +import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper; import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper; import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper; import com.baiyue.lastleg.dispatch.entity.DispatchItem; @@ -19,6 +21,7 @@ import com.baiyue.lastleg.entity.LastlegOrder; import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -47,10 +50,13 @@ class DeliveryTransferServiceTest { @Mock OutboundOrderMapper outboundOrders; @Mock OutboundPalletItemMapper outboundPallets; @Mock DeliveryOrderMapper deliveryOrders; + @Mock DeliveryFeeMapper deliveryFees; + @Mock DeliveryOperationMapper deliveryOperations; @Mock InboundPlanPalletAllocationMapper planAllocations; @Mock InboundPlanLineMapper planLines; @Mock LastlegOrderMapper tailOrders; @Mock LastlegOrderStatusHistoryMapper histories; + @Mock LastlegOrderDetailMapper tailOrderDetails; private DeliveryTransferService service; @@ -62,10 +68,13 @@ class DeliveryTransferServiceTest { ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders); ReflectionTestUtils.setField(service, "outboundPallets", outboundPallets); ReflectionTestUtils.setField(service, "deliveryOrders", deliveryOrders); + ReflectionTestUtils.setField(service, "deliveryFees", deliveryFees); + ReflectionTestUtils.setField(service, "deliveryOperations", deliveryOperations); ReflectionTestUtils.setField(service, "planAllocations", planAllocations); ReflectionTestUtils.setField(service, "planLines", planLines); ReflectionTestUtils.setField(service, "tailOrders", tailOrders); ReflectionTestUtils.setField(service, "histories", histories); + ReflectionTestUtils.setField(service, "tailOrderDetails", tailOrderDetails); } @Test diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java index 737b4e848..6746ae723 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java @@ -111,8 +111,8 @@ class LastlegOrderServiceTest { assertEquals("100", fee.getCustomerId()); assertEquals("清关费", fee.getFeeItem()); assertEquals(new BigDecimal("420.00"), fee.getAmount()); - verify(fees).insert(any()); - verify(operations).insert(any()); + verify(fees).insert(any(com.baiyue.lastleg.entity.LastlegOrderFee.class)); + verify(operations).insert(any(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class)); } @Test diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index 58a4dd551..5f5fe442e 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -3842,3 +3842,9 @@ CREATE TABLE IF NOT EXISTS ll_order_operation_log ( created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', PRIMARY KEY(id), KEY idx_ll_order_operation_customer_order(customer_id,order_id,created_at) ) ENGINE=InnoDB COMMENT='尾程订单操作记录'; + +-- 派送单列表与详情快照、转派送费用及操作记录。 +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='overseas_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name VARCHAR(255) NULL COMMENT ''海外仓名称快照'', ADD COLUMN customer_name VARCHAR(255) NULL COMMENT ''客户名称快照'', ADD COLUMN waybill_no VARCHAR(128) NULL COMMENT ''承运运单号'', ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式'', ADD COLUMN carrier_name VARCHAR(255) NULL COMMENT ''派送承运商'', ADD COLUMN pickup_method VARCHAR(64) NULL COMMENT ''提货方式'', ADD COLUMN loading_type VARCHAR(64) NULL COMMENT ''装车类型'', ADD COLUMN vehicle_type VARCHAR(64) NULL COMMENT ''车辆类型'', ADD COLUMN vehicle_size VARCHAR(64) NULL COMMENT ''车辆大小'', ADD COLUMN vehicle_plate VARCHAR(64) NULL COMMENT ''车牌号'', ADD COLUMN driver_name VARCHAR(128) NULL COMMENT ''司机或车队名称'', ADD COLUMN driver_phone VARCHAR(64) NULL COMMENT ''车队联系电话'', ADD COLUMN appointment_account VARCHAR(128) NULL COMMENT ''预约账号'', ADD COLUMN seller_name VARCHAR(128) NULL COMMENT ''卖家姓名'', ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注''')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +CREATE TABLE IF NOT EXISTS ll_delivery_fee (id BIGINT NOT NULL COMMENT '派送费用主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', fee_direction VARCHAR(16) NOT NULL DEFAULT 'PAYABLE' COMMENT '应收应付方向', fee_type VARCHAR(64) NOT NULL COMMENT '费用类型', currency VARCHAR(8) NOT NULL COMMENT '币种', amount DECIMAL(18,2) NOT NULL COMMENT '费用金额', remark VARCHAR(500) NULL COMMENT '费用备注', created_by VARCHAR(128) NULL COMMENT '创建人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_delivery_fee_order(customer_id,delivery_order_id,id)) ENGINE=InnoDB COMMENT='派送单费用明细'; +CREATE TABLE IF NOT EXISTS ll_delivery_operation (id BIGINT NOT NULL COMMENT '派送操作主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', operation_node VARCHAR(64) NOT NULL COMMENT '操作节点', operator_id VARCHAR(128) NULL COMMENT '操作人ID', operator_name VARCHAR(128) NULL COMMENT '操作人名称', result VARCHAR(500) NOT NULL COMMENT '处理结果', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', PRIMARY KEY(id), KEY idx_ll_delivery_operation_order(customer_id,delivery_order_id,created_at,id)) ENGINE=InnoDB COMMENT='派送单操作记录';