feat(fleet): enforce reviewed trips and original fee coverage for dispatch
This commit is contained in:
+1
@@ -54,6 +54,7 @@ public class OverseasFeeOutboxRelay implements EventDeliveryProducer {
|
||||
failures, next, "投递租约超时,消费结果未知"));
|
||||
continue; // Recovery always waits for the new backoff window; never sends in this pass.
|
||||
}
|
||||
if (!repository.prerequisitePublished(candidate)) continue;
|
||||
String worker = UUID.randomUUID().toString();
|
||||
String type = Stream.FEE.type(candidate.getEventType());
|
||||
if (!journal.begin(worker, Stream.FEE.service, type, String.valueOf(candidate.getId()),
|
||||
|
||||
+27
-8
@@ -11,6 +11,7 @@ import com.baiyue.lastleg.financefee.entity.OverseasFeeCommand;
|
||||
import com.baiyue.lastleg.financefee.entity.OverseasFeeSource;
|
||||
import com.baiyue.lastleg.financefee.mapper.OverseasFeeCommandMapper;
|
||||
import com.baiyue.lastleg.financefee.mapper.OverseasFeeSourceMapper;
|
||||
import com.baiyue.lastleg.financefee.state.OverseasFeeCancellation;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -41,31 +42,43 @@ public class OverseasFeeSourceService {
|
||||
private final OverseasFeeOutboxService outbox;
|
||||
private final OverseasFeeCurrencyConverter currencyConverter;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OverseasFeeCancellation cancellation;
|
||||
|
||||
public List<OverseasFeeSource> list(String customerId, String businessType, String businessNo,
|
||||
Long containerBookingId) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
return cancellation.describe(mapper.selectList(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
.eq(text(customerId) != null, OverseasFeeSource::getCustomerId, text(customerId))
|
||||
.eq(text(businessType) != null, OverseasFeeSource::getBusinessType, text(businessType))
|
||||
.like(text(businessNo) != null, OverseasFeeSource::getBusinessNo, text(businessNo))
|
||||
.eq(containerBookingId != null, OverseasFeeSource::getContainerBookingId, containerBookingId)
|
||||
.inSql(OverseasFeeSource::getId, "SELECT MAX(v.id) FROM ll_finance_fee_source v GROUP BY v.source_fee_id")
|
||||
.orderByDesc(OverseasFeeSource::getCreatedAt).orderByDesc(OverseasFeeSource::getId));
|
||||
.orderByDesc(OverseasFeeSource::getCreatedAt).orderByDesc(OverseasFeeSource::getId)));
|
||||
}
|
||||
|
||||
public List<OverseasFeeSource> listForBusiness(String customerId, String businessType, long businessId) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
return cancellation.describe(mapper.selectList(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
.eq(OverseasFeeSource::getCustomerId, customerId)
|
||||
.eq(OverseasFeeSource::getBusinessType, businessType)
|
||||
.eq(OverseasFeeSource::getBusinessId, businessId)
|
||||
.inSql(OverseasFeeSource::getId, "SELECT MAX(v.id) FROM ll_finance_fee_source v GROUP BY v.source_fee_id")
|
||||
.orderByAsc(OverseasFeeSource::getCreatedAt).orderByAsc(OverseasFeeSource::getId));
|
||||
.orderByAsc(OverseasFeeSource::getCreatedAt).orderByAsc(OverseasFeeSource::getId)));
|
||||
}
|
||||
|
||||
public List<ContainerCustomerOption> listContainerCustomers(long containerBookingId) {
|
||||
return businessGuard.listContainerCustomers(containerBookingId);
|
||||
}
|
||||
|
||||
public void requireNoActiveDeliveryReceivables(String customerId, List<String> inboundCodes) {
|
||||
if (inboundCodes.isEmpty()) return;
|
||||
long existing = mapper.selectCount(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
.eq(OverseasFeeSource::getCustomerId, customerId).eq(OverseasFeeSource::getBusinessType, "DELIVERY_ORDER")
|
||||
.eq(OverseasFeeSource::getDirection, "RECEIVABLE").eq(OverseasFeeSource::getFeeItemCode, "DELIVERY_FEE")
|
||||
.in(OverseasFeeSource::getInboundCode, inboundCodes).ne(OverseasFeeSource::getSourceStatus, "VOIDED")
|
||||
.and(state -> state.eq(OverseasFeeSource::getReviewStatus, "APPROVED").or().gt(OverseasFeeSource::getSourceVersion, 1))
|
||||
.inSql(OverseasFeeSource::getId, "SELECT MAX(v.id) FROM ll_finance_fee_source v GROUP BY v.source_fee_id"));
|
||||
if (existing > 0) throw conflict("外接货已有派送应收尚未确认作废,请先完成原费用处理再重新转派送");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public OverseasFeeSource create(String operatorId, String key, SaveRequest request) {
|
||||
OverseasFeeCommand replay = replay(operatorId, "CREATE", key, request);
|
||||
@@ -125,11 +138,12 @@ public class OverseasFeeSourceService {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Transactional(isolation = org.springframework.transaction.annotation.Isolation.READ_COMMITTED)
|
||||
public OverseasFeeSource adjust(long sourceFeeId, String operatorId, AdjustRequest request) {
|
||||
OverseasFeeCommand replay = replay(operatorId, "ADJUST", request.idempotencyKey(), request);
|
||||
if (replay != null) return current(replay.getSourceFeeId());
|
||||
OverseasFeeSource old = lock(sourceFeeId);
|
||||
if (cancellation.requested(old)) throw conflict("该费用已申请作废,请先处理财务结果");
|
||||
if (!"APPROVED".equals(old.getReviewStatus()) || !"ACTIVE".equals(old.getSourceStatus())
|
||||
|| old.getSourceVersion() != request.expectedVersion()) throw conflict("只有当前已发布版本可以调整");
|
||||
validate(request.fee()); businessGuard.requireOwned(request.fee());
|
||||
@@ -157,10 +171,15 @@ public class OverseasFeeSourceService {
|
||||
OverseasFeeSource value = lock(sourceFeeId);
|
||||
if (!"APPROVED".equals(value.getReviewStatus()) || !"ACTIVE".equals(value.getSourceStatus())
|
||||
|| value.getSourceVersion() != request.expectedVersion()) throw conflict("只有当前已发布版本可以作废");
|
||||
value.setSourceStatus("VOIDED"); value.setReviewReason(request.reason().trim());
|
||||
if (cancellation.requested(value)) return cancellation.describe(List.of(value)).get(0);
|
||||
value.setReviewReason(request.reason().trim());
|
||||
value.setUpdatedBy(operatorId); value.setUpdatedAt(now()); mapper.updateById(value);
|
||||
outbox.publish(value, "VOIDED");
|
||||
var pending = new OverseasFeeSource();
|
||||
org.springframework.beans.BeanUtils.copyProperties(value, pending);
|
||||
pending.setSourceStatus("VOIDED");
|
||||
outbox.publish(pending, "VOIDED");
|
||||
command(operatorId, "VOID", request.idempotencyKey(), request, sourceFeeId, value.getSourceVersion());
|
||||
value.setVoidStatus("PENDING_FINANCE");
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -233,7 +252,7 @@ public class OverseasFeeSourceService {
|
||||
private OverseasFeeSource current(long sourceFeeId) {
|
||||
OverseasFeeSource value = mapper.selectOne(new LambdaQueryWrapper<OverseasFeeSource>()
|
||||
.eq(OverseasFeeSource::getSourceFeeId, sourceFeeId).orderByDesc(OverseasFeeSource::getSourceVersion).last("LIMIT 1"));
|
||||
if (value == null) throw notFound(); return value;
|
||||
if (value == null) throw notFound(); return cancellation.describe(List.of(value)).get(0);
|
||||
}
|
||||
private OverseasFeeSource lock(long sourceFeeId) { OverseasFeeSource value = mapper.lockCurrent(sourceFeeId); if (value == null) throw notFound(); return value; }
|
||||
private OverseasFeeCommand replay(String operator, String action, String key, Object request) {
|
||||
|
||||
Reference in New Issue
Block a user