挂账需求

This commit is contained in:
Tankaikai
2025-03-05 14:18:27 +08:00
parent e3833b8bfc
commit 3f03cd2583
30 changed files with 1983 additions and 1 deletions

View File

@@ -0,0 +1,22 @@
package com.czg.service.order.mapper;
import com.czg.order.dto.CreditBuyerDTO;
import com.czg.order.entity.CreditBuyer;
import com.czg.order.param.CreditBuyerQueryParam;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 挂账人
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Mapper
public interface CreditBuyerMapper extends BaseMapper<CreditBuyer> {
List<CreditBuyerDTO> findCreditBuyerList(CreditBuyerQueryParam param);
}

View File

@@ -0,0 +1,33 @@
package com.czg.service.order.mapper;
import com.czg.order.dto.CreditBuyerOrderDTO;
import com.czg.order.entity.CreditBuyerOrder;
import com.czg.order.param.CreditBuyerOrderQueryParam;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.util.List;
/**
* 挂账账单
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Mapper
public interface CreditBuyerOrderMapper extends BaseMapper<CreditBuyerOrder> {
List<CreditBuyerOrderDTO> findCreditBuyerOrderList(CreditBuyerOrderQueryParam param);
long getCreditBuyerOrderCount(CreditBuyerOrderQueryParam param);
BigDecimal getSumPayAmount(CreditBuyerOrderQueryParam param);
BigDecimal getSumPaidAmount(CreditBuyerOrderQueryParam param);
BigDecimal getSumUnpaidAmount(CreditBuyerOrderQueryParam param);
CreditBuyerOrderDTO getOne(CreditBuyerOrderQueryParam param);
}

View File

@@ -0,0 +1,16 @@
package com.czg.service.order.mapper;
import com.czg.order.entity.CreditPaymentRecord;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 挂账账单付款记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Mapper
public interface CreditPaymentRecordMapper extends BaseMapper<CreditPaymentRecord> {
}

View File

@@ -0,0 +1,337 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.enums.DeleteEnum;
import com.czg.exception.CzgException;
import com.czg.order.dto.CreditBuyerDTO;
import com.czg.order.dto.CreditBuyerOrderDTO;
import com.czg.order.entity.CreditBuyer;
import com.czg.order.entity.CreditBuyerOrder;
import com.czg.order.entity.CreditPaymentRecord;
import com.czg.order.entity.OrderInfo;
import com.czg.order.param.CreditBuyerOrderQueryParam;
import com.czg.order.service.CreditBuyerOrderService;
import com.czg.order.service.CreditBuyerService;
import com.czg.order.vo.CreditBuyerOrderSummaryVo;
import com.czg.service.order.mapper.CreditBuyerMapper;
import com.czg.service.order.mapper.CreditBuyerOrderMapper;
import com.czg.service.order.mapper.CreditPaymentRecordMapper;
import com.czg.service.order.mapper.OrderInfoMapper;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageParam;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 挂账账单
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Service
public class CreditBuyerOrderServiceImpl extends ServiceImpl<CreditBuyerOrderMapper, CreditBuyerOrder> implements CreditBuyerOrderService {
@Resource
private CreditBuyerMapper creditBuyerMapper;
@Resource
private CreditPaymentRecordMapper creditPaymentRecordMapper;
@Resource
@Lazy
private CreditBuyerService creditBuyerService;
@Resource
private OrderInfoMapper orderInfoMapper;
@Override
public Page<CreditBuyerOrderDTO> getCreditBuyerOrderPage(CreditBuyerOrderQueryParam param) {
PageParam pageParam = PageUtil.buildPageHelp();
PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize(), StrUtil.blankToDefault(pageParam.getOrderBy(), "order_id desc"));
PageInfo<CreditBuyerOrderDTO> pageInfo = new PageInfo<>(super.mapper.findCreditBuyerOrderList(param));
return PageUtil.convert(pageInfo);
}
@Override
public CreditBuyerOrderSummaryVo summary(CreditBuyerOrderQueryParam param) {
CreditBuyerOrderSummaryVo data = new CreditBuyerOrderSummaryVo();
long count = super.mapper.getCreditBuyerOrderCount(param);
// 总交易笔数
data.setCount(count);
// 总交易金额
BigDecimal payAmount = super.mapper.getSumPayAmount(param);
data.setPayAmountTotal(payAmount);
// 待支付笔数
param.setStatusList(List.of("unpaid", "partial"));
long unpaidCount = super.mapper.getCreditBuyerOrderCount(param);
data.setUnpaidCount(unpaidCount);
param.setStatusList(List.of("paid", "partial"));
BigDecimal paidAmount = super.mapper.getSumPaidAmount(param);
param.setStatusList(List.of("unpaid", "partial"));
BigDecimal unpaidAmount = super.mapper.getSumUnpaidAmount(param);
data.setPaidAmountTotal(paidAmount);
data.setUnpaidAmountTotal(unpaidAmount);
return data;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void pay(CreditPaymentRecord record) {
try {
Assert.notNull(record.getCreditBuyerId(), "{}({})不能为空", "挂账人id", "creditBuyerId");
Assert.notNull(record.getOrderId(), "{}({})不能为空", "订单id", "orderId");
Assert.notNull(record.getRepaymentAmount(), "{}({})不能为空", "还款金额", "repaymentAmount");
Assert.notEmpty(record.getPaymentMethod(), "{}({})不能为空", "支付方式", "paymentMethod");
//Assert.notNull(record.getPaymentTime(), "{}({})不能为空", "还款时间", "paymentTime");
} catch (IllegalArgumentException e) {
throw new CzgException(e.getMessage());
}
CreditBuyer creditBuyer = creditBuyerMapper.selectOneById(record.getCreditBuyerId());
if (creditBuyer == null) {
throw new CzgException("挂账人不存在");
}
String repaymentMethod = creditBuyer.getRepaymentMethod();
if (!"order".equals(repaymentMethod)) {
throw new CzgException("该挂账人不支持按订单付款");
}
CreditBuyerOrderQueryParam param = new CreditBuyerOrderQueryParam();
param.setCreditBuyerId(record.getCreditBuyerId());
param.setOrderId(Convert.toStr(record.getOrderId()));
CreditBuyerOrderDTO dto = super.mapper.getOne(param);
if (dto == null) {
throw new CzgException("挂账订单不存在");
}
if ("paid".equals(dto.getStatus())) {
throw new CzgException("挂账订单已还清,无需还款");
}
if (NumberUtil.isLess(record.getRepaymentAmount(), BigDecimal.ZERO)) {
throw new CzgException("还款金额不能小于0");
}
if (NumberUtil.isGreater(record.getRepaymentAmount(), dto.getUnpaidAmount())) {
throw new CzgException("还款金额不能大于未支付金额");
}
CreditBuyerOrder entity = BeanUtil.copyProperties(dto, CreditBuyerOrder.class);
if (NumberUtil.equals(record.getRepaymentAmount(), dto.getUnpaidAmount())) {
entity.setStatus("paid");
} else {
entity.setStatus("partial");
}
entity.setPaidAmount(NumberUtil.add(entity.getPaidAmount(), record.getRepaymentAmount()));
entity.setLastPaymentTime(LocalDateTime.now());
entity.setLastPaymentMethod(record.getPaymentMethod());
entity.setRemark(record.getRemark());
super.updateById(entity);
record.setPaymentTime(LocalDateTime.now());
creditPaymentRecordMapper.insert(record);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean save(String creditBuyerId, Long orderId) {
if (StrUtil.isBlank(creditBuyerId)) {
throw new CzgException("挂账人id不能为空");
}
CreditBuyerDTO creditBuyer = creditBuyerService.getCreditBuyerById(creditBuyerId);
if (creditBuyer == null) {
throw new CzgException("挂账人不存在");
}
Integer delFlag = creditBuyer.getIsDel();
if (delFlag != null && delFlag == DeleteEnum.DELETED.value()) {
throw new CzgException("挂账人已删除");
}
Integer status = creditBuyer.getStatus();
if (status != null && status == DeleteEnum.NORMAL.value()) {
throw new CzgException("挂账人已被停用");
}
OrderInfo orderInfo = orderInfoMapper.selectOneById(orderId);
if (orderInfo == null) {
throw new CzgException("订单不存在");
}
// 账户余额
BigDecimal accountBalance = creditBuyer.getAccountBalance();
// 如果有余额的话从余额里面扣除没有余额的话从信用额度里面扣除余额和信用额度都为0则不允许挂账余额+信用额度刚好够支付这笔订单的话需要同时减余额减信用额度
if (NumberUtil.isGreaterOrEqual(accountBalance, orderInfo.getOrderAmount())) {
// 减余额
creditBuyer.setAccountBalance(NumberUtil.sub(accountBalance, orderInfo.getOrderAmount()));
CreditBuyer dbRecord = BeanUtil.copyProperties(creditBuyer, CreditBuyer.class);
creditBuyerMapper.update(dbRecord);
// 记录还款记录
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(creditBuyerId);
record.setOrderId(orderId);
record.setRepaymentAmount(orderInfo.getOrderAmount());
record.setPaymentMethod("余额支付");
record.setPaymentTime(LocalDateTime.now());
record.setRemark("挂账时余额充足,直接从余额扣除");
creditPaymentRecordMapper.insert(record);
CreditBuyerOrder entity = new CreditBuyerOrder();
entity.setCreditBuyerId(creditBuyerId);
entity.setOrderId(orderId);
entity.setPaidAmount(orderInfo.getOrderAmount());
entity.setStatus("paid");
entity.setLastPaymentTime(LocalDateTime.now());
entity.setLastPaymentMethod(record.getPaymentMethod());
entity.setRemark(record.getRemark());
return super.save(entity);
}
CreditBuyerOrder entity = null;
if (NumberUtil.isGreater(accountBalance, BigDecimal.ZERO)) {
// 减余额
creditBuyer.setAccountBalance(BigDecimal.ZERO);
CreditBuyer dbRecord = BeanUtil.copyProperties(creditBuyer, CreditBuyer.class);
creditBuyerMapper.update(dbRecord);
// 记录还款记录
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(creditBuyerId);
record.setOrderId(orderId);
record.setRepaymentAmount(accountBalance);
record.setPaymentMethod("余额支付");
record.setPaymentTime(LocalDateTime.now());
record.setRemark("挂账时余额不足,先扣除现有余额,其他的从挂账额度中扣除");
creditPaymentRecordMapper.insert(record);
entity = new CreditBuyerOrder();
entity.setCreditBuyerId(creditBuyerId);
entity.setOrderId(orderId);
entity.setPaidAmount(accountBalance);
entity.setStatus("partial");
entity.setLastPaymentTime(LocalDateTime.now());
entity.setLastPaymentMethod(record.getPaymentMethod());
entity.setRemark(record.getRemark());
//super.save(entity);
orderInfo.setPayAmount(NumberUtil.sub(orderInfo.getOrderAmount(), accountBalance));
}
// 剩余挂账额度
BigDecimal remainingAmount = creditBuyer.getRemainingAmount();
// 验证挂账金额是否大于剩余额度
boolean greater = NumberUtil.isGreater(orderInfo.getOrderAmount(), remainingAmount);
if (greater) {
throw new CzgException(StrUtil.format("{}:¥{}不能大于剩余挂账额度({})", "挂账金额", orderInfo.getOrderAmount(), remainingAmount));
}
if (entity == null) {
entity = new CreditBuyerOrder();
entity.setStatus("unpaid");
entity.setPaidAmount(BigDecimal.ZERO);
entity.setCreditBuyerId(creditBuyerId);
entity.setOrderId(orderId);
}
return super.saveOrUpdate(entity);
}
@Override
@Deprecated
@Transactional(rollbackFor = Exception.class)
public boolean refund(String creditBuyerId, Long orderId) {
if (StrUtil.isBlank(creditBuyerId)) {
throw new CzgException("挂账人id不能为空");
}
CreditBuyer creditBuyer = creditBuyerService.getById(creditBuyerId);
if (creditBuyer == null) {
throw new CzgException("挂账人不存在");
}
OrderInfo orderInfo = orderInfoMapper.selectOneById(orderId);
if (orderInfo == null) {
throw new CzgException("订单不存在");
}
CreditBuyerOrderQueryParam param = new CreditBuyerOrderQueryParam();
param.setCreditBuyerId(creditBuyerId);
param.setOrderId(Convert.toStr(orderId));
CreditBuyerOrderDTO dto = super.mapper.getOne(param);
if (dto == null) {
throw new CzgException("挂账订单不存在");
}
// 1.只挂账未还款的情况,直接删除挂账订单
if ("unpaid".equals(dto.getStatus())) {
super.mapper.deleteById(dto.getId());
return true;
}
// 2.部分还款/已还款,删除挂账订单+红冲还款记录,并把已还款金额退回余额或挂账额度
if ("partial".equals(dto.getStatus()) || "paid".equals(dto.getStatus())) {
// 已还款金额
BigDecimal paidAmount = dto.getPaidAmount();
// 已还款金额进行红冲
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(creditBuyerId);
record.setOrderId(orderId);
record.setRepaymentAmount(NumberUtil.sub(BigDecimal.ZERO, paidAmount));
record.setPaymentMethod("挂账退款");
record.setPaymentTime(LocalDateTime.now());
record.setRemark(StrUtil.format("挂账订单:{}申请退款,已归还挂账额度或账户余额", orderInfo.getOrderNo()));
creditPaymentRecordMapper.insert(record);
// 删除挂账订单,恢复挂账额度
super.mapper.deleteById(dto.getId());
// 退回余额
creditBuyer.setAccountBalance(NumberUtil.add(creditBuyer.getAccountBalance(), paidAmount));
creditBuyerService.updateById(creditBuyer);
return true;
}
return false;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean partRefund(String creditBuyerId, Long orderId, BigDecimal refundAmount) {
if (StrUtil.isBlank(creditBuyerId)) {
throw new CzgException("挂账人id不能为空");
}
CreditBuyerDTO creditBuyer = creditBuyerService.getCreditBuyerById(creditBuyerId);
if (creditBuyer == null) {
throw new CzgException("挂账人不存在");
}
OrderInfo orderInfo = orderInfoMapper.selectOneById(orderId);
if (orderInfo == null) {
throw new CzgException("订单不存在");
}
CreditBuyerOrderQueryParam param = new CreditBuyerOrderQueryParam();
param.setCreditBuyerId(creditBuyerId);
param.setOrderId(Convert.toStr(orderId));
CreditBuyerOrderDTO dto = mapper.getOne(param);
if (dto == null) {
throw new CzgException("挂账订单不存在");
}
// 已还款金额进行红冲
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(creditBuyerId);
record.setOrderId(orderId);
record.setRepaymentAmount(NumberUtil.sub(BigDecimal.ZERO, refundAmount));
record.setPaymentMethod("挂账退款");
record.setPaymentTime(LocalDateTime.now());
record.setRemark(StrUtil.format("挂账订单:{},申请退款¥{}元,已恢复挂账额度。", orderInfo.getOrderNo(), refundAmount));
creditPaymentRecordMapper.insert(record);
dto = mapper.getOne(param);
BigDecimal sub = NumberUtil.sub(refundAmount, dto.getUnpaidAmount());
if (NumberUtil.isGreater(sub, BigDecimal.ZERO)) {
CreditPaymentRecord flow = new CreditPaymentRecord();
flow.setCreditBuyerId(creditBuyerId);
flow.setOrderId(orderId);
flow.setRepaymentAmount(sub);
flow.setPaymentMethod("转储余额");
flow.setPaymentTime(LocalDateTime.now());
flow.setRemark(StrUtil.format("挂账订单:{},申请退款¥{}元,由于此挂账订单已提前还款,溢出部分¥{}元将转储至账户余额。", orderInfo.getOrderNo(), refundAmount, sub));
creditPaymentRecordMapper.insert(flow);
UpdateChain.of(CreditBuyerOrder.class)
.set(CreditBuyerOrder::getPaidAmount, NumberUtil.sub(dto.getPaidAmount(), sub))
.eq(CreditBuyerOrder::getId, dto.getId())
.update();
// 退回余额
creditBuyer.setAccountBalance(NumberUtil.add(creditBuyer.getAccountBalance(), sub));
CreditBuyer entity = BeanUtil.copyProperties(dto, CreditBuyer.class);
creditBuyerService.updateById(entity);
}
return true;
}
}

View File

@@ -0,0 +1,278 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.enums.DeleteEnum;
import com.czg.enums.StatusEnum;
import com.czg.exception.CzgException;
import com.czg.order.dto.CreditBuyerDTO;
import com.czg.order.dto.CreditBuyerOrderDTO;
import com.czg.order.entity.CreditBuyer;
import com.czg.order.entity.CreditBuyerOrder;
import com.czg.order.entity.CreditPaymentRecord;
import com.czg.order.param.CreditBuyerOrderQueryParam;
import com.czg.order.param.CreditBuyerQueryParam;
import com.czg.order.param.CreditBuyerRepaymentParam;
import com.czg.order.service.CreditBuyerService;
import com.czg.order.vo.CreditBuyerRepaymentVo;
import com.czg.sa.StpKit;
import com.czg.service.order.mapper.CreditBuyerMapper;
import com.czg.service.order.mapper.CreditBuyerOrderMapper;
import com.czg.service.order.mapper.CreditPaymentRecordMapper;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageParam;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 挂账人
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Service
public class CreditBuyerServiceImpl extends ServiceImpl<CreditBuyerMapper, CreditBuyer> implements CreditBuyerService {
@Resource
private CreditPaymentRecordMapper creditPaymentRecordMapper;
@Resource
private CreditBuyerOrderMapper creditBuyerOrderMapper;
@Override
public Page<CreditBuyerDTO> getCreditBuyerPage(CreditBuyerQueryParam param) {
PageParam pageParam = PageUtil.buildPageHelp();
PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getOrderBy());
PageInfo<CreditBuyerDTO> pageInfo = new PageInfo<>(super.mapper.findCreditBuyerList(param));
return PageUtil.convert(pageInfo);
}
private void commonVerify(CreditBuyerDTO dto) {
try {
Assert.notNull(dto.getShopId(), "{}({})不能为空", "店铺id", "shopId");
Assert.notNull(dto.getStatus(), "{}({})不能为空", "状态", "status");
Assert.notEmpty(dto.getDebtor(), "{}({})不能为空", "挂账人", "debtor");
Assert.notEmpty(dto.getMobile(), "{}({})不能为空", "手机号", "mobile");
Assert.notNull(dto.getCreditAmount(), "{}({})不能为空", "挂账额度", "creditAmount");
} catch (IllegalArgumentException e) {
throw new CzgException(e.getMessage());
}
if (!Validator.isMobile(dto.getMobile())) {
throw new CzgException(StrUtil.format("{}({})不合法", "手机号", "mobile"));
}
if (NumberUtil.isLessOrEqual(dto.getCreditAmount(), BigDecimal.ZERO)) {
throw new CzgException(StrUtil.format("{}({})必须大于0", "挂账额度", "creditAmount"));
}
}
@Override
public CreditBuyerDTO getCreditBuyerById(String id) {
Long shopId = StpKit.USER.getShopId(0L);
CreditBuyerQueryParam param = new CreditBuyerQueryParam();
param.setId(id);
param.setShopId(shopId);
List<CreditBuyerDTO> list = super.mapper.findCreditBuyerList(param);
if (CollUtil.isEmpty(list)) {
return null;
}
return list.getFirst();
}
@Override
public void addCreditBuyer(CreditBuyerDTO dto) {
commonVerify(dto);
try {
Assert.notEmpty(dto.getRepaymentMethod(), "{}({})不能为空", "还款方式", "repaymentMethod");
} catch (IllegalArgumentException e) {
throw new CzgException(e.getMessage());
}
if (!ArrayUtil.contains(new String[]{"total", "order"}, dto.getRepaymentMethod())) {
throw new CzgException(StrUtil.format("{}({})不合法", "还款方式", "repaymentMethod"));
}
dto.setAccountBalance(BigDecimal.ZERO);
CreditBuyer entity = BeanUtil.copyProperties(dto, CreditBuyer.class);
super.save(entity);
}
@Override
public void updateCreditBuyer(CreditBuyerDTO dto) {
try {
Assert.notEmpty(dto.getId(), "{}不能为空", "id");
} catch (IllegalArgumentException e) {
throw new CzgException(e.getMessage());
}
commonVerify(dto);
CreditBuyer entity = getById(dto.getId());
if (entity == null) {
throw new CzgException("挂账人不存在");
}
CreditBuyerDTO record = getCreditBuyerById(dto.getId());
// 验证挂账额度是否小于已挂账金额
boolean less = NumberUtil.isLess(dto.getCreditAmount(), NumberUtil.nullToZero(record.getOwedAmount()));
if (less) {
throw new CzgException(StrUtil.format("{}({})不能小于已挂账金额({})", "挂账额度", "creditAmount", record.getOwedAmount()));
}
BeanUtil.copyProperties(dto, entity, CopyOptions.create().setIgnoreNullValue(false).setIgnoreProperties("repaymentMethod", "accountBalance"));
super.updateById(entity);
}
@Override
public void deleteCreditBuyer(String id) {
Long shopId = StpKit.USER.getShopId(0L);
UpdateChain.of(CreditBuyer.class)
.set(CreditBuyer::getIsDel, StatusEnum.DISABLE.value())
.eq(CreditBuyer::getId, id)
.eq(CreditBuyer::getShopId, shopId)
.update();
}
@Override
@Transactional(rollbackFor = Exception.class)
public CreditBuyerRepaymentVo repayment(CreditBuyerRepaymentParam param) {
// 还款金额
BigDecimal repaymentAmount = param.getRepaymentAmount();
try {
Assert.notEmpty(param.getId(), "{}不能为空", "id");
Assert.notNull(repaymentAmount, "{}({})不能为空", "还款金额", "repaymentAmount");
Assert.notNull(param.getPaymentMethod(), "{}({})不能为空", "支付方式", "paymentMethod");
} catch (IllegalArgumentException e) {
throw new CzgException(e.getMessage());
}
CreditBuyerDTO dto = getCreditBuyerById(param.getId());
if (dto == null) {
throw new CzgException("挂账人不存在");
}
Integer isDel = dto.getIsDel();
if (isDel == DeleteEnum.DELETED.value()) {
throw new CzgException("挂账人已删除");
}
if (!"total".equals(dto.getRepaymentMethod())) {
throw new CzgException("此挂账人不能以【按总账户还款】进行还款");
}
if (NumberUtil.isLess(repaymentAmount, BigDecimal.ZERO)) {
throw new CzgException("还款金额不能小于0");
}
BigDecimal initRepaymentAmount = NumberUtil.add(repaymentAmount, BigDecimal.ZERO);
// 已挂账金额
BigDecimal owedAmount = dto.getOwedAmount();
if (NumberUtil.equals(owedAmount, BigDecimal.ZERO)) {
dto.setAccountBalance(NumberUtil.add(dto.getAccountBalance(), repaymentAmount));
CreditBuyer entity = BeanUtil.copyProperties(dto, CreditBuyer.class);
super.updateById(entity);
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(param.getId());
record.setRepaymentAmount(repaymentAmount);
record.setPaymentMethod(param.getPaymentMethod());
record.setPaymentTime(LocalDateTime.now());
record.setRemark(param.getRemark());
creditPaymentRecordMapper.insert(record);
CreditBuyerRepaymentVo result = new CreditBuyerRepaymentVo();
result.setRepaymentCount(0);
result.setRepaymentAmount(repaymentAmount);
result.setRepaymentMsg(StrUtil.format("账单无需还款,{}元已转储至余额。", repaymentAmount));
return result;
}
// 转存余额
BigDecimal rechargeAmount = BigDecimal.ZERO;
if (NumberUtil.isGreater(repaymentAmount, owedAmount)) {
rechargeAmount = NumberUtil.sub(repaymentAmount, owedAmount);
dto.setAccountBalance(NumberUtil.add(dto.getAccountBalance(), rechargeAmount));
CreditBuyer entity = BeanUtil.copyProperties(dto, CreditBuyer.class);
super.updateById(entity);
CreditPaymentRecord record = new CreditPaymentRecord();
record.setCreditBuyerId(param.getId());
record.setRepaymentAmount(rechargeAmount);
record.setPaymentMethod(param.getPaymentMethod());
record.setPaymentTime(LocalDateTime.now());
record.setRemark(param.getRemark());
creditPaymentRecordMapper.insert(record);
}
// 校验完毕,可以批量还款
CreditBuyerOrderQueryParam where = new CreditBuyerOrderQueryParam();
where.setCreditBuyerId(param.getId());
where.setStatusList(List.of("unpaid", "partial"));
List<CreditBuyerOrderDTO> list = creditBuyerOrderMapper.findCreditBuyerOrderList(where);
if (CollUtil.isEmpty(list)) {
throw new CzgException("没有需要还款的订单");
}
int repaymentCount = 0;
List<CreditBuyerOrderDTO> orderList = list.stream().sorted(Comparator.comparing(CreditBuyerOrderDTO::getOrderId)).collect(Collectors.toList());
for (CreditBuyerOrderDTO item : orderList) {
// 未付款金额
BigDecimal unpaidAmount = item.getUnpaidAmount();
// 记录还款记录
CreditPaymentRecord record = new CreditPaymentRecord();
if (NumberUtil.isGreaterOrEqual(repaymentAmount, unpaidAmount)) {
// 还全额
UpdateChain.of(CreditBuyerOrder.class)
.set(CreditBuyerOrder::getStatus, "paid")
.set(CreditBuyerOrder::getPaidAmount, NumberUtil.add(item.getPaidAmount(), unpaidAmount))
.set(CreditBuyerOrder::getRemark, param.getRemark())
.set(CreditBuyerOrder::getLastPaymentMethod, param.getPaymentMethod())
.set(CreditBuyerOrder::getLastPaymentTime, LocalDateTime.now())
.eq(CreditBuyerOrder::getStatus, item.getStatus())
.eq(CreditBuyerOrder::getId, item.getId())
.eq(CreditBuyerOrder::getOrderId, item.getOrderId())
.eq(CreditBuyerOrder::getCreditBuyerId, item.getCreditBuyerId())
.eq(CreditBuyerOrder::getPaidAmount, item.getPaidAmount())
.update();
record.setRepaymentAmount(unpaidAmount);
repaymentAmount = NumberUtil.sub(repaymentAmount, unpaidAmount);
} else if (NumberUtil.isLess(repaymentAmount, unpaidAmount)) {
// 还部分
UpdateChain.of(CreditBuyerOrder.class)
.set(CreditBuyerOrder::getStatus, "partial")
.set(CreditBuyerOrder::getPaidAmount, NumberUtil.add(item.getPaidAmount(), repaymentAmount))
.set(CreditBuyerOrder::getRemark, param.getRemark())
.set(CreditBuyerOrder::getLastPaymentMethod, param.getPaymentMethod())
.set(CreditBuyerOrder::getLastPaymentTime, LocalDateTime.now())
.eq(CreditBuyerOrder::getStatus, item.getStatus())
.eq(CreditBuyerOrder::getId, item.getId())
.eq(CreditBuyerOrder::getOrderId, item.getOrderId())
.eq(CreditBuyerOrder::getCreditBuyerId, item.getCreditBuyerId())
.eq(CreditBuyerOrder::getPaidAmount, item.getPaidAmount())
.update();
record.setRepaymentAmount(repaymentAmount);
repaymentAmount = BigDecimal.ZERO;
}
record.setCreditBuyerId(item.getCreditBuyerId());
record.setOrderId(item.getOrderId());
record.setPaymentMethod(param.getPaymentMethod());
record.setPaymentTime(LocalDateTime.now());
record.setRemark(param.getRemark());
creditPaymentRecordMapper.insert(record);
repaymentCount++;
if (NumberUtil.equals(repaymentAmount, BigDecimal.ZERO)) {
break;
}
}
CreditBuyerRepaymentVo result = new CreditBuyerRepaymentVo();
BigDecimal payAmount = NumberUtil.sub(initRepaymentAmount, repaymentAmount);
result.setRepaymentCount(repaymentCount);
result.setRepaymentAmount(initRepaymentAmount);
result.setPayAmount(payAmount);
result.setRechargeAmount(rechargeAmount);
result.setRepaymentMsg(StrUtil.format("共计还款{}笔,还款金额:{}元,支付欠款:{}元,转存余额:{}元,当前余额:{}元。", repaymentCount, initRepaymentAmount, payAmount, rechargeAmount, dto.getAccountBalance()));
return result;
}
}

View File

@@ -0,0 +1,47 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.czg.order.dto.CreditPaymentRecordDTO;
import com.czg.order.entity.CreditPaymentRecord;
import com.czg.order.param.CreditPaymentRecordQueryParam;
import com.czg.order.service.CreditPaymentRecordService;
import com.czg.service.order.mapper.CreditPaymentRecordMapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 挂账账单付款记录
*
* @author Tankaikai tankaikai@aliyun.com
* @since 1.0 2025-03-04
*/
@Service
public class CreditPaymentRecordServiceImpl extends ServiceImpl<CreditPaymentRecordMapper, CreditPaymentRecord> implements CreditPaymentRecordService {
private QueryWrapper buildQueryWrapper(CreditPaymentRecordQueryParam param) {
QueryWrapper queryWrapper = PageUtil.buildSortQueryWrapper();
if (StrUtil.isNotEmpty(param.getCreditBuyerId())) {
queryWrapper.eq(CreditPaymentRecord::getCreditBuyerId, param.getCreditBuyerId());
}
if (ObjUtil.isNotNull(param.getOrderId())) {
queryWrapper.eq(CreditPaymentRecord::getOrderId, param.getOrderId());
}
if (StrUtil.isNotEmpty(param.getPaymentMethod())) {
queryWrapper.eq(CreditPaymentRecord::getPaymentMethod, param.getPaymentMethod());
}
queryWrapper.orderBy(CreditPaymentRecord::getId, false);
return queryWrapper;
}
@Override
public Page<CreditPaymentRecordDTO> getCreditPaymentRecordPage(CreditPaymentRecordQueryParam param) {
QueryWrapper queryWrapper = buildQueryWrapper(param);
return super.pageAs(PageUtil.buildPage(), queryWrapper, CreditPaymentRecordDTO.class);
}
}

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.order.mapper.CreditBuyerMapper">
<sql id="view_credit_buyer_order_count">
(SELECT
credit_buyer_id AS credit_buyer_id,
status AS status,
count( 0 ) AS count
FROM
<include refid="view_credit_buyer_order"/>
GROUP BY credit_buyer_id,status)
</sql>
<sql id="view_credit_buyer_order">
(SELECT t1.id AS id,
t1.credit_buyer_id AS credit_buyer_id,
t1.order_id AS order_id,
t2.pay_amount - t2.refund_amount AS pay_amount,
t1.paid_amount AS paid_amount,
(t2.pay_amount - t2.refund_amount) - t1.paid_amount AS unpaid_amount,
t1.status AS status,
t2.create_time AS create_time,
t1.last_payment_time AS last_payment_time,
t1.last_payment_method AS last_payment_method,
t1.remark AS remark
FROM tb_credit_buyer_order t1
LEFT JOIN tb_order_info t2 ON t1.order_id = t2.id)
</sql>
<select id="findCreditBuyerList" resultType="com.czg.order.dto.CreditBuyerDTO">
select x1.*,
x2.shop_name,
x3.owed_amount,
x3.accumulate_amount
from tb_credit_buyer x1
left join tb_shop_info t2 on x1.shop_id = x2.id
left join (select credit_buyer_id,ifnull(sum(unpaid_amount),0) as owed_amount,ifnull(sum(pay_amount),0) as
accumulate_amount from
<include refid="view_credit_buyer_order"/>
group by credit_buyer_id) x3 on x1.id = x3.credit_buyer_id
<where>
and x1.is_del = 0
and x1.shop_id = #{shopId}
<if test="id !=null and id != ''">
and x1.id = #{id}
</if>
<if test="responsiblePerson !=null and responsiblePerson != ''">
and x1.responsible_person like concat('%',#{responsiblePerson},'%')
</if>
<if test="status !=null">
and x1.status = #{status}
</if>
<if test="keywords !=null and keywords != ''">
and (x1.debtor like concat('%',#{keywords},'%') or x1.mobile like concat('%',#{keywords},'%'))
</if>
<if test="repaymentStatus !=null and repaymentStatus != ''">
<if test="repaymentStatus == 'unpaid'">
and 0 &lt; ifnull((select x.count from
<include refid="view_credit_buyer_order_count"/>
x where x.credit_buyer_id = tb_credit_buyer.id and x.status = 'unpaid'),0)
and 0 = ifnull((select x.count from
<include refid="view_credit_buyer_order_count"/>
x where x.credit_buyer_id = tb_credit_buyer.id and x.status = 'partial'),0)
</if>
<if test="repaymentStatus == 'partial'">
and 0 &lt; ifnull((select x.count from
<include refid="view_credit_buyer_order_count"/>
x where x.credit_buyer_id = tb_credit_buyer.id and x.status = 'partial'),0)
</if>
<if test="repaymentStatus == 'paid'">
and 0 = ifnull((select sum(x.count) from
<include refid="view_credit_buyer_order_count"/>
x where x.credit_buyer_id = tb_credit_buyer.id and x.status in ('unpaid','partial')),0)
</if>
</if>
</where>
order by x1.status desc,x1.id desc
</select>
</mapper>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.order.mapper.CreditBuyerOrderMapper">
<sql id="view_credit_buyer_order">
(SELECT t1.id AS id,
t1.credit_buyer_id AS credit_buyer_id,
t1.order_id AS order_id,
t2.pay_amount - t2.refund_amount AS pay_amount,
t1.paid_amount AS paid_amount,
(t2.pay_amount - t2.refund_amount) - t1.paid_amount AS unpaid_amount,
t1.status AS status,
t2.create_time AS create_time,
t1.last_payment_time AS last_payment_time,
t1.last_payment_method AS last_payment_method,
t1.remark AS remark
FROM tb_credit_buyer_order t1
LEFT JOIN tb_order_info t2 ON t1.order_id = t2.id)
</sql>
<sql id="commonWhere">
<where>
and x1.credit_buyer_id = #{creditBuyerId}
<if test="orderId != null and orderId != ''">
and x1.order_id = #{orderId}
</if>
<if test="beginDate != null and beginDate != ''">
and x1.create_time >= str_to_date(concat(#{beginDate},' 00:00:00'), '%Y-%m-%d %H:%i:%s')
</if>
<if test="endDate != null and endDate != ''">
<![CDATA[
and x1.create_time <= str_to_date(concat(#{endDate},' 23:59:59'), '%Y-%m-%d %H:%i:%s')
]]>
</if>
<if test="status != null and status != ''">
and x1.status = #{status}
</if>
<if test="statusList != null">
and x1.status in
<foreach item="status" collection="statusList" open="(" separator="," close=")">
#{status}
</foreach>
</if>
<if test="id != null">
and x1.id = #{id}
</if>
</where>
</sql>
<select id="findCreditBuyerOrderList" resultType="com.czg.order.dto.CreditBuyerOrderDTO">
select * from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
</select>
<select id="getCreditBuyerOrderCount" resultType="java.lang.Long">
select count(*) from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
</select>
<select id="getSumPayAmount" resultType="java.math.BigDecimal">
select ifnull(sum(t1.pay_amount),0) from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
</select>
<select id="getSumPaidAmount" resultType="java.math.BigDecimal">
select ifnull(sum(t1.paid_amount),0) from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
</select>
<select id="getSumUnpaidAmount" resultType="java.math.BigDecimal">
select ifnull(sum(t1.unpaid_amount),0) from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
</select>
<select id="getOne" resultType="com.czg.order.dto.CreditBuyerOrderDTO">
select * from
<include refid="view_credit_buyer_order"/>
x1
<include refid="commonWhere"/>
order by x1.order_id desc
limit 1
</select>
</mapper>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.order.mapper.CreditPaymentRecordMapper">
</mapper>