提交
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
package com.baiyue.lastleg.discard.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.lastleg.discard.entity.CargoDiscardApplication;
|
||||
import com.baiyue.lastleg.discard.entity.CargoDiscardItem;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
|
||||
import com.baiyue.lastleg.inventory.mapper.InventoryLedgerMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryBalanceMapper;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 废弃实物扣减对应的退货余额和普通库存总账。 */
|
||||
@Repository
|
||||
public class CargoDiscardStockLedgerRepository {
|
||||
@Resource private ReturnInventoryBalanceMapper returnBalances;
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
@Resource private InventoryBalanceMapper balances;
|
||||
@Resource private InventoryLedgerMapper ledgers;
|
||||
|
||||
/** 箱内 SKU 扣减时同步扣汇总,只有该 SKU 在箱内耗尽才减一箱。 */
|
||||
public void deductReturn(String customerId, CargoDiscardApplication application, CargoDiscardItem item,
|
||||
long before, long quantity) {
|
||||
var carton = returnCartons.lockId(item.getReturnCartonId());
|
||||
if (carton == null || !customerId.equals(carton.getCustomerId())
|
||||
|| !application.getOverseasWarehouseId().equals(carton.getOverseasWarehouseId())
|
||||
|| !"DISCARD_PENDING".equals(carton.getStatus())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "退货废弃箱归属或状态已变化");
|
||||
}
|
||||
long boxes = before == quantity && !"PARCEL".equals(carton.getSourceUnitType()) ? 1 : 0;
|
||||
var balance = returnBalances.lockSku(customerId, carton.getOverseasWarehouseId(), item.getSkuCode());
|
||||
if (balance == null || returnBalances.deductConversion(customerId, carton.getOverseasWarehouseId(),
|
||||
item.getSkuCode(), quantity, boxes) != 1) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "退货库存汇总数量不足,不能完成废弃");
|
||||
}
|
||||
}
|
||||
|
||||
/** 一件代发已扣实物仓位后,按同事务余额快照追加库存总账。 */
|
||||
public void recordDropship(String customerId, CargoDiscardApplication application, CargoDiscardItem item,
|
||||
InventoryPosition position, long quantity, String operator, LocalDateTime at) {
|
||||
var balance = balances.selectById(position.getBalanceId());
|
||||
if (balance == null || !customerId.equals(balance.getCustomerId())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "废弃库存余额不存在");
|
||||
}
|
||||
var fact = new InventoryLedger();
|
||||
fact.setId(IdWorker.getId()); fact.setCustomerId(customerId); fact.setCustomerName(balance.getCustomerName());
|
||||
fact.setBalanceId(balance.getId()); fact.setPositionId(position.getId());
|
||||
fact.setOverseasWarehouseId(balance.getOverseasWarehouseId()); fact.setDestinationWarehouseCode(balance.getDestinationWarehouseCode());
|
||||
fact.setInboundOrderId(balance.getInboundOrderId()); fact.setInboundCode(balance.getInboundCode());
|
||||
fact.setInboundOrderNo(balance.getInboundOrderNo()); fact.setPalletId(position.getPalletId());
|
||||
fact.setPalletNo(position.getPalletNo()); fact.setBatchNo(position.getBatchNo());
|
||||
fact.setWarehouseLocationCode(position.getWarehouseLocationCode()); fact.setSkuCode(item.getSkuCode());
|
||||
fact.setBusinessType("DISCARD"); fact.setBusinessOrderId(application.getId()); fact.setBusinessOrderNo(application.getDiscardNo());
|
||||
fact.setIdempotencyKey("CARGO_DISCARD:" + application.getId() + ":" + item.getId() + ":STOCK");
|
||||
fact.setStatusFrom("AVAILABLE"); fact.setStatusTo("DISCARDED");
|
||||
fact.setBeforePackages(balance.getAvailablePackages() + quantity); fact.setDeltaPackages(-quantity);
|
||||
fact.setAfterPackages(balance.getAvailablePackages()); fact.setOperatorId(operator);
|
||||
fact.setRemark("货物废弃"); fact.setOccurredAt(at); fact.setCreatedAt(at);
|
||||
if (ledgers.insert(fact) != 1) throw new IllegalStateException("废弃库存流水写入失败");
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -33,6 +33,7 @@ import java.util.Map;
|
||||
/** Final inventory mutation for a warehouse-approved cargo discard. */
|
||||
@Service
|
||||
public class CargoDiscardInventoryAdjustmentService {
|
||||
@Resource private com.baiyue.lastleg.discard.repository.CargoDiscardStockLedgerRepository stockLedger;
|
||||
@Resource private CargoDiscardInventoryMapper inventory;
|
||||
@Resource private CargoDiscardStockService stockService;
|
||||
@Resource private DropshipCartonLedgerMapper cartonLedgers;
|
||||
@@ -44,6 +45,7 @@ public class CargoDiscardInventoryAdjustmentService {
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
@Resource private ReturnInventoryCartonItemMapper returnCartonItems;
|
||||
|
||||
/** 实物废弃、来源库存扣减与流水在同一事务内完成。 */
|
||||
@Transactional(propagation = Propagation.MANDATORY)
|
||||
public AdjustmentResult discardPalletItem(String customerId, CargoDiscardApplication application,
|
||||
CargoDiscardItem item, List<CargoDiscardCarton> links,
|
||||
@@ -53,7 +55,7 @@ public class CargoDiscardInventoryAdjustmentService {
|
||||
stockService.discard(customerId, operatorId, application, item, actualPackages);
|
||||
return new AdjustmentResult(item.getDiscardPackages(), actualPackages, 0);
|
||||
}
|
||||
if (isReturnItem(item)) return discardReturnItem(customerId, item, actualPackages);
|
||||
if (isReturnItem(item)) return discardReturnItem(customerId, application, item, actualPackages);
|
||||
if (actualPackages < 0 || actualPackages > links.size()) throw conflict("实际废弃数量超出该托盘申请数量");
|
||||
DropshipSkuInventory sku = inventory.lockSkuInventory(customerId, item.getSkuInventoryId());
|
||||
if (sku == null) throw conflict("托盘 SKU 库存不存在: " + item.getSkuCode());
|
||||
@@ -97,10 +99,12 @@ public class CargoDiscardInventoryAdjustmentService {
|
||||
}
|
||||
long after = value(inventory.skuPackages(customerId, sku.getId()));
|
||||
if (after != before - actualPackages) throw conflict("托盘 SKU 库存扣减不守恒");
|
||||
stockLedger.recordDropship(customerId, application, item, position, actualPackages, operatorId, occurredAt);
|
||||
return new AdjustmentResult(before, actualPackages, after);
|
||||
}
|
||||
|
||||
private AdjustmentResult discardReturnItem(String customerId, CargoDiscardItem item, int actualPackages) {
|
||||
/** 退货箱按件扣减,同时同步库存余额;完成事实由明细的 PDA 完成快照提供。 */
|
||||
private AdjustmentResult discardReturnItem(String customerId, CargoDiscardApplication application, CargoDiscardItem item, int actualPackages) {
|
||||
if (actualPackages < 0 || actualPackages > item.getDiscardPackages()) {
|
||||
throw conflict("实际废弃数量超出退货库存申请数量");
|
||||
}
|
||||
@@ -110,6 +114,7 @@ public class CargoDiscardInventoryAdjustmentService {
|
||||
if (before < actualPackages || (actualPackages > 0 && returnCartonItems.deduct(sku.getId(), actualPackages) != 1)) {
|
||||
throw conflict("退货库存数量不足");
|
||||
}
|
||||
if (actualPackages > 0) stockLedger.deductReturn(customerId, application, item, before, actualPackages);
|
||||
return new AdjustmentResult(before, actualPackages, before - actualPackages);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -26,7 +26,10 @@ public final class ReturnInventoryResponses {
|
||||
int skuTypes, long totalQuantity, BigDecimal length, BigDecimal width, BigDecimal height,
|
||||
BigDecimal weight, LocalDateTime receivedAt, List<CartonItem> items,
|
||||
String locationCode, String palletCode, LocalDateTime putawayAt, List<MergeHistory> mergeHistory,
|
||||
long relabelledQuantity, long relabelReservedQuantity, List<SkuConversion> skuConversions) { }
|
||||
long relabelledQuantity, long relabelReservedQuantity, List<SkuConversion> skuConversions,
|
||||
List<DiscardHistory> discardHistory) { }
|
||||
public record DiscardHistory(String discardNo, String skuCode, String productNameZh, long beforeQuantity,
|
||||
long quantity, long afterQuantity, String operatorId, LocalDateTime occurredAt) { }
|
||||
public record SkuConversion(String relabelNo, String cartonMark, String oldSkuCode, String newSkuCode,
|
||||
long quantity, String status, LocalDateTime completedAt) {
|
||||
public static SkuConversion from(com.baiyue.lastleg.returnreceipt.entity.ReturnSkuConversion row) {
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.lastleg.returnreceipt.mapper;
|
||||
|
||||
import com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.util.List;
|
||||
|
||||
/** 从不可重复执行的 PDA 完成快照读取退货废弃事实,兼容已完成的历史单据。 */
|
||||
@Mapper
|
||||
public interface ReturnDiscardMovementMapper {
|
||||
String FACTS = " FROM ll_cargo_discard_item i JOIN ll_cargo_discard_application a " +
|
||||
"ON a.id=i.application_id AND a.customer_id=i.customer_id " +
|
||||
"JOIN ll_return_inventory_carton c ON c.id=i.return_carton_id AND c.customer_id=i.customer_id " +
|
||||
"AND c.overseas_warehouse_id=a.overseas_warehouse_id " +
|
||||
"WHERE i.pda_status='COMPLETED' AND i.actual_discard_packages>0 ";
|
||||
String MOVEMENTS = "SELECT i.customer_id customerId,a.overseas_warehouse_id overseasWarehouseId,i.sku_code skuCode,i.product_name productNameZh," +
|
||||
"'DISCARD' movementType,a.discard_no referenceNo,-i.actual_discard_packages totalDelta,-i.actual_discard_packages availableDelta,i.pda_completed_at occurredAt" + FACTS;
|
||||
|
||||
/** 按客户与实物箱读取每次废弃的件数、操作者和操作时间。 */
|
||||
@Select("SELECT a.discard_no discardNo,i.sku_code skuCode,i.product_name productNameZh," +
|
||||
"i.available_packages_snapshot beforeQuantity,i.actual_discard_packages quantity," +
|
||||
"i.available_packages_snapshot-i.actual_discard_packages afterQuantity," +
|
||||
"i.pda_operator_id operatorId,i.pda_completed_at occurredAt" + FACTS +
|
||||
"AND i.customer_id=#{customerId} AND i.return_carton_id=#{cartonId} ORDER BY i.pda_completed_at DESC,i.id DESC")
|
||||
List<ReturnInventoryResponses.DiscardHistory> cartonHistory(@Param("customerId") String customerId, @Param("cartonId") long cartonId);
|
||||
}
|
||||
+2
-1
@@ -22,7 +22,8 @@ public interface ReturnInventoryMovementMapper extends com.baomidou.mybatisplus.
|
||||
" UNION ALL SELECT customer_id,overseas_warehouse_id,old_sku_code,product_name_zh,'SKU_CONVERSION_OUT',relabel_no,-quantity,-quantity,completed_at FROM ll_return_sku_conversion" +
|
||||
" UNION ALL SELECT customer_id,overseas_warehouse_id,new_sku_code,product_name_zh,'SKU_CONVERSION_IN',relabel_no,quantity,quantity,completed_at FROM ll_return_sku_conversion" +
|
||||
" UNION ALL SELECT customer_id,overseas_warehouse_id,old_sku_code,product_name_zh,'SKU_CONVERSION_REVERSE_IN',relabel_no,quantity,quantity,reversed_at FROM ll_return_sku_conversion WHERE status='REVERSED'" +
|
||||
" UNION ALL SELECT customer_id,overseas_warehouse_id,new_sku_code,product_name_zh,'SKU_CONVERSION_REVERSE_OUT',relabel_no,-quantity,-quantity,reversed_at FROM ll_return_sku_conversion WHERE status='REVERSED'";
|
||||
" UNION ALL SELECT customer_id,overseas_warehouse_id,new_sku_code,product_name_zh,'SKU_CONVERSION_REVERSE_OUT',relabel_no,-quantity,-quantity,reversed_at FROM ll_return_sku_conversion WHERE status='REVERSED'" +
|
||||
" UNION ALL " + ReturnDiscardMovementMapper.MOVEMENTS;
|
||||
|
||||
@Select("<script>SELECT COUNT(*) FROM (" + MOVEMENTS + ") m WHERE 1=1 " +
|
||||
"<if test='customerId != null and customerId != \"\"'>AND m.customerId=#{customerId} </if>" +
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.baiyue.lastleg.returnreceipt.repository;
|
||||
|
||||
import com.baiyue.lastleg.returnreceipt.dto.ReturnInventoryResponses;
|
||||
import com.baiyue.lastleg.returnreceipt.mapper.ReturnDiscardMovementMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
/** 退货箱废弃历史的持久化查询入口。 */
|
||||
@Repository
|
||||
public class ReturnDiscardMovementRepository {
|
||||
@Resource private ReturnDiscardMovementMapper movements;
|
||||
|
||||
/** 仅返回指定客户实物箱已经完成的废弃事实。 */
|
||||
public List<ReturnInventoryResponses.DiscardHistory> cartonHistory(String customerId, long cartonId) {
|
||||
return movements.cartonHistory(customerId, cartonId);
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -63,6 +63,7 @@ public class ReturnInventoryService {
|
||||
@Resource private ReturnInventoryOperationMapper operations;
|
||||
@Resource private ReturnInventoryQueryMapper queries;
|
||||
@Resource private ReturnInventoryMovementMapper movements;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.repository.ReturnDiscardMovementRepository discardMovements;
|
||||
@Resource private LastlegOrderNoGenerator numbers;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper orders;
|
||||
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderItemMapper orderItems;
|
||||
@@ -1242,6 +1243,7 @@ public class ReturnInventoryService {
|
||||
return "PENDING".equals(carton.getCountStatus()) && AVAILABLE.equals(carton.getStatus()) ? "PENDING_COUNT" : carton.getStatus();
|
||||
}
|
||||
|
||||
/** 组装箱内余量及合箱、换标、废弃操作历史。 */
|
||||
private ReturnInventoryResponses.CartonDetail cartonDetail(ReturnInventoryCarton carton) {
|
||||
List<ReturnInventoryResponses.CartonItem> items = cartonItems.listCarton(carton.getId()).stream()
|
||||
.map(item -> new ReturnInventoryResponses.CartonItem(item.getSkuCode(), item.getProductNameZh(), item.getQuantity(),
|
||||
@@ -1253,7 +1255,8 @@ public class ReturnInventoryService {
|
||||
return new ReturnInventoryResponses.CartonDetail(carton.getCartonMark(), carton.getCustomerId(), carton.getOverseasWarehouseId(), carton.getSourceBatchNo(), carton.getSourceReturnNo(),
|
||||
displayStatus(carton), carton.getFbaCartonMark(), carton.getDestinationWarehouseCode(), carton.getDestinationWarehouseName(), carton.getRelabelNo(), relabelProgressStatus(carton), carton.getSkuTypes() == null ? 0 : carton.getSkuTypes(), value(carton.getTotalQuantity()), carton.getLength(), carton.getWidth(), carton.getHeight(), carton.getWeight(), carton.getCreatedAt(), items, carton.getLocationCode(), carton.getPalletCode(), carton.getPutawayAt(), packingTrace.listCartonHistory(carton.getCustomerId(), carton.getCartonMark()),
|
||||
completedRelabelQuantity(carton), cartons.relabelReservedQuantity(carton.getId()),
|
||||
skuConversions.listCarton(carton.getCustomerId(), carton.getId()).stream().map(ReturnInventoryResponses.SkuConversion::from).toList());
|
||||
skuConversions.listCarton(carton.getCustomerId(), carton.getId()).stream().map(ReturnInventoryResponses.SkuConversion::from).toList(),
|
||||
discardMovements.cartonHistory(carton.getCustomerId(), carton.getId()));
|
||||
}
|
||||
|
||||
private String relabelProgressStatus(ReturnInventoryCarton carton) {
|
||||
|
||||
Reference in New Issue
Block a user