废弃 ,换标文件上传 ,排车托盘锁粒度

This commit is contained in:
2026-09-14 16:49:00 +08:00
parent 60ebc2e376
commit 78bbd69a26
23 changed files with 443 additions and 21 deletions
@@ -9,6 +9,9 @@ import com.baiyue.lastleg.dropship.entity.DropshipCarton;
import com.baiyue.lastleg.dropship.entity.DropshipCartonLedger;
import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory;
import com.baiyue.lastleg.dropship.mapper.DropshipCartonLedgerMapper;
import com.baiyue.lastleg.dropship.mapper.DropshipOrderProfileMapper;
import com.baiyue.lastleg.inventory.mapper.InventoryPositionMapper;
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@@ -25,6 +28,42 @@ import java.util.Map;
public class CargoDiscardInventoryAdjustmentService {
@Resource private CargoDiscardInventoryMapper inventory;
@Resource private DropshipCartonLedgerMapper cartonLedgers;
@Resource private DropshipOrderProfileMapper profiles;
@Resource private InventoryPositionMapper positions;
@Resource private InventoryBalanceMapper balances;
@Transactional(propagation = Propagation.MANDATORY)
public AdjustmentResult discardPalletItem(String customerId, CargoDiscardApplication application,
CargoDiscardItem item, List<CargoDiscardCarton> links,
int actualPackages, String operatorId, LocalDateTime occurredAt) {
if (actualPackages < 0 || actualPackages > links.size()) throw conflict("实际废弃数量超出该托盘申请数量");
DropshipSkuInventory sku = inventory.lockSkuInventory(customerId, item.getSkuInventoryId());
if (sku == null) throw conflict("托盘 SKU 库存不存在: " + item.getSkuCode());
long before = value(sku.getPackageCount());
if (before < actualPackages || inventory.deductSkuInventory(customerId, sku.getId(), actualPackages) != 1) {
throw conflict("托盘 SKU 库存不足: " + item.getSkuCode());
}
var profile = profiles.selectById(item.getProfileId());
var position = profile == null || profile.getInventoryPositionId() == null ? null
: positions.findTenantPosition(customerId, profile.getInventoryPositionId());
if (profile == null || !customerId.equals(profile.getCustomerId()) || position == null
|| positions.deductAvailable(customerId, profile.getInventoryPositionId(), actualPackages) != 1
|| balances.deductAvailable(customerId, position.getBalanceId(), actualPackages) != 1) {
throw conflict("托盘库存明细数量不足或托盘位置不存在");
}
for (int index = 0; index < links.size(); index++) {
CargoDiscardCarton link = links.get(index);
String target = index < actualPackages ? "DISCARDED" : "AVAILABLE";
DropshipCarton carton = inventory.lockCarton(customerId, link.getCartonId());
if (carton == null || inventory.changeCartonStatus(customerId, carton.getId(), "DISCARD_PENDING", target) != 1) {
throw conflict("托盘预留箱状态已变化");
}
if (index < actualPackages) cartonLedgers.insert(discardFact(customerId, application, item, carton, operatorId, occurredAt));
}
long after = value(inventory.skuPackages(customerId, sku.getId()));
if (after != before - actualPackages) throw conflict("托盘 SKU 库存扣减不守恒");
return new AdjustmentResult(before, actualPackages, after);
}
@Transactional(propagation = Propagation.MANDATORY)
public AdjustmentResult discard(String customerId, CargoDiscardApplication application,
@@ -19,7 +19,9 @@ import com.baiyue.lastleg.discard.model.CargoDiscardCandidateRow;
import com.baiyue.lastleg.dropship.entity.DropshipCarton;
import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
@@ -49,7 +51,10 @@ public class CargoDiscardService {
public static final String PENDING_REVIEW = "PENDING_REVIEW";
public static final String APPROVED = "APPROVED";
public static final String REJECTED = "REJECTED";
private static final Set<String> STATUSES = Set.of(PENDING_REVIEW, APPROVED, REJECTED);
public static final String CANCELLED = "CANCELLED";
public static final String PDA_COMPLETED = "PDA_COMPLETED";
public static final String COMPLETED = "COMPLETED";
private static final Set<String> STATUSES = Set.of(PENDING_REVIEW, APPROVED, REJECTED, CANCELLED, PDA_COMPLETED, COMPLETED);
@Resource private CargoDiscardApplicationMapper applications;
@Resource private CargoDiscardItemMapper items;
@@ -58,6 +63,7 @@ public class CargoDiscardService {
@Resource private CargoDiscardInventoryMapper inventory;
@Resource private CargoDiscardInventoryAdjustmentService adjustments;
@Resource private LastlegOrderMapper orders;
@Resource private LastlegOrderStatusHistoryMapper orderHistories;
@Resource private ObjectMapper objectMapper;
@Value("${baiyue.lastleg.cargo-discard.fee-per-package-usd:5.00}")
private BigDecimal feePerPackage = new BigDecimal("5.00");
@@ -118,6 +124,37 @@ public class CargoDiscardService {
application.setCreatedAt(now); application.setUpdatedAt(now);
applications.insert(application);
// A discard request is also a customer-visible tail-leg order. Keep the
// application as the workflow record and link the order through its
// immutable source id so cancellation/status changes stay traceable.
LastlegOrder tailOrder = new LastlegOrder();
tailOrder.setId(IdWorker.getId());
tailOrder.setOrderNo(application.getDiscardNo());
tailOrder.setCustomerId(customerId);
tailOrder.setOrderType("CARGO_DISCARD");
tailOrder.setInboundCode(application.getInboundCodes());
tailOrder.setInboundOrderNo(application.getDiscardNo());
tailOrder.setSourceRegion("LASTLEG_DISCARD");
tailOrder.setSourceInboundId("CARGO_DISCARD:" + application.getId());
tailOrder.setSourceOrderId(String.valueOf(application.getId()));
tailOrder.setSourceOrderNo(application.getDiscardNo());
tailOrder.setOverseasWarehouseId(warehouseId);
tailOrder.setOverseasWarehouseName(warehouseName);
tailOrder.setPackageCount(total);
tailOrder.setCurrentStatus(PENDING_REVIEW);
tailOrder.setVersion(0);
tailOrder.setCreatedAt(now);
tailOrder.setUpdatedAt(now);
tailOrder.setSourceEventId("CARGO_DISCARD:" + application.getId());
tailOrder.setSourceLoadingPlanId("CARGO_DISCARD");
tailOrder.setSourcePayloadHash(requestHash);
orders.insert(tailOrder);
LastlegOrderStatusHistory initialOrderHistory = new LastlegOrderStatusHistory();
initialOrderHistory.setId(IdWorker.getId()); initialOrderHistory.setCustomerId(customerId); initialOrderHistory.setOrderId(tailOrder.getId());
initialOrderHistory.setFromStatus(null); initialOrderHistory.setToStatus(PENDING_REVIEW);
initialOrderHistory.setEventId("CARGO_DISCARD_CREATE:" + application.getId()); initialOrderHistory.setRemark(trim(request.remark())); initialOrderHistory.setCreatedAt(now);
orderHistories.insert(initialOrderHistory);
for (CargoDiscardRequests.Item input : request.items()) {
reserve(customerId, application, orderCache.get(input.orderId()), input, now);
}
@@ -175,11 +212,7 @@ public class CargoDiscardService {
Map<Long, List<CargoDiscardCarton>> linksByItem = links.stream()
.collect(java.util.stream.Collectors.groupingBy(CargoDiscardCarton::getItemId));
String historyRemark = request.remark();
if (APPROVED.equals(next)) {
CargoDiscardInventoryAdjustmentService.AdjustmentResult result = adjustments.discard(
customerId, application, applicationItems, linksByItem, operatorId, now());
historyRemark = result.auditRemark(request.remark());
} else {
if (REJECTED.equals(next)) {
release(customerId, links);
}
@@ -187,10 +220,74 @@ public class CargoDiscardService {
application.setStatus(next); application.setReviewedBy(operatorId);
application.setReviewRemark(trim(request.remark())); application.setReviewedAt(reviewedAt);
application.setUpdatedAt(reviewedAt); applications.updateById(application);
LastlegOrder linked = orders.findSource(customerId, "LASTLEG_DISCARD", "CARGO_DISCARD:" + application.getId());
if (linked != null) {
linked.setCurrentStatus(next); linked.setVersion((linked.getVersion() == null ? 0 : linked.getVersion()) + 1); linked.setUpdatedAt(reviewedAt);
orders.updateById(linked);
LastlegOrderStatusHistory orderHistory = new LastlegOrderStatusHistory(); orderHistory.setId(IdWorker.getId());
orderHistory.setCustomerId(customerId); orderHistory.setOrderId(linked.getId()); orderHistory.setFromStatus(PENDING_REVIEW);
orderHistory.setToStatus(next); orderHistory.setEventId("CARGO_DISCARD_REVIEW:" + application.getId()); orderHistory.setRemark(historyRemark); orderHistory.setCreatedAt(reviewedAt);
orderHistories.insert(orderHistory);
}
history(customerId, application.getId(), PENDING_REVIEW, next, operatorId, historyRemark, reviewedAt);
return detail(customerId, discardNo);
}
@Transactional
public CargoDiscardResponses.Detail complete(String customerId, String discardNo, String operatorId) {
CargoDiscardApplication application = applications.lockNo(customerId, discardNo);
if (application == null) throw notFound("Cargo discard application not found");
if (COMPLETED.equals(application.getStatus())) return detail(customerId, discardNo);
if (!PDA_COMPLETED.equals(application.getStatus())) throw conflict("全部托盘 PDA 作业完成后才能确认废弃完成");
updateLinkedStatus(customerId, application, PDA_COMPLETED, COMPLETED, operatorId, "仓库确认废弃完成");
return detail(customerId, discardNo);
}
public void updateLinkedStatus(String customerId, CargoDiscardApplication application, String from, String to,
String operatorId, String remark) {
LocalDateTime at = now(); application.setStatus(to); application.setUpdatedAt(at); applications.updateById(application);
LastlegOrder linked = orders.findSource(customerId, "LASTLEG_DISCARD", "CARGO_DISCARD:" + application.getId());
if (linked != null) {
linked.setCurrentStatus(to); linked.setPackageCount(application.getTotalPackages());
linked.setVersion((linked.getVersion() == null ? 0 : linked.getVersion()) + 1); linked.setUpdatedAt(at); orders.updateById(linked);
LastlegOrderStatusHistory h = new LastlegOrderStatusHistory(); h.setId(IdWorker.getId()); h.setCustomerId(customerId); h.setOrderId(linked.getId());
h.setFromStatus(from); h.setToStatus(to); h.setEventId("CARGO_DISCARD_STATUS:" + application.getId() + ":" + to); h.setRemark(remark); h.setCreatedAt(at); orderHistories.insert(h);
}
history(customerId, application.getId(), from, to, operatorId, remark, at);
}
@Transactional
public CargoDiscardResponses.Detail cancel(String customerId, String discardNo, String operatorId, boolean platform) {
CargoDiscardApplication application = applications.lockNo(customerId, discardNo);
if (application == null) throw notFound("Cargo discard application not found");
if (CANCELLED.equals(application.getStatus())) return detail(customerId, discardNo);
if (REJECTED.equals(application.getStatus())) throw conflict("已驳回的废弃申请不能取消");
if (PDA_COMPLETED.equals(application.getStatus()) || COMPLETED.equals(application.getStatus())) throw conflict("PDA 已完成库存扣减,废弃申请不能取消");
if (items.listApplication(customerId, application.getId()).stream().anyMatch(i -> "COMPLETED".equals(i.getPdaStatus()))) {
throw conflict("已有托盘完成废弃作业,不能取消申请");
}
if (!platform && !PENDING_REVIEW.equals(application.getStatus())) {
throw conflict("客户只能取消待审核的废弃申请");
}
List<CargoDiscardCarton> links = discardCartons.listApplication(customerId, application.getId());
release(customerId, links);
LocalDateTime at = now();
String from = application.getStatus();
application.setStatus(CANCELLED); application.setReviewedBy(platform ? operatorId : application.getReviewedBy());
application.setReviewRemark(platform ? "平台取消" : application.getReviewRemark());
application.setUpdatedAt(at); applications.updateById(application);
LastlegOrder order = orders.findSource(customerId, "LASTLEG_DISCARD", "CARGO_DISCARD:" + application.getId());
if (order != null) {
order.setCurrentStatus(CANCELLED); order.setVersion((order.getVersion() == null ? 0 : order.getVersion()) + 1); order.setUpdatedAt(at);
orders.updateById(order);
LastlegOrderStatusHistory h = new LastlegOrderStatusHistory(); h.setId(IdWorker.getId()); h.setCustomerId(customerId); h.setOrderId(order.getId());
h.setFromStatus(from); h.setToStatus(CANCELLED); h.setEventId("CARGO_DISCARD_CANCEL:" + application.getId()); h.setRemark("废弃申请已取消"); h.setCreatedAt(at);
orderHistories.insert(h);
}
history(customerId, application.getId(), from, CANCELLED, operatorId, "废弃申请已取消", at);
return detail(customerId, discardNo);
}
private void reserve(String customerId, CargoDiscardApplication application, LastlegOrder order,
CargoDiscardRequests.Item input, LocalDateTime now) {
String skuCode = input.skuCode().trim();