新增海外仓阶梯费用
This commit is contained in:
+93
@@ -0,0 +1,93 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.warehouse.dto.OverseasStorageFeeDtos;
|
||||
import com.baiyue.warehouse.service.OverseasStorageFeeService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 平台海外仓阶梯费用管理接口。 */
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/overseas-storage-fees")
|
||||
@Tag(name = "海外仓阶梯费用", description = "按客户维护海外仓仓储费阶梯")
|
||||
public class PlatformOverseasStorageFeeController {
|
||||
@Resource
|
||||
private OverseasStorageFeeService service;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('platform:overseas-storage-fee:list')")
|
||||
@Operation(summary = "分页查询海外仓阶梯费用")
|
||||
public ApiResponse<PageResult<OverseasStorageFeeDtos.View>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @ParameterObject OverseasStorageFeeDtos.PageQuery query) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.page(query));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('platform:overseas-storage-fee:list')")
|
||||
@Operation(summary = "查询海外仓阶梯费用详情")
|
||||
public ApiResponse<OverseasStorageFeeDtos.View> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Min(1) long id) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.detail(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('platform:overseas-storage-fee:create')")
|
||||
@Operation(summary = "新增海外仓阶梯费用")
|
||||
public ApiResponse<Long> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody OverseasStorageFeeDtos.CreateRequest request) {
|
||||
requirePlatform(user);
|
||||
return ApiResponse.success(service.create(request));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('platform:overseas-storage-fee:edit')")
|
||||
@Operation(summary = "编辑海外仓阶梯费用")
|
||||
public ApiResponse<Void> update(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Min(1) long id,
|
||||
@Valid @RequestBody OverseasStorageFeeDtos.UpdateRequest request) {
|
||||
requirePlatform(user);
|
||||
service.update(id, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('platform:overseas-storage-fee:delete')")
|
||||
@Operation(summary = "删除海外仓阶梯费用")
|
||||
public ApiResponse<Void> delete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Min(1) long id,
|
||||
@RequestParam @Min(0) int version) {
|
||||
requirePlatform(user);
|
||||
service.delete(id, version);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
private static void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || !"PLATFORM".equals(user.appCode())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "仅允许平台端访问");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.Digits;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/** 海外仓阶梯费用接口契约。 */
|
||||
public final class OverseasStorageFeeDtos {
|
||||
private OverseasStorageFeeDtos() {
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeePageQuery", description = "海外仓阶梯费用分页条件")
|
||||
public record PageQuery(
|
||||
@Min(1) long page,
|
||||
@Min(1) @Max(200) long size,
|
||||
@Schema(description = "客户ID,不传时查询全部客户") @Positive Long customerId,
|
||||
@Schema(description = "客户公司名称关键字") @Size(max = 128) String customerName
|
||||
) {
|
||||
public PageQuery {
|
||||
if (page == 0) {
|
||||
page = 1;
|
||||
}
|
||||
if (size == 0) {
|
||||
size = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeeTierRequest", description = "仓储费阶梯")
|
||||
public record TierRequest(
|
||||
@NotNull @Min(1) @Max(36500) Integer startDay,
|
||||
@NotNull @Min(1) @Max(36500) Integer endDay,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 6)
|
||||
BigDecimal unitPrice
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeeCreateRequest", description = "新增海外仓阶梯费用")
|
||||
public record CreateRequest(
|
||||
@NotNull @Positive Long customerId,
|
||||
@NotBlank @Size(max = 128) String customerName,
|
||||
@NotBlank @Pattern(regexp = "^[A-Z]{3}$") String currency,
|
||||
@NotEmpty @Size(max = 100) List<@Valid TierRequest> tiers
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeeUpdateRequest", description = "编辑海外仓阶梯费用")
|
||||
public record UpdateRequest(
|
||||
@NotNull @Positive Long customerId,
|
||||
@NotBlank @Size(max = 128) String customerName,
|
||||
@NotBlank @Pattern(regexp = "^[A-Z]{3}$") String currency,
|
||||
@NotEmpty @Size(max = 100) List<@Valid TierRequest> tiers,
|
||||
@NotNull @Min(0) Integer version
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeeTierView", description = "仓储费阶梯明细")
|
||||
public record TierView(Long id, Integer startDay, Integer endDay, BigDecimal unitPrice, Integer sortNo) {
|
||||
}
|
||||
|
||||
@Schema(name = "OverseasStorageFeeView", description = "海外仓阶梯费用详情")
|
||||
public record View(
|
||||
Long id,
|
||||
Long customerId,
|
||||
String customerName,
|
||||
String currency,
|
||||
List<TierView> tiers,
|
||||
Integer version,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/** 客户维度的海外仓阶梯仓储费主记录。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("wh_overseas_storage_fee_rule")
|
||||
public class OverseasStorageFeeRuleEntity extends BaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private String customerName;
|
||||
private String currency;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/** 海外仓仓储费阶梯明细。 */
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("wh_overseas_storage_fee_tier")
|
||||
public class OverseasStorageFeeTierEntity extends BaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long ruleId;
|
||||
private Integer startDay;
|
||||
private Integer endDay;
|
||||
private BigDecimal unitPrice;
|
||||
private Integer sortNo;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.baiyue.warehouse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeRuleEntity;
|
||||
|
||||
/** 海外仓阶梯费用主记录 Mapper。 */
|
||||
public interface OverseasStorageFeeRuleMapper extends BaseMapper<OverseasStorageFeeRuleEntity> {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.baiyue.warehouse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeTierEntity;
|
||||
|
||||
/** 海外仓阶梯费用明细 Mapper。 */
|
||||
public interface OverseasStorageFeeTierMapper extends BaseMapper<OverseasStorageFeeTierEntity> {
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.warehouse.dto.OverseasStorageFeeDtos;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeRuleEntity;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeTierEntity;
|
||||
import com.baiyue.warehouse.mapper.OverseasStorageFeeRuleMapper;
|
||||
import com.baiyue.warehouse.mapper.OverseasStorageFeeTierMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/** 海外仓阶梯费用持久化仓储。 */
|
||||
@Repository
|
||||
public class OverseasStorageFeeRepository {
|
||||
@Resource
|
||||
private OverseasStorageFeeRuleMapper ruleMapper;
|
||||
@Resource
|
||||
private OverseasStorageFeeTierMapper tierMapper;
|
||||
|
||||
public PageResult<OverseasStorageFeeRuleEntity> page(OverseasStorageFeeDtos.PageQuery query) {
|
||||
LambdaQueryWrapper<OverseasStorageFeeRuleEntity> wrapper =
|
||||
new LambdaQueryWrapper<OverseasStorageFeeRuleEntity>()
|
||||
.eq(query.customerId() != null, OverseasStorageFeeRuleEntity::getTenantId, query.customerId())
|
||||
.like(StringUtils.hasText(query.customerName()),
|
||||
OverseasStorageFeeRuleEntity::getCustomerName, query.customerName())
|
||||
.orderByDesc(OverseasStorageFeeRuleEntity::getUpdatedAt)
|
||||
.orderByDesc(OverseasStorageFeeRuleEntity::getId);
|
||||
return PageResult.from(ruleMapper.selectPage(new Page<>(query.page(), query.size()), wrapper));
|
||||
}
|
||||
|
||||
public OverseasStorageFeeRuleEntity find(long id) {
|
||||
return ruleMapper.selectById(id);
|
||||
}
|
||||
|
||||
public boolean existsCustomer(long customerId, Long excludedId) {
|
||||
return ruleMapper.selectCount(new LambdaQueryWrapper<OverseasStorageFeeRuleEntity>()
|
||||
.eq(OverseasStorageFeeRuleEntity::getTenantId, customerId)
|
||||
.ne(excludedId != null, OverseasStorageFeeRuleEntity::getId, excludedId)) > 0;
|
||||
}
|
||||
|
||||
public List<OverseasStorageFeeTierEntity> findTiers(Collection<Long> ruleIds) {
|
||||
if (ruleIds == null || ruleIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return tierMapper.selectList(new LambdaQueryWrapper<OverseasStorageFeeTierEntity>()
|
||||
.in(OverseasStorageFeeTierEntity::getRuleId, ruleIds)
|
||||
.orderByAsc(OverseasStorageFeeTierEntity::getRuleId)
|
||||
.orderByAsc(OverseasStorageFeeTierEntity::getSortNo)
|
||||
.orderByAsc(OverseasStorageFeeTierEntity::getId));
|
||||
}
|
||||
|
||||
public void insertRule(OverseasStorageFeeRuleEntity entity) {
|
||||
ruleMapper.insert(entity);
|
||||
}
|
||||
|
||||
public int updateRule(OverseasStorageFeeRuleEntity entity) {
|
||||
return ruleMapper.updateById(entity);
|
||||
}
|
||||
|
||||
public void insertTiers(List<OverseasStorageFeeTierEntity> tiers) {
|
||||
tiers.forEach(tierMapper::insert);
|
||||
}
|
||||
|
||||
public void deleteTiers(long ruleId) {
|
||||
tierMapper.delete(new LambdaQueryWrapper<OverseasStorageFeeTierEntity>()
|
||||
.eq(OverseasStorageFeeTierEntity::getRuleId, ruleId));
|
||||
}
|
||||
|
||||
public int deleteRule(long id, int version) {
|
||||
return ruleMapper.update(null, new LambdaUpdateWrapper<OverseasStorageFeeRuleEntity>()
|
||||
.eq(OverseasStorageFeeRuleEntity::getId, id)
|
||||
.eq(OverseasStorageFeeRuleEntity::getVersion, version)
|
||||
.set(OverseasStorageFeeRuleEntity::getDeleted, true)
|
||||
.set(OverseasStorageFeeRuleEntity::getVersion, version + 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.OverseasStorageFeeDtos;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeRuleEntity;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeTierEntity;
|
||||
import com.baiyue.warehouse.repository.OverseasStorageFeeRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** 海外仓阶梯费用管理服务。 */
|
||||
@Service
|
||||
public class OverseasStorageFeeService {
|
||||
@Resource
|
||||
private OverseasStorageFeeRepository repository;
|
||||
|
||||
public PageResult<OverseasStorageFeeDtos.View> page(OverseasStorageFeeDtos.PageQuery query) {
|
||||
PageResult<OverseasStorageFeeRuleEntity> source = repository.page(query);
|
||||
Map<Long, List<OverseasStorageFeeTierEntity>> tiers = repository.findTiers(
|
||||
source.records().stream().map(OverseasStorageFeeRuleEntity::getId).toList())
|
||||
.stream().collect(Collectors.groupingBy(OverseasStorageFeeTierEntity::getRuleId));
|
||||
return new PageResult<>(source.records().stream()
|
||||
.map(rule -> view(rule, tiers.getOrDefault(rule.getId(), List.of())))
|
||||
.toList(), source.page(), source.size(), source.total(), source.pages());
|
||||
}
|
||||
|
||||
public OverseasStorageFeeDtos.View detail(long id) {
|
||||
OverseasStorageFeeRuleEntity rule = required(id);
|
||||
return view(rule, repository.findTiers(List.of(id)));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "OVERSEAS_STORAGE_FEE_CREATE", resourceType = "OVERSEAS_STORAGE_FEE",
|
||||
description = "新增海外仓阶梯费用", captureBefore = false)
|
||||
public Long create(OverseasStorageFeeDtos.CreateRequest request) {
|
||||
ensureCustomerUnique(request.customerId(), null);
|
||||
List<OverseasStorageFeeDtos.TierRequest> tiers = normalize(request.tiers());
|
||||
OverseasStorageFeeRuleEntity rule = new OverseasStorageFeeRuleEntity();
|
||||
copy(rule, request.customerId(), request.customerName(), request.currency());
|
||||
try {
|
||||
repository.insertRule(rule);
|
||||
repository.insertTiers(tierEntities(rule.getId(), request.customerId(), tiers));
|
||||
} catch (DataIntegrityViolationException exception) {
|
||||
throw new BizException(ResultCode.CONFLICT, "该客户已配置海外仓阶梯费用或阶梯区间重复");
|
||||
}
|
||||
return rule.getId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "OVERSEAS_STORAGE_FEE_UPDATE", resourceType = "OVERSEAS_STORAGE_FEE",
|
||||
resourceId = "#id", description = "编辑海外仓阶梯费用")
|
||||
public void update(long id, OverseasStorageFeeDtos.UpdateRequest request) {
|
||||
OverseasStorageFeeRuleEntity rule = required(id);
|
||||
if (!rule.getVersion().equals(request.version())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "费用配置已被修改,请刷新后重试");
|
||||
}
|
||||
ensureCustomerUnique(request.customerId(), id);
|
||||
List<OverseasStorageFeeDtos.TierRequest> tiers = normalize(request.tiers());
|
||||
copy(rule, request.customerId(), request.customerName(), request.currency());
|
||||
try {
|
||||
if (repository.updateRule(rule) != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "费用配置已被并发修改,请刷新后重试");
|
||||
}
|
||||
repository.deleteTiers(id);
|
||||
repository.insertTiers(tierEntities(id, request.customerId(), tiers));
|
||||
} catch (DataIntegrityViolationException exception) {
|
||||
throw new BizException(ResultCode.CONFLICT, "该客户已配置海外仓阶梯费用或阶梯区间重复");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "OVERSEAS_STORAGE_FEE_DELETE", resourceType = "OVERSEAS_STORAGE_FEE",
|
||||
resourceId = "#id", description = "删除海外仓阶梯费用")
|
||||
public void delete(long id, int version) {
|
||||
required(id);
|
||||
repository.deleteTiers(id);
|
||||
if (repository.deleteRule(id, version) != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "费用配置已被并发修改,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
private OverseasStorageFeeRuleEntity required(long id) {
|
||||
OverseasStorageFeeRuleEntity rule = repository.find(id);
|
||||
if (rule == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "海外仓阶梯费用不存在");
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
private void ensureCustomerUnique(long customerId, Long excludedId) {
|
||||
if (repository.existsCustomer(customerId, excludedId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "该客户已配置海外仓阶梯费用");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<OverseasStorageFeeDtos.TierRequest> normalize(
|
||||
List<OverseasStorageFeeDtos.TierRequest> source) {
|
||||
if (source == null || source.isEmpty() || source.size() > 100) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "阶梯费用明细必须为1至100组");
|
||||
}
|
||||
if (source.stream().anyMatch(tier -> tier == null || tier.startDay() == null
|
||||
|| tier.endDay() == null || tier.unitPrice() == null
|
||||
|| tier.unitPrice().signum() <= 0)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "阶梯天数和仓储费标准必须为有效正数");
|
||||
}
|
||||
List<OverseasStorageFeeDtos.TierRequest> tiers = new ArrayList<>(source);
|
||||
tiers.sort(Comparator.comparingInt(OverseasStorageFeeDtos.TierRequest::startDay));
|
||||
int expectedStart = 1;
|
||||
for (OverseasStorageFeeDtos.TierRequest tier : tiers) {
|
||||
if (tier.startDay() != expectedStart || tier.endDay() < tier.startDay()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"阶梯必须从第1天开始,且区间连续、不能重叠或倒置");
|
||||
}
|
||||
expectedStart = tier.endDay() + 1;
|
||||
}
|
||||
return List.copyOf(tiers);
|
||||
}
|
||||
|
||||
private static void copy(OverseasStorageFeeRuleEntity rule, long customerId,
|
||||
String customerName, String currency) {
|
||||
rule.setTenantId(customerId);
|
||||
rule.setCustomerName(customerName.trim());
|
||||
rule.setCurrency(currency);
|
||||
}
|
||||
|
||||
private static List<OverseasStorageFeeTierEntity> tierEntities(
|
||||
long ruleId, long customerId, List<OverseasStorageFeeDtos.TierRequest> source) {
|
||||
List<OverseasStorageFeeTierEntity> result = new ArrayList<>(source.size());
|
||||
for (int index = 0; index < source.size(); index++) {
|
||||
OverseasStorageFeeDtos.TierRequest request = source.get(index);
|
||||
OverseasStorageFeeTierEntity tier = new OverseasStorageFeeTierEntity();
|
||||
tier.setTenantId(customerId);
|
||||
tier.setRuleId(ruleId);
|
||||
tier.setStartDay(request.startDay());
|
||||
tier.setEndDay(request.endDay());
|
||||
tier.setUnitPrice(request.unitPrice());
|
||||
tier.setSortNo(index + 1);
|
||||
result.add(tier);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static OverseasStorageFeeDtos.View view(OverseasStorageFeeRuleEntity rule,
|
||||
List<OverseasStorageFeeTierEntity> tiers) {
|
||||
return new OverseasStorageFeeDtos.View(rule.getId(), rule.getTenantId(), rule.getCustomerName(),
|
||||
rule.getCurrency(), tiers.stream().map(tier -> new OverseasStorageFeeDtos.TierView(
|
||||
tier.getId(), tier.getStartDay(), tier.getEndDay(), tier.getUnitPrice(), tier.getSortNo()))
|
||||
.toList(), rule.getVersion(), rule.getCreatedAt(), rule.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -22,7 +22,9 @@ public class WarehouseSchemaReadinessService {
|
||||
"SELECT id, tenant_id, company_code, company_name, status, deleted FROM wh_express_company LIMIT 0",
|
||||
"SELECT id, tenant_id, warehouse_id, zone_code, zone_name, location_code, location_type, max_weight_kg, shared_overflow, status, deleted, version FROM wh_location LIMIT 0",
|
||||
"SELECT id, tenant_id, warehouse_id, pallet_id, location_id, shared_overflow, reservation_status, expires_at, version FROM wh_location_reservation LIMIT 0",
|
||||
"SELECT id, tenant_id, warehouse_id, pallet_id, location_id, shared_overflow, occupancy_status, active_location_occupancy, active_pallet_occupancy, version FROM wh_location_occupancy LIMIT 0"
|
||||
"SELECT id, tenant_id, warehouse_id, pallet_id, location_id, shared_overflow, occupancy_status, active_location_occupancy, active_pallet_occupancy, version FROM wh_location_occupancy LIMIT 0",
|
||||
"SELECT id, tenant_id, customer_name, currency, version FROM wh_overseas_storage_fee_rule LIMIT 0",
|
||||
"SELECT id, tenant_id, rule_id, start_day, end_day, unit_price, sort_no, version FROM wh_overseas_storage_fee_tier LIMIT 0"
|
||||
);
|
||||
|
||||
@Resource
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformOverseasStorageFeeControllerContractTest {
|
||||
@Test
|
||||
void exposesCrud() {
|
||||
assertThat(PlatformOverseasStorageFeeController.class.getAnnotation(RequestMapping.class).value())
|
||||
.containsExactly("/api/platform/v1/overseas-storage-fees");
|
||||
assertMethod("page", GetMapping.class, "platform:overseas-storage-fee:list");
|
||||
assertMethod("detail", GetMapping.class, "platform:overseas-storage-fee:list");
|
||||
assertMethod("create", PostMapping.class, "platform:overseas-storage-fee:create");
|
||||
assertMethod("update", PutMapping.class, "platform:overseas-storage-fee:edit");
|
||||
assertMethod("delete", DeleteMapping.class, "platform:overseas-storage-fee:delete");
|
||||
}
|
||||
|
||||
private static void assertMethod(String name, Class<? extends Annotation> mapping, String authority) {
|
||||
Method method = Arrays.stream(PlatformOverseasStorageFeeController.class.getDeclaredMethods())
|
||||
.filter(candidate -> candidate.getName().equals(name)).findFirst().orElseThrow();
|
||||
assertThat(method.getAnnotation(mapping)).isNotNull();
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
.isEqualTo("hasAuthority('" + authority + "')");
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OverseasStorageFeeSqlContractTest {
|
||||
@Test
|
||||
void containsSchemaAndPermissions() throws IOException {
|
||||
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
|
||||
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_rule")
|
||||
.contains("CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_tier")
|
||||
.contains("uk_overseas_fee_active_customer")
|
||||
.contains("idx_overseas_fee_page")
|
||||
.contains("idx_overseas_fee_customer_name")
|
||||
.contains("uk_overseas_fee_tier_start")
|
||||
.contains("idx_overseas_fee_tier_rule")
|
||||
.contains("platform:overseas-storage-fee:list")
|
||||
.contains("platform:overseas-storage-fee:create")
|
||||
.contains("platform:overseas-storage-fee:edit")
|
||||
.contains("platform:overseas-storage-fee:delete")
|
||||
.contains("CALL unified_assert_overseas_storage_fee()")
|
||||
.contains("海外仓阶梯费用表、索引和权限校验通过");
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.OverseasStorageFeeDtos;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeRuleEntity;
|
||||
import com.baiyue.warehouse.entity.OverseasStorageFeeTierEntity;
|
||||
import com.baiyue.warehouse.repository.OverseasStorageFeeRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(OverseasStorageFeeService.class)
|
||||
class OverseasStorageFeeServiceTest {
|
||||
@MockBean
|
||||
private OverseasStorageFeeRepository repository;
|
||||
@Resource
|
||||
private OverseasStorageFeeService service;
|
||||
|
||||
@Test
|
||||
void createsRule() {
|
||||
doAnswer(invocation -> {
|
||||
OverseasStorageFeeRuleEntity rule = invocation.getArgument(0);
|
||||
rule.setId(10L);
|
||||
return null;
|
||||
}).when(repository).insertRule(any());
|
||||
|
||||
Long id = service.create(new OverseasStorageFeeDtos.CreateRequest(
|
||||
10001L, "示例客户", "USD", List.of(tier(8, 30, "0.8"), tier(1, 7, "0.5"))));
|
||||
|
||||
assertThat(id).isEqualTo(10L);
|
||||
verify(repository).insertTiers(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTierGap() {
|
||||
OverseasStorageFeeDtos.CreateRequest request = new OverseasStorageFeeDtos.CreateRequest(
|
||||
10001L, "示例客户", "USD", List.of(tier(1, 7, "0.5"), tier(9, 30, "0.8")));
|
||||
|
||||
assertThatThrownBy(() -> service.create(request)).isInstanceOf(BizException.class);
|
||||
verify(repository, never()).insertRule(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateCustomer() {
|
||||
when(repository.existsCustomer(10001L, null)).thenReturn(true);
|
||||
OverseasStorageFeeDtos.CreateRequest request = new OverseasStorageFeeDtos.CreateRequest(
|
||||
10001L, "示例客户", "USD", List.of(tier(1, 30, "0.8")));
|
||||
|
||||
assertThatThrownBy(() -> service.create(request)).isInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pagesWithTiers() {
|
||||
OverseasStorageFeeRuleEntity rule = rule();
|
||||
OverseasStorageFeeTierEntity tier = new OverseasStorageFeeTierEntity();
|
||||
tier.setId(20L);
|
||||
tier.setRuleId(10L);
|
||||
tier.setStartDay(1);
|
||||
tier.setEndDay(30);
|
||||
tier.setUnitPrice(new BigDecimal("0.8"));
|
||||
tier.setSortNo(1);
|
||||
OverseasStorageFeeDtos.PageQuery query = new OverseasStorageFeeDtos.PageQuery(1, 20, null, null);
|
||||
when(repository.page(query)).thenReturn(new PageResult<>(List.of(rule), 1, 20, 1, 1));
|
||||
when(repository.findTiers(List.of(10L))).thenReturn(List.of(tier));
|
||||
|
||||
OverseasStorageFeeDtos.View result = service.page(query).records().get(0);
|
||||
|
||||
assertThat(result.customerId()).isEqualTo(10001L);
|
||||
assertThat(result.tiers()).singleElement().extracting(OverseasStorageFeeDtos.TierView::unitPrice)
|
||||
.isEqualTo(new BigDecimal("0.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsStaleUpdate() {
|
||||
OverseasStorageFeeRuleEntity rule = rule();
|
||||
when(repository.find(10L)).thenReturn(rule);
|
||||
OverseasStorageFeeDtos.UpdateRequest request = new OverseasStorageFeeDtos.UpdateRequest(
|
||||
10001L, "示例客户", "USD", List.of(tier(1, 30, "0.8")), 2);
|
||||
|
||||
assertThatThrownBy(() -> service.update(10L, request)).isInstanceOf(BizException.class);
|
||||
verify(repository, never()).updateRule(any());
|
||||
}
|
||||
|
||||
private static OverseasStorageFeeDtos.TierRequest tier(int start, int end, String price) {
|
||||
return new OverseasStorageFeeDtos.TierRequest(start, end, new BigDecimal(price));
|
||||
}
|
||||
|
||||
private static OverseasStorageFeeRuleEntity rule() {
|
||||
OverseasStorageFeeRuleEntity rule = new OverseasStorageFeeRuleEntity();
|
||||
rule.setId(10L);
|
||||
rule.setTenantId(10001L);
|
||||
rule.setCustomerName("示例客户");
|
||||
rule.setCurrency("USD");
|
||||
rule.setVersion(1);
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user