修改提交

This commit is contained in:
2026-09-08 20:25:05 +08:00
parent 2a27e28f26
commit 249bf0ab4b
10 changed files with 134 additions and 22 deletions
@@ -126,7 +126,7 @@ public class RelabelApplicationService implements WalletResultHandler {
|| !Objects.equals(balance.getInboundOrderId(), item.sourceInboundOrderId())) { || !Objects.equals(balance.getInboundOrderId(), item.sourceInboundOrderId())) {
throw conflict("Selected inventory does not belong to this customer, operation warehouse and inbound order"); throw conflict("Selected inventory does not belong to this customer, operation warehouse and inbound order");
} }
if (value(position.getAvailablePackages()) < item.totalPackageCount()) { if (value(position.getAvailablePackages()) < item.notifiedPackageCount()) {
throw conflict("Available inventory changed for inbound " + balance.getInboundOrderNo()); throw conflict("Available inventory changed for inbound " + balance.getInboundOrderNo());
} }
sources.add(new SourceInventory(item, balance)); sources.add(new SourceInventory(item, balance));
@@ -137,7 +137,7 @@ public class RelabelApplicationService implements WalletResultHandler {
String relabelNo = orderNoGenerator.next("RELABEL", operationWarehouseId, applicationId); String relabelNo = orderNoGenerator.next("RELABEL", operationWarehouseId, applicationId);
for (SourceInventory source : sources) { for (SourceInventory source : sources) {
inventoryCommands.hold(customerId, operatorId, new InventoryCommandRequests.Move( inventoryCommands.hold(customerId, operatorId, new InventoryCommandRequests.Move(
source.request().sourceInventoryPositionId(), source.request().totalPackageCount(), relabelNo, source.request().sourceInventoryPositionId(), source.request().notifiedPackageCount(), relabelNo,
null, "RELABEL:" + relabelNo, request.idempotencyKey().trim() + ":HOLD:" null, "RELABEL:" + relabelNo, request.idempotencyKey().trim() + ":HOLD:"
+ source.request().sourceInventoryPositionId(), "贴标申请冻结原入仓单库存")); + source.request().sourceInventoryPositionId(), "贴标申请冻结原入仓单库存"));
} }
@@ -398,9 +398,9 @@ public class RelabelApplicationService implements WalletResultHandler {
private void releaseInventory(String customerId, String operatorId, RelabelApplication application) { private void releaseInventory(String customerId, String operatorId, RelabelApplication application) {
for (RelabelItem item : itemList(application.getId())) { for (RelabelItem item : itemList(application.getId())) {
if (item.getSourceInventoryPositionId() == null || item.getTotalPackageCount() == null) continue; if (item.getSourceInventoryPositionId() == null || item.getNotifiedPackageCount() == null) continue;
inventoryCommands.release(customerId, operatorId, new InventoryCommandRequests.Move( inventoryCommands.release(customerId, operatorId, new InventoryCommandRequests.Move(
item.getSourceInventoryPositionId(), item.getTotalPackageCount(), application.getRelabelNo(), null, item.getSourceInventoryPositionId(), item.getNotifiedPackageCount(), application.getRelabelNo(), null,
"RELABEL:" + application.getRelabelNo(), application.getIdempotencyKey() + ":RELEASE:" "RELABEL:" + application.getRelabelNo(), application.getIdempotencyKey() + ":RELEASE:"
+ item.getSourceInventoryPositionId(), "贴标结束释放库存至原入仓单")); + item.getSourceInventoryPositionId(), "贴标结束释放库存至原入仓单"));
} }
@@ -30,6 +30,12 @@ public class PlatformReturnInventoryController {
return ApiResponse.success(service.platformCartonPage(request)); return ApiResponse.success(service.platformCartonPage(request));
} }
@GetMapping("/movements")
@PreAuthorize("hasAuthority('platform:overseas-warehouse:return-inventory:list')")
public ApiResponse<PageResult<ReturnInventoryResponses.PlatformMovement>> movements(@Valid ReturnInventoryRequests.PlatformQuery request) {
return ApiResponse.success(service.platformMovementPage(request));
}
@GetMapping("/cartons/{cartonMark}") @GetMapping("/cartons/{cartonMark}")
@PreAuthorize("hasAuthority('platform:overseas-warehouse:return-cartons:list')") @PreAuthorize("hasAuthority('platform:overseas-warehouse:return-cartons:list')")
public ApiResponse<ReturnInventoryResponses.CartonDetail> carton(@PathVariable String cartonMark) { public ApiResponse<ReturnInventoryResponses.CartonDetail> carton(@PathVariable String cartonMark) {
@@ -11,8 +11,8 @@ public final class ReturnInventoryResponses {
String productNameZh, long quantity, int cartonSkuTypes, long cartonTotalQuantity, String productNameZh, long quantity, int cartonSkuTypes, long cartonTotalQuantity,
LocalDateTime receivedAt) { } LocalDateTime receivedAt) { }
public record SummaryItem(String overseasWarehouseId, String skuCode, String productNameZh, public record SummaryItem(String overseasWarehouseId, String skuCode, String productNameZh,
long availableQuantity, long cartonQuantity, LocalDateTime updatedAt) { } long totalQuantity, long availableQuantity, long cartonQuantity, LocalDateTime updatedAt) { }
public record CartonDetail(String cartonMark, String overseasWarehouseId, String sourceBatchNo, String sourceReturnNo, String status, public record CartonDetail(String cartonMark, String customerId, String overseasWarehouseId, String sourceBatchNo, String sourceReturnNo, String status,
int skuTypes, long totalQuantity, BigDecimal length, BigDecimal width, BigDecimal height, int skuTypes, long totalQuantity, BigDecimal length, BigDecimal width, BigDecimal height,
BigDecimal weight, LocalDateTime receivedAt, List<CartonItem> items) { } BigDecimal weight, LocalDateTime receivedAt, List<CartonItem> items) { }
public record CartonItem(String skuCode, String productNameZh, long quantity) { } public record CartonItem(String skuCode, String productNameZh, long quantity) { }
@@ -24,9 +24,12 @@ public final class ReturnInventoryResponses {
public record PackingSku(String skuCode, String productNameZh, long availableQuantity) { } public record PackingSku(String skuCode, String productNameZh, long availableQuantity) { }
public record PackingPreview(List<PackingOrder> orders, String customerId, List<PackingSku> skus) { } public record PackingPreview(List<PackingOrder> orders, String customerId, List<PackingSku> skus) { }
public record PlatformStock(String customerId, String overseasWarehouseId, String skuCode, String productNameZh, public record PlatformStock(String customerId, String overseasWarehouseId, String skuCode, String productNameZh,
long availableQuantity, long cartonQuantity, LocalDateTime updatedAt) { } long totalQuantity, long availableQuantity, long cartonQuantity, LocalDateTime updatedAt) { }
public record PlatformCarton(String customerId, String overseasWarehouseId, String cartonMark, String sourceReturnNo, public record PlatformCarton(String customerId, String overseasWarehouseId, String cartonMark, String sourceReturnNo,
String sourceBatchNo, String status, int skuTypes, long totalQuantity, String sourceBatchNo, String status, int skuTypes, long totalQuantity,
BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight, BigDecimal length, BigDecimal width, BigDecimal height, BigDecimal weight,
LocalDateTime createdAt) { } LocalDateTime createdAt) { }
public record PlatformMovement(String customerId, String overseasWarehouseId, String skuCode, String productNameZh,
String movementType, String referenceNo, long totalDelta, long availableDelta,
LocalDateTime occurredAt) { }
} }
@@ -16,6 +16,8 @@ public class ReturnInventoryBalance {
private String overseasWarehouseId; private String overseasWarehouseId;
private String skuCode; private String skuCode;
private String productNameZh; private String productNameZh;
/** 实物总库存;只有实际出库才会减少。 */
private Long totalQuantity;
private Long availableQuantity; private Long availableQuantity;
private Long cartonQuantity; private Long cartonQuantity;
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -11,13 +11,14 @@ import java.util.List;
@Mapper @Mapper
public interface ReturnInventoryBalanceMapper extends BaseMapper<ReturnInventoryBalance> { public interface ReturnInventoryBalanceMapper extends BaseMapper<ReturnInventoryBalance> {
@Insert("INSERT INTO ll_return_inventory_balance (id,customer_id,overseas_warehouse_id,sku_code,product_name_zh,available_quantity,carton_quantity,created_at,updated_at) " + @Insert("INSERT INTO ll_return_inventory_balance (id,customer_id,overseas_warehouse_id,sku_code,product_name_zh,total_quantity,available_quantity,carton_quantity,created_at,updated_at) " +
"VALUES (#{id},#{customerId},#{warehouseId},#{skuCode},#{productNameZh},#{quantity},#{cartons},NOW(3),NOW(3)) " + "VALUES (#{id},#{customerId},#{warehouseId},#{skuCode},#{productNameZh},#{totalQuantity},#{availableQuantity},#{cartons},NOW(3),NOW(3)) " +
"ON DUPLICATE KEY UPDATE available_quantity=available_quantity+VALUES(available_quantity),carton_quantity=carton_quantity+VALUES(carton_quantity), " + "ON DUPLICATE KEY UPDATE total_quantity=total_quantity+VALUES(total_quantity),available_quantity=available_quantity+VALUES(available_quantity),carton_quantity=carton_quantity+VALUES(carton_quantity), " +
"product_name_zh=COALESCE(NULLIF(VALUES(product_name_zh),''),product_name_zh),updated_at=NOW(3)") "product_name_zh=COALESCE(NULLIF(VALUES(product_name_zh),''),product_name_zh),updated_at=NOW(3)")
int adjust(@Param("id") long id, @Param("customerId") String customerId, @Param("warehouseId") String warehouseId, int adjust(@Param("id") long id, @Param("customerId") String customerId, @Param("warehouseId") String warehouseId,
@Param("skuCode") String skuCode, @Param("productNameZh") String productNameZh, @Param("skuCode") String skuCode, @Param("productNameZh") String productNameZh,
@Param("quantity") long quantity, @Param("cartons") long cartons); @Param("totalQuantity") long totalQuantity, @Param("availableQuantity") long availableQuantity,
@Param("cartons") long cartons);
@Select("<script>SELECT COUNT(*) FROM ll_return_inventory_balance WHERE customer_id=#{customerId} " + @Select("<script>SELECT COUNT(*) FROM ll_return_inventory_balance WHERE customer_id=#{customerId} " +
"<if test='warehouseId != null and warehouseId != \"\"'>AND overseas_warehouse_id=#{warehouseId} </if>" + "<if test='warehouseId != null and warehouseId != \"\"'>AND overseas_warehouse_id=#{warehouseId} </if>" +
@@ -0,0 +1,33 @@
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;
/** Read-only return inventory movement ledger reconstructed from signed receipts and merged cartons. */
@Mapper
public interface ReturnInventoryMovementMapper {
String MOVEMENTS = "SELECT o.customer_id customerId,o.overseas_warehouse_id overseasWarehouseId,ri.sku_code skuCode,oi.product_name_zh productNameZh," +
"'RECEIPT' movementType,r.receipt_no referenceNo,ri.received_quantity totalDelta,ri.received_quantity availableDelta,r.signed_at occurredAt " +
"FROM ll_return_receipt r JOIN ll_return_order o ON o.id=r.return_order_id JOIN ll_return_receipt_item ri ON ri.receipt_id=r.id " +
"JOIN ll_return_order_item oi ON oi.id=ri.return_order_item_id " +
"UNION ALL SELECT c.customer_id,c.overseas_warehouse_id,i.sku_code,i.product_name_zh,'MERGE',c.carton_mark,0,-i.quantity,c.created_at " +
"FROM ll_return_inventory_carton c JOIN ll_return_inventory_carton_item i ON i.carton_id=c.id WHERE c.source_batch_no='MERGED'";
@Select("<script>SELECT COUNT(*) FROM (" + MOVEMENTS + ") m WHERE 1=1 " +
"<if test='customerId != null and customerId != \"\"'>AND m.customerId=#{customerId} </if>" +
"<if test='warehouseId != null and warehouseId != \"\"'>AND m.overseasWarehouseId=#{warehouseId} </if>" +
"<if test='skuCode != null and skuCode != \"\"'>AND m.skuCode LIKE CONCAT('%',#{skuCode},'%') </if></script>")
long platformCount(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId, @Param("skuCode") String skuCode);
@Select("<script>SELECT * FROM (" + MOVEMENTS + ") m WHERE 1=1 " +
"<if test='customerId != null and customerId != \"\"'>AND m.customerId=#{customerId} </if>" +
"<if test='warehouseId != null and warehouseId != \"\"'>AND m.overseasWarehouseId=#{warehouseId} </if>" +
"<if test='skuCode != null and skuCode != \"\"'>AND m.skuCode LIKE CONCAT('%',#{skuCode},'%') </if>" +
"ORDER BY occurredAt DESC LIMIT #{offset},#{size}</script>")
List<ReturnInventoryResponses.PlatformMovement> platformPage(@Param("customerId") String customerId, @Param("warehouseId") String warehouseId,
@Param("skuCode") String skuCode, @Param("offset") long offset, @Param("size") long size);
}
@@ -15,6 +15,7 @@ import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonItemMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper; import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryCartonMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryOperationMapper; import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryOperationMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryQueryMapper; import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryQueryMapper;
import com.baiyue.lastleg.returnreceipt.mapper.ReturnInventoryMovementMapper;
import com.baiyue.lastleg.returnreceipt.model.ReturnInventoryRow; import com.baiyue.lastleg.returnreceipt.model.ReturnInventoryRow;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -47,6 +48,7 @@ public class ReturnInventoryService {
@Resource private ReturnInventoryBalanceMapper balances; @Resource private ReturnInventoryBalanceMapper balances;
@Resource private ReturnInventoryOperationMapper operations; @Resource private ReturnInventoryOperationMapper operations;
@Resource private ReturnInventoryQueryMapper queries; @Resource private ReturnInventoryQueryMapper queries;
@Resource private ReturnInventoryMovementMapper movements;
@Resource private LastlegOrderNoGenerator numbers; @Resource private LastlegOrderNoGenerator numbers;
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper orders; @Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnOrderMapper orders;
@Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptMapper receiptMapper; @Resource private com.baiyue.lastleg.returnreceipt.mapper.ReturnReceiptMapper receiptMapper;
@@ -72,7 +74,7 @@ public class ReturnInventoryService {
item.setSkuCode(line.skuCode()); item.setProductNameZh(text(line.productNameZh())); item.setQuantity(line.quantity()); item.setSkuCode(line.skuCode()); item.setProductNameZh(text(line.productNameZh())); item.setQuantity(line.quantity());
cartonItems.insert(item); cartonItems.insert(item);
balances.adjust(IdWorker.getId(), order.getCustomerId(), order.getOverseasWarehouseId(), line.skuCode(), balances.adjust(IdWorker.getId(), order.getCustomerId(), order.getOverseasWarehouseId(), line.skuCode(),
text(line.productNameZh()), line.quantity(), 1); text(line.productNameZh()), line.quantity(), line.quantity(), 1);
} }
return carton; return carton;
} }
@@ -105,7 +107,16 @@ public class ReturnInventoryService {
List<ReturnInventoryResponses.PlatformStock> records = total == 0 ? List.of() : balances.platformPage(customerId, warehouseId, sku, List<ReturnInventoryResponses.PlatformStock> records = total == 0 ? List.of() : balances.platformPage(customerId, warehouseId, sku,
(request.page() - 1) * request.size(), request.size()).stream().map(balance -> new ReturnInventoryResponses.PlatformStock( (request.page() - 1) * request.size(), request.size()).stream().map(balance -> new ReturnInventoryResponses.PlatformStock(
balance.getCustomerId(), balance.getOverseasWarehouseId(), balance.getSkuCode(), balance.getProductNameZh(), balance.getCustomerId(), balance.getOverseasWarehouseId(), balance.getSkuCode(), balance.getProductNameZh(),
value(balance.getAvailableQuantity()), value(balance.getCartonQuantity()), balance.getUpdatedAt())).toList(); value(balance.getTotalQuantity()), value(balance.getAvailableQuantity()), value(balance.getCartonQuantity()), balance.getUpdatedAt())).toList();
return new PageResult<>(records, request.page(), request.size(), total, (total + request.size() - 1) / request.size());
}
@Transactional(readOnly = true)
public PageResult<ReturnInventoryResponses.PlatformMovement> platformMovementPage(ReturnInventoryRequests.PlatformQuery request) {
String customerId = text(request.customerId()); String warehouseId = text(request.overseasWarehouseId()); String sku = text(request.skuCode());
long total = movements.platformCount(customerId, warehouseId, sku);
List<ReturnInventoryResponses.PlatformMovement> records = total == 0 ? List.of() : movements.platformPage(customerId, warehouseId, sku,
(request.page() - 1) * request.size(), request.size());
return new PageResult<>(records, request.page(), request.size(), total, (total + request.size() - 1) / request.size()); return new PageResult<>(records, request.page(), request.size(), total, (total + request.size() - 1) / request.size());
} }
@@ -251,7 +262,7 @@ public class ReturnInventoryService {
targetItem.setId(IdWorker.getId()); targetItem.setCartonId(targetId); targetItem.setCustomerId(customerId); targetItem.setSkuCode(entry.getKey()); targetItem.setId(IdWorker.getId()); targetItem.setCartonId(targetId); targetItem.setCustomerId(customerId); targetItem.setSkuCode(entry.getKey());
targetItem.setProductNameZh(productNames.get(entry.getKey())); targetItem.setQuantity(entry.getValue()); cartonItems.insert(targetItem); targetItem.setProductNameZh(productNames.get(entry.getKey())); targetItem.setQuantity(entry.getValue()); cartonItems.insert(targetItem);
balances.adjust(IdWorker.getId(), customerId, target.getOverseasWarehouseId(), entry.getKey(), productNames.get(entry.getKey()), 0, balances.adjust(IdWorker.getId(), customerId, target.getOverseasWarehouseId(), entry.getKey(), productNames.get(entry.getKey()), 0,
1L - emptiedBySku.getOrDefault(entry.getKey(), 0)); -entry.getValue(), 1L - emptiedBySku.getOrDefault(entry.getKey(), 0));
} }
operation.setResultCartonId(targetId); operation.setUpdatedAt(now); operations.updateById(operation); operation.setResultCartonId(targetId); operation.setUpdatedAt(now); operations.updateById(operation);
return new ReturnInventoryResponses.MergeResult(target.getCartonMark(), requested.keySet().iterator().next(), total, label(target)); return new ReturnInventoryResponses.MergeResult(target.getCartonMark(), requested.keySet().iterator().next(), total, label(target));
@@ -281,13 +292,13 @@ public class ReturnInventoryService {
private ReturnInventoryResponses.SummaryItem summary(com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryBalance balance) { private ReturnInventoryResponses.SummaryItem summary(com.baiyue.lastleg.returnreceipt.entity.ReturnInventoryBalance balance) {
return new ReturnInventoryResponses.SummaryItem(balance.getOverseasWarehouseId(), balance.getSkuCode(), balance.getProductNameZh(), return new ReturnInventoryResponses.SummaryItem(balance.getOverseasWarehouseId(), balance.getSkuCode(), balance.getProductNameZh(),
value(balance.getAvailableQuantity()), value(balance.getCartonQuantity()), balance.getUpdatedAt()); value(balance.getTotalQuantity()), value(balance.getAvailableQuantity()), value(balance.getCartonQuantity()), balance.getUpdatedAt());
} }
private ReturnInventoryResponses.CartonDetail cartonDetail(ReturnInventoryCarton carton) { private ReturnInventoryResponses.CartonDetail cartonDetail(ReturnInventoryCarton carton) {
List<ReturnInventoryResponses.CartonItem> items = cartonItems.listCarton(carton.getId()).stream() List<ReturnInventoryResponses.CartonItem> items = cartonItems.listCarton(carton.getId()).stream()
.map(item -> new ReturnInventoryResponses.CartonItem(item.getSkuCode(), item.getProductNameZh(), item.getQuantity())).toList(); .map(item -> new ReturnInventoryResponses.CartonItem(item.getSkuCode(), item.getProductNameZh(), item.getQuantity())).toList();
return new ReturnInventoryResponses.CartonDetail(carton.getCartonMark(), carton.getOverseasWarehouseId(), carton.getSourceBatchNo(), carton.getSourceReturnNo(), return new ReturnInventoryResponses.CartonDetail(carton.getCartonMark(), carton.getCustomerId(), carton.getOverseasWarehouseId(), carton.getSourceBatchNo(), carton.getSourceReturnNo(),
carton.getStatus(), carton.getSkuTypes() == null ? 0 : carton.getSkuTypes(), value(carton.getTotalQuantity()), carton.getLength(), carton.getWidth(), carton.getHeight(), carton.getWeight(), carton.getCreatedAt(), items); carton.getStatus(), carton.getSkuTypes() == null ? 0 : carton.getSkuTypes(), value(carton.getTotalQuantity()), carton.getLength(), carton.getWidth(), carton.getHeight(), carton.getWeight(), carton.getCreatedAt(), items);
} }
@@ -2050,8 +2050,12 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
PREPARE return_receipt_carton_mark_stmt FROM @ddl; EXECUTE return_receipt_carton_mark_stmt; DEALLOCATE PREPARE return_receipt_carton_mark_stmt; PREPARE return_receipt_carton_mark_stmt FROM @ddl; EXECUTE return_receipt_carton_mark_stmt; DEALLOCATE PREPARE return_receipt_carton_mark_stmt;
CREATE TABLE IF NOT EXISTS ll_return_inventory_carton ( CREATE TABLE IF NOT EXISTS ll_return_inventory_carton (
id BIGINT NOT NULL COMMENT '退货库存箱ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', source_receipt_id BIGINT NULL COMMENT '来源PDA签收单ID', source_batch_no VARCHAR(128) NULL COMMENT '来源签收批次号', carton_mark VARCHAR(128) NOT NULL COMMENT '退货箱号和箱唛编码', status VARCHAR(32) NOT NULL COMMENT 'AVAILABLE可用或MERGED已合箱', sku_types INT NOT NULL COMMENT '箱内SKU种类数', total_quantity BIGINT NOT NULL COMMENT '箱内总件数', length DECIMAL(12,3) NULL COMMENT '箱长CM', width DECIMAL(12,3) NULL COMMENT '箱宽CM', height DECIMAL(12,3) NULL COMMENT '箱高CM', weight DECIMAL(12,3) NULL COMMENT '箱重KG', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', merged_at DATETIME(3) NULL COMMENT '合箱完成时间', id BIGINT NOT NULL COMMENT '退货库存箱ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', source_receipt_id BIGINT NULL COMMENT '来源PDA签收单ID', source_batch_no VARCHAR(128) NULL COMMENT '来源签收批次号', carton_mark VARCHAR(128) NOT NULL COMMENT '退货箱号和箱唛编码', status VARCHAR(32) NOT NULL COMMENT 'AVAILABLE可用或MERGED已合箱', sku_types INT NOT NULL COMMENT '箱内SKU种类数', total_quantity BIGINT NOT NULL COMMENT '箱内总件数', length DECIMAL(12,3) NULL COMMENT '箱长CM', width DECIMAL(12,3) NULL COMMENT '箱宽CM', height DECIMAL(12,3) NULL COMMENT '箱高CM', weight DECIMAL(12,3) NULL COMMENT '箱重KG', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', merged_at DATETIME(3) NULL COMMENT '合箱完成时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_mark(carton_mark), UNIQUE KEY uk_ll_return_inventory_carton_receipt(source_receipt_id), KEY idx_ll_return_inventory_carton_customer(customer_id,overseas_warehouse_id,status,created_at), CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK(sku_types>0 AND total_quantity>0), CONSTRAINT chk_ll_return_inventory_carton_status CHECK(status IN('AVAILABLE','MERGED')) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_mark(carton_mark), UNIQUE KEY uk_ll_return_inventory_carton_receipt(source_receipt_id), KEY idx_ll_return_inventory_carton_customer(customer_id,overseas_warehouse_id,status,created_at), CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK((status='AVAILABLE' AND sku_types>0 AND total_quantity>0) OR (status='MERGED' AND sku_types=0 AND total_quantity=0)), CONSTRAINT chk_ll_return_inventory_carton_status CHECK(status IN('AVAILABLE','MERGED'))
) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛'; ) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛';
-- 已合箱的来源箱保留审计记录,但其箱内库存必须归零;动态 SQL 兼容 Spring 的数据库初始化器。
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND constraint_name='chk_ll_return_inventory_carton_quantity'), 'ALTER TABLE ll_return_inventory_carton DROP CHECK chk_ll_return_inventory_carton_quantity', 'SELECT 1'));
PREPARE return_inventory_carton_check_drop_stmt FROM @ddl; EXECUTE return_inventory_carton_check_drop_stmt; DEALLOCATE PREPARE return_inventory_carton_check_drop_stmt;
ALTER TABLE ll_return_inventory_carton ADD CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK((status='AVAILABLE' AND sku_types>0 AND total_quantity>0) OR (status='MERGED' AND sku_types=0 AND total_quantity=0));
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='source_return_no'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN source_return_no VARCHAR(2000) NULL COMMENT ''来源退货单号'' AFTER source_batch_no')); SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='source_return_no'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN source_return_no VARCHAR(2000) NULL COMMENT ''来源退货单号'' AFTER source_batch_no'));
PREPARE return_inventory_carton_return_no_stmt FROM @ddl; EXECUTE return_inventory_carton_return_no_stmt; DEALLOCATE PREPARE return_inventory_carton_return_no_stmt; PREPARE return_inventory_carton_return_no_stmt FROM @ddl; EXECUTE return_inventory_carton_return_no_stmt; DEALLOCATE PREPARE return_inventory_carton_return_no_stmt;
CREATE TABLE IF NOT EXISTS ll_return_inventory_carton_item ( CREATE TABLE IF NOT EXISTS ll_return_inventory_carton_item (
@@ -2059,9 +2063,14 @@ CREATE TABLE IF NOT EXISTS ll_return_inventory_carton_item (
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_item(carton_id,sku_code), KEY idx_ll_return_inventory_carton_item_customer(customer_id,sku_code), CONSTRAINT chk_ll_return_inventory_carton_item_quantity CHECK(quantity>0) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_item(carton_id,sku_code), KEY idx_ll_return_inventory_carton_item_customer(customer_id,sku_code), CONSTRAINT chk_ll_return_inventory_carton_item_quantity CHECK(quantity>0)
) ENGINE=InnoDB COMMENT='退货库存箱内SKU和数量'; ) ENGINE=InnoDB COMMENT='退货库存箱内SKU和数量';
CREATE TABLE IF NOT EXISTS ll_return_inventory_balance ( CREATE TABLE IF NOT EXISTS ll_return_inventory_balance (
id BIGINT NOT NULL COMMENT '退货库存余额ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', available_quantity BIGINT NOT NULL COMMENT '可用上架数量', carton_quantity BIGINT NOT NULL COMMENT '包含该SKU的可用箱数', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', id BIGINT NOT NULL COMMENT '退货库存余额ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', total_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '实物总库存,仅实际出库扣减', available_quantity BIGINT NOT NULL COMMENT '未合箱可用上架数量', carton_quantity BIGINT NOT NULL COMMENT '包含该SKU的可用箱数', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_balance(customer_id,overseas_warehouse_id,sku_code), CONSTRAINT chk_ll_return_inventory_balance_quantity CHECK(available_quantity>=0 AND carton_quantity>=0) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_balance(customer_id,overseas_warehouse_id,sku_code), CONSTRAINT chk_ll_return_inventory_balance_quantity CHECK(total_quantity>=available_quantity AND available_quantity>=0 AND carton_quantity>=0)
) ENGINE=InnoDB COMMENT='退货库存按客户海外仓SKU汇总余额'; ) ENGINE=InnoDB COMMENT='退货库存按客户海外仓SKU汇总余额';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_balance' AND column_name='total_quantity'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_balance ADD COLUMN total_quantity BIGINT NULL COMMENT ''实物总库存,仅实际出库扣减'' AFTER product_name_zh'));
PREPARE return_inventory_balance_total_stmt FROM @ddl; EXECUTE return_inventory_balance_total_stmt; DEALLOCATE PREPARE return_inventory_balance_total_stmt;
-- 旧数据的可用量原先包含已合箱库存;升级时一次性拆分为总库存和未合箱可用库存。
UPDATE ll_return_inventory_balance b LEFT JOIN (SELECT c.customer_id,c.overseas_warehouse_id,i.sku_code,SUM(i.quantity) merged_quantity FROM ll_return_inventory_carton c JOIN ll_return_inventory_carton_item i ON i.carton_id=c.id WHERE c.source_batch_no='MERGED' GROUP BY c.customer_id,c.overseas_warehouse_id,i.sku_code) m ON m.customer_id=b.customer_id AND m.overseas_warehouse_id=b.overseas_warehouse_id AND m.sku_code=b.sku_code SET b.total_quantity=b.available_quantity,b.available_quantity=GREATEST(0,b.available_quantity-COALESCE(m.merged_quantity,0)) WHERE b.total_quantity IS NULL;
ALTER TABLE ll_return_inventory_balance MODIFY COLUMN total_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '实物总库存,仅实际出库扣减';
CREATE TABLE IF NOT EXISTS ll_return_inventory_operation ( CREATE TABLE IF NOT EXISTS ll_return_inventory_operation (
id BIGINT NOT NULL COMMENT '退货库存PDA操作ID', operator_id VARCHAR(128) NOT NULL COMMENT 'PDA操作人ID', idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型,当前MERGE', result_carton_id BIGINT NULL COMMENT '合箱结果退货库存箱ID', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', id BIGINT NOT NULL COMMENT '退货库存PDA操作ID', operator_id VARCHAR(128) NOT NULL COMMENT 'PDA操作人ID', idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型,当前MERGE', result_carton_id BIGINT NULL COMMENT '合箱结果退货库存箱ID', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_operation_operator_key(operator_id,idempotency_key), KEY idx_ll_return_inventory_operation_result(result_carton_id) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_operation_operator_key(operator_id,idempotency_key), KEY idx_ll_return_inventory_operation_result(result_carton_id)
@@ -17,6 +17,7 @@ import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.baiyue.lastleg.wallet.WalletCommandOutboxService; import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
import com.baiyue.lastleg.inventory.entity.InventoryBalance; import com.baiyue.lastleg.inventory.entity.InventoryBalance;
import com.baiyue.lastleg.inventory.entity.InventoryPosition; import com.baiyue.lastleg.inventory.entity.InventoryPosition;
import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper; import com.baiyue.lastleg.inventory.mapper.InventoryBalanceMapper;
@@ -129,6 +130,25 @@ class RelabelApplicationServiceTest {
assertEquals("FBR-1", result.application().relabelNo()); assertEquals("FBR-1", result.application().relabelNo());
} }
@Test
void partialRelabelFreezesOnlyNotifiedPackages() {
RelabelRequests.Create request = request("key-partial", 10, 2);
RelabelApplication persisted = application(11L, "FBR-PARTIAL", "10001", "key-partial", null,
RelabelApplicationService.PENDING_PROCESSING);
persisted.setLastlegOrderId(21L);
when(applications.findNo(eq("10001"), any())).thenReturn(persisted);
when(items.selectList(any())).thenReturn(List.of(item(31L, 11L, "SKU-A", 10, 2)));
when(histories.selectList(any())).thenReturn(List.of());
when(orders.selectById(21L)).thenReturn(order(21L, "10001", "LMO-RELABEL-PARTIAL",
RelabelApplicationService.PENDING_PROCESSING));
service.create("10001", "operator-1", request);
ArgumentCaptor<InventoryCommandRequests.Move> move = ArgumentCaptor.forClass(InventoryCommandRequests.Move.class);
verify(inventoryCommands).hold(eq("10001"), eq("operator-1"), move.capture());
assertEquals(2L, move.getValue().packages());
}
@Test @Test
void createRejectsNotifiedBoxesAboveTotalBeforeAnyWrite() { void createRejectsNotifiedBoxesAboveTotalBeforeAnyWrite() {
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
@@ -315,6 +335,26 @@ class RelabelApplicationServiceTest {
assertEquals("COMPLETED", order.getRelabelStatus()); assertEquals("COMPLETED", order.getRelabelStatus());
} }
@Test
void completingPartialRelabelReleasesOnlyNotifiedPackages() {
RelabelApplication application = application(11L, "FBR-PARTIAL", "10001", "key-partial", "hash", "PROCESSING");
application.setLastlegOrderId(21L);
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-PARTIAL", "PROCESSING");
when(applications.lockNo("10001", "FBR-PARTIAL")).thenReturn(application);
stubDetail(application);
RelabelItem relabelItem = item(31L, application.getId(), "SKU-A", 10, 6);
relabelItem.setSourceInventoryPositionId(201L);
when(items.selectList(any())).thenReturn(List.of(relabelItem));
when(orders.selectById(21L)).thenReturn(order);
service.changeStatus("10001", "FBR-PARTIAL", "operator-2",
new RelabelRequests.ChangeStatus("COMPLETED", "finished"));
ArgumentCaptor<InventoryCommandRequests.Move> move = ArgumentCaptor.forClass(InventoryCommandRequests.Move.class);
verify(inventoryCommands).release(eq("10001"), eq("operator-2"), move.capture());
assertEquals(6L, move.getValue().packages());
}
private void stubDetail(RelabelApplication application) { private void stubDetail(RelabelApplication application) {
when(applications.findNo(application.getCustomerId(), application.getRelabelNo())).thenReturn(application); when(applications.findNo(application.getCustomerId(), application.getRelabelNo())).thenReturn(application);
when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 10, 6))); when(items.selectList(any())).thenReturn(List.of(item(31L, application.getId(), "SKU-A", 10, 6)));
+10 -3
View File
@@ -3977,8 +3977,11 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
PREPARE return_receipt_carton_mark_incremental_stmt FROM @ddl; EXECUTE return_receipt_carton_mark_incremental_stmt; DEALLOCATE PREPARE return_receipt_carton_mark_incremental_stmt; PREPARE return_receipt_carton_mark_incremental_stmt FROM @ddl; EXECUTE return_receipt_carton_mark_incremental_stmt; DEALLOCATE PREPARE return_receipt_carton_mark_incremental_stmt;
CREATE TABLE IF NOT EXISTS ll_return_inventory_carton ( CREATE TABLE IF NOT EXISTS ll_return_inventory_carton (
id BIGINT NOT NULL COMMENT '退货库存箱ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', source_receipt_id BIGINT NULL COMMENT '来源PDA签收单ID', source_batch_no VARCHAR(128) NULL COMMENT '来源签收批次号', carton_mark VARCHAR(128) NOT NULL COMMENT '退货箱号和箱唛编码', status VARCHAR(32) NOT NULL COMMENT 'AVAILABLE可用或MERGED已合箱', sku_types INT NOT NULL COMMENT '箱内SKU种类数', total_quantity BIGINT NOT NULL COMMENT '箱内总件数', length DECIMAL(12,3) NULL COMMENT '箱长CM', width DECIMAL(12,3) NULL COMMENT '箱宽CM', height DECIMAL(12,3) NULL COMMENT '箱高CM', weight DECIMAL(12,3) NULL COMMENT '箱重KG', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', merged_at DATETIME(3) NULL COMMENT '合箱完成时间', id BIGINT NOT NULL COMMENT '退货库存箱ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', source_receipt_id BIGINT NULL COMMENT '来源PDA签收单ID', source_batch_no VARCHAR(128) NULL COMMENT '来源签收批次号', carton_mark VARCHAR(128) NOT NULL COMMENT '退货箱号和箱唛编码', status VARCHAR(32) NOT NULL COMMENT 'AVAILABLE可用或MERGED已合箱', sku_types INT NOT NULL COMMENT '箱内SKU种类数', total_quantity BIGINT NOT NULL COMMENT '箱内总件数', length DECIMAL(12,3) NULL COMMENT '箱长CM', width DECIMAL(12,3) NULL COMMENT '箱宽CM', height DECIMAL(12,3) NULL COMMENT '箱高CM', weight DECIMAL(12,3) NULL COMMENT '箱重KG', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', merged_at DATETIME(3) NULL COMMENT '合箱完成时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_mark(carton_mark), UNIQUE KEY uk_ll_return_inventory_carton_receipt(source_receipt_id), KEY idx_ll_return_inventory_carton_customer(customer_id,overseas_warehouse_id,status,created_at), CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK(sku_types>0 AND total_quantity>0), CONSTRAINT chk_ll_return_inventory_carton_status CHECK(status IN('AVAILABLE','MERGED')) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_mark(carton_mark), UNIQUE KEY uk_ll_return_inventory_carton_receipt(source_receipt_id), KEY idx_ll_return_inventory_carton_customer(customer_id,overseas_warehouse_id,status,created_at), CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK((status='AVAILABLE' AND sku_types>0 AND total_quantity>0) OR (status='MERGED' AND sku_types=0 AND total_quantity=0)), CONSTRAINT chk_ll_return_inventory_carton_status CHECK(status IN('AVAILABLE','MERGED'))
) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛'; ) ENGINE=InnoDB COMMENT='退货实物库存箱及箱唛';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND constraint_name='chk_ll_return_inventory_carton_quantity'), 'ALTER TABLE ll_return_inventory_carton DROP CHECK chk_ll_return_inventory_carton_quantity', 'SELECT 1'));
PREPARE return_inventory_carton_check_drop_incremental_stmt FROM @ddl; EXECUTE return_inventory_carton_check_drop_incremental_stmt; DEALLOCATE PREPARE return_inventory_carton_check_drop_incremental_stmt;
ALTER TABLE ll_return_inventory_carton ADD CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK((status='AVAILABLE' AND sku_types>0 AND total_quantity>0) OR (status='MERGED' AND sku_types=0 AND total_quantity=0));
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='source_return_no'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN source_return_no VARCHAR(2000) NULL COMMENT ''来源退货单号'' AFTER source_batch_no')); SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='source_return_no'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN source_return_no VARCHAR(2000) NULL COMMENT ''来源退货单号'' AFTER source_batch_no'));
PREPARE return_inventory_carton_return_no_stmt FROM @ddl; EXECUTE return_inventory_carton_return_no_stmt; DEALLOCATE PREPARE return_inventory_carton_return_no_stmt; PREPARE return_inventory_carton_return_no_stmt FROM @ddl; EXECUTE return_inventory_carton_return_no_stmt; DEALLOCATE PREPARE return_inventory_carton_return_no_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='length'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN length DECIMAL(12,3) NULL COMMENT ''箱长CM'', ADD COLUMN width DECIMAL(12,3) NULL COMMENT ''箱宽CM'', ADD COLUMN height DECIMAL(12,3) NULL COMMENT ''箱高CM'', ADD COLUMN weight DECIMAL(12,3) NULL COMMENT ''箱重KG''')); SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='length'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN length DECIMAL(12,3) NULL COMMENT ''箱长CM'', ADD COLUMN width DECIMAL(12,3) NULL COMMENT ''箱宽CM'', ADD COLUMN height DECIMAL(12,3) NULL COMMENT ''箱高CM'', ADD COLUMN weight DECIMAL(12,3) NULL COMMENT ''箱重KG'''));
@@ -3988,9 +3991,13 @@ CREATE TABLE IF NOT EXISTS ll_return_inventory_carton_item (
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_item(carton_id,sku_code), KEY idx_ll_return_inventory_carton_item_customer(customer_id,sku_code), CONSTRAINT chk_ll_return_inventory_carton_item_quantity CHECK(quantity>0) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_item(carton_id,sku_code), KEY idx_ll_return_inventory_carton_item_customer(customer_id,sku_code), CONSTRAINT chk_ll_return_inventory_carton_item_quantity CHECK(quantity>0)
) ENGINE=InnoDB COMMENT='退货库存箱内SKU和数量'; ) ENGINE=InnoDB COMMENT='退货库存箱内SKU和数量';
CREATE TABLE IF NOT EXISTS ll_return_inventory_balance ( CREATE TABLE IF NOT EXISTS ll_return_inventory_balance (
id BIGINT NOT NULL COMMENT '退货库存余额ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', available_quantity BIGINT NOT NULL COMMENT '可用上架数量', carton_quantity BIGINT NOT NULL COMMENT '包含该SKU的可用箱数', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', id BIGINT NOT NULL COMMENT '退货库存余额ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', total_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '实物总库存,仅实际出库扣减', available_quantity BIGINT NOT NULL COMMENT '未合箱可用上架数量', carton_quantity BIGINT NOT NULL COMMENT '包含该SKU的可用箱数', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_balance(customer_id,overseas_warehouse_id,sku_code), CONSTRAINT chk_ll_return_inventory_balance_quantity CHECK(available_quantity>=0 AND carton_quantity>=0) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_balance(customer_id,overseas_warehouse_id,sku_code), CONSTRAINT chk_ll_return_inventory_balance_quantity CHECK(total_quantity>=available_quantity AND available_quantity>=0 AND carton_quantity>=0)
) ENGINE=InnoDB COMMENT='退货库存按客户海外仓SKU汇总余额'; ) ENGINE=InnoDB COMMENT='退货库存按客户海外仓SKU汇总余额';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_balance' AND column_name='total_quantity'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_balance ADD COLUMN total_quantity BIGINT NULL COMMENT ''实物总库存,仅实际出库扣减'' AFTER product_name_zh'));
PREPARE return_inventory_balance_total_incremental_stmt FROM @ddl; EXECUTE return_inventory_balance_total_incremental_stmt; DEALLOCATE PREPARE return_inventory_balance_total_incremental_stmt;
UPDATE ll_return_inventory_balance b LEFT JOIN (SELECT c.customer_id,c.overseas_warehouse_id,i.sku_code,SUM(i.quantity) merged_quantity FROM ll_return_inventory_carton c JOIN ll_return_inventory_carton_item i ON i.carton_id=c.id WHERE c.source_batch_no='MERGED' GROUP BY c.customer_id,c.overseas_warehouse_id,i.sku_code) m ON m.customer_id=b.customer_id AND m.overseas_warehouse_id=b.overseas_warehouse_id AND m.sku_code=b.sku_code SET b.total_quantity=b.available_quantity,b.available_quantity=GREATEST(0,b.available_quantity-COALESCE(m.merged_quantity,0)) WHERE b.total_quantity IS NULL;
ALTER TABLE ll_return_inventory_balance MODIFY COLUMN total_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '实物总库存,仅实际出库扣减';
CREATE TABLE IF NOT EXISTS ll_return_inventory_operation ( CREATE TABLE IF NOT EXISTS ll_return_inventory_operation (
id BIGINT NOT NULL COMMENT '退货库存PDA操作ID', operator_id VARCHAR(128) NOT NULL COMMENT 'PDA操作人ID', idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型,当前MERGE', result_carton_id BIGINT NULL COMMENT '合箱结果退货库存箱ID', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', id BIGINT NOT NULL COMMENT '退货库存PDA操作ID', operator_id VARCHAR(128) NOT NULL COMMENT 'PDA操作人ID', idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型,当前MERGE', result_carton_id BIGINT NULL COMMENT '合箱结果退货库存箱ID', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_operation_operator_key(operator_id,idempotency_key), KEY idx_ll_return_inventory_operation_result(result_carton_id) PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_operation_operator_key(operator_id,idempotency_key), KEY idx_ll_return_inventory_operation_result(result_carton_id)