fix(firstleg): source sailing channels from active price book
This commit is contained in:
+21
-1
@@ -43,7 +43,7 @@ public interface PriceBookQueryMapper extends PriceBookQueryReader {
|
||||
Long findEffectiveVersionId(@Param("customerId") long customerId);
|
||||
|
||||
@Select("""
|
||||
SELECT c.price_book_version_id AS priceBookVersionId,
|
||||
SELECT catalog.id AS channelCatalogId, c.price_book_version_id AS priceBookVersionId,
|
||||
c.channel_code AS channelCode, c.channel_name AS channelName,
|
||||
c.price_model AS priceModel,
|
||||
c.estimated_departure_date AS estimatedDepartureDate,
|
||||
@@ -53,6 +53,8 @@ public interface PriceBookQueryMapper extends PriceBookQueryReader {
|
||||
c.surcharge_description AS surchargeDescription,
|
||||
c.remark AS remark
|
||||
FROM fl_price_book_channel c
|
||||
JOIN fl_price_channel_catalog catalog
|
||||
ON catalog.channel_code = c.channel_code AND catalog.status = 'ACTIVE'
|
||||
WHERE c.price_book_version_id = #{versionId}
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM fl_carrier_fba_rate r
|
||||
@@ -66,6 +68,24 @@ public interface PriceBookQueryMapper extends PriceBookQueryReader {
|
||||
List<ChannelRow> findChannels(@Param("versionId") long versionId,
|
||||
@Param("originWarehouseId") long originWarehouseId);
|
||||
|
||||
@Select("""
|
||||
SELECT catalog.id AS channelCatalogId, c.price_book_version_id AS priceBookVersionId,
|
||||
c.channel_code AS channelCode, c.channel_name AS channelName,
|
||||
c.price_model AS priceModel,
|
||||
c.estimated_departure_date AS estimatedDepartureDate,
|
||||
c.estimated_departure_text AS estimatedDepartureText,
|
||||
c.latest_warehouse_entry AS latestWarehouseEntry,
|
||||
c.reference_transit AS referenceTransit,
|
||||
c.surcharge_description AS surchargeDescription,
|
||||
c.remark AS remark
|
||||
FROM fl_price_book_channel c
|
||||
JOIN fl_price_channel_catalog catalog
|
||||
ON catalog.channel_code = c.channel_code AND catalog.status = 'ACTIVE'
|
||||
WHERE c.price_book_version_id = #{versionId}
|
||||
ORDER BY c.id
|
||||
""")
|
||||
List<ChannelRow> findAllChannels(@Param("versionId") long versionId);
|
||||
|
||||
@Select("""
|
||||
SELECT r.channel_code AS channelCode, r.pricing_tax_mode AS pricingTaxMode
|
||||
FROM fl_carrier_fba_rate r
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ public class PriceBookQueryService implements PriceBookQueryUseCase {
|
||||
@Override public List<ChannelRow> channels(long versionId, long originWarehouseId) {
|
||||
return reader.findChannels(versionId, originWarehouseId);
|
||||
}
|
||||
@Override public List<ChannelRow> allChannels(long versionId) { return reader.findAllChannels(versionId); }
|
||||
@Override public List<ChannelTaxModeRow> channelTaxModes(long versionId, long originWarehouseId) {
|
||||
return reader.findChannelTaxModes(versionId, originWarehouseId);
|
||||
}
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ public final class PriceBookQueryRows {
|
||||
@Getter
|
||||
@Setter
|
||||
public static class ChannelRow {
|
||||
private Long channelCatalogId;
|
||||
private Long priceBookVersionId;
|
||||
private String channelCode;
|
||||
private String channelName;
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ public interface PriceBookQueryUseCase {
|
||||
Long activeVersionId();
|
||||
Long effectiveVersionId(long customerId);
|
||||
List<ChannelRow> channels(long versionId, long originWarehouseId);
|
||||
List<ChannelRow> allChannels(long versionId);
|
||||
List<ChannelTaxModeRow> channelTaxModes(long versionId, long originWarehouseId);
|
||||
List<RateRow> fbaRates(long versionId, long originWarehouseId, String channelCode,
|
||||
String destinationCode, String pricingTaxMode);
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ public interface PriceBookQueryReader {
|
||||
Long findActiveVersionId();
|
||||
Long findEffectiveVersionId(long customerId);
|
||||
List<ChannelRow> findChannels(long versionId, long originWarehouseId);
|
||||
List<ChannelRow> findAllChannels(long versionId);
|
||||
List<ChannelTaxModeRow> findChannelTaxModes(long versionId, long originWarehouseId);
|
||||
List<RateRow> findFbaRates(long versionId, long originWarehouseId, String channelCode,
|
||||
String destinationCode, String pricingTaxMode);
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
public final class SailingCalendarDtos {
|
||||
private SailingCalendarDtos() {}
|
||||
|
||||
public record ChannelQuery(@Size(max = 64) String keyword, String status) {}
|
||||
public record ChannelQuery(@Size(max = 64) String keyword, String status, @Positive Long warehouseId) {}
|
||||
public record ChannelData(@NotBlank @Size(max = 64) String channelCode,
|
||||
@NotBlank @Size(max = 128) String channelName,
|
||||
@NotBlank String sourceType,
|
||||
|
||||
+52
-25
@@ -29,6 +29,8 @@ import com.baiyue.firstleg.sailingcalendar.fact.entity.SailingAssignmentEntity;
|
||||
import com.baiyue.firstleg.sailingcalendar.fact.entity.SailingBookingLinkEntity;
|
||||
import com.baiyue.firstleg.sailingcalendar.fact.entity.SailingInboundVolumeForecastEntity;
|
||||
import com.baiyue.firstleg.sailingcalendar.fact.entity.SailingReceiptBatchFactEntity;
|
||||
import com.baiyue.firstleg.pricebookquery.application.model.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.pricebookquery.application.port.in.PriceBookQueryUseCase;
|
||||
import com.baiyue.firstleg.sailingcalendar.mapper.SailingCalendarDisplayMapper;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
@@ -41,30 +43,30 @@ public class SailingCalendarService {
|
||||
private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final String OPEN = "OPEN";
|
||||
@Resource private SailingCalendarRepository repository;
|
||||
@Resource private PriceBookQueryUseCase priceBooks;
|
||||
|
||||
public List<ChannelView> channels(ChannelQuery query) {
|
||||
return repository.channels(query).stream().map(v -> new ChannelView(v, "ACTIVE".equals(v.getStatus()))).toList();
|
||||
Long versionId = priceBooks.activeVersionId();
|
||||
if (versionId == null || "INACTIVE".equalsIgnoreCase(trim(query.status()))) return List.of();
|
||||
List<ChannelRow> rows = query.warehouseId() == null
|
||||
? priceBooks.allChannels(versionId)
|
||||
: priceBooks.channels(versionId, query.warehouseId());
|
||||
String keyword = trimUpper(query.keyword());
|
||||
return rows.stream()
|
||||
.filter(row -> !StringUtils.hasText(keyword)
|
||||
|| (row.getChannelCode() != null && row.getChannelCode().toUpperCase().contains(keyword))
|
||||
|| (row.getChannelName() != null && row.getChannelName().toUpperCase().contains(keyword)))
|
||||
.map(this::priceBookChannel)
|
||||
.map(v -> new ChannelView(v, false))
|
||||
.toList();
|
||||
}
|
||||
@Transactional
|
||||
public SailingChannelEntity createChannel(long operatorId, String operatorName, ChannelCreateRequest request) {
|
||||
if (version(request.expectedVersion()) != 0) throw bad("新建渠道expectedVersion必须为0");
|
||||
validateChannel(request.data());
|
||||
if (repository.channelByCode(request.data().channelCode().trim().toUpperCase()) != null) throw conflict("渠道代码已存在");
|
||||
SailingChannelEntity value = new SailingChannelEntity(); value.setId(IdWorker.getId());
|
||||
copy(value, request.data()); value.setStatus("ACTIVE"); value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
value.setUpdatedBy(operatorId); value.setUpdatedByName(operatorName); value.setVersion(0); now(value);
|
||||
try { repository.insert(value); } catch (DuplicateKeyException ex) { throw conflict("渠道代码已存在"); }
|
||||
return value;
|
||||
throw conflict("船期渠道来自当前生效船期价格表,不能手工维护");
|
||||
}
|
||||
@Transactional
|
||||
public SailingChannelEntity updateChannel(long id, long operatorId, String operatorName, ChannelUpdateRequest request) {
|
||||
SailingChannelEntity before = requiredChannel(id); validateChannel(request.data());
|
||||
if (!before.getChannelCode().equalsIgnoreCase(request.data().channelCode().trim())
|
||||
&& repository.channelByCode(request.data().channelCode().trim().toUpperCase()) != null) throw conflict("渠道代码已存在");
|
||||
SailingChannelEntity value = new SailingChannelEntity(); value.setId(id); copy(value, request.data());
|
||||
value.setStatus(normalizeStatus(request.status(), "渠道状态"));
|
||||
if (repository.update(value, version(request.expectedVersion()), operatorId, operatorName) != 1) throw conflict("渠道已被其他人修改");
|
||||
return requiredChannel(id);
|
||||
throw conflict("船期渠道来自当前生效船期价格表,不能手工维护");
|
||||
}
|
||||
|
||||
public PageResult<SailingTemplateEntity> templates(TemplateQuery query) { return repository.templates(query); }
|
||||
@@ -73,7 +75,7 @@ public class SailingCalendarService {
|
||||
@Transactional
|
||||
public SailingTemplateEntity createTemplate(long operatorId, String operatorName, TemplateCreateRequest request) {
|
||||
if (version(request.expectedVersion()) != 0) throw bad("新建模板expectedVersion必须为0");
|
||||
SailingChannelEntity channel = requiredChannel(request.data().channelId()); validateTemplate(request.data(), channel);
|
||||
SailingChannelEntity channel = resolvePriceBookChannel(request.data().channelId(), request.data().warehouseId(), null); validateTemplate(request.data(), channel);
|
||||
if (!"ACTIVE".equals(channel.getStatus())) throw conflict("停用渠道不能创建模板");
|
||||
SailingTemplateEntity value = new SailingTemplateEntity(); value.setId(IdWorker.getId()); value.setTemplateNo("ST-" + value.getId());
|
||||
copy(value, request.data(), channel); value.setStatus("ACTIVE"); value.setCreatedBy(operatorId); value.setCreatedByName(operatorName);
|
||||
@@ -82,7 +84,7 @@ public class SailingCalendarService {
|
||||
}
|
||||
@Transactional
|
||||
public SailingTemplateEntity updateTemplate(long id, long operatorId, String operatorName, TemplateUpdateRequest request) {
|
||||
requiredTemplate(id); SailingChannelEntity channel = requiredChannel(request.data().channelId()); validateTemplate(request.data(), channel);
|
||||
requiredTemplate(id); SailingChannelEntity channel = resolvePriceBookChannel(request.data().channelId(), request.data().warehouseId(), null); validateTemplate(request.data(), channel);
|
||||
SailingTemplateEntity value = new SailingTemplateEntity(); value.setId(id); copy(value, request.data(), channel);
|
||||
value.setStatus(StringUtils.hasText(request.status()) ? normalizeStatus(request.status(), "模板状态") : requiredTemplate(id).getStatus());
|
||||
if (repository.update(value, version(request.expectedVersion()), operatorId, operatorName) != 1) throw conflict("模板已被其他人修改");
|
||||
@@ -196,8 +198,8 @@ public class SailingCalendarService {
|
||||
throw bad("预计体积必须大于0");
|
||||
}
|
||||
String channelCode = trimUpper(request.channelCode());
|
||||
SailingChannelEntity channel = StringUtils.hasText(channelCode) ? repository.channelByCode(channelCode) : null;
|
||||
if (channel == null || !"ACTIVE".equals(channel.getStatus())) throw bad("预报必须填写已启用的渠道编码");
|
||||
SailingChannelEntity channel = StringUtils.hasText(channelCode) ? resolvePriceBookChannelByCode(request.receivingWarehouseId(), channelCode) : null;
|
||||
if (channel == null) throw bad("预报必须填写当前生效船期价格表中的渠道编码");
|
||||
String departurePort = trimUpper(request.departurePortCode());
|
||||
String destinationPort = trimUpper(request.destinationPortCode());
|
||||
if (!StringUtils.hasText(departurePort)) departurePort = trimUpper(channel.getDefaultDeparturePortCode());
|
||||
@@ -225,8 +227,8 @@ public class SailingCalendarService {
|
||||
if (request.forecastVolumeCbm().signum() <= 0) throw bad("预计体积必须大于0");
|
||||
String channelCode = trimUpper(request.channelCode());
|
||||
if (!StringUtils.hasText(channelCode)) channelCode = current.getChannelCode();
|
||||
SailingChannelEntity channel = repository.channelByCode(channelCode);
|
||||
if (channel == null || !"ACTIVE".equals(channel.getStatus())) throw bad("预报必须填写已启用的渠道编码");
|
||||
SailingChannelEntity channel = resolvePriceBookChannelByCode(current.getReceivingWarehouseId(), channelCode);
|
||||
if (channel == null) throw bad("预报必须填写当前生效船期价格表中的渠道编码");
|
||||
String departurePort = trimUpper(request.departurePortCode());
|
||||
String destinationPort = trimUpper(request.destinationPortCode());
|
||||
if (!StringUtils.hasText(departurePort)) departurePort = trimUpper(channel.getDefaultDeparturePortCode());
|
||||
@@ -391,7 +393,7 @@ public class SailingCalendarService {
|
||||
@Transactional
|
||||
public List<SailingEntity> generate(long operatorId, String operatorName, long templateId, LocalDate from, LocalDate to) {
|
||||
SailingTemplateEntity template = requiredTemplate(templateId); if (!"ACTIVE".equals(template.getStatus())) throw conflict("停用模板不能生成船期");
|
||||
SailingChannelEntity channel = requiredChannel(template.getChannelId());
|
||||
SailingChannelEntity channel = resolvePriceBookChannel(template.getChannelId(), template.getWarehouseId(), template.getChannelCodeSnapshot());
|
||||
if (!"ACTIVE".equals(channel.getStatus())) throw conflict("停用渠道不能生成船期");
|
||||
validateTemplateSnapshot(template);
|
||||
List<SailingEntity> result = new java.util.ArrayList<>();
|
||||
@@ -473,8 +475,33 @@ public class SailingCalendarService {
|
||||
if (d.etaAt() != null && d.etaAt().isBefore(d.etdAt())) throw bad("ETA不能早于ETD");
|
||||
if (d.cutoffAt() == null || !d.cutoffAt().isBefore(d.etdAt())) throw bad("收货截止必须早于开船时间");
|
||||
}
|
||||
private SailingChannelEntity resolveChannel(SailingData d) { if (d.channelId() != null) return requiredChannel(d.channelId()); if (!StringUtils.hasText(d.channelCode())) throw bad("必须选择价格表渠道"); SailingChannelEntity v = repository.channelByCode(d.channelCode().trim().toUpperCase()); if (v == null) throw notFound("渠道不存在"); return v; }
|
||||
private SailingChannelEntity requiredChannel(long id) { SailingChannelEntity v = repository.channel(id); if (v == null) throw notFound("渠道不存在"); return v; }
|
||||
private SailingChannelEntity resolveChannel(SailingData d) {
|
||||
return resolvePriceBookChannel(d.channelId(), d.warehouseId(), d.channelCode());
|
||||
}
|
||||
private SailingChannelEntity resolvePriceBookChannel(Long channelId, Long warehouseId, String channelCode) {
|
||||
if (warehouseId == null) throw bad("必须选择收货仓后选择价格表渠道");
|
||||
Long versionId = priceBooks.activeVersionId();
|
||||
if (versionId == null) throw conflict("当前没有生效的船期价格表");
|
||||
List<ChannelRow> rows = priceBooks.channels(versionId, warehouseId);
|
||||
String normalized = trimUpper(channelCode);
|
||||
ChannelRow row = rows.stream()
|
||||
.filter(v -> channelId != null && Objects.equals(v.getChannelCatalogId(), channelId))
|
||||
.findFirst()
|
||||
.orElseGet(() -> rows.stream()
|
||||
.filter(v -> normalized != null && normalized.equalsIgnoreCase(v.getChannelCode()))
|
||||
.findFirst().orElse(null));
|
||||
if (row == null) throw conflict("渠道必须来自当前生效船期价格表,且在当前收货仓有有效报价");
|
||||
return priceBookChannel(row);
|
||||
}
|
||||
private SailingChannelEntity resolvePriceBookChannelByCode(Long warehouseId, String channelCode) {
|
||||
return resolvePriceBookChannel(null, warehouseId, channelCode);
|
||||
}
|
||||
private SailingChannelEntity priceBookChannel(ChannelRow row) {
|
||||
SailingChannelEntity value = new SailingChannelEntity();
|
||||
value.setId(row.getChannelCatalogId()); value.setChannelCode(trimUpper(row.getChannelCode())); value.setChannelName(trim(row.getChannelName()));
|
||||
value.setSourceType("PRICE_BOOK"); value.setDefaultCutoffDays(3); value.setStatus("ACTIVE"); value.setRemark(row.getRemark()); value.setVersion(0);
|
||||
return value;
|
||||
}
|
||||
private SailingTemplateEntity requiredTemplate(long id) { SailingTemplateEntity v = repository.template(id); if (v == null) throw notFound("船期模板不存在"); return v; }
|
||||
private SailingEntity requiredSailing(long id) { SailingEntity v = repository.sailing(id); if (v == null) throw notFound("船期不存在"); return v; }
|
||||
private boolean isCopyOf(SailingEntity value, String reason) {
|
||||
|
||||
Reference in New Issue
Block a user