This commit is contained in:
2026-08-06 14:55:24 +08:00
parent dca3a48284
commit f3cfbdf59b
9 changed files with 351 additions and 20 deletions
@@ -22,15 +22,18 @@ public final class PalletRequests {
@NotNull @Min(1)
@Schema(description = "所属仓库ID", example = "190000000000000002", requiredMode = Schema.RequiredMode.REQUIRED)
Long warehouseId,
@NotBlank @Size(max = 50)
@Schema(description = "仓库内唯一托盘编码", example = "TP-001", requiredMode = Schema.RequiredMode.REQUIRED)
String palletCode,
@NotBlank @Size(max = 32)
@Schema(description = "托盘类型", example = "木托盘", requiredMode = Schema.RequiredMode.REQUIRED)
String palletType,
@NotNull @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 6)
@Schema(description = "托盘容积,单位m³", example = "1.200000", requiredMode = Schema.RequiredMode.REQUIRED)
BigDecimal capacityCbm,
@NotNull @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 3)
@Schema(description = "托盘重量,单位kg", example = "8.000", requiredMode = Schema.RequiredMode.REQUIRED)
BigDecimal weightKg,
@NotBlank @Size(max = 32)
@Schema(description = "托盘颜色", example = "黄色", requiredMode = Schema.RequiredMode.REQUIRED)
String color,
@Size(max = 500)
@Schema(description = "备注", example = "标准川字托盘")
String remark
@@ -64,6 +67,8 @@ public final class PalletRequests {
String palletCode,
String palletType,
BigDecimal capacityCbm,
BigDecimal weightKg,
String color,
String remark,
LocalDateTime createdAt,
LocalDateTime updatedAt
@@ -11,7 +11,7 @@ import lombok.Setter;
import java.math.BigDecimal;
/**
* 托盘主数据实体。状态同时承载可用、预占、占用和停用四种资源状态。
* 托盘主数据实体。状态仅供内部预占、占用流程使用,不属于平台维护字段。
*/
@Getter
@Setter
@@ -37,6 +37,12 @@ public class PalletEntity extends BaseEntity {
@Schema(description = "托盘容积,单位m³", example = "1.200000")
private BigDecimal capacityCbm;
@Schema(description = "托盘重量,单位kg", example = "8.000")
private BigDecimal weightKg;
@Schema(description = "托盘颜色", example = "黄色")
private String color;
@Schema(description = "资源状态", allowableValues = {"AVAILABLE", "RESERVED", "OCCUPIED", "DISABLED"})
private String status;
@@ -13,16 +13,21 @@ import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** 托盘持久化仓储;平台分页支持跨租户筛选,资源操作始终包含租户和仓库边界。 */
@Repository
public class PalletRepository {
private static final Pattern PALLET_CODE = Pattern.compile("TP-(\\d+)");
@Resource
private PalletMapper mapper;
public PageResult<PalletEntity> page(Long tenantId, PalletRequests.PageQuery query) {
LambdaQueryWrapper<PalletEntity> wrapper = new LambdaQueryWrapper<PalletEntity>()
.eq(tenantId != null, PalletEntity::getTenantId, tenantId)
.eq(PalletEntity::getDeleted, false)
.eq(query.warehouseId() != null, PalletEntity::getWarehouseId, query.warehouseId())
.like(StringUtils.hasText(query.palletCode()), PalletEntity::getPalletCode, query.palletCode())
.eq(StringUtils.hasText(query.palletType()), PalletEntity::getPalletType, query.palletType())
@@ -36,6 +41,7 @@ public class PalletRepository {
return mapper.selectOne(new LambdaQueryWrapper<PalletEntity>()
.eq(PalletEntity::getTenantId, tenantId)
.eq(PalletEntity::getWarehouseId, warehouseId)
.eq(PalletEntity::getDeleted, false)
.eq(PalletEntity::getId, id));
}
@@ -44,6 +50,7 @@ public class PalletRepository {
.eq(PalletEntity::getTenantId, tenantId)
.eq(PalletEntity::getWarehouseId, warehouseId)
.eq(PalletEntity::getPalletCode, code)
.eq(PalletEntity::getDeleted, false)
.ne(excludedId != null, PalletEntity::getId, excludedId)) > 0;
}
@@ -52,6 +59,7 @@ public class PalletRepository {
return mapper.selectList(new LambdaQueryWrapper<PalletEntity>()
.eq(PalletEntity::getTenantId, tenantId)
.eq(PalletEntity::getWarehouseId, warehouseId)
.eq(PalletEntity::getDeleted, false)
.eq(PalletEntity::getStatus, "AVAILABLE")
.eq(StringUtils.hasText(palletType), PalletEntity::getPalletType, palletType)
.eq(capacityCbm != null, PalletEntity::getCapacityCbm, capacityCbm)
@@ -59,6 +67,23 @@ public class PalletRepository {
.last("LIMIT " + limit));
}
public String nextCode(long tenantId, long warehouseId) {
int maxSequence = mapper.selectList(new LambdaQueryWrapper<PalletEntity>()
.select(PalletEntity::getPalletCode)
.eq(PalletEntity::getTenantId, tenantId)
.eq(PalletEntity::getWarehouseId, warehouseId)
.likeRight(PalletEntity::getPalletCode, "TP-")
.eq(PalletEntity::getDeleted, false))
.stream()
.map(PalletEntity::getPalletCode)
.map(PALLET_CODE::matcher)
.filter(Matcher::matches)
.mapToInt(matcher -> Integer.parseInt(matcher.group(1)))
.max()
.orElse(0);
return "TP-%03d".formatted(maxSequence + 1);
}
public void insert(PalletEntity entity) {
mapper.insert(entity);
}
@@ -35,10 +35,10 @@ public class PalletApplicationService {
@BusinessAudit(event = "PALLET_CREATE", resourceType = "PALLET", description = "新增托盘", captureBefore = false)
public Long create(long tenantId, PalletRequests.SaveRequest request) {
requireWarehouse(tenantId, request.warehouseId());
ensureCodeUnique(tenantId, request.warehouseId(), request.palletCode(), null);
PalletEntity entity = new PalletEntity();
entity.setTenantId(tenantId);
entity.setWarehouseId(request.warehouseId());
entity.setPalletCode(repository.nextCode(tenantId, request.warehouseId()));
copy(entity, request);
entity.setStatus("AVAILABLE");
try {
@@ -56,7 +56,6 @@ public class PalletApplicationService {
throw new BizException(ResultCode.FORBIDDEN, "请求仓库与托盘所属仓库不一致");
}
PalletEntity entity = owned(tenantId, warehouseId, id);
ensureCodeUnique(tenantId, warehouseId, request.palletCode(), id);
copy(entity, request);
try {
if (repository.save(entity) != 1) {
@@ -90,22 +89,18 @@ public class PalletApplicationService {
}
}
private void ensureCodeUnique(long tenantId, long warehouseId, String code, Long excludedId) {
if (repository.existsCode(tenantId, warehouseId, code, excludedId)) {
throw new BizException(ResultCode.CONFLICT, "托盘编码已存在");
}
}
private static void copy(PalletEntity entity, PalletRequests.SaveRequest request) {
entity.setPalletCode(request.palletCode());
entity.setPalletType(request.palletType());
entity.setCapacityCbm(request.capacityCbm());
entity.setWeightKg(request.weightKg());
entity.setColor(request.color());
entity.setRemark(request.remark());
}
private static PalletRequests.View view(PalletEntity entity) {
return new PalletRequests.View(entity.getId(), entity.getTenantId(), entity.getWarehouseId(),
entity.getPalletCode(), entity.getPalletType(), entity.getCapacityCbm(), entity.getRemark(),
entity.getPalletCode(), entity.getPalletType(), entity.getCapacityCbm(), entity.getWeightKg(),
entity.getColor(), entity.getRemark(),
entity.getCreatedAt(), entity.getUpdatedAt());
}
}
@@ -3,6 +3,18 @@
SET NAMES utf8mb4;
USE baiyue_warehouse;
-- 托盘展示字段增量:重量、颜色;平台接口不暴露内部状态。
SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'weight_kg') = 0,
'ALTER TABLE wh_pallet ADD COLUMN weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''托盘重量kg'' AFTER capacity_cbm',
'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'color') = 0,
'ALTER TABLE wh_pallet ADD COLUMN color VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''托盘颜色'' AFTER weight_kg',
'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
-- 活动预占必须同时保证“一托一位”;A0 共享只放宽库位,不放宽托盘唯一性。
SET @schema_name = DATABASE();
DROP PROCEDURE IF EXISTS add_pallet_reservation_column_if_missing;
@@ -15,6 +15,8 @@ CREATE TABLE IF NOT EXISTS wh_pallet (
pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型',
pallet_name VARCHAR(100) NULL COMMENT '托盘名称',
capacity_cbm DECIMAL(18,6) NOT NULL COMMENT '托盘可用容积CBM',
weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '托盘重量kg',
color VARCHAR(32) NOT NULL DEFAULT '' COMMENT '托盘颜色',
status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT '内部状态:AVAILABLE、RESERVED或OCCUPIED',
remark VARCHAR(500) NULL COMMENT '备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
@@ -11,6 +11,13 @@ INSERT INTO wh_service_capability(id,tenant_id,warehouse_id,capability_code,capa
VALUES(190000000000000021,0,190000000000000001,'RECEIVING','头程收货',1,'16:00:00','工作日16点前预约可当日收货')
ON DUPLICATE KEY UPDATE tenant_id=0,enabled=1,cutoff_time=VALUES(cutoff_time);
-- 二期托盘初始化数据。编码由平台维护接口自动生成,种子固定为 TP-001、TP-002。
INSERT INTO wh_pallet(id,tenant_id,warehouse_id,pallet_code,pallet_type,capacity_cbm,weight_kg,color,status,remark,created_by,updated_by)
VALUES
(190000000000000101,0,190000000000000001,'TP-001','木托盘',1.200000,8.000,'黄色','AVAILABLE','无备注',0,0),
(190000000000000102,0,190000000000000001,'TP-002','胶托盘',2.400000,12.000,'黑色','AVAILABLE','无备注',0,0)
ON DUPLICATE KEY UPDATE tenant_id=0,warehouse_id=VALUES(warehouse_id),pallet_type=VALUES(pallet_type),capacity_cbm=VALUES(capacity_cbm),weight_kg=VALUES(weight_kg),color=VALUES(color),remark=VALUES(remark);
-- 一期联调快递公司主数据,供平台维护和订单履约回显验证。
INSERT INTO wh_express_company(id,tenant_id,company_code,company_name,service_phone,website,tracking_url_template,status,remark)
VALUES(192000000000000001,0,'SF','顺丰速运','95338','https://www.sf-express.com','https://www.sf-express.com/chn/sc/dynamic_function/waybill/#search/bill-number/{trackingNo}','ACTIVE','平台公共快递公司样本')