客户余额修改

This commit is contained in:
luojiaquan
2026-09-14 17:48:46 +08:00
parent aec5be9643
commit 6b48b1e9ff
7 changed files with 246 additions and 103 deletions
@@ -42,9 +42,15 @@ public class CustomerBalanceFlow extends BaseEntity
/** 客户账户ID */
private Long accountId;
/** 币种 */
/** 币种(业务原币) */
private String currency;
/** 对CNY汇率(1原币=?CNY,CNY为1) */
private BigDecimal exchangeRate;
/** 本位币金额(CNY,实际参与余额变动的金额) */
private BigDecimal baseAmount;
/** 业务类型:RECEIPT/BILL_PAY/REFUND/ADJUST/FREEZE/UNFREEZE */
private String bizType;
@@ -59,9 +59,15 @@ public class CustomerReceipt extends BaseEntity
/** 待核销金额 */
private BigDecimal unallocatedAmount;
/** 币种 */
/** 币种(收款原币) */
private String currency;
/** 对CNY汇率(1原币=?CNY,CNY为1;默认取汇率表,可手工修改) */
private BigDecimal exchangeRate;
/** 本位币金额(CNY,原币金额×汇率) */
private BigDecimal baseAmount;
/** 收款方式:BANK_TRANSFER/ALIPAY/WECHAT/CASH/OTHER */
private String payMethod;
@@ -1,6 +1,7 @@
package com.ruoyi.customer.customermoney.service.impl;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
@@ -20,6 +21,8 @@ import com.ruoyi.customer.customermoney.domain.CustomerBalanceFlow;
import com.ruoyi.customer.customermoney.domain.CustomerReceipt;
import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper;
import com.ruoyi.customer.customermoney.service.ICustomerMoneyService;
import com.ruoyi.system.domain.BmsExchangeRate;
import com.ruoyi.system.service.IBmsExchangeRateService;
/**
* 客户余额/收款/流水 Service实现
@@ -32,12 +35,20 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
{
private static final DateTimeFormatter NO_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
/** 本位币币种 */
private static final String BASE_CURRENCY = BmsExchangeRate.BASE_CURRENCY;
/** 金额保留位数 */
private static final int MONEY_SCALE = 2;
@Autowired
private CustomerMoneyMapper customerMoneyMapper;
@Autowired
private BmsCustomerMapper bmsCustomerMapper;
@Autowired
private IBmsExchangeRateService exchangeRateService;
@Override
public List<CustomerAccount> selectAccountList(Long customerId)
{
@@ -81,12 +92,41 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
}
if (!StringUtils.hasText(receipt.getCurrency()))
{
receipt.setCurrency("CNY");
receipt.setCurrency(BASE_CURRENCY);
}
else
{
receipt.setCurrency(receipt.getCurrency().trim().toUpperCase());
}
// 汇率与本位币金额:CNY 固定 1:1;外币默认取汇率表即期汇率,也允许页面手工修改后传入
BigDecimal rate = receipt.getExchangeRate();
if (BASE_CURRENCY.equals(receipt.getCurrency()))
{
rate = BigDecimal.ONE;
}
else
{
if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0)
{
Date bizDate = receipt.getReceivedAt() != null ? receipt.getReceivedAt() : new Date();
BmsExchangeRate rateConfig = exchangeRateService.resolveRate(
receipt.getCurrency(), BASE_CURRENCY, BmsExchangeRate.RATE_TYPE_SPOT, bizDate);
rate = rateConfig.getExchangeRate();
}
if (rate.compareTo(BigDecimal.ZERO) <= 0)
{
throw new ServiceException("汇率必须大于0,请检查录入的汇率");
}
}
receipt.setExchangeRate(rate.setScale(8, RoundingMode.HALF_UP));
BigDecimal baseAmount = receipt.getAmount().multiply(rate).setScale(MONEY_SCALE, RoundingMode.HALF_UP);
receipt.setBaseAmount(baseAmount);
receipt.setReceiptNo(generateNo("SK"));
receipt.setStatus(CustomerReceipt.STATUS_PENDING);
receipt.setAllocatedAmount(BigDecimal.ZERO);
receipt.setUnallocatedAmount(receipt.getAmount());
// 核销口径统一为本位币 CNY
receipt.setUnallocatedAmount(baseAmount);
if (!StringUtils.hasText(receipt.getInvoiceStatus()))
{
receipt.setInvoiceStatus("NONE");
@@ -113,17 +153,26 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
throw new ServiceException("仅待确认的收款单可以审核");
}
String currency = StringUtils.hasText(receipt.getCurrency()) ? receipt.getCurrency() : "CNY";
String currency = StringUtils.hasText(receipt.getCurrency()) ? receipt.getCurrency().trim().toUpperCase()
: BASE_CURRENCY;
String username = SecurityUtils.getUsername();
Long userId = SecurityUtils.getUserId();
// 1. 行锁锁定客户币种账户,不存在则自动初始化
CustomerAccount account = customerMoneyMapper.selectAccountForUpdate(receipt.getCustomerId(), currency);
// 本位币入账金额(兼容历史数据:未折算时按原金额/汇率1处理)
BigDecimal rate = receipt.getExchangeRate() != null && receipt.getExchangeRate().compareTo(BigDecimal.ZERO) > 0
? receipt.getExchangeRate() : BigDecimal.ONE;
BigDecimal originalAmount = nz(receipt.getAmount());
BigDecimal baseAmount = receipt.getBaseAmount() != null
? receipt.getBaseAmount()
: originalAmount.multiply(rate).setScale(MONEY_SCALE, RoundingMode.HALF_UP);
// 1. 行锁锁定客户本位币(CNY)账户,不存在则自动初始化。无论收什么币种,一律折算 CNY 入账
CustomerAccount account = customerMoneyMapper.selectAccountForUpdate(receipt.getCustomerId(), BASE_CURRENCY);
if (account == null)
{
account = new CustomerAccount();
account.setCustomerId(receipt.getCustomerId());
account.setCurrency(currency);
account.setCurrency(BASE_CURRENCY);
account.setBalance(BigDecimal.ZERO);
account.setFrozenBalance(BigDecimal.ZERO);
account.setTotalCredit(BigDecimal.ZERO);
@@ -135,16 +184,15 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
customerMoneyMapper.insertAccount(account);
}
// 2. 变动余额(预充值/混合增加余额;还款核销释放已用额度)
BigDecimal amount = nz(receipt.getAmount());
// 2. 本位币余额变动(预充值/混合增加余额;还款核销释放已用额度)
BigDecimal beforeBalance = nz(account.getBalance());
BigDecimal afterBalance = beforeBalance.add(amount);
BigDecimal afterBalance = beforeBalance.add(baseAmount);
BigDecimal frozenBalance = nz(account.getFrozenBalance());
account.setBalance(afterBalance);
if (CustomerReceipt.TYPE_REPAY.equals(receipt.getReceiptType()))
{
account.setUsedCredit(nz(account.getUsedCredit()).subtract(amount).max(BigDecimal.ZERO));
account.setUsedCredit(nz(account.getUsedCredit()).subtract(baseAmount).max(BigDecimal.ZERO));
}
account.setUpdateBy(username);
@@ -154,17 +202,19 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
throw new ServiceException("账户余额已被其他操作变动,请刷新后重试");
}
// 3. 写余额流水
// 3. 写余额流水:原币金额留痕,本位币金额参与余额变动
CustomerBalanceFlow flow = new CustomerBalanceFlow();
flow.setFlowNo(generateNo("LS"));
flow.setCustomerId(receipt.getCustomerId());
flow.setAccountId(account.getId());
flow.setCurrency(currency);
flow.setExchangeRate(rate);
flow.setBaseAmount(baseAmount);
flow.setBizType(CustomerBalanceFlow.BIZ_RECEIPT);
flow.setBizId(receipt.getId());
flow.setBizNo(receipt.getReceiptNo());
flow.setDirection(1);
flow.setAmount(amount);
flow.setAmount(originalAmount);
flow.setBeforeBalance(beforeBalance);
flow.setAfterBalance(afterBalance);
flow.setBeforeFrozenBalance(frozenBalance);
@@ -101,6 +101,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="allocatedAmount" column="allocated_amount" />
<result property="unallocatedAmount" column="unallocated_amount" />
<result property="currency" column="currency" />
<result property="exchangeRate" column="exchange_rate" />
<result property="baseAmount" column="base_amount" />
<result property="payMethod" column="pay_method" />
<result property="bankTransactionNo" column="bank_transaction_no" />
<result property="receivedAt" column="received_at" />
@@ -120,7 +122,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectCustomerReceiptVo">
select r.id, r.receipt_no, r.customer_id, r.account_id, r.receipt_type, r.amount, r.allocated_amount,
r.unallocated_amount, r.currency, r.pay_method, r.bank_transaction_no, r.received_at, r.status,
r.unallocated_amount, r.currency, r.exchange_rate, r.base_amount, r.pay_method, r.bank_transaction_no,
r.received_at, r.status,
r.confirmed_at, r.confirmed_by, r.invoice_status, r.version, r.create_by, r.create_time,
r.update_by, r.update_time, r.remark, r.del_flag,
(select count(1) from bms_customer_attachment a
@@ -170,6 +173,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="allocatedAmount != null">allocated_amount,</if>
<if test="unallocatedAmount != null">unallocated_amount,</if>
<if test="currency != null">currency,</if>
<if test="exchangeRate != null">exchange_rate,</if>
<if test="baseAmount != null">base_amount,</if>
<if test="payMethod != null">pay_method,</if>
<if test="bankTransactionNo != null">bank_transaction_no,</if>
<if test="receivedAt != null">received_at,</if>
@@ -191,6 +196,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="allocatedAmount != null">#{allocatedAmount},</if>
<if test="unallocatedAmount != null">#{unallocatedAmount},</if>
<if test="currency != null">#{currency},</if>
<if test="exchangeRate != null">#{exchangeRate},</if>
<if test="baseAmount != null">#{baseAmount},</if>
<if test="payMethod != null">#{payMethod},</if>
<if test="bankTransactionNo != null">#{bankTransactionNo},</if>
<if test="receivedAt != null">#{receivedAt},</if>
@@ -231,6 +238,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="customerId" column="customer_id" />
<result property="accountId" column="account_id" />
<result property="currency" column="currency" />
<result property="exchangeRate" column="exchange_rate" />
<result property="baseAmount" column="base_amount" />
<result property="bizType" column="biz_type" />
<result property="bizId" column="biz_id" />
<result property="bizNo" column="biz_no" />
@@ -250,7 +259,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectCustomerBalanceFlowVo">
select id, flow_no, customer_id, account_id, currency, biz_type, biz_id, biz_no, direction, amount,
select id, flow_no, customer_id, account_id, currency, exchange_rate, base_amount, biz_type, biz_id, biz_no,
direction, amount,
before_balance, after_balance, before_frozen_balance, after_frozen_balance, operator_id,
create_by, create_time, update_by, update_time, remark, del_flag
from customer_balance_flow
@@ -285,6 +295,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="customerId != null">customer_id,</if>
<if test="accountId != null">account_id,</if>
<if test="currency != null">currency,</if>
<if test="exchangeRate != null">exchange_rate,</if>
<if test="baseAmount != null">base_amount,</if>
<if test="bizType != null">biz_type,</if>
<if test="bizId != null">biz_id,</if>
<if test="bizNo != null">biz_no,</if>
@@ -304,6 +316,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="customerId != null">#{customerId},</if>
<if test="accountId != null">#{accountId},</if>
<if test="currency != null">#{currency},</if>
<if test="exchangeRate != null">#{exchangeRate},</if>
<if test="baseAmount != null">#{baseAmount},</if>
<if test="bizType != null">#{bizType},</if>
<if test="bizId != null">#{bizId},</if>
<if test="bizNo != null">#{bizNo},</if>
@@ -0,0 +1,20 @@
-- ----------------------------
-- 收款/流水 增加「对CNY汇率」「本位币金额(CNY)」
-- 统一本位币核算:原币登记,审核后一律折算为 CNY 入客户本位币账户
-- 汇率定义:1 单位原币 = exchange_rate 单位 CNY(CNY 本币汇率固定为 1)
-- ----------------------------
ALTER TABLE `customer_receipt`
ADD COLUMN `exchange_rate` decimal(18,8) DEFAULT 1.00000000 COMMENT '对CNY汇率(1原币=?CNY,CNY为1)' AFTER `currency`,
ADD COLUMN `base_amount` decimal(18,2) DEFAULT 0.00 COMMENT '本位币金额(CNY,原币金额×汇率)' AFTER `exchange_rate`;
-- 历史数据(均为CNY收款):汇率1,本位币金额=原金额
UPDATE `customer_receipt` SET `exchange_rate` = 1, `base_amount` = `amount`
WHERE `exchange_rate` = 1 AND (`base_amount` = 0 OR `base_amount` IS NULL);
ALTER TABLE `customer_balance_flow`
ADD COLUMN `exchange_rate` decimal(18,8) DEFAULT 1.00000000 COMMENT '对CNY汇率(1原币=?CNY,CNY为1)' AFTER `currency`,
ADD COLUMN `base_amount` decimal(18,2) DEFAULT 0.00 COMMENT '本位币金额(CNY,变动额折算)' AFTER `exchange_rate`;
UPDATE `customer_balance_flow` SET `exchange_rate` = 1, `base_amount` = `amount`
WHERE `exchange_rate` = 1 AND (`base_amount` = 0 OR `base_amount` IS NULL);