Commit a4c6d02d by jianan

出账检核-分期出账

parent 5c35da16
......@@ -412,6 +412,22 @@ public class ApiFortuneController {
}
/**
* 分期出账
*
* @param fortuneSplitRequest 分期出账请求
* @return
*/
@PostMapping("/split")
@Operation(summary = "分期出账")
public Result<Boolean> splitFortune(@RequestBody FortuneSplitRequest fortuneSplitRequest) {
if (fortuneSplitRequest == null || CollectionUtils.isEmpty(fortuneSplitRequest.getFortuneSplitDtoList())) {
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(), "分期出账请求不能为空");
}
// 操作数据库
return Result.success(fortuneService.splitFortune(fortuneSplitRequest));
}
/**
* 修改出账状态
*
* @param fortuneStatusUpdateRequest
......
......@@ -95,6 +95,12 @@ public class FortuneAddRequest implements Serializable {
private String currency;
/**
* 结算汇率
*/
@Schema(description = "结算汇率", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal exchangeRate;
/**
* 出账日期
*/
@Schema(description = "出账日期", requiredMode = Schema.RequiredMode.REQUIRED)
......
package com.yd.csf.service.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class FortuneSplitDto {
/**
* 出账比例
*/
@Schema(description = "出账比例")
private BigDecimal splitRatio;
/**
* 原币种金额(自动计算)
*/
@Schema(description = "原币种金额(自动计算)")
private BigDecimal originalAmount;
/**
* 结算汇率
*/
@Schema(description = "结算汇率")
private BigDecimal exchangeRate;
/**
* 港币出账金额
*/
@Schema(description = "港币出账金额")
private BigDecimal hkdAmount;
/**
* 出账年月(估)
*/
@Schema(description = "出账年月(估)")
private String payoutYearMonth;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
}
package com.yd.csf.service.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class FortuneSplitRequest implements Serializable {
/**
* 保单出账业务id
*/
@Schema(description = "保单出账业务id", requiredMode = Schema.RequiredMode.REQUIRED)
private String fortuneBizId;
/**
* 分期出账列表
*/
@Schema(description = "分期出账列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<FortuneSplitDto> fortuneSplitDtoList;
private static final long serialVersionUID = 1L;
}
......@@ -40,4 +40,6 @@ public interface FortuneService extends IService<Fortune> {
FortuneStatisticsVO getFortuneStatistics(List<Long> fortuneIdList);
Boolean addFortuneBatch(List<FortuneAddRequest> fortuneAddRequestList);
Boolean splitFortune(FortuneSplitRequest fortuneSplitRequest);
}
......@@ -45,4 +45,6 @@ public interface IExpectedFortuneService extends IService<ExpectedFortune> {
IPage<PayableReportVO> payableReportPage(Page<PayableReportVO> page, List<Long> expectedFortuneIds);
void updateBatchByBizId(List<String> expectedFortuneBizIdList, String status);
ExpectedFortune getByBizId(String expectedFortuneBizId);
}
......@@ -2,6 +2,7 @@ package com.yd.csf.service.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.csf.feign.request.expectedfortune.ApiExpectedFortunePageRequest;
......@@ -10,6 +11,7 @@ import com.yd.csf.service.dto.UserGradeDto;
import com.yd.csf.service.enums.CurrencyEnum;
import com.yd.csf.service.model.ExpectedFortune;
import com.yd.csf.service.dao.ExpectedFortuneMapper;
import com.yd.csf.service.model.Fortune;
import com.yd.csf.service.model.Policy;
import com.yd.csf.service.model.PolicyFollow;
import com.yd.csf.service.service.*;
......@@ -175,4 +177,9 @@ public class ExpectedFortuneServiceImpl extends ServiceImpl<ExpectedFortuneMappe
public void updateBatchByBizId(List<String> expectedFortuneBizIdList, String status) {
baseMapper.updateBatchByBizId(expectedFortuneBizIdList, status);
}
@Override
public ExpectedFortune getByBizId(String expectedFortuneBizId) {
return this.getOne(new QueryWrapper<ExpectedFortune>().eq("expected_fortune_biz_id", expectedFortuneBizId));
}
}
......@@ -2,6 +2,7 @@ package com.yd.csf.service.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
......@@ -12,7 +13,6 @@ import com.google.common.base.Joiner;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.auth.core.utils.SecurityUtil;
import com.yd.base.feign.client.exchangerate.ApiExchangeRateFeignClient;
import com.yd.common.constant.RedisConstants;
import com.yd.common.enums.CommonEnum;
import com.yd.common.enums.ResultCode;
import com.yd.common.exception.BusinessException;
......@@ -42,6 +42,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
......@@ -553,6 +554,9 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
if (ObjectUtils.isEmpty(fortuneAddRequest.getCurrency())) {
throw new BusinessException(ResultCode.FAIL.getCode(), "出账币种不能为空");
}
if (ObjectUtils.isEmpty(fortuneAddRequest.getExchangeRate())) {
throw new BusinessException(ResultCode.FAIL.getCode(), "结算汇率不能为空");
}
if (ObjectUtils.isEmpty(fortuneAddRequest.getFortuneType())) {
throw new BusinessException(ResultCode.FAIL.getCode(), "出账项目不能为空");
}
......@@ -570,13 +574,6 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
}
private String queryByDict(String fortuneType) {
//查询redis缓存的字典列表信息
List<GetDictItemListByDictTypeResponse> dictTypeResponses = redisUtil.getCacheObject(RedisConstants.DICT_LIST);
String fortuneName = GetDictItemListByDictTypeResponse.getItemLabel(dictTypeResponses,
"csf_fortune_type", fortuneType);
if (ObjectUtils.isNotEmpty(fortuneName)) {
return fortuneName;
}
Result<List<GetDictItemListByDictTypeResponse>> result = apiSysDictFeignClient.getDictItemListByDictType("csf_fortune_type");
if (CollectionUtils.isNotEmpty(result.getData())) {
for (GetDictItemListByDictTypeResponse dictItem : result.getData()) {
......@@ -723,8 +720,10 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
Fortune fortune = new Fortune();
BeanUtil.copyProperties(fortuneAddRequest, fortune);
// 计算港币金额
fortune.setExchangeRate(queryExchangeRateByFeign(fortuneAddRequest.getCurrency(), "HKD"));
fortune.setHkdAmount(fortuneAddRequest.getAmount().multiply(fortune.getExchangeRate()));
fortune.setHkdAmount(
NumberUtil.mul(fortuneAddRequest.getAmount(), fortuneAddRequest.getExchangeRate())
.setScale(2, RoundingMode.HALF_UP)
);
fortune.setCurrentPaymentHkdAmount(fortune.getHkdAmount());
if ("R".equals(fortuneAddRequest.getFortuneBizType())) {
......@@ -770,6 +769,213 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean splitFortune(FortuneSplitRequest fortuneSplitRequest) {
// 1. 参数验证
validSplitFortune(fortuneSplitRequest);
// 2. 查询并验证原始记录
Fortune originalFortune = this.getByFortuneBizId(fortuneSplitRequest.getFortuneBizId());
if (originalFortune == null) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "原出账记录不存在");
}
ExpectedFortune originalExpectedFortune = expectedFortuneService.getByBizId(originalFortune.getExpectedFortuneBizId());
if (originalExpectedFortune == null) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "对应的预计出账记录不存在");
}
if (!FortuneStatusEnum.CAN_SEND.getItemValue().equals(originalFortune.getStatus()) &&
!FortuneStatusEnum.RESERVED.getItemValue().equals(originalFortune.getStatus())) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "只有可出账或保留状态的记录才能分期");
}
if (originalFortune.getIsPart() != null && originalFortune.getIsPart() == 1) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "该记录已是分期拆分记录,不能再次分期");
}
if (StringUtils.isNotBlank(originalFortune.getFortuneAccountBizId())) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "该记录已有薪资发放,不能分期");
}
List<FortuneSplitDto> splitList = fortuneSplitRequest.getFortuneSplitDtoList();
// 3. 金额验证
BigDecimal totalSplitHkdAmount = splitList.stream()
.map(FortuneSplitDto::getHkdAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalSplitRatio = splitList.stream()
.map(FortuneSplitDto::getSplitRatio)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal tolerance = new BigDecimal("0.01");
BigDecimal hkdDifference = originalFortune.getHkdAmount().subtract(totalSplitHkdAmount).abs();
if (hkdDifference.compareTo(tolerance) > 0) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(),
String.format("分期港币金额总和(%s)与原记录港币金额(%s)不匹配", totalSplitHkdAmount, originalFortune.getHkdAmount()));
}
if (totalSplitRatio.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal ratioDifference = new BigDecimal("100").subtract(totalSplitRatio).abs();
if (ratioDifference.compareTo(new BigDecimal("0.1")) > 0) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(),
String.format("分期比例总和(%s%%)应为100%%", totalSplitRatio));
}
}
// 4. 生成新记录
List<Fortune> newFortuneList = new ArrayList<>();
List<ExpectedFortune> newExpectedFortuneList = new ArrayList<>();
Date now = new Date();
LocalDateTime localDateTime = LocalDateTime.now();
AuthUserDto currentLoginUser = SecurityUtil.getCurrentLoginUser();
String loginUserId = currentLoginUser.getId().toString();
String username = currentLoginUser.getUsername();
for (FortuneSplitDto splitDto : splitList) {
// 4.1 生成新的 ExpectedFortune
ExpectedFortune newExpectedFortune = new ExpectedFortune();
BeanUtils.copyProperties(originalExpectedFortune, newExpectedFortune,
"id", "expectedFortuneBizId", "amount", "hkdAmount",
"paidAmount", "unpaidAmount", "paidRatio", "unpaidRatio",
"status", "payoutDate", "actualPayoutDate", "remark");
String newExpectedFortuneBizId = RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EXPECTED_FORTUNE.getCode());
newExpectedFortune.setExpectedFortuneBizId(newExpectedFortuneBizId);
BigDecimal originalAmount = splitDto.getOriginalAmount();
newExpectedFortune.setAmount(originalAmount);
newExpectedFortune.setHkdAmount(splitDto.getHkdAmount());
newExpectedFortune.setDefaultExchangeRate(splitDto.getExchangeRate());
newExpectedFortune.setPaidAmount(BigDecimal.ZERO);
newExpectedFortune.setUnpaidAmount(splitDto.getHkdAmount());
newExpectedFortune.setPaidRatio(BigDecimal.ZERO);
newExpectedFortune.setUnpaidRatio(BigDecimal.valueOf(100));
newExpectedFortune.setStatus("0");
String[] yearMonth = splitDto.getPayoutYearMonth().split("-");
newExpectedFortune.setPayoutDate(LocalDate.of(
Integer.parseInt(yearMonth[0]),
Integer.parseInt(yearMonth[1]),
1
));
String expectedRemark = StringUtils.isBlank(splitDto.getRemark()) ? "" : splitDto.getRemark();
if (StringUtils.isNotBlank(originalExpectedFortune.getRemark())) {
expectedRemark = StringUtils.isBlank(expectedRemark) ?
originalExpectedFortune.getRemark() :
originalExpectedFortune.getRemark() + "; " + expectedRemark;
}
newExpectedFortune.setRemark(expectedRemark);
newExpectedFortune.setCreatorId(loginUserId);
newExpectedFortune.setCreateTime(localDateTime);
newExpectedFortune.setUpdaterId(loginUserId);
newExpectedFortune.setUpdateTime(localDateTime);
newExpectedFortuneList.add(newExpectedFortune);
// 4.2 生成新的 Fortune
Fortune newFortune = new Fortune();
BeanUtils.copyProperties(originalFortune, newFortune,
"id", "fortuneBizId", "expectedFortuneBizId",
"amount", "hkdAmount", "currentPaymentAmount", "currentPaymentHkdAmount",
"currentPaymentRatio", "status", "isPart",
"payoutDate", "actualPayoutDate", "remark");
newFortune.setFortuneBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_FORTUNE.getCode()));
newFortune.setExpectedFortuneBizId(newExpectedFortuneBizId);
newFortune.setAmount(originalAmount);
newFortune.setHkdAmount(splitDto.getHkdAmount());
newFortune.setExchangeRate(splitDto.getExchangeRate());
newFortune.setCurrentPaymentAmount(originalAmount);
newFortune.setCurrentPaymentHkdAmount(splitDto.getHkdAmount());
newFortune.setCurrentPaymentRatio(splitDto.getSplitRatio());
newFortune.setStatus(FortuneStatusEnum.CAN_SEND.getItemValue());
newFortune.setIsPart(1);
newFortune.setPayoutDate(newExpectedFortune.getPayoutDate());
String fortuneRemark = StringUtils.isBlank(splitDto.getRemark()) ? "" : splitDto.getRemark();
if (StringUtils.isNotBlank(originalFortune.getRemark())) {
fortuneRemark = StringUtils.isBlank(fortuneRemark) ?
originalFortune.getRemark() :
originalFortune.getRemark() + "; " + fortuneRemark;
}
newFortune.setRemark(fortuneRemark);
newFortune.setReconciliationOperator(username);
newFortune.setCreatorId(loginUserId);
newFortune.setCreateTime(now);
newFortune.setUpdaterId(loginUserId);
newFortune.setUpdateTime(now);
newFortuneList.add(newFortune);
}
// 5. 处理原始记录
this.removeById(originalFortune.getId());
expectedFortuneService.removeById(originalExpectedFortune.getId());
// 6. 批量保存新记录
boolean expectedSaveSuccess = expectedFortuneService.saveBatch(newExpectedFortuneList);
if (!expectedSaveSuccess) {
throw new BusinessException(ResultCode.FAIL.getCode(), "分期预计出账记录保存失败");
}
boolean fortuneSaveSuccess = this.saveBatch(newFortuneList);
if (!fortuneSaveSuccess) {
throw new BusinessException(ResultCode.FAIL.getCode(), "分期出账记录保存失败");
}
// 7. 返回结果
return true;
}
private void validSplitFortune(FortuneSplitRequest fortuneSplitRequest) {
if (fortuneSplitRequest == null) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "分期出账请求不能为空");
}
if (StringUtils.isBlank(fortuneSplitRequest.getFortuneBizId())) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "保单出账业务ID不能为空");
}
if (CollectionUtils.isEmpty(fortuneSplitRequest.getFortuneSplitDtoList())) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "分期配置列表不能为空");
}
if (fortuneSplitRequest.getFortuneSplitDtoList().size() < 2) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(), "分期至少需要2条配置");
}
List<FortuneSplitDto> splitList = fortuneSplitRequest.getFortuneSplitDtoList();
for (int i = 0; i < splitList.size(); i++) {
FortuneSplitDto dto = splitList.get(i);
if (dto.getHkdAmount() == null || dto.getHkdAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(),
"第" + (i + 1) + "期:港币金额必须大于0");
}
if (dto.getExchangeRate() == null || dto.getExchangeRate().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(),
"第" + (i + 1) + "期:汇率必须大于0");
}
if (StringUtils.isBlank(dto.getPayoutYearMonth())) {
throw new BusinessException(ResultCode.PARAM_CHECK_ERROR.getCode(),
"第" + (i + 1) + "期:出账年月不能为空");
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment