Merge branch 'dev-lyhfive' of http://git.buggoglobal.com/java-project/baiyue-firstleg into dev
This commit is contained in:
+32
-7
@@ -6,18 +6,18 @@ import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.baiyue.lastleg.delivery.service.DeliveryTransferService;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestController
|
||||
@ConditionalOnProperty(name = "lastleg.future-outbound-api.enabled", havingValue = "true")
|
||||
@RequestMapping("/api/platform/v1/last-leg")
|
||||
@Tag(name = "Outbound and delivery")
|
||||
public class DeliveryTransferController {
|
||||
@@ -32,11 +32,19 @@ public class DeliveryTransferController {
|
||||
return ApiResponse.success(service.transfer(scope(user, customerId), dispatchNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/outbound-orders/one-click-dropship")
|
||||
@Operation(summary = "Create one-click dropship outbound and push a PDA picking task")
|
||||
public ApiResponse<DeliveryResponses.PdaTask> createOneClick(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
|
||||
@Valid @RequestBody DeliveryRequests.OneClickOutbound request) {
|
||||
return ApiResponse.success(service.createOneClick(scope(user, customerId), operator(user), request));
|
||||
}
|
||||
|
||||
@GetMapping("/outbound-orders")
|
||||
public ApiResponse<PageResult<DeliveryResponses.OutboundRow>> outboundPage(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
|
||||
@Valid DeliveryRequests.Query query) {
|
||||
return ApiResponse.success(service.outboundPage(scope(user, customerId), query));
|
||||
return ApiResponse.success(service.outboundPage(listScope(user, customerId), query));
|
||||
}
|
||||
|
||||
@GetMapping("/outbound-orders/{outboundNo}")
|
||||
@@ -46,18 +54,30 @@ 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<Void> 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")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.DELIVERY_LIST + "')")
|
||||
public ApiResponse<PageResult<DeliveryResponses.DeliveryRow>> 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<DeliveryResponses.OutboundDetail> deliveryDetail(
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.DELIVERY_LIST + "')")
|
||||
public ApiResponse<DeliveryResponses.DeliveryDetail> 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));
|
||||
@PathVariable String deliveryNo) {
|
||||
return ApiResponse.success(service.deliveryDetail(scope(user, customerId), deliveryNo));
|
||||
}
|
||||
|
||||
private String scope(AuthenticatedUser user, String requestedCustomer) {
|
||||
@@ -73,4 +93,9 @@ public class DeliveryTransferController {
|
||||
private String operator(AuthenticatedUser user) {
|
||||
return user.userId() == null ? user.username() : String.valueOf(user.userId());
|
||||
}
|
||||
private String listScope(AuthenticatedUser user, String requestedCustomer) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) return requestedCustomer == null || requestedCustomer.isBlank() ? null : requestedCustomer.trim();
|
||||
return String.valueOf(user.tenantId());
|
||||
}
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.baiyue.lastleg.delivery.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.baiyue.lastleg.delivery.service.DeliveryTransferService;
|
||||
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/merchant/v1/last-leg/delivery-orders")
|
||||
public class MerchantDeliveryController {
|
||||
@Resource private DeliveryTransferService service;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResult<DeliveryResponses.DeliveryRow>> page(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid DeliveryRequests.Query query) {
|
||||
return ApiResponse.success(service.deliveryPage(customer(user), query));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<DeliveryResponses.TransferResult> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam String dispatchNo,
|
||||
@Valid @RequestBody DeliveryRequests.Transfer request) {
|
||||
return ApiResponse.success(service.transfer(customer(user), dispatchNo, operator(user), request));
|
||||
}
|
||||
|
||||
@GetMapping("/{deliveryNo}")
|
||||
public ApiResponse<DeliveryResponses.DeliveryDetail> detail(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String deliveryNo) {
|
||||
return ApiResponse.success(service.deliveryDetail(customer(user), deliveryNo));
|
||||
}
|
||||
|
||||
private String customer(AuthenticatedUser user) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if ("PLATFORM".equals(user.appCode()) || user.tenantId() == 0L) throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Merchant identity is required");
|
||||
return String.valueOf(user.tenantId());
|
||||
}
|
||||
private String operator(AuthenticatedUser user) { return user.userId() == null ? user.username() : String.valueOf(user.userId()); }
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.baiyue.lastleg.delivery.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.baiyue.lastleg.delivery.service.DeliveryTransferService;
|
||||
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/pda/v1/last-leg/outbound-tasks")
|
||||
public class OutboundPdaController {
|
||||
@Resource private DeliveryTransferService service;
|
||||
@PostMapping("/{taskNo}/pick")
|
||||
public ApiResponse<DeliveryResponses.PdaTask> pick(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String taskNo, @RequestParam(required=false) String customerId,
|
||||
@Valid @RequestBody DeliveryRequests.PdaPick request) {
|
||||
return ApiResponse.success(service.pdaPick(scope(user, customerId), operator(user), taskNo, request));
|
||||
}
|
||||
@GetMapping
|
||||
public ApiResponse<java.util.List<DeliveryResponses.PdaTask>> list(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required=false) String customerId, @RequestParam(required=false) String status) {
|
||||
return ApiResponse.success(service.pdaTasks(scope(user, customerId), status));
|
||||
}
|
||||
@PostMapping("/{taskNo}/complete")
|
||||
public ApiResponse<DeliveryResponses.PdaTask> complete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String taskNo, @RequestParam(required=false) String customerId) {
|
||||
return ApiResponse.success(service.completePda(scope(user, customerId), taskNo));
|
||||
}
|
||||
private String scope(AuthenticatedUser user, String requested) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if (requested != null && !requested.isBlank()) return requested.trim();
|
||||
return String.valueOf(user.tenantId());
|
||||
}
|
||||
private String operator(AuthenticatedUser user) { return user.userId() == null ? user.username() : String.valueOf(user.userId()); }
|
||||
}
|
||||
+39
-2
@@ -3,16 +3,43 @@ package com.baiyue.lastleg.delivery.dto;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
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,
|
||||
@NotBlank @Size(max = 64) String pickupMethod,
|
||||
@NotBlank @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,
|
||||
@NotNull @Valid @Size(min = 1, max = 20) List<FeeItem> fees) {}
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
@@ -20,4 +47,14 @@ public final class DeliveryRequests {
|
||||
String destinationWarehouseCode) {
|
||||
public PalletQuery { if (page == 0) page = 1; if (size == 0) size = 20; }
|
||||
}
|
||||
|
||||
public record OneClickOutbound(@NotBlank @Size(max=1000) String recipient,
|
||||
@NotNull @Size(min=1, max=200) List<@Valid OneClickItem> items,
|
||||
@Size(max=1000) String remark,
|
||||
@NotBlank @Size(max=255) String idempotencyKey) {}
|
||||
public record OneClickItem(@NotNull Long inventoryPositionId, @NotNull @Min(1) Long packages,
|
||||
@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) {}
|
||||
}
|
||||
|
||||
+28
-3
@@ -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,32 @@ 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<DeliverySku> skus,
|
||||
List<DeliveryFeeRow> fees, List<DeliveryOperationRow> operations) {}
|
||||
|
||||
public record OutboundDetail(OutboundRow outbound, DeliveryRow delivery) {}
|
||||
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -18,6 +18,8 @@ public class OutboundOrder {
|
||||
private Long dispatchOrderId;
|
||||
private String dispatchNo;
|
||||
private String overseasWarehouseId;
|
||||
private String outboundType;
|
||||
private String recipientInfo;
|
||||
private String status;
|
||||
private Integer packageCount;
|
||||
private Integer palletCount;
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ public class OutboundPalletItem {
|
||||
private String destinationWarehouseCode;
|
||||
private String destinationWarehouseName;
|
||||
private Integer packageCount;
|
||||
private Integer pickedPackageCount;
|
||||
private String status;
|
||||
private String skuCode;
|
||||
private String orderRefs;
|
||||
private String inboundCode;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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_outbound_pda_pick")
|
||||
public class OutboundPdaPick {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String customerId;
|
||||
private Long taskId;
|
||||
private Long outboundPalletItemId;
|
||||
private Long inventoryPositionId;
|
||||
private Long packages;
|
||||
private String idempotencyKey;
|
||||
private LocalDateTime pickedAt;
|
||||
}
|
||||
@@ -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_outbound_pda_task")
|
||||
public class OutboundPdaTask {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String customerId;
|
||||
private Long outboundOrderId;
|
||||
private String taskNo;
|
||||
private String status;
|
||||
private LocalDateTime pushedAt;
|
||||
private LocalDateTime completedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+16
@@ -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<DeliveryFee> {
|
||||
@Select("SELECT * FROM ll_delivery_fee WHERE customer_id=#{customerId} AND delivery_order_id=#{deliveryOrderId} ORDER BY id")
|
||||
List<DeliveryFee> findByDelivery(@Param("customerId") String customerId,
|
||||
@Param("deliveryOrderId") Long deliveryOrderId);
|
||||
}
|
||||
+16
@@ -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<DeliveryOperation> {
|
||||
@Select("SELECT * FROM ll_delivery_operation WHERE customer_id=#{customerId} AND delivery_order_id=#{deliveryOrderId} ORDER BY created_at,id")
|
||||
List<DeliveryOperation> findByDelivery(@Param("customerId") String customerId,
|
||||
@Param("deliveryOrderId") Long deliveryOrderId);
|
||||
}
|
||||
+2
@@ -14,4 +14,6 @@ public interface OutboundOrderMapper extends BaseMapper<OutboundOrder> {
|
||||
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);
|
||||
@Select("SELECT * FROM ll_outbound_order WHERE customer_id=#{customerId} AND outbound_no=#{outboundNo} LIMIT 1 FOR UPDATE")
|
||||
OutboundOrder lockByNo(@Param("customerId") String customerId, @Param("outboundNo") String outboundNo);
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
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<OutboundPdaPick> {
|
||||
@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<OutboundPdaPick> findByOutbound(@Param("customerId") String customerId, @Param("outboundId") Long outboundId);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.lastleg.delivery.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPdaTask;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OutboundPdaTaskMapper extends BaseMapper<OutboundPdaTask> {
|
||||
@Select("SELECT * FROM ll_outbound_pda_task WHERE customer_id=#{customerId} AND outbound_order_id=#{outboundOrderId} LIMIT 1 FOR UPDATE")
|
||||
OutboundPdaTask lockByOutbound(@Param("customerId") String customerId, @Param("outboundOrderId") Long outboundOrderId);
|
||||
@Select("SELECT * FROM ll_outbound_pda_task WHERE customer_id=#{customerId} AND task_no=#{taskNo} LIMIT 1 FOR UPDATE")
|
||||
OutboundPdaTask lockByNo(@Param("customerId") String customerId, @Param("taskNo") String taskNo);
|
||||
@Select("SELECT * FROM ll_outbound_pda_task WHERE customer_id=#{customerId} AND (#{status} IS NULL OR status=#{status}) ORDER BY pushed_at DESC LIMIT 200")
|
||||
List<OutboundPdaTask> list(@Param("customerId") String customerId, @Param("status") String status);
|
||||
}
|
||||
+220
-14
@@ -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,12 +22,27 @@ 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;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletAllocationMapper;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPdaTask;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPdaPick;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaTaskMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPdaPickMapper;
|
||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
|
||||
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
|
||||
import com.baiyue.lastleg.dropship.entity.DropshipOrderProfile;
|
||||
import com.baiyue.lastleg.dropship.entity.DropshipCarton;
|
||||
import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper;
|
||||
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;
|
||||
@@ -34,6 +53,8 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@@ -43,10 +64,21 @@ 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;
|
||||
@Resource private InventoryCommandService inventoryCommands;
|
||||
@Resource private DropshipOrderProfileMapper dropshipProfiles;
|
||||
@Resource private DropshipCartonMapper dropshipCartons;
|
||||
@Resource private DropshipSkuInventoryMapper dropshipSkuInventory;
|
||||
@Resource private InboundPlanPalletAllocationMapper planAllocations;
|
||||
@Resource private InboundPlanLineMapper planLines;
|
||||
@Resource private LastlegOrderMapper tailOrders;
|
||||
@Resource private LastlegOrderStatusHistoryMapper histories;
|
||||
@Resource private LastlegOrderDetailMapper tailOrderDetails;
|
||||
@Resource private LastlegOrderNoGenerator orderNoGenerator;
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.TransferResult transfer(String customerId, String dispatchNo, String operatorId,
|
||||
@@ -70,13 +102,14 @@ public class DeliveryTransferService {
|
||||
throw conflict("All dispatch pallets must be received before transfer to delivery");
|
||||
}
|
||||
if (!StringUtils.hasText(dispatch.getOverseasWarehouseId())) throw conflict("Dispatch warehouse is required");
|
||||
validateTransferFees(request);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
OutboundOrder outbound = new OutboundOrder();
|
||||
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
|
||||
outbound.setOutboundNo("OUT" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId());
|
||||
outbound.setOutboundNo(orderNoGenerator.next("OUTBOUND", dispatch.getOverseasWarehouseId(), outbound.getId()));
|
||||
outbound.setDispatchOrderId(dispatch.getId()); outbound.setDispatchNo(dispatch.getDispatchNo());
|
||||
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND");
|
||||
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND"); outbound.setOutboundType("DISPATCH_DELIVERY");
|
||||
outbound.setPackageCount(items.stream().map(DispatchItem::getPackageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum());
|
||||
outbound.setPalletCount(items.size()); outbound.setIdempotencyKey(request.idempotencyKey().trim());
|
||||
outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
|
||||
@@ -103,27 +136,179 @@ public class DeliveryTransferService {
|
||||
item.setDestinationWarehouseId(source.getDestinationWarehouseId());
|
||||
item.setDestinationWarehouseCode(source.getDestinationWarehouseCode());
|
||||
item.setDestinationWarehouseName(source.getDestinationWarehouseName());
|
||||
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0));
|
||||
item.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0)); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK");
|
||||
item.setOrderRefs(source.getOrderRefs()); item.setInboundCode(source.getInboundCode()); item.setCreatedAt(now);
|
||||
outboundPallets.insert(item);
|
||||
}
|
||||
|
||||
DeliveryOrder delivery = new DeliveryOrder();
|
||||
delivery.setId(IdWorker.getId()); delivery.setCustomerId(customerId);
|
||||
delivery.setDeliveryNo("DEL" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + delivery.getId());
|
||||
delivery.setDeliveryNo(orderNoGenerator.next("DELIVERY", dispatch.getOverseasWarehouseId(), 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<DeliveryRequests.FeeItem> 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);
|
||||
}
|
||||
|
||||
private void validateTransferFees(DeliveryRequests.Transfer request) {
|
||||
List<DeliveryRequests.FeeItem> inputs = request.fees() == null ? List.of() : request.fees();
|
||||
if (inputs.isEmpty()) throw conflict("Delivery fee is required");
|
||||
Set<String> allowed = Set.of("DELIVERY_FEE", "LOADING_FEE", "APPOINTMENT_FEE");
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (DeliveryRequests.FeeItem input : inputs) {
|
||||
String feeType = input.feeType().trim().toUpperCase(Locale.ROOT);
|
||||
if (!allowed.contains(feeType)) throw conflict("Unsupported delivery fee type: " + feeType);
|
||||
if (!seen.add(feeType)) throw conflict("Duplicate delivery fee type: " + feeType);
|
||||
if (!"USD".equalsIgnoreCase(input.currency().trim())) throw conflict("Delivery fees must use USD");
|
||||
if (input.amount().signum() < 0) throw conflict("Delivery fee amount cannot be negative");
|
||||
}
|
||||
if (!seen.contains("DELIVERY_FEE")) throw conflict("Delivery fee is required");
|
||||
if (Set.of("FLOOR", "MIXED").contains(request.loadingType().trim().toUpperCase(Locale.ROOT))
|
||||
&& !seen.contains("LOADING_FEE")) {
|
||||
throw conflict("Loading fee is required for floor or mixed loading");
|
||||
}
|
||||
if ("BUY_APPOINTMENT".equalsIgnoreCase(trim(request.appointmentAccount()))) {
|
||||
if (!StringUtils.hasText(request.sellerName())) throw conflict("Seller name is required when buying an appointment");
|
||||
if (!seen.contains("APPOINTMENT_FEE")) throw conflict("Appointment fee is required when buying an appointment");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.PdaTask createOneClick(String customerId, String operatorId, DeliveryRequests.OneClickOutbound request) {
|
||||
OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||
if (existing != null) return taskResult(existing, pdaTasks.lockByOutbound(customerId, existing.getId()));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
InventoryPosition firstPosition = inventoryPositions.selectById(request.items().get(0).inventoryPositionId());
|
||||
if (firstPosition == null || !customerId.equals(firstPosition.getCustomerId())) throw notFound("Inventory position not found");
|
||||
String selectedWarehouseId = firstPosition.getOverseasWarehouseId();
|
||||
OutboundOrder outbound = new OutboundOrder();
|
||||
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
|
||||
outbound.setOutboundNo(orderNoGenerator.next("ONE_CLICK_DROPSHIP", selectedWarehouseId, outbound.getId()));
|
||||
outbound.setOutboundType("ONE_CLICK_DROPSHIP"); outbound.setStatus("WAIT_PICK"); outbound.setRecipientInfo(request.recipient().trim());
|
||||
outbound.setPackageCount(request.items().stream().mapToInt(i -> i.packages().intValue()).sum()); outbound.setPalletCount(request.items().size());
|
||||
outbound.setIdempotencyKey(request.idempotencyKey().trim()); outbound.setCreatedBy(operatorId); outbound.setCreatedAt(now); outbound.setUpdatedAt(now);
|
||||
outboundOrders.insert(outbound);
|
||||
for (DeliveryRequests.OneClickItem input : request.items()) {
|
||||
InventoryPosition position = inventoryPositions.selectById(input.inventoryPositionId());
|
||||
if (position == null || !customerId.equals(position.getCustomerId())) throw notFound("Inventory position not found");
|
||||
DropshipOrderProfile profile = dropshipProfiles.findPosition(customerId, position.getId());
|
||||
if (profile == null) throw conflict("Selected pallet is not one-click dropship inventory");
|
||||
if (selectedWarehouseId == null) selectedWarehouseId = position.getOverseasWarehouseId();
|
||||
else if (!Objects.equals(selectedWarehouseId, position.getOverseasWarehouseId())) throw conflict("One outbound order must use inventory from the same overseas warehouse");
|
||||
if (value(position.getAvailablePackages()) < input.packages()) throw conflict("Insufficient available inventory for pallet " + position.getPalletNo());
|
||||
OutboundPalletItem item = new OutboundPalletItem(); item.setId(IdWorker.getId()); item.setCustomerId(customerId); item.setOutboundOrderId(outbound.getId());
|
||||
item.setWarehousePalletId(position.getPalletId()); item.setPalletNo(position.getPalletNo()); item.setPackageCount(input.packages().intValue()); item.setPickedPackageCount(0); item.setStatus("WAIT_PICK"); item.setSkuCode(input.sku().trim()); item.setCreatedAt(now);
|
||||
outboundPallets.insert(item);
|
||||
}
|
||||
outbound.setOverseasWarehouseId(selectedWarehouseId); outboundOrders.updateById(outbound);
|
||||
OutboundPdaTask task = new OutboundPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setOutboundOrderId(outbound.getId());
|
||||
task.setTaskNo("PDA" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + task.getId()); task.setStatus("PENDING"); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); pdaTasks.insert(task);
|
||||
return taskResult(outbound, task);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.PdaTask pdaPick(String customerId, String operatorId, String taskNo, DeliveryRequests.PdaPick request) {
|
||||
OutboundPdaTask task = pdaTasks.lockByNo(customerId, taskNo); if (task == null) throw notFound("PDA task not found");
|
||||
OutboundPdaPick replay = pdaPicks.findByKey(customerId, request.idempotencyKey().trim()); if (replay != null) return taskResult(outboundOrders.selectById(task.getOutboundOrderId()), task);
|
||||
OutboundOrder outbound = outboundOrders.lockByNo(customerId, outboundNo(task.getOutboundOrderId()));
|
||||
OutboundPalletItem item = outboundPallets.selectById(request.outboundPalletItemId());
|
||||
if (item == null || !task.getOutboundOrderId().equals(item.getOutboundOrderId())) throw notFound("Outbound pallet item not found");
|
||||
if (request.packages() > item.getPackageCount() - value(item.getPickedPackageCount())) throw conflict("Picked quantity exceeds outbound pallet quantity");
|
||||
InventoryPosition selectedPosition = inventoryPositions.selectById(request.inventoryPositionId());
|
||||
if (selectedPosition == null || !customerId.equals(selectedPosition.getCustomerId()) || !Objects.equals(selectedPosition.getPalletId(), item.getWarehousePalletId())) throw conflict("Scanned inventory position does not belong to the outbound pallet");
|
||||
DropshipOrderProfile profile = dropshipProfiles.findPosition(customerId, request.inventoryPositionId());
|
||||
if (profile == null) throw conflict("Scanned pallet is not one-click dropship inventory");
|
||||
List<DropshipCarton> cartons = dropshipCartons.lockAvailable(customerId, profile.getId(), item.getSkuCode(), request.packages());
|
||||
if (cartons.size() != request.packages()) throw conflict("Insufficient available one-click dropship cartons");
|
||||
InventoryCommandRequests.Move move = new InventoryCommandRequests.Move(request.inventoryPositionId(), request.packages(), outbound.getOutboundNo(), outbound.getOutboundNo(), null, request.idempotencyKey() + ":ALLOCATE", null);
|
||||
inventoryCommands.allocate(customerId, operatorId, move);
|
||||
inventoryCommands.pick(customerId, operatorId, new InventoryCommandRequests.Move(request.inventoryPositionId(), request.packages(), outbound.getOutboundNo(), outbound.getOutboundNo(), null, request.idempotencyKey() + ":PICK", null));
|
||||
inventoryCommands.ship(customerId, operatorId, new InventoryCommandRequests.Move(request.inventoryPositionId(), request.packages(), outbound.getOutboundNo(), outbound.getOutboundNo(), null, request.idempotencyKey() + ":SHIP", null));
|
||||
if (dropshipSkuInventory.deduct(customerId, profile.getId(), item.getSkuCode(), request.packages()) != 1) throw conflict("Insufficient one-click dropship SKU inventory");
|
||||
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<OutboundPalletItem> 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);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.PdaTask completePda(String customerId, String taskNo) {
|
||||
OutboundPdaTask task = pdaTasks.lockByNo(customerId, taskNo); if (task == null) throw notFound("PDA task not found");
|
||||
OutboundOrder outbound = outboundOrders.selectById(task.getOutboundOrderId());
|
||||
List<OutboundPalletItem> items = outboundPallets.findByOutbound(customerId, outbound.getId());
|
||||
if (items.stream().anyMatch(i -> value(i.getPickedPackageCount()) < value(i.getPackageCount()))) throw conflict("All outbound pallets must be picked before completion");
|
||||
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<OutboundPalletItem> 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<LastlegOrder>().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.setCustomerId(customerId); 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<DeliveryResponses.PdaTask> 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<DeliveryResponses.OutboundRow> outboundPage(String customerId, DeliveryRequests.Query query) {
|
||||
Page<OutboundOrder> page = outboundOrders.selectPage(new Page<>(query.page(), query.size()),
|
||||
new LambdaQueryWrapper<OutboundOrder>().eq(OutboundOrder::getCustomerId, customerId)
|
||||
new LambdaQueryWrapper<OutboundOrder>().eq(StringUtils.hasText(customerId), 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())
|
||||
@@ -141,21 +326,34 @@ public class DeliveryTransferService {
|
||||
|
||||
public PageResult<DeliveryResponses.DeliveryRow> deliveryPage(String customerId, DeliveryRequests.Query query) {
|
||||
Page<DeliveryOrder> page = deliveryOrders.selectPage(new Page<>(query.page(), query.size()),
|
||||
new LambdaQueryWrapper<DeliveryOrder>().eq(DeliveryOrder::getCustomerId, customerId)
|
||||
new LambdaQueryWrapper<DeliveryOrder>().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,
|
||||
DeliveryRequests.PalletQuery query) {
|
||||
public DeliveryResponses.DeliveryDetail deliveryDetail(String customerId, String 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 || !customerId.equals(outbound.getCustomerId())) throw notFound("Outbound order not found");
|
||||
return outboundDetail(customerId, outbound.getOutboundNo(), query);
|
||||
List<DispatchItem> items = dispatchItems.findByDispatchOrder(customerId, delivery.getDispatchOrderId());
|
||||
List<Long> orderIds = items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList();
|
||||
Map<Long, LastlegOrder> 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<LastlegOrderDetail> details = orderIds.isEmpty() ? List.of() : tailOrderDetails.selectList(
|
||||
new LambdaQueryWrapper<LastlegOrderDetail>().eq(LastlegOrderDetail::getCustomerId, customerId).in(LastlegOrderDetail::getOrderId, orderIds));
|
||||
List<DeliveryResponses.DeliverySku> 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<DispatchItem> items, String outboundNo, String deliveryNo,
|
||||
@@ -180,7 +378,7 @@ public class DeliveryTransferService {
|
||||
order.setDeliveryOrderNo(deliveryNo); order.setCurrentStatus("WAIT_OUTBOUND"); order.setUpdatedAt(now);
|
||||
order.setSubOrderStatus("OUTBOUND:WAIT_OUTBOUND;DELIVERY:WAIT_DELIVERY");
|
||||
tailOrders.updateById(order);
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setOrderId(order.getId());
|
||||
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId());
|
||||
history.setFromStatus(from); history.setToStatus("WAIT_OUTBOUND");
|
||||
history.setEventId("TRANSFER_DELIVERY:" + deliveryNo);
|
||||
history.setRemark(StringUtils.hasText(remark) ? remark.trim() : "Transferred to outbound and delivery documents");
|
||||
@@ -198,7 +396,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(), null, 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<DispatchItem> 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<DispatchItem> items) { List<Long> ids = items.stream().map(DispatchItem::getOrderId).filter(Objects::nonNull).distinct().toList(); if (ids.isEmpty()) return BigDecimal.ZERO; return tailOrderDetails.selectList(new LambdaQueryWrapper<LastlegOrderDetail>().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 <T> PageResult<T> page(Page<?> page, List<T> 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); }
|
||||
|
||||
Reference in New Issue
Block a user