贴标申请,仓储转运
This commit is contained in:
+21
@@ -258,6 +258,27 @@ public interface DispatchCandidateMapper {
|
||||
@Param("orderNo") String orderNo,
|
||||
@Param("relabelNo") String relabelNo);
|
||||
|
||||
@Select("<script>SELECT a.customer_id AS customerId,a.lastleg_order_id AS orderId,a.overseas_warehouse_id AS overseasWarehouseId," +
|
||||
"MIN(ri.source_warehouse_name) AS overseasWarehouseName,a.relabel_no AS orderNo,a.relabel_no AS orderRefs," +
|
||||
"GROUP_CONCAT(DISTINCT c.source_return_no ORDER BY c.source_return_no SEPARATOR ',') AS inboundCode," +
|
||||
"c.pallet_code AS palletNo,COUNT(DISTINCT c.id) AS packageCount,'RELABEL' AS sourceStage,1 AS returnCartonSource," +
|
||||
"GROUP_CONCAT(DISTINCT c.carton_mark ORDER BY c.carton_mark SEPARATOR ',') AS relabelCartonMarks," +
|
||||
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS fbaWarehouseCode," +
|
||||
"GROUP_CONCAT(DISTINCT ri.destination_fba_warehouse_code ORDER BY ri.destination_fba_warehouse_code SEPARATOR ',') AS destinationWarehouseCode " +
|
||||
"FROM ll_relabel_application a JOIN ll_relabel_item ri ON ri.application_id=a.id AND ri.customer_id=a.customer_id " +
|
||||
"JOIN ll_return_inventory_carton c ON c.id=ri.return_carton_id AND c.customer_id=a.customer_id " +
|
||||
"AND c.relabel_application_id=a.id AND c.overseas_warehouse_id=a.overseas_warehouse_id " +
|
||||
"WHERE a.delivery_method='TRUCK' AND a.status='READY_FOR_OUTBOUND' AND a.fulfillment_status='WAIT_PRE_DISPATCH' " +
|
||||
"AND c.status='RELABEL_PENDING' AND c.pallet_code IS NOT NULL " +
|
||||
"<if test='customerId != null and customerId != \"\"'>AND a.customer_id=#{customerId} </if>" +
|
||||
"<if test='relabelNo != null and relabelNo != \"\"'>AND a.relabel_no=#{relabelNo} </if>" +
|
||||
"<if test='orderNo != null and orderNo != \"\"'>AND a.relabel_no LIKE CONCAT('%',#{orderNo},'%') </if>" +
|
||||
"AND NOT EXISTS(SELECT 1 FROM ll_dispatch_item di JOIN ll_dispatch_order d ON d.id=di.dispatch_order_id " +
|
||||
"AND d.customer_id=di.customer_id WHERE di.customer_id=a.customer_id AND di.source_business_no=a.relabel_no AND d.status!='CANCELLED') " +
|
||||
"GROUP BY a.id,a.customer_id,a.lastleg_order_id,a.overseas_warehouse_id,a.relabel_no,c.pallet_code</script>")
|
||||
List<DispatchPalletCandidate> nativeReturnRelabelAvailable(@Param("customerId") String customerId,
|
||||
@Param("orderNo") String orderNo, @Param("relabelNo") String relabelNo);
|
||||
|
||||
@org.apache.ibatis.annotations.Update("UPDATE ll_relabel_application SET fulfillment_status='PRE_DISPATCH_CREATED',updated_at=CURRENT_TIMESTAMP(3) " +
|
||||
"WHERE customer_id=#{customerId} AND relabel_no=#{relabelNo} AND fulfillment_status='WAIT_PRE_DISPATCH'")
|
||||
int markRelabelPreDispatched(@Param("customerId") String customerId, @Param("relabelNo") String relabelNo);
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ public class DispatchPalletCandidate {
|
||||
private String contactPhone;
|
||||
private Long returnReceiptId;
|
||||
private String returnReceiptNo;
|
||||
private boolean returnCartonSource;
|
||||
@com.fasterxml.jackson.annotation.JsonProperty("sourceType")
|
||||
public String sourceType() { return returnReceiptId == null ? "INBOUND_PLAN" : "RETURN_RECEIPT"; }
|
||||
|
||||
|
||||
+6
-2
@@ -8,8 +8,12 @@ import jakarta.validation.constraints.Size;
|
||||
|
||||
public final class DispatchPdaRequests {
|
||||
private DispatchPdaRequests() {}
|
||||
public record Pick(@NotNull Long positionId, @Positive long packages, @PositiveOrZero long productQuantity,
|
||||
@NotBlank String idempotencyKey, String remark) {}
|
||||
public record Pick(Long positionId, @Positive long packages, @PositiveOrZero long productQuantity,
|
||||
@NotBlank String idempotencyKey, String remark, @Size(max = 255) String cartonMark) {
|
||||
public Pick(Long positionId, long packages, long productQuantity, String idempotencyKey, String remark) {
|
||||
this(positionId, packages, productQuantity, idempotencyKey, remark, null);
|
||||
}
|
||||
}
|
||||
public record Depart(@NotBlank String idempotencyKey, String remark) {}
|
||||
public record CompletePicking(@NotBlank String idempotencyKey) {}
|
||||
}
|
||||
|
||||
+9
-1
@@ -16,7 +16,15 @@ public final class DispatchPdaResponses {
|
||||
public record Item(Long dispatchItemId, String palletNo, String destinationWarehouseCode,
|
||||
String destinationWarehouseName, String fbaWarehouseCode, String warehouseRouteLabel,
|
||||
int scheduledPackages,
|
||||
long pickedPackages, List<Position> positions) {}
|
||||
long pickedPackages, List<Position> positions, List<ReturnCarton> returnCartons) {
|
||||
public Item(Long dispatchItemId, String palletNo, String destinationWarehouseCode,
|
||||
String destinationWarehouseName, String fbaWarehouseCode, String warehouseRouteLabel,
|
||||
int scheduledPackages, long pickedPackages, List<Position> positions) {
|
||||
this(dispatchItemId, palletNo, destinationWarehouseCode, destinationWarehouseName, fbaWarehouseCode,
|
||||
warehouseRouteLabel, scheduledPackages, pickedPackages, positions, List.of());
|
||||
}
|
||||
}
|
||||
public record ReturnCarton(Long id, String cartonMark, String status, long productQuantity) {}
|
||||
public record Position(Long id, Long balanceId, long availablePackages, long allocatedPackages,
|
||||
long pickedPackages) {}
|
||||
public record Picked(Long dispatchItemId, Long positionId, long packages, long pickedPackages,
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ public class DispatchPdaPick {
|
||||
private Long taskId;
|
||||
private Long dispatchItemId;
|
||||
private Long inventoryPositionId;
|
||||
private Long returnCartonId;
|
||||
private Long packages;
|
||||
private Long productQuantity;
|
||||
private String idempotencyKey;
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ public class DispatchPdaPickOrder {
|
||||
private Long pickId;
|
||||
private Long receiptId;
|
||||
private Long returnReceiptId;
|
||||
private Long returnCartonId;
|
||||
private Long orderId;
|
||||
private Long packages;
|
||||
private Long productQuantity;
|
||||
|
||||
+25
-5
@@ -54,6 +54,7 @@ public class DispatchPdaWorkflowService {
|
||||
@Resource private WarehouseTransferOrderMapper warehouseTransfers;
|
||||
@Resource private DispatchPickOrderAttributionService pickAttribution;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private com.baiyue.lastleg.dispatch.service.ReturnRelabelDispatchStock returnRelabelStock;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<DispatchPdaResponses.Task> list(String warehouseId, String status) {
|
||||
@@ -115,13 +116,23 @@ public class DispatchPdaWorkflowService {
|
||||
throw notFound("Dispatch pallet item not found");
|
||||
}
|
||||
if (!warehouseId.equals(item.getOverseasWarehouseId())) throw conflict("Pallet does not belong to selected warehouse");
|
||||
InventoryPosition position = positions.selectById(request.positionId());
|
||||
if (position == null || !customerId.equals(position.getCustomerId())
|
||||
|| item.getWarehousePalletId() == null || !item.getWarehousePalletId().equals(position.getPalletId())) {
|
||||
boolean nativeReturn = "RETURN_RELABEL".equals(item.getSourceStage());
|
||||
if (!nativeReturn && request.positionId() == null) throw conflict("普通入仓拣货必须选择库存仓位");
|
||||
if (nativeReturn && request.positionId() != null) throw conflict("退货箱拣货请扫描来源箱唛,不使用普通库存仓位");
|
||||
InventoryPosition position = nativeReturn ? null : positions.selectById(request.positionId());
|
||||
if (!nativeReturn && (position == null || !customerId.equals(position.getCustomerId())
|
||||
|| item.getWarehousePalletId() == null || !item.getWarehousePalletId().equals(position.getPalletId()))) {
|
||||
throw conflict("Inventory position does not belong to this dispatch pallet");
|
||||
}
|
||||
long picked = picks.totalItem(customerId, task.getId(), itemId);
|
||||
if (picked + request.packages() > item.getPackageCount()) throw conflict("Picked packages exceed dispatch pallet packages");
|
||||
long pickId = IdWorker.getId();
|
||||
Long returnCartonId = null;
|
||||
if (nativeReturn) {
|
||||
pickAttribution.prepareNativeReturn(customerId, item.getOrderId());
|
||||
returnCartonId = returnRelabelStock.pick(item, request.cartonMark(), request.packages(), request.productQuantity(),
|
||||
pickId, order.getDispatchNo()).getId();
|
||||
} else {
|
||||
pickAttribution.preparePicking(customerId, request.positionId(),
|
||||
"RELABEL_ORDER".equals(item.getSourceBusinessType()) ? item.getOrderId() : null);
|
||||
String keyBase = "DISPATCH:" + order.getDispatchNo() + ":" + itemId + ":" + request.idempotencyKey();
|
||||
@@ -142,12 +153,14 @@ public class DispatchPdaWorkflowService {
|
||||
inventory.ship(customerId, operatorId, new InventoryCommandRequests.Move(request.positionId(),
|
||||
request.packages(), order.getDispatchNo(), order.getDispatchNo(), null,
|
||||
keyBase + ":SHIP", request.remark()));
|
||||
}
|
||||
DispatchPdaPick pick = new DispatchPdaPick();
|
||||
pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId());
|
||||
pick.setId(pickId); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setReturnCartonId(returnCartonId);
|
||||
pick.setDispatchItemId(itemId); pick.setInventoryPositionId(request.positionId()); pick.setPackages(request.packages());
|
||||
pick.setProductQuantity(request.productQuantity()); pick.setIdempotencyKey(request.idempotencyKey());
|
||||
pick.setPickedAt(LocalDateTime.now()); pick.setCreatedAt(pick.getPickedAt()); picks.insert(pick);
|
||||
pickAttribution.attribute(customerId, pick,
|
||||
if (nativeReturn) pickAttribution.attributeNativeReturn(customerId, pick, item.getOrderId());
|
||||
else pickAttribution.attribute(customerId, pick,
|
||||
"RELABEL_ORDER".equals(item.getSourceBusinessType()) ? item.getOrderId() : null, item.getReturnReceiptId());
|
||||
long total = picked + request.packages();
|
||||
order.setPickedPackageCount((int) (n(order.getPickedPackageCount()) + request.packages()));
|
||||
@@ -289,6 +302,13 @@ public class DispatchPdaWorkflowService {
|
||||
private DispatchPdaResponses.Task task(String customerId, DispatchOrder order, DispatchPdaTask task, List<DispatchItem> source) {
|
||||
List<DispatchPdaResponses.Item> values = new ArrayList<>();
|
||||
for (DispatchItem item : source) {
|
||||
if ("RETURN_RELABEL".equals(item.getSourceStage())) {
|
||||
values.add(new DispatchPdaResponses.Item(item.getId(), item.getPalletNo(), item.getDestinationWarehouseCode(),
|
||||
item.getDestinationWarehouseName(), item.getFbaWarehouseCode(),
|
||||
warehouseRouteLabel(item.getFbaWarehouseCode(), item.getDestinationWarehouseCode()), item.getPackageCount(),
|
||||
picks.totalItem(customerId, task.getId(), item.getId()), List.of(), returnRelabelStock.detail(item)));
|
||||
continue;
|
||||
}
|
||||
List<InventoryPosition> palletPositions = item.getWarehousePalletId() == null ? List.of()
|
||||
: positions.listPallet(customerId, item.getWarehousePalletId());
|
||||
boolean relabel = "RELABEL_ORDER".equals(item.getSourceBusinessType());
|
||||
|
||||
+18
@@ -162,6 +162,24 @@ public class DispatchPickOrderAttributionService {
|
||||
}
|
||||
|
||||
public record OrderPackages(Long orderId, long packages) {}
|
||||
/** Use the same order-before-stock lock order as ordinary dispatch picks. */
|
||||
@org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)
|
||||
public void prepareNativeReturn(String customerId, Long orderId) {
|
||||
LastlegOrder order = orderId == null ? null : orders.lockById(customerId, orderId);
|
||||
if (order == null || Boolean.TRUE.equals(order.getDetained()))
|
||||
throw conflict("退货换标履约订单缺失或暂扣");
|
||||
}
|
||||
/** Native return boxes have their own stock ledger, but fulfill this relabel tail order. */
|
||||
public void attributeNativeReturn(String customerId, DispatchPdaPick pick, Long orderId) {
|
||||
LastlegOrder order = orderId == null ? null : orders.lockById(customerId, orderId);
|
||||
if (order == null || Boolean.TRUE.equals(order.getDetained())) throw conflict("退货换标履约订单缺失或暂扣");
|
||||
DispatchPdaPickOrder row = new DispatchPdaPickOrder();
|
||||
row.setId(IdWorker.getId()); row.setCustomerId(customerId); row.setPickId(pick.getId()); row.setOrderId(orderId);
|
||||
row.setReturnCartonId(pick.getReturnCartonId());
|
||||
row.setPackages(pick.getPackages()); row.setProductQuantity(pick.getProductQuantity()); row.setCreatedAt(pick.getPickedAt());
|
||||
if (attributions.insert(row) != 1 || orders.increasePicked(customerId, orderId, pick.getPackages()) != 1)
|
||||
throw conflict("退货换标排车拣货归属或箱数不一致");
|
||||
}
|
||||
private static long value(Long value) { return value == null ? 0 : value; }
|
||||
private static long value(Integer value) { return value == null ? 0 : value; }
|
||||
private static ResponseStatusException conflict(String message) {
|
||||
|
||||
+29
-4
@@ -78,6 +78,7 @@ public class DispatchService {
|
||||
@Resource private com.baiyue.lastleg.fleet.trip.FleetDispatchWorkflowQuery fleetWorkflow;
|
||||
@Resource private RelabelApplicationMapper relabelApplications;
|
||||
@Resource private com.baiyue.lastleg.relabel.service.RelabelBindingGuard relabelBindingGuard;
|
||||
@Resource private ReturnRelabelDispatchStock returnRelabelStock;
|
||||
|
||||
public String resolveCustomerScopeForPlans(List<Long> planIds) {
|
||||
return requireSingleCustomerScope(candidates.findCustomerScopesByPlanIds(normalizeIds(planIds, "Plan IDs")),
|
||||
@@ -122,7 +123,9 @@ public class DispatchService {
|
||||
List<DispatchPalletCandidate> result = "RELABEL".equals(sourceStage) ? List.of() : candidates.available(null, null, trimToNull(orderNo), trimToNull(fbaWarehouseCode),
|
||||
trimToNull(sourceStage), trimToNull(draftToken));
|
||||
if (!StringUtils.hasText(sourceStage) || Set.of("RECEIVED", "RELABEL").contains(sourceStage)) {
|
||||
List<DispatchPalletCandidate> relabel = candidates.relabelAvailable(null, trimToNull(orderNo), null);
|
||||
List<DispatchPalletCandidate> relabel = java.util.stream.Stream.concat(
|
||||
candidates.relabelAvailable(null, trimToNull(orderNo), null).stream(),
|
||||
returnRelabelStock.candidates(null, trimToNull(orderNo), null).stream()).toList();
|
||||
if (relabel != null && !relabel.isEmpty()) {
|
||||
result = java.util.stream.Stream.concat(result.stream(), relabel.stream().filter(item ->
|
||||
!StringUtils.hasText(fbaWarehouseCode) || Objects.toString(item.fbaWarehouseCode(), "")
|
||||
@@ -567,15 +570,22 @@ public class DispatchService {
|
||||
|
||||
@Transactional
|
||||
private DispatchResponse preArrangeRelabel(String customerId, String operatorId, DispatchRequests.PreArrange request) {
|
||||
RelabelApplication application = relabelApplications.findNo(customerId, request.relabelNo());
|
||||
RelabelApplication application = relabelApplications.lockNo(customerId, request.relabelNo());
|
||||
if (application == null || !RelabelApplicationService.READY_FOR_OUTBOUND.equals(application.getStatus())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标尚未完成或客户尚未确认,无法进行排车");
|
||||
}
|
||||
relabelBindingGuard.requireBound(application);
|
||||
List<DispatchPalletCandidate> selected = candidates.relabelAvailable(customerId, null, request.relabelNo());
|
||||
if (!"TRUCK".equals(application.getDeliveryMethod()) || !"WAIT_PRE_DISPATCH".equals(application.getFulfillmentStatus()))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "换标单不是待排车状态");
|
||||
List<DispatchPalletCandidate> selected = java.util.stream.Stream.concat(
|
||||
candidates.relabelAvailable(customerId, null, request.relabelNo()).stream(),
|
||||
returnRelabelStock.candidates(customerId, null, request.relabelNo()).stream()).toList();
|
||||
Set<Long> requested = request.planPalletIds().stream().map(Long::valueOf).collect(Collectors.toSet());
|
||||
if (selected.stream().anyMatch(DispatchPalletCandidate::returnCartonSource)
|
||||
&& !requested.equals(selected.stream().map(DispatchPalletCandidate::warehousePalletId).collect(Collectors.toSet())))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "退货换标排车须选择本申请全部已绑定托盘");
|
||||
selected = selected.stream().filter(c -> requested.contains(c.warehousePalletId())).toList();
|
||||
if (selected.isEmpty() || selected.size() != requested.size()) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单托盘候选已变化,请刷新后重试");
|
||||
if (selected.isEmpty() || selected.stream().map(DispatchPalletCandidate::warehousePalletId).distinct().count() != requested.size()) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单托盘候选已变化,请刷新后重试");
|
||||
String warehouse = selected.get(0).overseasWarehouseId();
|
||||
if (selected.stream().anyMatch(c -> !Objects.equals(warehouse, c.overseasWarehouseId()))) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单只能选择同一海外仓");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -584,6 +594,12 @@ public class DispatchService {
|
||||
dispatch.setRoute(candidateRoute(selected)); dispatch.setFbaAppointmentNo(trimToNull(request.fbaAppointmentNo())); dispatch.setFbaAppointmentStatus(defaultStatus(request.fbaAppointmentStatus())); dispatch.setVehicleAppointmentStatus(defaultStatus(request.vehicleAppointmentStatus())); dispatch.setStatus(PENDING_DEPARTURE); dispatch.setItemCount(selected.size()); dispatch.setPalletCount(selected.size()); dispatch.setPackageCount(selected.stream().map(DispatchPalletCandidate::packageCount).filter(Objects::nonNull).mapToInt(Integer::intValue).sum()); dispatch.setPickedPackageCount(0); dispatch.setShippedPackageCount(0); dispatch.setCreatedBy(trimToNull(operatorId)); dispatch.setCreatedAt(now); dispatch.setUpdatedAt(now); orders.insert(dispatch);
|
||||
for (DispatchPalletCandidate candidate : selected) { DispatchItem item = new DispatchItem(); item.setId(IdWorker.getId()); item.setDispatchOrderId(dispatch.getId()); item.setCustomerId(customerId); item.setWarehousePalletId(candidate.warehousePalletId()); item.setOrderId(candidate.orderId()); item.setOverseasWarehouseId(candidate.overseasWarehouseId()); item.setOverseasWarehouseName(candidate.overseasWarehouseName()); item.setOrderNo(candidate.orderNo()); item.setOrderRefs(candidate.orderRefs()); item.setInboundCode(candidate.inboundCode()); item.setPalletNo(candidate.palletNo()); item.setDestinationWarehouseCode(candidate.destinationWarehouseCode()); item.setDestinationWarehouseName(candidate.destinationWarehouseName()); item.setFbaWarehouseCode(candidate.fbaWarehouseCode()); item.setPackageCount(Objects.requireNonNullElse(candidate.packageCount(), 0)); item.setGrossWeightKg(candidate.grossWeightKg()); item.setVolumeWeightKg(Objects.requireNonNullElse(candidate.volumeWeightKg(), BigDecimal.ZERO)); item.setVolumeCbm(Objects.requireNonNullElse(candidate.volumeCbm(), BigDecimal.ZERO)); item.setSourceStage("RELABEL"); item.setCartonMarks(candidate.relabelCartonMarks()); item.setSourceBusinessType("RELABEL_ORDER"); item.setSourceBusinessNo(request.relabelNo()); item.setCreatedAt(now); items.insert(item); }
|
||||
DispatchPdaTask task = new DispatchPdaTask(); task.setId(IdWorker.getId()); task.setCustomerId(customerId); task.setDispatchOrderId(dispatch.getId()); task.setTaskNo("DPDA" + task.getId()); task.setStatus("PDA_PENDING"); task.setWarehouseId(warehouse); task.setPushedAt(now); task.setCreatedAt(now); task.setUpdatedAt(now); pdaTasks.insert(task);
|
||||
for (DispatchItem item : items.findByDispatchOrder(customerId, dispatch.getId())) {
|
||||
if (selected.stream().anyMatch(c -> c.returnCartonSource() && Objects.equals(c.warehousePalletId(), item.getWarehousePalletId())
|
||||
&& Objects.equals(c.relabelCartonMarks(), item.getCartonMarks()))) {
|
||||
item.setSourceStage("RETURN_RELABEL"); items.updateById(item); returnRelabelStock.reserve(item);
|
||||
}
|
||||
}
|
||||
if (candidates.markRelabelPreDispatched(customerId, request.relabelNo()) != 1) throw new ResponseStatusException(HttpStatus.CONFLICT, "换标订单已被其他排车占用");
|
||||
return response(dispatch, items.findByDispatchOrder(customerId, dispatch.getId()));
|
||||
}
|
||||
@@ -685,6 +701,8 @@ public class DispatchService {
|
||||
requirePalletEditingAllowed(dispatch);
|
||||
DispatchItem item = items.lockById(dispatch.getCustomerId(), dispatch.getId(), itemId);
|
||||
if (item == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Dispatch pallet not found");
|
||||
if ("RETURN_RELABEL".equals(item.getSourceStage()))
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "退货换标按整单排车,请取消整张排车单后重新排车");
|
||||
|
||||
List<DispatchPalletLock> activeLocks = locks.selectList(new LambdaQueryWrapper<DispatchPalletLock>()
|
||||
.eq(DispatchPalletLock::getCustomerId, dispatch.getCustomerId())
|
||||
@@ -734,10 +752,17 @@ public class DispatchService {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Only a pending dispatch can be cancelled");
|
||||
}
|
||||
String customerId = dispatch.getCustomerId();
|
||||
if (Objects.requireNonNullElse(dispatch.getPickedPackageCount(), 0) > 0)
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "排车已开始拣货,不能取消并释放已出库箱");
|
||||
if (outboundOrders.findByDispatch(customerId, dispatch.getId()) != null)
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "已生成出库单,请先取消对应出库单再取消排车");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
dispatch.setStatus(CANCELLED);
|
||||
dispatch.setUpdatedAt(now);
|
||||
orders.updateById(dispatch);
|
||||
for (DispatchItem item : items.findByDispatchOrder(customerId, dispatch.getId())) {
|
||||
if ("RETURN_RELABEL".equals(item.getSourceStage())) returnRelabelStock.release(item);
|
||||
}
|
||||
items.findByDispatchOrder(customerId, dispatch.getId()).stream()
|
||||
.filter(item -> "RELABEL_ORDER".equals(item.getSourceBusinessType()))
|
||||
.map(DispatchItem::getSourceBusinessNo).filter(StringUtils::hasText).distinct()
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package com.baiyue.lastleg.dispatch.service;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchCandidateMapper;
|
||||
import com.baiyue.lastleg.dispatch.model.DispatchPalletCandidate;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaResponses;
|
||||
import com.baiyue.lastleg.palletops.gateway.PalletPhysicalGateway;
|
||||
import com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryCarton;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.service.ReturnInventoryService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/** Native return stock stays in its own ledger; no synthetic InventoryPosition rows. */
|
||||
@Service
|
||||
public class ReturnRelabelDispatchStock {
|
||||
@Resource private DispatchCandidateMapper candidates;
|
||||
@Resource private ReturnInventoryCartonMapper cartons;
|
||||
@Resource private PalletPhysicalGateway physical;
|
||||
@Resource private ReturnInventoryService inventory;
|
||||
|
||||
public List<DispatchPalletCandidate> candidates(String customer, String orderNo, String relabelNo) {
|
||||
List<DispatchPalletCandidate> result = new ArrayList<>();
|
||||
for (var candidate : candidates.nativeReturnRelabelAvailable(customer, orderNo, relabelNo)) {
|
||||
var pallet = physical.resolve(candidate.palletNo());
|
||||
if (pallet == null || !String.valueOf(pallet.warehouseId()).equals(candidate.overseasWarehouseId())
|
||||
|| !Set.of("IDLE", "OCCUPIED").contains(pallet.status())) continue;
|
||||
candidate.warehousePalletId(pallet.palletId());
|
||||
result.add(candidate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void reserve(DispatchItem item) {
|
||||
for (String mark : marks(item).stream().sorted().toList()) {
|
||||
var carton = require(item, mark, "RELABEL_PENDING");
|
||||
carton.setStatus("OUTBOUND_PENDING"); carton.setUpdatedAt(LocalDateTime.now());
|
||||
if (cartons.updateById(carton) != 1) throw conflict("退货箱排车预占失败");
|
||||
}
|
||||
}
|
||||
|
||||
public void release(DispatchItem item) {
|
||||
for (String mark : marks(item).stream().sorted().toList()) {
|
||||
var carton = require(item, mark, "OUTBOUND_PENDING");
|
||||
carton.setStatus("RELABEL_PENDING"); carton.setUpdatedAt(LocalDateTime.now());
|
||||
if (cartons.updateById(carton) != 1) throw conflict("退货箱排车取消释放失败");
|
||||
}
|
||||
}
|
||||
|
||||
public ReturnInventoryCarton pick(DispatchItem item, String mark, long packages, long quantity, long pickId, String dispatchNo) {
|
||||
if (packages != 1 || !marks(item).contains(mark)) throw conflict("请扫描本排车的来源箱唛,每次拣货一箱");
|
||||
var carton = require(item, mark, "OUTBOUND_PENDING");
|
||||
if (quantity != carton.getTotalQuantity()) throw conflict("退货箱 SKU 总数量已变化,请刷新后重试");
|
||||
inventory.shipCarton(item.getCustomerId(), item.getOverseasWarehouseId(), carton.getSourceReturnNo(),
|
||||
carton.getCartonMark(), dispatchNo, pickId);
|
||||
return carton;
|
||||
}
|
||||
|
||||
public List<DispatchPdaResponses.ReturnCarton> detail(DispatchItem item) {
|
||||
return marks(item).stream().map(mark -> {
|
||||
var carton = cartons.findCustomerMark(item.getCustomerId(), mark);
|
||||
if (carton == null || !item.getOverseasWarehouseId().equals(carton.getOverseasWarehouseId()))
|
||||
throw conflict("排车来源退货箱缺失");
|
||||
return new DispatchPdaResponses.ReturnCarton(carton.getId(), mark, carton.getStatus(), carton.getTotalQuantity());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private ReturnInventoryCarton require(DispatchItem item, String mark, String status) {
|
||||
var carton = cartons.lockMark(mark);
|
||||
if (carton == null || !item.getCustomerId().equals(carton.getCustomerId())
|
||||
|| !item.getOverseasWarehouseId().equals(carton.getOverseasWarehouseId())
|
||||
|| !Objects.equals(item.getPalletNo(), carton.getPalletCode())
|
||||
|| !Objects.equals(item.getSourceBusinessNo(), carton.getRelabelNo()) || !status.equals(carton.getStatus()))
|
||||
throw conflict("退货箱已被占用或来源、客户、仓库、托盘不一致");
|
||||
return carton;
|
||||
}
|
||||
private Set<String> marks(DispatchItem item) {
|
||||
return new LinkedHashSet<>(Arrays.asList(Objects.toString(item.getCartonMarks(), "").split(",")));
|
||||
}
|
||||
private ResponseStatusException conflict(String message) { return new ResponseStatusException(HttpStatus.CONFLICT, message); }
|
||||
}
|
||||
Reference in New Issue
Block a user