feat(firstleg): align simulated containers with channel groups
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
package com.baiyue.firstleg.domain;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Stable planning groups used by simulated containers and smart-loading filters. */
|
||||
public final class SmartLoadingChannelGroupCatalog {
|
||||
private SmartLoadingChannelGroupCatalog() {}
|
||||
|
||||
public static final List<Definition> GROUPS = List.of(
|
||||
group("MATSON_CLX", "美森正班", "MATSON_CLX_TRUCK", "MATSON_CLX_PARCEL", "ZBYHK", "ZBYHWK", "ZBYHP"),
|
||||
group("MATSON_MAX", "美森加班", "MATSON_MAX_TRUCK", "MATSON_MAX_PARCEL", "BYHK", "BYHWK", "BYHP"),
|
||||
group("EXX", "EXX", "EXX_TRUCK", "EXX_RAIL_NY", "EXXHK", "EXXHWK", "NYEXX"),
|
||||
group("HEDE", "合德", "HEDE_TRUCK", "HEDE_PARCEL", "HDHK", "HDHWK", "HDHP"),
|
||||
group("OA_LA", "洛杉矶普船", "OA_LA_TRUCK", "OA_LA_FULL_TRUCK", "OA_PARCEL", "PCHK", "PCHWK", "PCKK", "PCHP"),
|
||||
group("OA_CHICAGO", "芝加哥普船", "OA_CHICAGO_RAIL", "CPHT"),
|
||||
group("OA_HOUSTON", "休斯顿普船", "OA_HOUSTON_TRUCK", "HPHK"),
|
||||
group("OA_SAVANNAH", "萨凡纳普船", "OA_SAVANNAH_TRUCK", "SPHK"),
|
||||
group("OA_NEW_YORK_TRUCK", "纽约普船海卡", "OA_NEW_YORK_TRUCK", "NPHK", "NPHWK")
|
||||
);
|
||||
|
||||
private static final Map<String, Definition> BY_CODE = indexByCode();
|
||||
private static final Map<String, Definition> BY_CHANNEL = indexByChannel();
|
||||
|
||||
public static Definition require(String value) {
|
||||
Definition result = BY_CODE.get(normalize(value));
|
||||
if (result == null) throw new IllegalArgumentException("不支持的渠道分组: " + value);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Definition resolveChannel(String channelCode) {
|
||||
return BY_CHANNEL.get(normalize(channelCode));
|
||||
}
|
||||
|
||||
public static boolean contains(String groupCode, String channelCode) {
|
||||
Definition group = BY_CODE.get(normalize(groupCode));
|
||||
return group != null && group.channelCodes().contains(normalize(channelCode));
|
||||
}
|
||||
|
||||
private static Definition group(String code, String name, String... channelCodes) {
|
||||
return new Definition(code, name, Set.of(channelCodes));
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
return value == null ? "" : value.trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static Map<String, Definition> indexByCode() {
|
||||
Map<String, Definition> result = new LinkedHashMap<>();
|
||||
GROUPS.forEach(group -> result.put(group.code(), group));
|
||||
return Map.copyOf(result);
|
||||
}
|
||||
|
||||
private static Map<String, Definition> indexByChannel() {
|
||||
Map<String, Definition> result = new LinkedHashMap<>();
|
||||
GROUPS.forEach(group -> group.channelCodes().forEach(channel -> result.put(channel, group)));
|
||||
return Map.copyOf(result);
|
||||
}
|
||||
|
||||
public record Definition(String code, String name, Set<String> channelCodes) {}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public final class ContainerDtos {
|
||||
) {}
|
||||
|
||||
@Schema(name = "ContainerSimulationCreateRequest", description = "新增内部模拟柜;内部柜号由服务端生成")
|
||||
public record SimulationCreateRequest(@NotBlank @Size(max = 64) String channelCode,
|
||||
public record SimulationCreateRequest(@NotBlank @Size(max = 64) String channelGroupCode,
|
||||
@NotNull @Positive Long warehouseId, @NotBlank @Size(max = 32) String containerType,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg) {}
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){}
|
||||
}
|
||||
}
|
||||
public record WorkbenchContainer(Long bookingId, String bookingNo, String cabinetNo, String containerNo,
|
||||
Long warehouseId, String warehouseName, String channelCode, String channelName, String containerType,
|
||||
Long warehouseId, String warehouseName, String channelGroupCode, String channelCode, String channelName, String containerType,
|
||||
BigDecimal capacityCbm, BigDecimal maxWeightKg,
|
||||
Long planId, String planNo, String planStatus, Integer version) {}
|
||||
public record Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode,
|
||||
|
||||
@@ -24,6 +24,7 @@ public class ContainerBookingEntity {
|
||||
private String sourceType;
|
||||
private String recordStatus;
|
||||
private Long mergedIntoContainerBookingId;
|
||||
private String channelGroupCode;
|
||||
private String channelCode;
|
||||
private String channelName;
|
||||
private Long oceanBookingId;
|
||||
|
||||
@@ -321,7 +321,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT b.id bookingId,b.booking_no bookingNo,b.cabinet_no cabinetNo,b.container_no containerNo,
|
||||
b.warehouse_id warehouseId,b.warehouse_name warehouseName,b.channel_code channelCode,
|
||||
b.warehouse_id warehouseId,b.warehouse_name warehouseName,b.channel_group_code channelGroupCode,b.channel_code channelCode,
|
||||
b.channel_name channelName,b.container_type containerType,b.capacity_cbm capacityCbm,
|
||||
b.max_weight_kg maxWeightKg,p.id planId,p.plan_no planNo,
|
||||
CASE WHEN p.id IS NULL OR NOT EXISTS (SELECT 1 FROM fl_loading_plan_item state_item
|
||||
@@ -331,8 +331,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
LEFT JOIN fl_loading_plan p ON p.container_booking_id=b.id
|
||||
WHERE b.record_status='ACTIVE'
|
||||
AND (p.id IS NULL OR p.plan_status IN ('EDITING','LOADING'))
|
||||
AND b.channel_code IN
|
||||
<foreach collection="actualChannels" item="channel" open="(" separator="," close=")">#{channel}</foreach>
|
||||
AND b.channel_group_code IN
|
||||
<foreach collection="channelGroups" item="channelGroup" open="(" separator="," close=")">#{channelGroup}</foreach>
|
||||
<if test="query.warehouseId != null">AND b.warehouse_id=#{query.warehouseId}</if>
|
||||
<if test="query.customerId != null or query.inboundCode != null or query.createdStart != null or query.createdEnd != null">
|
||||
AND EXISTS (
|
||||
@@ -351,7 +351,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ba
|
||||
""")
|
||||
Page<WorkbenchContainer> workbenchContainers(Page<WorkbenchContainer> page,
|
||||
@Param("query") WorkbenchContainerQuery query,
|
||||
@Param("actualChannels") Set<String> actualChannels);
|
||||
@Param("channelGroups") Set<String> channelGroups);
|
||||
|
||||
@Select("""
|
||||
SELECT o.tenant_id accountId,o.id inboundId,i.inbound_detail_id inboundDetailId,
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class OceanBookingDtos {
|
||||
}
|
||||
|
||||
public record LinkCandidateQuery(@NotNull @Positive Long warehouseId,
|
||||
@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotBlank @Size(max = 64) String channelGroupCode,
|
||||
@NotBlank @Size(max = 32) String containerType,
|
||||
@Min(1) Long page,
|
||||
@Min(1) @Max(200) Long size) {
|
||||
|
||||
+6
-2
@@ -54,6 +54,7 @@ public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
||||
Page<OceanBookingEntity> selectPage(Page<OceanBookingEntity> page, @Param("query") Query query);
|
||||
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT booking.id ocean_booking_id,booking.booking_no,requirement.id requirement_id,
|
||||
requirement.container_type,requirement.capacity_cbm,requirement.max_weight_kg,
|
||||
requirement.quantity required_quantity,
|
||||
@@ -75,13 +76,16 @@ public interface OceanBookingMapper extends BaseMapper<OceanBookingEntity> {
|
||||
AND allocated.requirement_id=requirement.id
|
||||
WHERE booking.status='RELEASED'
|
||||
AND booking.warehouse_id=#{query.warehouseId}
|
||||
AND booking.channel_code=#{query.channelCode}
|
||||
AND booking.channel_code IN
|
||||
<foreach collection="channelCodes" item="channelCode" open="(" separator="," close=")">#{channelCode}</foreach>
|
||||
AND requirement.container_type=#{query.containerType}
|
||||
AND requirement.quantity>COALESCE(allocated.allocated_quantity,0)
|
||||
ORDER BY booking.updated_at DESC,booking.id DESC,requirement.sort_no ASC,requirement.id ASC
|
||||
</script>
|
||||
""")
|
||||
Page<LinkCandidate> selectLinkCandidates(Page<LinkCandidate> page,
|
||||
@Param("query") LinkCandidateQuery query);
|
||||
@Param("query") LinkCandidateQuery query,
|
||||
@Param("channelCodes") java.util.Set<String> channelCodes);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_ocean_booking SET status=#{toStatus},
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@ public class OceanBookingRepository {
|
||||
public PageResult<OceanBookingEntity> page(Query query) {
|
||||
return PageResult.from(bookings.selectPage(new Page<>(query.page(), query.size()), query));
|
||||
}
|
||||
public PageResult<LinkCandidate> linkCandidates(LinkCandidateQuery query) {
|
||||
return PageResult.from(bookings.selectLinkCandidates(new Page<>(query.page(), query.size()), query));
|
||||
public PageResult<LinkCandidate> linkCandidates(LinkCandidateQuery query, java.util.Set<String> channelCodes) {
|
||||
return PageResult.from(bookings.selectLinkCandidates(new Page<>(query.page(), query.size()), query, channelCodes));
|
||||
}
|
||||
public OceanBookingEntity find(long id) { return bookings.selectById(id); }
|
||||
public OceanBookingEntity findForUpdate(long id) { return bookings.selectForUpdate(id); }
|
||||
|
||||
+5
-1
@@ -10,6 +10,7 @@ import com.baiyue.firstleg.client.WarehousePartnerClient;
|
||||
import com.baiyue.firstleg.client.WarehouseSupplierClient;
|
||||
import com.baiyue.firstleg.oceanbooking.domain.OceanBookingStatus;
|
||||
import com.baiyue.firstleg.domain.ContainerSpecificationCatalog;
|
||||
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.*;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.*;
|
||||
import com.baiyue.firstleg.oceanbooking.repository.OceanBookingRepository;
|
||||
@@ -71,8 +72,11 @@ public class OceanBookingService {
|
||||
}
|
||||
|
||||
public PageResult<LinkCandidate> linkCandidates(LinkCandidateQuery query) {
|
||||
SmartLoadingChannelGroupCatalog.Definition group;
|
||||
try { group = SmartLoadingChannelGroupCatalog.require(query.channelGroupCode()); }
|
||||
catch (IllegalArgumentException exception) { throw badRequest(exception.getMessage()); }
|
||||
return repository.linkCandidates(new LinkCandidateQuery(query.warehouseId(),
|
||||
query.channelCode().trim(), query.containerType().trim(), query.page(), query.size()));
|
||||
group.code(), query.containerType().trim(), query.page(), query.size()), group.channelCodes());
|
||||
}
|
||||
|
||||
public List<ChannelOption> channels(long originWarehouseId) {
|
||||
|
||||
@@ -49,9 +49,9 @@ public class SmartLoadingRepository {
|
||||
return units.isEmpty() ? List.of() : candidates.globalCandidatesByUnits(units);
|
||||
}
|
||||
public PageResult<WorkbenchContainer> workbenchContainers(WorkbenchContainerQuery query,
|
||||
Set<String> actualChannels) {
|
||||
Set<String> channelGroups) {
|
||||
return PageResult.from(candidates.workbenchContainers(
|
||||
new Page<>(query.page(), query.size()), query, actualChannels));
|
||||
new Page<>(query.page(), query.size()), query, channelGroups));
|
||||
}
|
||||
public List<ChannelContainerCountRow> channelContainerCounts(long warehouseId) {
|
||||
return candidates.channelContainerCounts(warehouseId);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
|
||||
import com.baiyue.firstleg.domain.SmartLoadingStatus;
|
||||
import com.baiyue.firstleg.domain.ContainerSpecificationCatalog;
|
||||
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.FullUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SimulationCreateRequest;
|
||||
@@ -55,13 +56,16 @@ public class ContainerService {
|
||||
WarehouseMasterDataClient.Warehouse warehouse = requireDomesticTransitWarehouse(request.warehouseId());
|
||||
Long versionId = priceBooks.activeVersionId();
|
||||
if (versionId == null) throw conflict("当前没有 ACTIVE 价格版本");
|
||||
ChannelRow channel = priceBooks.channels(versionId, warehouse.id()).stream()
|
||||
.filter(row -> request.channelCode().trim().equals(row.getChannelCode())).findFirst()
|
||||
.orElseThrow(() -> conflict("所选物流渠道已失效,请重新选择"));
|
||||
SmartLoadingChannelGroupCatalog.Definition channelGroup;
|
||||
try { channelGroup = SmartLoadingChannelGroupCatalog.require(request.channelGroupCode()); }
|
||||
catch (IllegalArgumentException exception) { throw conflict(exception.getMessage()); }
|
||||
boolean activeGroup = priceBooks.channels(versionId, warehouse.id()).stream()
|
||||
.map(ChannelRow::getChannelCode).anyMatch(channelGroup.channelCodes()::contains);
|
||||
if (!activeGroup) throw conflict("所选物流渠道分组在当前仓库已失效,请重新选择");
|
||||
ContainerBookingEntity value = new ContainerBookingEntity(); value.setId(IdWorker.getId());
|
||||
value.setBookingNo(documentNumbers.next("CB")); value.setCabinetNo(documentNumbers.next("CAB"));
|
||||
value.setSourceType("SIMULATION"); value.setRecordStatus("ACTIVE");
|
||||
value.setChannelCode(channel.getChannelCode()); value.setChannelName(channel.getChannelName());
|
||||
value.setChannelGroupCode(channelGroup.code()); value.setChannelCode(null); value.setChannelName(null);
|
||||
value.setWarehouseId(warehouse.id()); value.setWarehouseName(warehouse.warehouseName());
|
||||
value.setContainerType(specification.containerType()); value.setCapacityCbm(specification.capacityCbm());
|
||||
value.setMaxWeightKg(specification.maxWeightKg()); value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
@@ -85,9 +89,9 @@ public class ContainerService {
|
||||
String containerNo = requestedContainerNo == null ? null : Iso6346ContainerNumber.require(requestedContainerNo);
|
||||
if (containerNo != null && repository.containerNoExists(containerNo, id)) throw conflict("集装箱号已存在");
|
||||
if (!java.util.Objects.equals(container.getWarehouseId(), booking.getWarehouseId())
|
||||
|| !java.util.Objects.equals(container.getChannelCode(), booking.getChannelCode())
|
||||
|| !SmartLoadingChannelGroupCatalog.contains(container.getChannelGroupCode(), booking.getChannelCode())
|
||||
|| !java.util.Objects.equals(container.getContainerType(), requirement.getContainerType())) {
|
||||
throw conflict("模拟柜与真实订舱柜仓库、渠道或柜型不一致");
|
||||
throw conflict("模拟柜与真实订舱柜仓库、渠道分组或柜型不一致");
|
||||
}
|
||||
try {
|
||||
if (repository.linkReleasedBooking(id, request.oceanBookingId(), request.requirementId(), containerNo,
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.firstleg.dto.SmartLoadingDtos.*;
|
||||
import com.baiyue.firstleg.domain.SmartLoadingChannelGroupCatalog;
|
||||
import com.baiyue.firstleg.client.OrderCargoClient;
|
||||
import com.baiyue.firstleg.client.PendingReviewOrderClient;
|
||||
import com.baiyue.firstleg.entity.*;
|
||||
@@ -42,17 +43,6 @@ import java.util.*;
|
||||
@Service
|
||||
public class SmartLoadingService {
|
||||
private static final BigDecimal CAPACITY_LIMIT_RATIO = new BigDecimal("1.50");
|
||||
private static final Map<String, Set<String>> WORKBENCH_CHANNEL_GROUPS = Map.of(
|
||||
"MATSON_CLX", Set.of("MATSON_CLX_TRUCK", "MATSON_CLX_PARCEL"),
|
||||
"MATSON_MAX", Set.of("MATSON_MAX_TRUCK", "MATSON_MAX_PARCEL"),
|
||||
"EXX", Set.of("EXX_TRUCK", "EXX_RAIL_NY"),
|
||||
"HEDE", Set.of("HEDE_TRUCK", "HEDE_PARCEL"),
|
||||
"OA_LA", Set.of("OA_LA_TRUCK", "OA_LA_FULL_TRUCK", "OA_PARCEL"),
|
||||
"OA_CHICAGO", Set.of("OA_CHICAGO_RAIL"),
|
||||
"OA_HOUSTON", Set.of("OA_HOUSTON_TRUCK"),
|
||||
"OA_SAVANNAH", Set.of("OA_SAVANNAH_TRUCK"),
|
||||
"OA_NEW_YORK_TRUCK", Set.of("OA_NEW_YORK_TRUCK")
|
||||
);
|
||||
@Resource private SmartLoadingRepository repository;
|
||||
@Resource private com.baiyue.firstleg.mapper.DropshipPalletizationMapper dropshipPallets;
|
||||
@Resource private OrderCargoClient orderCargoClient;
|
||||
@@ -102,7 +92,7 @@ public class SmartLoadingService {
|
||||
public List<Candidate> candidates(long bookingId, long warehouseId) {
|
||||
ContainerBookingEntity booking = requireBooking(bookingId);
|
||||
if (!Objects.equals(booking.getWarehouseId(), warehouseId)) throw validation("柜次仓库与候选仓库不一致");
|
||||
if (!StringUtils.hasText(booking.getChannelCode())) throw validation("柜次缺少渠道快照,请先完成存量数据回填");
|
||||
if (!StringUtils.hasText(booking.getChannelGroupCode())) throw validation("柜次缺少计划渠道,请重新创建模拟柜");
|
||||
List<Candidate> values = candidates(warehouseId).stream()
|
||||
.filter(candidate -> sameChannel(booking, candidate) && sameCustomerScope(booking, candidate)).toList();
|
||||
return enrichColoadCandidates(booking, repository.planByBooking(bookingId), values);
|
||||
@@ -266,7 +256,7 @@ public class SmartLoadingService {
|
||||
&& query.createdStart().isAfter(query.createdEnd())) {
|
||||
throw validation("创建开始日期不能晚于结束日期");
|
||||
}
|
||||
return repository.workbenchContainers(query, actualChannels(query.channelGroupCodes()));
|
||||
return repository.workbenchContainers(query, normalizedGroups(query.channelGroupCodes()));
|
||||
}
|
||||
|
||||
private List<InboundCandidate> inboundCandidates(List<Candidate> details) {
|
||||
@@ -384,13 +374,17 @@ public class SmartLoadingService {
|
||||
private static Set<String> actualChannels(List<String> groups) {
|
||||
Set<String> actual = new LinkedHashSet<>();
|
||||
for (String value : groups) {
|
||||
String normalized = value == null ? "" : value.trim().toUpperCase(Locale.ROOT);
|
||||
Set<String> mapped = WORKBENCH_CHANNEL_GROUPS.get(normalized);
|
||||
if (mapped == null) throw validation("不支持的渠道分组: " + value);
|
||||
actual.addAll(mapped);
|
||||
try { actual.addAll(SmartLoadingChannelGroupCatalog.require(value).channelCodes()); }
|
||||
catch (IllegalArgumentException exception) { throw validation(exception.getMessage()); }
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
private static Set<String> normalizedGroups(List<String> groups) {
|
||||
return groups.stream().map(value -> {
|
||||
try { return SmartLoadingChannelGroupCatalog.require(value).code(); }
|
||||
catch (IllegalArgumentException exception) { throw validation(exception.getMessage()); }
|
||||
}).collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Plan draftByBooking(long bookingId) {
|
||||
@@ -1689,8 +1683,10 @@ public class SmartLoadingService {
|
||||
}
|
||||
private ContainerBookingEntity requireBooking(long id){ContainerBookingEntity b=repository.booking(id);if(b==null)throw new BizException(ResultCode.NOT_FOUND,"货柜预订不存在");return b;}
|
||||
private static boolean sameChannel(ContainerBookingEntity booking, Candidate candidate) {
|
||||
return StringUtils.hasText(booking.getChannelCode()) && StringUtils.hasText(candidate.channelCode())
|
||||
&& booking.getChannelCode().equals(candidate.channelCode());
|
||||
SmartLoadingChannelGroupCatalog.Definition candidateGroup =
|
||||
SmartLoadingChannelGroupCatalog.resolveChannel(candidate.channelCode());
|
||||
return StringUtils.hasText(booking.getChannelGroupCode()) && candidateGroup != null
|
||||
&& booking.getChannelGroupCode().equals(candidateGroup.code());
|
||||
}
|
||||
private static boolean sameCustomerScope(ContainerBookingEntity booking, Candidate candidate) {
|
||||
return booking.getCustomerId() == null || Objects.equals(booking.getCustomerId(), candidate.accountId());
|
||||
|
||||
@@ -34,6 +34,9 @@ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='channel_code') THEN
|
||||
ALTER TABLE fl_container_booking ADD COLUMN channel_code VARCHAR(64) NULL COMMENT '订舱渠道编码快照' AFTER cabinet_no;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='channel_group_code') THEN
|
||||
ALTER TABLE fl_container_booking ADD COLUMN channel_group_code VARCHAR(64) NULL COMMENT '模拟柜计划渠道分组;关联真实订舱后继续保留' AFTER merged_into_container_booking_id;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='channel_name') THEN
|
||||
ALTER TABLE fl_container_booking ADD COLUMN channel_name VARCHAR(128) NULL COMMENT '订舱渠道名称快照' AFTER channel_code;
|
||||
END IF;
|
||||
@@ -93,6 +96,7 @@ BEGIN
|
||||
IF current_plan_status IN ('LOADING', 'COMPLETED') AND (
|
||||
NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no)
|
||||
OR NOT (OLD.cabinet_no <=> NEW.cabinet_no)
|
||||
OR NOT (OLD.channel_group_code <=> NEW.channel_group_code)
|
||||
OR NOT (OLD.channel_code <=> NEW.channel_code) OR NOT (OLD.channel_name <=> NEW.channel_name)
|
||||
OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id)
|
||||
OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)
|
||||
|
||||
@@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS fl_container_booking (
|
||||
source_type VARCHAR(24) NOT NULL DEFAULT 'SIMULATION' COMMENT '记录来源:SIMULATION内部模拟柜、OCEAN_BOOKING订舱真实柜',
|
||||
record_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '记录状态:ACTIVE可操作、MERGED已合并历史',
|
||||
merged_into_container_booking_id BIGINT UNSIGNED NULL COMMENT '真实柜被模拟柜吸收后的目标稳定柜次ID',
|
||||
channel_group_code VARCHAR(64) NULL COMMENT '模拟柜计划渠道分组;关联真实订舱后继续保留',
|
||||
channel_code VARCHAR(64) NULL COMMENT '订舱渠道编码快照;存量回填前禁止混装',
|
||||
channel_name VARCHAR(128) NULL COMMENT '订舱渠道名称快照',
|
||||
ocean_booking_id BIGINT UNSIGNED NULL COMMENT '来源订舱ID',
|
||||
|
||||
@@ -2174,7 +2174,7 @@ components:
|
||||
allowedActions: {type: array, items: {type: string, enum: [SELECT_FLEET, SCHEDULE_PICKUP, RECORD_PICKUP, RECORD_EMPTY_RETURN, SEND_PICKUP_EMAIL]}}
|
||||
ContainerBooking:
|
||||
type: object
|
||||
required: [id, bookingNo, cabinetNo, sourceType, recordStatus, warehouseId, warehouseName, containerType, capacityCbm, maxWeightKg, planStatus, exceptionActive, destinationClearanceDocumentStatus, customsDeclarationDocumentStatus, deliveryOrderDocumentStatus, version]
|
||||
required: [id, bookingNo, cabinetNo, sourceType, recordStatus, warehouseId, warehouseName, channelGroupCode, containerType, capacityCbm, maxWeightKg, planStatus, exceptionActive, destinationClearanceDocumentStatus, customsDeclarationDocumentStatus, deliveryOrderDocumentStatus, version]
|
||||
properties:
|
||||
id: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
planId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: 关联智能装柜计划ID;未创建计划时为空}
|
||||
@@ -2185,8 +2185,9 @@ components:
|
||||
sourceType: {type: string, enum: [SIMULATION, OCEAN_BOOKING], description: SIMULATION 为内部模拟柜;OCEAN_BOOKING 为订舱登记直接生成的真实柜}
|
||||
recordStatus: {type: string, enum: [ACTIVE, MERGED], description: 正常列表和详情仅返回 ACTIVE;真实订舱柜被模拟柜消费后变为 MERGED 且不再作为可操作柜返回}
|
||||
mergedIntoContainerBookingId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: MERGED 源真实柜指向保留的模拟柜稳定 containerBookingId;ACTIVE 时为空}
|
||||
channelCode: {type: string, maxLength: 64, nullable: true, description: Missing legacy snapshots are blocked from loading until explicitly backfilled}
|
||||
channelName: {type: string, maxLength: 128, nullable: true}
|
||||
channelGroupCode: {type: string, maxLength: 64, enum: [MATSON_CLX, MATSON_MAX, EXX, HEDE, OA_LA, OA_CHICAGO, OA_HOUSTON, OA_SAVANNAH, OA_NEW_YORK_TRUCK], description: 模拟柜创建时确定的九类计划渠道分组,关联真实订舱后继续保留}
|
||||
channelCode: {type: string, maxLength: 64, nullable: true, description: 关联真实订舱后冻结的实际物流渠道编码}
|
||||
channelName: {type: string, maxLength: 128, nullable: true, description: 关联真实订舱后冻结的实际物流渠道名称}
|
||||
oceanBookingId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
oceanBookingRequirementId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
@@ -2444,8 +2445,8 @@ components:
|
||||
pages: {type: string, pattern: '^[0-9]+$'}
|
||||
SmartLoadingWorkbenchContainer:
|
||||
type: object
|
||||
required: [bookingId, bookingNo, cabinetNo, warehouseId, warehouseName, channelCode,
|
||||
channelName, containerType, capacityCbm, maxWeightKg, planStatus, version]
|
||||
required: [bookingId, bookingNo, cabinetNo, warehouseId, warehouseName, channelGroupCode,
|
||||
containerType, capacityCbm, maxWeightKg, planStatus, version]
|
||||
properties:
|
||||
bookingId: {type: string, pattern: '^[1-9][0-9]*$', description: containerBookingId 技术关联}
|
||||
bookingNo: {type: string, description: 货柜登记业务号}
|
||||
@@ -2453,8 +2454,9 @@ components:
|
||||
containerNo: {type: string, nullable: true, description: 真实集装箱号,允许在装柜前后补}
|
||||
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
warehouseName: {type: string}
|
||||
channelCode: {type: string}
|
||||
channelName: {type: string}
|
||||
channelGroupCode: {type: string, enum: [MATSON_CLX, MATSON_MAX, EXX, HEDE, OA_LA, OA_CHICAGO, OA_HOUSTON, OA_SAVANNAH, OA_NEW_YORK_TRUCK], description: 智能装柜匹配使用的计划渠道分组}
|
||||
channelCode: {type: string, nullable: true, description: 关联真实订舱后冻结的实际物流渠道编码}
|
||||
channelName: {type: string, nullable: true, description: 关联真实订舱后冻结的实际物流渠道名称}
|
||||
containerType: {type: string}
|
||||
capacityCbm: {type: number, nullable: true, description: 柜型基础容积;装柜上限按正式规则计算}
|
||||
maxWeightKg: {type: number, nullable: true, description: 柜型基础载重;装柜上限按正式规则计算}
|
||||
@@ -2706,9 +2708,9 @@ components:
|
||||
reason: {type: string, maxLength: 500}
|
||||
ContainerSimulationCreateRequest:
|
||||
type: object
|
||||
required: [channelCode, warehouseId, containerType, capacityCbm, maxWeightKg]
|
||||
required: [channelGroupCode, warehouseId, containerType, capacityCbm, maxWeightKg]
|
||||
properties:
|
||||
channelCode: {type: string, maxLength: 64, description: 当前 ACTIVE 价格簿中的真实物流渠道}
|
||||
channelGroupCode: {type: string, maxLength: 64, enum: [MATSON_CLX, MATSON_MAX, EXX, HEDE, OA_LA, OA_CHICAGO, OA_HOUSTON, OA_SAVANNAH, OA_NEW_YORK_TRUCK], description: 智能装柜使用的九类计划渠道分组}
|
||||
warehouseId: {type: integer, format: int64, minimum: 1}
|
||||
containerType: {type: string, enum: [20GP, 40GP, 40HC, 40NR, 45HC], description: 与订舱共用的平台固定柜型}
|
||||
capacityCbm: {type: number, minimum: 0, exclusiveMinimum: true, description: 必须等于柜型固定容量}
|
||||
|
||||
@@ -107,7 +107,8 @@ paths:
|
||||
summary: Page released booking requirement candidates for one container
|
||||
description: >-
|
||||
Requires platform:firstleg:ocean-booking:list. Returns only linkable requirement candidates:
|
||||
RELEASED, remainingQuantity greater than zero, and exact warehouseId, channelCode and containerType.
|
||||
RELEASED, remainingQuantity greater than zero, exact warehouseId and containerType, and a concrete
|
||||
booking channel belonging to channelGroupCode.
|
||||
Capacity and max weight are derived display snapshots and never eligibility fields. Allocated quantity
|
||||
counts only ACTIVE containers still pointing to the same booking requirement and is calculated by one
|
||||
bounded set query; booking detail calls are not used. An empty result is a normal successful page.
|
||||
@@ -115,7 +116,7 @@ paths:
|
||||
x-permission: platform:firstleg:ocean-booking:list
|
||||
parameters:
|
||||
- {name: warehouseId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
- {name: channelCode, in: query, required: true, schema: {type: string, maxLength: 64}}
|
||||
- {name: channelGroupCode, in: query, required: true, schema: {type: string, maxLength: 64}}
|
||||
- {name: containerType, in: query, required: true, schema: {type: string, maxLength: 32}}
|
||||
- {name: page, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, default: 1}}
|
||||
- {name: size, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}}
|
||||
|
||||
Reference in New Issue
Block a user