Merge remote-tracking branch 'origin/test' into dev

This commit is contained in:
2026-09-09 18:31:14 +08:00
18 changed files with 143 additions and 28 deletions
@@ -18,7 +18,7 @@ public interface WarehouseMasterDataClient {
record Detail(Warehouse warehouse) {
}
record Warehouse(Long id, Long tenantId, String warehouseCode, String warehouseName,
record Warehouse(Long id, Long tenantId, String warehouseCode, String internalCode, String warehouseName,
String warehouseType, String status) {
}
}
@@ -133,13 +133,14 @@ public final class LoadingListDtos {
public record PalletLabel(
Long labelId, Long itemId, Long accountId, Long inboundId, String inboundCode,
String containerNo, String destinationWarehouseCode, String destinationWarehouseName,
String containerNo, String destinationWarehouseCode, String destinationWarehouseInternalCode,
String destinationWarehouseName,
int palletIndex, String palletCode
) {
}
public record PalletLabelGroup(
String destinationWarehouseCode, String destinationWarehouseName,
String destinationWarehouseCode, String destinationWarehouseInternalCode, String destinationWarehouseName,
int totalCount, List<PalletLabel> labels
) {
public PalletLabelGroup {
@@ -13,7 +13,7 @@ import java.util.List;
public interface LoadingPalletLabelMapper {
@Select("""
SELECT p.id AS plan_id,p.plan_no,p.plan_status,b.container_no,b.warehouse_id,
i.id AS item_id,i.account_id,i.inbound_id,i.inbound_code,
i.id AS item_id,i.account_id,i.inbound_id,i.inbound_code,i.destination_warehouse_id,
i.destination_warehouse_code,i.destination_warehouse_name,
COALESCE(NULLIF(i.pallet_count,0),
CEIL(COALESCE(NULLIF(o.pallet_count,0),
@@ -56,6 +56,7 @@ public interface LoadingPalletLabelMapper {
class SourceRow {
private Long planId; private String planNo; private String planStatus; private String containerNo;
private Long warehouseId; private Long itemId; private Long accountId; private Long inboundId;
private Long destinationWarehouseId;
private String inboundCode; private String destinationWarehouseCode; private String destinationWarehouseName;
private Integer palletCount; private String fulfillmentType; private Integer loadSequence;
private String destinationType;
@@ -577,7 +577,7 @@ public class LoadingListService {
}
/** 完成 worker 在远程结算前验证最终跨区快照能够完整生成。 */
@Transactional(readOnly = true)
@Transactional
public void requireCompletedEventReady(long planId) {
loadingCompletedOutboxService.requirePublishable(planId);
}
@@ -6,6 +6,7 @@ import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.event.ContainerNumbers;
import com.baiyue.firstleg.client.PrintPdfClient;
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
import com.baiyue.firstleg.dto.LoadingListDtos.PalletLabel;
import com.baiyue.firstleg.dto.LoadingListDtos.PalletLabelGroup;
import com.baiyue.firstleg.dto.LoadingListDtos.PalletLabelManifest;
@@ -34,12 +35,13 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/** 散货托盘码是柜号与 FBA 仓维度的稳定业务标识;一件代发继续使用装箱打托事实。 */
/** 散货托盘码按真实柜号、FBA 地址内部编号和仓内序号组成;一件代发继续使用装箱打托事实。 */
@Service
public class LoadingPalletLabelService {
private static final BigDecimal PAGE_MM = new BigDecimal("100");
@Resource private LoadingPalletLabelMapper mapper;
@Resource private PrintPdfClient printClient;
@Resource private WarehouseMasterDataClient warehouseClient;
@Resource private ObjectMapper objectMapper;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
@@ -58,11 +60,13 @@ public class LoadingPalletLabelService {
List<SourceRow> standard = sources.stream().filter(row -> !"ONE_CLICK_DROPSHIP".equals(row.getFulfillmentType()))
.filter(row -> "FBA".equals(row.getDestinationType()))
.filter(row -> Objects.requireNonNullElse(row.getPalletCount(), 0) > 0).toList();
if (standard.stream().anyMatch(row -> !StringUtils.hasText(row.getDestinationWarehouseCode()))) {
throw new BizException(ResultCode.CONFLICT, "散货明细缺少 FBA 仓编码,不能生成托盘码");
if (standard.stream().anyMatch(row -> row.getDestinationWarehouseId() == null
|| !StringUtils.hasText(row.getDestinationWarehouseCode()))) {
throw new BizException(ResultCode.CONFLICT, "散货明细缺少 FBA 仓信息,不能生成托盘码");
}
Map<Long, String> internalCodes = resolveInternalCodes(standard);
List<LabelRow> existing = mapper.labels(planId);
List<LabelRow> expected = expected(first, standard, containerNo);
List<LabelRow> expected = expected(first, standard, containerNo, internalCodes);
if (existing.isEmpty()) {
expected.forEach(row -> { if (mapper.insert(row) != 1) throw new BizException(ResultCode.CONFLICT, "托盘码生成失败,请刷新后重试"); });
existing = mapper.labels(planId);
@@ -91,19 +95,21 @@ public class LoadingPalletLabelService {
catch (FeignException | NoFallbackAvailableException exception) { throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "打印服务暂不可用,请稍后重试"); }
}
private List<LabelRow> expected(SourceRow plan, List<SourceRow> sources, String containerNo) {
Map<String, Integer> indexes = new LinkedHashMap<>();
private List<LabelRow> expected(SourceRow plan, List<SourceRow> sources, String containerNo,
Map<Long, String> internalCodes) {
Map<Long, Integer> indexes = new LinkedHashMap<>();
List<LabelRow> result = new ArrayList<>();
for (SourceRow source : sources) {
String code = source.getDestinationWarehouseCode().trim().toUpperCase(Locale.ROOT);
String warehouseCode = source.getDestinationWarehouseCode().trim().toUpperCase(Locale.ROOT);
String internalCode = internalCodes.get(source.getDestinationWarehouseId());
for (int local = 0; local < source.getPalletCount(); local++) {
int index = indexes.merge(code, 1, Integer::sum);
int index = indexes.merge(source.getDestinationWarehouseId(), 1, Integer::sum);
if (index > 999) throw new BizException(ResultCode.CONFLICT, "单柜同一 FBA 仓托盘数不能超过 999");
LabelRow row = new LabelRow(); row.setLabelId(IdWorker.getId()); row.setPlanId(plan.getPlanId());
row.setItemId(source.getItemId()); row.setAccountId(source.getAccountId()); row.setInboundId(source.getInboundId());
row.setInboundCode(source.getInboundCode()); row.setContainerNo(containerNo);
row.setDestinationWarehouseCode(code); row.setDestinationWarehouseName(source.getDestinationWarehouseName());
row.setPalletIndex(index); row.setPalletCode(containerNo + "-" + code + "-" + String.format("%03d", index));
row.setDestinationWarehouseCode(warehouseCode); row.setDestinationWarehouseName(source.getDestinationWarehouseName());
row.setPalletIndex(index); row.setPalletCode(containerNo + "-" + internalCode + "-" + String.format("%03d", index));
if (row.getPalletCode().length() > 128) throw new BizException(ResultCode.CONFLICT, "托盘码长度超过系统限制");
result.add(row);
}
@@ -111,6 +117,37 @@ public class LoadingPalletLabelService {
return result;
}
private Map<Long, String> resolveInternalCodes(List<SourceRow> sources) {
Map<Long, String> codes = new LinkedHashMap<>();
Map<String, Long> owners = new LinkedHashMap<>();
for (SourceRow source : sources) {
long warehouseId = source.getDestinationWarehouseId();
if (codes.containsKey(warehouseId)) continue;
WarehouseMasterDataClient.Warehouse warehouse;
try {
ApiResponse<WarehouseMasterDataClient.Detail> response = warehouseClient.active(internalToken, 0L, warehouseId);
warehouse = response == null || !response.successful() || response.data() == null
? null : response.data().warehouse();
} catch (FeignException | NoFallbackAvailableException exception) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "仓库主数据暂不可用,请稍后重试");
}
if (warehouse == null || !"FBA".equals(warehouse.warehouseType())) {
throw new BizException(ResultCode.CONFLICT, "目的 FBA 仓不存在或未启用");
}
String internalCode = normalize(warehouse.internalCode());
if (internalCode == null) {
throw new BizException(ResultCode.CONFLICT, "FBA 仓“" + warehouse.warehouseCode() + "”未配置内部编号");
}
internalCode = internalCode.toUpperCase(Locale.ROOT);
Long owner = owners.putIfAbsent(internalCode, warehouseId);
if (owner != null && owner != warehouseId) {
throw new BizException(ResultCode.CONFLICT, "同柜 FBA 仓内部编号重复,请先修正仓库主数据");
}
codes.put(warehouseId, internalCode);
}
return codes;
}
private void requireSame(List<LabelRow> expected, List<LabelRow> actual) {
List<String> left = expected.stream().map(this::identity).toList();
List<String> right = actual.stream().map(this::identity).toList();
@@ -122,12 +159,19 @@ public class LoadingPalletLabelService {
Map<String, List<LabelRow>> grouped = rows.stream().collect(java.util.stream.Collectors.groupingBy(
LabelRow::getDestinationWarehouseCode, LinkedHashMap::new, java.util.stream.Collectors.toList()));
List<PalletLabelGroup> groups = grouped.values().stream().map(values -> new PalletLabelGroup(
values.get(0).getDestinationWarehouseCode(), values.get(0).getDestinationWarehouseName(), values.size(),
values.get(0).getDestinationWarehouseCode(), internalCode(values.get(0)),
values.get(0).getDestinationWarehouseName(), values.size(),
values.stream().map(this::label).toList())).toList();
String containerNo = rows.isEmpty() ? plan.getContainerNo() : rows.get(0).getContainerNo();
return new PalletLabelManifest(plan.getPlanId(), plan.getPlanNo(), containerNo, rows.size(), groups);
}
private PalletLabel label(LabelRow row) { return new PalletLabel(row.getLabelId(), row.getItemId(), row.getAccountId(), row.getInboundId(), row.getInboundCode(), row.getContainerNo(), row.getDestinationWarehouseCode(), row.getDestinationWarehouseName(), row.getPalletIndex(), row.getPalletCode()); }
private PalletLabel label(LabelRow row) { return new PalletLabel(row.getLabelId(), row.getItemId(), row.getAccountId(), row.getInboundId(), row.getInboundCode(), row.getContainerNo(), row.getDestinationWarehouseCode(), internalCode(row), row.getDestinationWarehouseName(), row.getPalletIndex(), row.getPalletCode()); }
private String internalCode(LabelRow row) {
String prefix = row.getContainerNo() + "-";
String suffix = "-" + String.format("%03d", row.getPalletIndex());
String palletCode = row.getPalletCode();
return palletCode.substring(prefix.length(), palletCode.length() - suffix.length());
}
private static String normalize(String value) { return value == null || value.isBlank() ? null : value.trim(); }
private ResponseEntity<byte[]> requirePdf(ResponseEntity<byte[]> response) {