费用提交
This commit is contained in:
+8
@@ -19,6 +19,14 @@ public class InternalOverseasStorageFeeController {
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
private byte[] expected;
|
||||
@PostConstruct void init() { expected = internalToken.getBytes(StandardCharsets.UTF_8); }
|
||||
|
||||
/** 内部计租只读入口,先校验服务身份再查询客户配置。 */
|
||||
@GetMapping("/{customerId}/pricing")
|
||||
public ApiResponse<OverseasStorageFeeDtos.PricingView> pricing(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable long customerId) {
|
||||
verify(token); return ApiResponse.success(service.pricing(customerId));
|
||||
}
|
||||
@PostMapping("/defaults")
|
||||
public ApiResponse<OverseasStorageFeeDtos.DefaultInitializationResponse> defaults(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
|
||||
@@ -97,4 +97,9 @@ public final class OverseasStorageFeeDtos {
|
||||
Long operatorId, String customerName) {}
|
||||
public record DefaultInitializationResponse(String configId, boolean createdByThisInitialization) {}
|
||||
public record CompensationRequest(@NotNull @Positive Long tenantId, @NotBlank String initializationKey) {}
|
||||
|
||||
/** 计租专用的客户配置一致性快照;默认档位不生成配置记录。 */
|
||||
@Schema(name = "OverseasStorageFeePricingView", description = "客户海外仓计租配置快照")
|
||||
public record PricingView(String configId, Long customerId, Integer version, String currency,
|
||||
List<TierView> tiers, String source) {}
|
||||
}
|
||||
|
||||
+6
@@ -38,6 +38,12 @@ public class OverseasStorageFeeRepository {
|
||||
public OverseasStorageFeeRuleEntity find(long id) {
|
||||
return ruleMapper.selectById(id);
|
||||
}
|
||||
|
||||
/** 按客户读取唯一有效配置,不借用其他客户或默认初始化标识。 */
|
||||
public OverseasStorageFeeRuleEntity findByCustomer(long customerId) {
|
||||
return ruleMapper.selectOne(new LambdaQueryWrapper<OverseasStorageFeeRuleEntity>()
|
||||
.eq(OverseasStorageFeeRuleEntity::getTenantId, customerId));
|
||||
}
|
||||
public OverseasStorageFeeRuleEntity findByTenantAndKey(long tenantId, String key) {
|
||||
return ruleMapper.selectOne(new LambdaQueryWrapper<OverseasStorageFeeRuleEntity>()
|
||||
.eq(OverseasStorageFeeRuleEntity::getTenantId, tenantId)
|
||||
|
||||
@@ -12,6 +12,7 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -26,6 +27,53 @@ public class OverseasStorageFeeService {
|
||||
@Resource
|
||||
private OverseasStorageFeeRepository repository;
|
||||
|
||||
/** 同一可重复读快照中读取主档和阶梯;异常配置拒绝计租,不写入或修补存量。 */
|
||||
@Transactional(readOnly = true, isolation = Isolation.REPEATABLE_READ)
|
||||
public OverseasStorageFeeDtos.PricingView pricing(long customerId) {
|
||||
if (customerId <= 0) throw new BizException(ResultCode.VALIDATION_FAILED, "客户ID必须为正数");
|
||||
var rule = repository.findByCustomer(customerId);
|
||||
if (rule == null) return new OverseasStorageFeeDtos.PricingView(null, customerId, null, "USD",
|
||||
pricingViews(tierEntities(0, customerId, defaultTiers())), "DEFAULT");
|
||||
if (!Long.valueOf(customerId).equals(rule.getTenantId()) || rule.getId() == null
|
||||
|| rule.getVersion() == null || rule.getVersion() < 0 || Boolean.TRUE.equals(rule.getDeleted())
|
||||
|| rule.getCurrency() == null || !rule.getCurrency().matches("[A-Z]{3}"))
|
||||
throw new BizException(ResultCode.CONFLICT, "客户仓租配置无效,请先核对配置");
|
||||
var effective = new ArrayList<>(repository.findTiers(List.of(rule.getId())));
|
||||
if (effective.stream().anyMatch(tier -> tier == null || !rule.getId().equals(tier.getRuleId())
|
||||
|| !Long.valueOf(customerId).equals(tier.getTenantId())))
|
||||
throw new BizException(ResultCode.CONFLICT, "客户仓租阶梯归属不一致");
|
||||
if (effective.stream().anyMatch(tier -> tier.getStartDay() == null || tier.getStartDay() < 1
|
||||
|| tier.getStartDay() > 36500 || tier.getEndDay() != null && (tier.getEndDay() < 1 || tier.getEndDay() > 36500)))
|
||||
throw new BizException(ResultCode.CONFLICT, "客户仓租阶梯天数无效");
|
||||
for (String type : List.of("STORAGE", "RETURN_STORAGE")) {
|
||||
if (effective.stream().noneMatch(tier -> type.equals(tier.getFeeType()))) {
|
||||
if (!"USD".equals(rule.getCurrency()))
|
||||
throw new BizException(ResultCode.CONFLICT, "非美元仓租配置缺少计租类型,请先补全配置");
|
||||
effective.addAll(tierEntities(rule.getId(), customerId,
|
||||
defaultTiers().stream().filter(tier -> type.equals(tier.feeType())).toList()));
|
||||
}
|
||||
}
|
||||
var normalized = normalize(effective.stream().map(tier -> new OverseasStorageFeeDtos.TierRequest(
|
||||
tier.getFeeType(), tier.getBillingUnit(), tier.getStartDay(), tier.getEndDay(), tier.getUnitPrice())).toList());
|
||||
for (String type : List.of("STORAGE", "RETURN_STORAGE")) {
|
||||
var group = normalized.stream().filter(tier -> type.equals(tier.feeType())).toList();
|
||||
if (group.get(group.size() - 1).endDay() != null)
|
||||
throw new BizException(ResultCode.CONFLICT, "客户仓租阶梯必须包含无上限尾档");
|
||||
}
|
||||
effective.sort(Comparator.comparingInt((OverseasStorageFeeTierEntity tier) ->
|
||||
"STORAGE".equals(tier.getFeeType()) ? 0 : 1).thenComparingInt(OverseasStorageFeeTierEntity::getStartDay));
|
||||
return new OverseasStorageFeeDtos.PricingView(String.valueOf(rule.getId()), customerId, rule.getVersion(),
|
||||
rule.getCurrency(), pricingViews(effective), "CUSTOMER");
|
||||
}
|
||||
|
||||
/** 转换为稳定顺序的只读计租档位,补齐默认组的档位ID保持为空。 */
|
||||
private static List<OverseasStorageFeeDtos.TierView> pricingViews(List<OverseasStorageFeeTierEntity> tiers) {
|
||||
var result = new ArrayList<OverseasStorageFeeDtos.TierView>();
|
||||
for (var tier : tiers) result.add(new OverseasStorageFeeDtos.TierView(tier.getId(), tier.getStartDay(),
|
||||
tier.getEndDay(), tier.getUnitPrice(), result.size() + 1, tier.getFeeType(), tier.getBillingUnit()));
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
public PageResult<OverseasStorageFeeDtos.View> page(OverseasStorageFeeDtos.PageQuery query) {
|
||||
PageResult<OverseasStorageFeeRuleEntity> source = repository.page(query);
|
||||
Map<Long, List<OverseasStorageFeeTierEntity>> tiers = repository.findTiers(
|
||||
|
||||
@@ -59,6 +59,7 @@ BEGIN
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL supplier_add_column_if_missing('license_file_id', "VARCHAR(128) NULL COMMENT 'File服务营业执照文件ID' AFTER business_license_no");
|
||||
CALL supplier_add_column_if_missing('trucking_scope', "VARCHAR(16) NULL COMMENT '拖车类型:DOMESTIC国内拖车、OVERSEAS海外车队;非拖车为空' AFTER category");
|
||||
CALL supplier_add_column_if_missing('created_by', "BIGINT UNSIGNED NULL COMMENT '创建人ID'");
|
||||
CALL supplier_add_column_if_missing('created_at', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间'");
|
||||
CALL supplier_add_column_if_missing('updated_by', "BIGINT UNSIGNED NULL COMMENT '最后修改人ID'");
|
||||
|
||||
@@ -3,6 +3,31 @@ info:
|
||||
title: Warehouse catalog and receiving options
|
||||
version: 1.0.0
|
||||
paths:
|
||||
/internal/warehouse/v1/customer-overseas-storage-fees/{customerId}/pricing:
|
||||
get:
|
||||
summary: 读取客户海外仓自动计租配置
|
||||
description: 内部只读接口,不经公网网关。按客户读取同一REPEATABLE_READ快照中的配置及阶梯,不创建或修改配置。无配置返回完整USD默认中转及退货两表;USD配置漏组仅补齐缺失组。非USD配置漏组、已配置组不连续或缺少无上限尾档时明确失败,不能因读取异常降级为默认价。完整客户配置保留原币种,由调用方按确认汇率换算。
|
||||
security:
|
||||
- internalServiceToken: []
|
||||
parameters:
|
||||
- name: customerId
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
responses:
|
||||
'200':
|
||||
description: 统一业务响应,code为200时返回完整计租快照;客户ID或配置非法返回业务失败code,不返回默认价。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [code]
|
||||
properties:
|
||||
code: {type: string}
|
||||
message: {type: string}
|
||||
data:
|
||||
$ref: '#/components/schemas/OverseasStorageFeePricingView'
|
||||
'403': {description: 内部服务凭据缺失或错误}
|
||||
/internal/warehouses/platform-metadata/batch:
|
||||
post:
|
||||
summary: 内部批量读取公共仓库历史展示资料
|
||||
@@ -94,6 +119,31 @@ paths:
|
||||
'200': {description: 返回仓库 ID、编码、名称、类型和国家编码}
|
||||
components:
|
||||
schemas:
|
||||
OverseasStorageFeePricingView:
|
||||
type: object
|
||||
required: [customerId, currency, tiers, source]
|
||||
properties:
|
||||
configId: {type: string, nullable: true, description: 客户配置ID,无配置时为空}
|
||||
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
version: {type: integer, nullable: true, minimum: 0, description: 客户配置版本,无配置时为空}
|
||||
currency: {type: string, pattern: '^[A-Z]{3}$'}
|
||||
source: {type: string, enum: [CUSTOMER, DEFAULT], description: CUSTOMER包含仅补全缺失默认组的USD客户配置}
|
||||
tiers:
|
||||
type: array
|
||||
minItems: 2
|
||||
items:
|
||||
$ref: '#/components/schemas/OverseasStorageFeePricingTier'
|
||||
OverseasStorageFeePricingTier:
|
||||
type: object
|
||||
required: [startDay, unitPrice, sortNo, feeType, billingUnit]
|
||||
properties:
|
||||
id: {type: string, nullable: true, description: 客户阶梯ID,默认或补齐档位为空}
|
||||
startDay: {type: integer, minimum: 1}
|
||||
endDay: {type: integer, nullable: true, minimum: 1, description: 含当日,空值为无上限尾档}
|
||||
unitPrice: {type: number, minimum: 0, description: 每CBM每天或每托每天的单价,币种取快照currency}
|
||||
sortNo: {type: integer, minimum: 1}
|
||||
feeType: {type: string, enum: [STORAGE, RETURN_STORAGE], description: STORAGE中转仓租,RETURN_STORAGE退货仓租}
|
||||
billingUnit: {type: string, enum: [CBM, PALLET]}
|
||||
ReceivingWarehouseOption:
|
||||
type: object
|
||||
required: [id, customerId, warehouseCode, warehouseName, warehouseType, status]
|
||||
|
||||
Reference in New Issue
Block a user