fix(firstleg): preserve sailing forecast batch facts
This commit is contained in:
@@ -182,14 +182,18 @@ public class MeasurementReviewService {
|
||||
fact.setReceiptBatchKey("INBOUND-" + order.getId() + "-MEASUREMENT-" + measurement.getId()); fact.setFactVersion(1);
|
||||
fact.setInboundId(order.getId()); fact.setMeasurementId(measurement.getId()); fact.setReceivingWarehouseId(order.getWarehouseId());
|
||||
fact.setChannelCode(order.getChannelCode()); fact.setChannelName(order.getChannelName()); fact.setReceivedAt(receivedAt);
|
||||
fact.setActualVolumeCbm(order.getActualVolumeCbm()); fact.setFactStatus("APPROVED"); fact.setSourceType("FIRSTLEG_MEASUREMENT_REVIEW");
|
||||
// The reviewed measurement is the stable receipt-batch volume. The inbound
|
||||
// aggregate is updated by the measurement workflow too, but must not be used
|
||||
// here because split/child inbounds can be reviewed independently.
|
||||
BigDecimal actualVolume = measurement.getTotalVolumeCbm();
|
||||
fact.setActualVolumeCbm(actualVolume); fact.setFactStatus("APPROVED"); fact.setSourceType("FIRSTLEG_MEASUREMENT_REVIEW");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
fact.setCreatedBy(operatorId); fact.setCreatedAt(now); fact.setUpdatedAt(now);
|
||||
SailingChannelEntity channel = order.getChannelCode() == null ? null : sailingCalendar.channelByCode(order.getChannelCode().trim().toUpperCase());
|
||||
if (channel != null) { fact.setDeparturePortCode(channel.getDefaultDeparturePortCode()); fact.setDestinationPortCode(channel.getDefaultDestinationPortCode()); }
|
||||
sailingFacts.insertReceiptFact(fact);
|
||||
List<SailingInboundVolumeForecastEntity> forecasts = sailingFacts.currentForecasts(order.getTenantId(), order.getId());
|
||||
SailingInboundVolumeForecastEntity forecast = selectForecastForReceipt(forecasts, measurement, order.getActualVolumeCbm());
|
||||
SailingInboundVolumeForecastEntity forecast = selectForecastForReceipt(forecasts, measurement, actualVolume);
|
||||
boolean ambiguousForecast = forecast == null && forecasts.size() > 1;
|
||||
SailingAssignmentEntity forecastAssignment = forecast == null
|
||||
? null : sailingFacts.currentAssignmentForForecast(order.getTenantId(), forecast.getId());
|
||||
@@ -198,7 +202,7 @@ public class MeasurementReviewService {
|
||||
if (sailingFacts.consumeForecast(order.getTenantId(), forecast.getId(), now) != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "预报批次已被其他收货事件消费,请刷新后重试");
|
||||
}
|
||||
BigDecimal remaining = forecast.getForecastVolumeCbm().subtract(order.getActualVolumeCbm());
|
||||
BigDecimal remaining = forecast.getForecastVolumeCbm().subtract(actualVolume);
|
||||
if (remaining.signum() > 0) {
|
||||
SailingInboundVolumeForecastEntity residual = new SailingInboundVolumeForecastEntity();
|
||||
residual.setId(IdWorker.getId()); residual.setTenantId(forecast.getTenantId()); residual.setForecastKey(forecast.getForecastKey());
|
||||
@@ -228,7 +232,7 @@ public class MeasurementReviewService {
|
||||
SailingAssignmentEntity assignment = new SailingAssignmentEntity(); assignment.setId(IdWorker.getId()); assignment.setTenantId(order.getTenantId());
|
||||
assignment.setSailingId(sailing == null ? null : sailing.getId()); assignment.setInboundId(order.getId()); assignment.setInboundCode(order.getInboundCode()); assignment.setReceiptBatchId(fact.getId()); assignment.setForecastId(forecast == null ? null : forecast.getId()); assignment.setAssignmentSource("AUTO"); assignment.setAssignmentVersion(forecastAssignment == null || forecastAssignment.getAssignmentVersion() == null ? 1 : forecastAssignment.getAssignmentVersion() + 1);
|
||||
boolean late = sailing != null && receivedAt.isAfter(sailing.getWarehouseCutoffAt());
|
||||
assignment.setMatchStatus(sailing == null ? "UNMATCHED" : late ? "LATE" : "RECEIVED"); assignment.setVolumeCbm(order.getActualVolumeCbm());
|
||||
assignment.setMatchStatus(sailing == null ? "UNMATCHED" : late ? "LATE" : "RECEIVED"); assignment.setVolumeCbm(actualVolume);
|
||||
assignment.setRouteSource(channel == null ? "INBOUND_FACT" : "CHANNEL_DEFAULT"); assignment.setVolumeSource("MEASURED_ACTUAL"); assignment.setRuleVersion("V1_CUTOFF_3D_18");
|
||||
assignment.setMatchedAt(now); assignment.setLockedAt(late ? now : null);
|
||||
assignment.setLockReason(late ? "CUTOFF" : null); assignment.setReason(ambiguousForecast ? "MULTI_FORECAST_REVIEW_REQUIRED" : sailing == null ? "NO_MATCHING_OPEN_SAILING" : forecast != null ? "FORECAST_REPLACED_BY_RECEIPT" : null); assignment.setOperatorId(operatorId); assignment.setCreatedAt(now);
|
||||
@@ -248,14 +252,11 @@ public class MeasurementReviewService {
|
||||
return null;
|
||||
}
|
||||
if (forecasts.size() == 1) return forecasts.get(0);
|
||||
SailingInboundVolumeForecastEntity exact = null;
|
||||
for (SailingInboundVolumeForecastEntity forecast : forecasts) {
|
||||
if (forecast.getForecastVolumeCbm() != null && actualVolume != null && forecast.getForecastVolumeCbm().compareTo(actualVolume) == 0) {
|
||||
if (exact != null) return null;
|
||||
exact = forecast;
|
||||
}
|
||||
}
|
||||
return exact;
|
||||
// Several valid forecasts represent separate physical batches. Volume is
|
||||
// not a stable batch key (two batches can have the same CBM, and a partial
|
||||
// receipt can differ from its forecast), so leave the receipt unmatched
|
||||
// until the caller supplies the explicit forecast key.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
Reference in New Issue
Block a user