统一财务费用来源入池链路

This commit is contained in:
2026-09-09 16:39:20 +08:00
parent 9fdb199972
commit 17b9f3eb73
35 changed files with 442 additions and 195 deletions
@@ -20,8 +20,8 @@ public final class OverseasFeeSourceDtos {
public record SaveRequest(
@NotBlank @Size(max = 128) String customerId,
@Size(max = 200) String customerName,
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND_FEE|OVERSEAS_PROCUREMENT_FEE|OVERSEAS_TRUCK_FEE|OVERSEAS_LOOSEBOARD_FEE|OVERSEAS_WAREHOUSE_FEE|OVERSEAS_CONTAINER_FEE") String sourceType,
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND|OVERSEAS_PROCUREMENT|US_TRUCK|LOCAL_LOOSEBOARD|WAREHOUSE_SERVICE|OVERSEAS_CONTAINER") String businessType,
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND_FEE|OVERSEAS_PROCUREMENT_FEE|OVERSEAS_TRUCK_FEE|OVERSEAS_LOOSEBOARD_FEE|OVERSEAS_WAREHOUSE_FEE|OVERSEAS_CONTAINER_FEE|OVERSEAS_ORDER_FEE|OVERSEAS_DELIVERY_FEE|OVERSEAS_RELABEL_FEE") String sourceType,
@NotBlank @Pattern(regexp = "OVERSEAS_INBOUND|OVERSEAS_PROCUREMENT|PROCUREMENT_FEE|US_TRUCK|LOCAL_LOOSEBOARD|WAREHOUSE_SERVICE|OVERSEAS_CONTAINER|LASTLEG_ORDER|DELIVERY_ORDER|RELABEL_ORDER") String businessType,
@Min(1) Long businessId,
@NotBlank @Size(max = 128) String businessNo,
@Min(1) Long orderId,
@@ -2,7 +2,11 @@ package com.baiyue.lastleg.financefee.service;
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos.SaveRequest;
import com.baiyue.lastleg.financefee.dto.OverseasFeeSourceDtos.ContainerCustomerOption;
import com.baiyue.lastleg.client.DomesticWarehouseDirectoryClient;
import feign.FeignException;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -14,6 +18,8 @@ import java.util.List;
@RequiredArgsConstructor
public class OverseasFeeBusinessGuard {
private final JdbcTemplate jdbc;
@Resource private DomesticWarehouseDirectoryClient warehouses;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
public List<ContainerCustomerOption> listContainerCustomers(long containerBookingId) {
List<ContainerCustomerOption> customers = jdbc.query(
@@ -37,13 +43,18 @@ public class OverseasFeeBusinessGuard {
throw bad("业务记录ID不能为空");
if (fee.warehouseId() == null)
throw bad("海外仓服务费必须关联海外仓;入仓记录和入仓码按实际业务选填");
requireActiveOverseasWarehouse(fee.warehouseId());
return;
}
Table table = switch (fee.businessType()) {
case "OVERSEAS_INBOUND" -> new Table("ll_inbound_plan", "plan_no");
case "OVERSEAS_PROCUREMENT" -> new Table("ll_overseas_procurement_order", "order_no");
case "PROCUREMENT_FEE" -> new Table("ll_overseas_procurement_fee", "fee_no");
case "US_TRUCK" -> new Table("ll_us_truck_order", "order_no");
case "LOCAL_LOOSEBOARD" -> new Table("ll_local_looseboard_order", "order_no");
case "LASTLEG_ORDER" -> new Table("ll_order", "order_no");
case "DELIVERY_ORDER" -> new Table("ll_delivery_order", "delivery_no");
case "RELABEL_ORDER" -> new Table("ll_relabel_application", "relabel_no");
default -> throw bad("业务类型与业务记录ID不匹配");
};
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM " + table.name
@@ -83,6 +94,21 @@ public class OverseasFeeBusinessGuard {
if (owned == null || owned < 1) throw bad("该客户不属于费用关联的真实货柜");
}
private void requireActiveOverseasWarehouse(long warehouseId) {
try {
var response = warehouses.activeWarehouse(warehouseId, internalToken);
var warehouse = response == null || response.data() == null ? null : response.data().warehouse();
if (warehouse == null || !response.successful() || !Long.valueOf(warehouseId).equals(warehouse.id())
|| !Long.valueOf(0L).equals(warehouse.tenantId()) || !"ACTIVE".equals(warehouse.status())
|| !List.of("OVERSEAS", "OVERSEAS_PARTNER").contains(warehouse.warehouseType())) {
throw bad("海外仓不存在、已停用或类型不正确");
}
} catch (FeignException exception) {
if (exception.status() >= 400 && exception.status() < 500) throw bad("海外仓不存在、已停用或类型不正确");
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "仓库主数据暂不可用", exception);
}
}
private static boolean blank(String value) { return value == null || value.isBlank(); }
private static ResponseStatusException bad(String message) {
return new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
@@ -53,6 +53,15 @@ public class OverseasFeeSourceService {
.orderByDesc(OverseasFeeSource::getCreatedAt).orderByDesc(OverseasFeeSource::getId));
}
public List<OverseasFeeSource> listForBusiness(String customerId, String businessType, long businessId) {
return 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));
}
public List<ContainerCustomerOption> listContainerCustomers(long containerBookingId) {
return businessGuard.listContainerCustomers(containerBookingId);
}
@@ -70,6 +79,22 @@ public class OverseasFeeSourceService {
return value;
}
@Transactional
public OverseasFeeSource publishApproved(String operatorId, String key, SaveRequest request) {
OverseasFeeSource value = create(operatorId, key, request);
if ("DRAFT".equals(value.getReviewStatus()) || "REJECTED".equals(value.getReviewStatus())) {
value = submit(value.getSourceFeeId(), operatorId, childKey(key, "submit"));
}
if ("PENDING".equals(value.getReviewStatus())) {
value = review(value.getSourceFeeId(), operatorId,
new ReviewRequest("APPROVE", "业务完成自动确认费用", childKey(key, "approve")));
}
if (!"APPROVED".equals(value.getReviewStatus()) || !"PUBLISHED".equals(value.getPublicationStatus())) {
throw conflict("业务费用未能审核发布");
}
return value;
}
@Transactional
public OverseasFeeSource submit(long sourceFeeId, String operatorId, String key) {
OverseasFeeCommand replay = replay(operatorId, "SUBMIT", key, sourceFeeId);
@@ -168,10 +193,14 @@ public class OverseasFeeSourceService {
String expected = switch (value.businessType()) {
case "OVERSEAS_INBOUND" -> "OVERSEAS_INBOUND_FEE";
case "OVERSEAS_PROCUREMENT" -> "OVERSEAS_PROCUREMENT_FEE";
case "PROCUREMENT_FEE" -> "OVERSEAS_PROCUREMENT_FEE";
case "US_TRUCK" -> "OVERSEAS_TRUCK_FEE";
case "LOCAL_LOOSEBOARD" -> "OVERSEAS_LOOSEBOARD_FEE";
case "WAREHOUSE_SERVICE" -> "OVERSEAS_WAREHOUSE_FEE";
case "OVERSEAS_CONTAINER" -> "OVERSEAS_CONTAINER_FEE";
case "LASTLEG_ORDER" -> "OVERSEAS_ORDER_FEE";
case "DELIVERY_ORDER" -> "OVERSEAS_DELIVERY_FEE";
case "RELABEL_ORDER" -> "OVERSEAS_RELABEL_FEE";
default -> "";
};
if (!expected.equals(value.sourceType())) throw bad("来源类型与业务类型不匹配");
@@ -212,6 +241,12 @@ public class OverseasFeeSourceService {
if (existing != null && !existing.getRequestHash().equals(hash(request))) throw conflict("幂等键已被不同请求使用");
return existing;
}
private static String childKey(String key, String suffix) {
String prefix = key == null ? "" : key.trim();
int maxPrefix = Math.max(0, 128 - suffix.length() - 1);
return prefix.substring(0, Math.min(prefix.length(), maxPrefix)) + ':' + suffix;
}
private void command(String operator, String action, String key, Object request, long sourceFeeId, int version) {
OverseasFeeCommand value = new OverseasFeeCommand(); value.setId(IdWorker.getId()); value.setOperatorId(operator);
value.setAction(action); value.setIdempotencyKey(key.trim()); value.setRequestHash(hash(request)); value.setSourceFeeId(sourceFeeId);