This commit is contained in:
2026-08-25 19:58:08 +08:00
parent 749a0d40a7
commit 3bf68c1083
18 changed files with 295 additions and 6 deletions
@@ -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}")
@@ -73,4 +81,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());
}
}
@@ -0,0 +1,43 @@
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.boot.autoconfigure.condition.ConditionalOnProperty;
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
@ConditionalOnProperty(name="lastleg.future-outbound-api.enabled", havingValue="true")
@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()); }
}
@@ -3,7 +3,10 @@ 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;
public final class DeliveryRequests {
private DeliveryRequests() {}
@@ -20,4 +23,13 @@ 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) {}
}
@@ -17,4 +17,5 @@ public final class DeliveryResponses {
int packageCount, LocalDateTime createdAt) {}
public record OutboundDetail(OutboundRow outbound, DeliveryRow delivery) {}
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
}
@@ -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;
@@ -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;
}
@@ -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);
}
@@ -0,0 +1,11 @@
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.*;
@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);
}
@@ -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);
}
@@ -22,6 +22,19 @@ 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 jakarta.annotation.Resource;
@@ -43,6 +56,13 @@ public class DeliveryTransferService {
@Resource private OutboundOrderMapper outboundOrders;
@Resource private OutboundPalletItemMapper outboundPallets;
@Resource private DeliveryOrderMapper deliveryOrders;
@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;
@@ -76,7 +96,7 @@ public class DeliveryTransferService {
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
outbound.setOutboundNo("OUT" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + outbound.getId());
outbound.setDispatchOrderId(dispatch.getId()); outbound.setDispatchNo(dispatch.getDispatchNo());
outbound.setOverseasWarehouseId(dispatch.getOverseasWarehouseId()); outbound.setStatus("WAIT_OUTBOUND");
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,7 +123,7 @@ 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);
}
@@ -121,9 +141,81 @@ public class DeliveryTransferService {
return result(outbound, delivery, false);
}
@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();
OutboundOrder outbound = new OutboundOrder();
outbound.setId(IdWorker.getId()); outbound.setCustomerId(customerId);
outbound.setOutboundNo("FBOO" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + 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);
String selectedWarehouseId = null;
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);
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);
}
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; }
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())
@@ -12,4 +12,6 @@ public interface DropshipCartonMapper extends BaseMapper<DropshipCarton> {
@Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY profile_id,carton_sequence")
List<DropshipCarton> 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<DropshipCarton> lockAvailable(@Param("customerId") String customerId, @Param("profileId") Long profileId, @Param("sku") String sku, @Param("packages") long packages);
}
@@ -6,4 +6,6 @@ import java.util.List;
public interface DropshipOrderProfileMapper extends BaseMapper<DropshipOrderProfile> {
@Select("SELECT * FROM ll_dropship_order_profile WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY source_pallet_no")
List<DropshipOrderProfile> listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId);
@Select("SELECT * FROM ll_dropship_order_profile WHERE customer_id=#{customerId} AND inventory_position_id=#{positionId} LIMIT 1")
DropshipOrderProfile findPosition(@Param("customerId") String customerId, @Param("positionId") Long positionId);
}
@@ -4,10 +4,13 @@ import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface DropshipSkuInventoryMapper extends BaseMapper<DropshipSkuInventory> {
@Select("SELECT * FROM ll_dropship_sku_inventory WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY profile_id,sku_code")
List<DropshipSkuInventory> 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);
}
@@ -39,6 +39,8 @@ lastleg:
enabled: ${LASTLEG_DISPATCH_QUERY_API_ENABLED:true}
smart-dispatch-api:
enabled: ${LASTLEG_SMART_DISPATCH_API_ENABLED:true}
future-outbound-api:
enabled: ${LASTLEG_FUTURE_OUTBOUND_API_ENABLED:true}
baiyue:
http:
@@ -1103,11 +1103,13 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick_order (
-- Transfer a dispatch to delivery: one outbound header, many pallet rows, one delivery header.
CREATE TABLE IF NOT EXISTS ll_outbound_order (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NOT NULL COMMENT '派车单ID', dispatch_no VARCHAR(64) NOT NULL COMMENT '派车单号',
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空',
overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数',
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发',
recipient_info VARCHAR(1000) NULL COMMENT '一件代发收件信息',
PRIMARY KEY (id), UNIQUE KEY uk_ll_outbound_no (customer_id,outbound_no),
UNIQUE KEY uk_ll_outbound_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_outbound_dispatch (customer_id,dispatch_order_id),
KEY idx_ll_outbound_list (customer_id,status,created_at)
@@ -1117,12 +1119,39 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id),
UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id),
KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code)
) ENGINE=InnoDB COMMENT='出库托盘明细';
CREATE TABLE IF NOT EXISTS ll_outbound_pda_task (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
task_no VARCHAR(64) NOT NULL COMMENT 'PDA任务号', status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '任务状态:待拣货、拣货中、已完成',
pushed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '推送时间', completed_at DATETIME(3) NULL COMMENT '完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY(id), UNIQUE KEY uk_ll_outbound_pda_task_order(customer_id,outbound_order_id), UNIQUE KEY uk_ll_outbound_pda_task_no(customer_id,task_no)
) ENGINE=InnoDB COMMENT='一件代发出库PDA拣货任务';
CREATE TABLE IF NOT EXISTS ll_outbound_pda_pick (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL, task_id BIGINT NOT NULL COMMENT 'PDA任务ID', outbound_pallet_item_id BIGINT NOT NULL COMMENT '出库托盘明细ID',
inventory_position_id BIGINT NOT NULL COMMENT '库存仓位ID', packages BIGINT NOT NULL COMMENT '拣货箱数', idempotency_key VARCHAR(255) NOT NULL,
picked_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY(id), UNIQUE KEY uk_ll_outbound_pda_pick_key(customer_id,idempotency_key)
) ENGINE=InnoDB COMMENT='一件代发出库PDA托盘拣货记录';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU'' AFTER status'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='recipient_info'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN recipient_info VARCHAR(1000) NULL COMMENT ''一件代发收件信息'' AFTER outbound_type'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='picked_package_count'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN picked_package_count INT NOT NULL DEFAULT 0 COMMENT ''已拣箱数'' AFTER package_count'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='status'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT ''WAIT_PICK'' COMMENT ''拣货状态'' AFTER picked_package_count'));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='dispatch_order_id' AND is_nullable='YES'), 'SELECT 1', 'ALTER TABLE ll_outbound_order MODIFY COLUMN dispatch_order_id BIGINT NULL COMMENT ''派车单ID,一件代发为空'', MODIFY COLUMN dispatch_no VARCHAR(64) NULL COMMENT ''派车单号,一件代发为空'''));
PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt;
CREATE TABLE IF NOT EXISTS ll_delivery_order (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
delivery_no VARCHAR(64) NOT NULL COMMENT '派送单号', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID', outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号',
+15
View File
@@ -3805,6 +3805,21 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
AND table_name='ll_cargo_discard_application' AND column_name='review_idempotency_key'),
'SELECT 1', 'ALTER TABLE ll_cargo_discard_application ADD COLUMN review_idempotency_key VARCHAR(255) NULL COMMENT ''审核幂等键'' AFTER request_hash'));
PREPARE phase4_discard_review_stmt FROM @ddl; EXECUTE phase4_discard_review_stmt; DEALLOCATE PREPARE phase4_discard_review_stmt;
-- 一件代发出库与 PDA 按托盘拣货出库。
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status'));
PREPARE dropship_outbound_stmt FROM @ddl; EXECUTE dropship_outbound_stmt; DEALLOCATE PREPARE dropship_outbound_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='recipient_info'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN recipient_info VARCHAR(1000) NULL COMMENT ''一件代发收件信息'' AFTER outbound_type'));
PREPARE dropship_outbound_stmt FROM @ddl; EXECUTE dropship_outbound_stmt; DEALLOCATE PREPARE dropship_outbound_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='picked_package_count'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN picked_package_count INT NOT NULL DEFAULT 0 COMMENT ''已拣箱数'' AFTER package_count'));
PREPARE dropship_outbound_stmt FROM @ddl; EXECUTE dropship_outbound_stmt; DEALLOCATE PREPARE dropship_outbound_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='status'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT ''WAIT_PICK'' COMMENT ''拣货状态'' AFTER picked_package_count'));
PREPARE dropship_outbound_stmt FROM @ddl; EXECUTE dropship_outbound_stmt; DEALLOCATE PREPARE dropship_outbound_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU'' AFTER status'));
PREPARE dropship_outbound_stmt FROM @ddl; EXECUTE dropship_outbound_stmt; DEALLOCATE PREPARE dropship_outbound_stmt;
ALTER TABLE ll_outbound_order MODIFY COLUMN dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', MODIFY COLUMN dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空';
CREATE TABLE IF NOT EXISTS ll_outbound_pda_task (id BIGINT NOT NULL,customer_id VARCHAR(128) NOT NULL,outbound_order_id BIGINT NOT NULL,task_no VARCHAR(64) NOT NULL,status VARCHAR(32) NOT NULL DEFAULT 'PENDING',pushed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),completed_at DATETIME(3) NULL,created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),PRIMARY KEY(id),UNIQUE KEY uk_ll_outbound_pda_task_order(customer_id,outbound_order_id),UNIQUE KEY uk_ll_outbound_pda_task_no(customer_id,task_no)) ENGINE=InnoDB COMMENT='一件代发出库PDA拣货任务';
CREATE TABLE IF NOT EXISTS ll_outbound_pda_pick (id BIGINT NOT NULL,customer_id VARCHAR(128) NOT NULL,task_id BIGINT NOT NULL,outbound_pallet_item_id BIGINT NOT NULL,inventory_position_id BIGINT NOT NULL,packages BIGINT NOT NULL,idempotency_key VARCHAR(255) NOT NULL,picked_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),PRIMARY KEY(id),UNIQUE KEY uk_ll_outbound_pda_pick_key(customer_id,idempotency_key)) ENGINE=InnoDB COMMENT='一件代发出库PDA托盘拣货记录';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ll_cargo_discard_application' AND column_name='review_request_hash'),
'SELECT 1', 'ALTER TABLE ll_cargo_discard_application ADD COLUMN review_request_hash CHAR(64) NULL COMMENT ''审核请求内容哈希'' AFTER review_idempotency_key'));