废弃 ,换标文件上传 ,排车托盘锁粒度
This commit is contained in:
+39
@@ -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,
|
||||
|
||||
+103
-6
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user