额度流水显示问题修改

This commit is contained in:
luojiaquan
2026-09-17 17:27:35 +08:00
parent 5dbee51a3a
commit 41ddcc525a
7 changed files with 531 additions and 19 deletions
@@ -3,6 +3,7 @@ package com.ruoyi.customer.customermoney.api;
import com.ruoyi.customer.customermoney.domain.CustomerAccount;
import com.ruoyi.customer.customermoney.domain.CustomerBalanceFlow;
import com.ruoyi.customer.customermoney.domain.CustomerCreditFlow;
import com.ruoyi.customer.customermoney.vo.CustomerAccountStatusVO;
/**
* 客户资金公共服务:客户余额扣减/回补、授信额度占用/释放的统一入口。
@@ -90,4 +91,15 @@ public interface ICustomerAccountApi
* @return 已落库的余额流水(含ID、账户ID)
*/
CustomerBalanceFlow refundBalance(BalanceChangeCommand command);
/**
* 查询客户账号状态(收货扣件准入控制用)。支持 customerId / customerCode 二选一,只读轻量查询,
* 不锁账户也不物化临时额度失效——临时额度已过期时按"视为0"计算可用额度,但 DB 保持原状,
* 由定时任务或后续真实资金入口落库。
*
* <p>欠费定义:可用额度 &lt; 0(已用额度超过授信总额+有效临时额度)。</p>
*
* @return 账号状态VO;客户不存在时返回 null
*/
CustomerAccountStatusVO getAccountStatus(Long customerId, String customerCode);
}
@@ -28,6 +28,7 @@ import com.ruoyi.customer.customermoney.api.CustomerFundBizType;
import com.ruoyi.customer.customermoney.api.ICustomerAccountApi;
import com.ruoyi.customer.customermoney.domain.CustomerAccount;
import com.ruoyi.customer.customermoney.domain.CustomerBalanceFlow;
import com.ruoyi.customer.customermoney.vo.CustomerAccountStatusVO;
import com.ruoyi.customer.customermoney.domain.CustomerCreditFlow;
import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper;
import com.ruoyi.system.domain.BmsExchangeRate;
@@ -574,7 +575,7 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
}
/**
* 解析客户标识:customerId 优先;未传时按 customerCode 精确查询有效客户档案。
* 解析额度命令的客户标识:customerId 优先;未传时按 customerCode 精确查询有效客户档案。
* 编码不存在或匹配多条时抛异常,避免资金变动落到错误客户。
*/
private Long resolveCustomerId(CreditUsageCommand command)
@@ -583,24 +584,108 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
{
return command.getCustomerId();
}
String customerCode = command.getCustomerCode().trim();
BmsCustomer query = new BmsCustomer();
query.setCustomerCode(customerCode);
List<BmsCustomer> customers = bmsCustomerMapper.selectBmsCustomerList(query);
if (customers == null || customers.isEmpty())
{
throw new ServiceException("客户编码不存在:" + customerCode);
}
if (customers.size() > 1)
{
throw new ServiceException("客户编码匹配到多个客户:" + customerCode);
}
Long customerId = customers.get(0).getCustomerId();
Long customerId = resolveCustomerIdByCode(command.getCustomerCode());
// 回填,便于调用方/流水排查时直接取到解析结果
command.setCustomerId(customerId);
return customerId;
}
/**
* 按客户编码精确查询有效客户(selectBmsCustomerList 自带 del_flag='0' 过滤)。
* 编码不存在或匹配多条时抛异常,避免资金/状态判定落到错误客户。
*/
Long resolveCustomerIdByCode(String customerCode)
{
if (!StringUtils.hasText(customerCode))
{
throw new ServiceException("客户编码不能为空");
}
String code = customerCode.trim();
BmsCustomer query = new BmsCustomer();
query.setCustomerCode(code);
List<BmsCustomer> customers = bmsCustomerMapper.selectBmsCustomerList(query);
if (customers == null || customers.isEmpty())
{
throw new ServiceException("客户编码不存在:" + code);
}
if (customers.size() > 1)
{
throw new ServiceException("客户编码匹配到多个客户:" + code);
}
return customers.get(0).getCustomerId();
}
@Override
public CustomerAccountStatusVO getAccountStatus(Long customerId, String customerCode)
{
if (customerId == null && !StringUtils.hasText(customerCode))
{
throw new ServiceException("客户ID与客户编码至少传一个");
}
Long cid = customerId != null ? customerId : resolveCustomerIdByCode(customerCode);
// 1. 读客户档案(不加锁)
BmsCustomer cust = bmsCustomerMapper.selectBmsCustomerByCustomerId(cid);
if (cust == null)
{
return null;
}
// 2. 读CNY账户(普通读不加锁;新客户尚未初始化账户时按0额度处理)
CustomerAccount account = customerMoneyMapper.selectAccountByCustomerCurrency(cid, BASE_CURRENCY);
// 3. 组装状态VO
CustomerAccountStatusVO vo = new CustomerAccountStatusVO();
vo.setCustomerId(cid);
vo.setCustomerCode(cust.getCustomerCode());
vo.setCustomerName(cust.getCustomerName());
vo.setIsEnable(cust.getIsEnable());
vo.setIsEnableLabel("0".equals(cust.getIsEnable()) ? "正常" : "停用");
BigDecimal totalCredit = account != null ? nz(account.getTotalCredit()) : BigDecimal.ZERO;
BigDecimal rawExtra = account != null ? nz(account.getExtraCredit()) : BigDecimal.ZERO;
BigDecimal usedCredit = account != null ? nz(account.getUsedCredit()) : BigDecimal.ZERO;
// 临时额度已过期则视为0(仅内存计算,不物化DB;由定时任务/真实资金入口落库)
boolean expiredPending = isTempCreditExpired(account);
BigDecimal effectiveExtra = expiredPending ? BigDecimal.ZERO : rawExtra;
vo.setBalance(account != null ? nz(account.getBalance()) : BigDecimal.ZERO);
vo.setFrozenBalance(account != null ? nz(account.getFrozenBalance()) : BigDecimal.ZERO);
vo.setTotalCredit(totalCredit);
vo.setExtraCredit(rawExtra);
vo.setEffectiveExtraCredit(effectiveExtra);
vo.setUsedCredit(usedCredit);
BigDecimal available = totalCredit.add(effectiveExtra).subtract(usedCredit);
vo.setAvailableCredit(available);
boolean overdue = available.compareTo(BigDecimal.ZERO) < 0;
vo.setOverdue(overdue);
vo.setExpiredTempCreditPending(expiredPending);
boolean disabled = "1".equals(cust.getIsEnable());
if (disabled && overdue)
{
vo.setAccountStatus(CustomerAccountStatusVO.STATUS_DISABLED_AND_OVERDUE);
vo.setAccountStatusDesc("客户已停用且可用额度不足(欠费),不可新下单");
}
else if (disabled)
{
vo.setAccountStatus(CustomerAccountStatusVO.STATUS_DISABLED);
vo.setAccountStatusDesc("客户已停用,不可新下单");
}
else if (overdue)
{
vo.setAccountStatus(CustomerAccountStatusVO.STATUS_OVERDUE);
vo.setAccountStatusDesc("客户可用额度不足(欠费),需先回款核销恢复额度");
}
else
{
vo.setAccountStatus(CustomerAccountStatusVO.STATUS_NORMAL);
vo.setAccountStatusDesc("客户状态正常,可正常下单");
}
return vo;
}
private void validateBalanceCommand(BalanceChangeCommand command)
{
if (command == null || command.getCustomerId() == null)
@@ -0,0 +1,97 @@
package com.ruoyi.customer.customermoney.vo;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 客户账号状态查询VO(收货扣件准入控制用)。
*
* <p>只读轻量,不锁账户也不物化临时额度失效:临时额度已过期时按"视为0"计算可用额度,
* 但 DB 保持原状——由定时任务或后续真实资金入口落库。</p>
*
* <p>欠费定义:availableCredit &lt; 0(已用额度超过授信总额+有效临时额度)。</p>
*
* @author ruoyi
* @date 2026-09-17
*/
public class CustomerAccountStatusVO implements Serializable
{
private static final long serialVersionUID = 1L;
/** 综合状态:正常 */
public static final String STATUS_NORMAL = "NORMAL";
/** 综合状态:停用 */
public static final String STATUS_DISABLED = "DISABLED";
/** 综合状态:欠费(可用额度不足) */
public static final String STATUS_OVERDUE = "OVERDUE";
/** 综合状态:停用且欠费 */
public static final String STATUS_DISABLED_AND_OVERDUE = "DISABLED_AND_OVERDUE";
/** 客户ID */
private Long customerId;
/** 客户编码 */
private String customerCode;
/** 客户名称 */
private String customerName;
/** 启用状态(0正常 1停用)—— 来自 bms_customer.is_enable */
private String isEnable;
/** 启用状态文案(正常 / 停用) */
private String isEnableLabel;
/** CNY账户余额 */
private BigDecimal balance;
/** CNY账户冻结余额 */
private BigDecimal frozenBalance;
/** 授信总额度(CNY) */
private BigDecimal totalCredit;
/** 临时额度原值(CNY,DB 中 extra_credit 字段) */
private BigDecimal extraCredit;
/** 临时额度实际有效值(过期时为0,接口计算得出,不物化DB) */
private BigDecimal effectiveExtraCredit;
/** 已用额度(CNY) */
private BigDecimal usedCredit;
/** 可用额度(总额度 + 有效临时额度 - 已用额度;可能为负) */
private BigDecimal availableCredit;
/** 是否欠费(可用额度 < 0) */
private boolean overdue;
/** 是否存在过期未物化的临时额度(仅作提示,不影响判定) */
private boolean expiredTempCreditPending;
/** 综合状态:NORMAL / DISABLED / OVERDUE / DISABLED_AND_OVERDUE */
private String accountStatus;
/** 综合状态说明 */
private String accountStatusDesc;
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public String getCustomerCode() { return customerCode; }
public void setCustomerCode(String customerCode) { this.customerCode = customerCode; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public String getIsEnable() { return isEnable; }
public void setIsEnable(String isEnable) { this.isEnable = isEnable; }
public String getIsEnableLabel() { return isEnableLabel; }
public void setIsEnableLabel(String isEnableLabel) { this.isEnableLabel = isEnableLabel; }
public BigDecimal getBalance() { return balance; }
public void setBalance(BigDecimal balance) { this.balance = balance; }
public BigDecimal getFrozenBalance() { return frozenBalance; }
public void setFrozenBalance(BigDecimal frozenBalance) { this.frozenBalance = frozenBalance; }
public BigDecimal getTotalCredit() { return totalCredit; }
public void setTotalCredit(BigDecimal totalCredit) { this.totalCredit = totalCredit; }
public BigDecimal getExtraCredit() { return extraCredit; }
public void setExtraCredit(BigDecimal extraCredit) { this.extraCredit = extraCredit; }
public BigDecimal getEffectiveExtraCredit() { return effectiveExtraCredit; }
public void setEffectiveExtraCredit(BigDecimal effectiveExtraCredit) { this.effectiveExtraCredit = effectiveExtraCredit; }
public BigDecimal getUsedCredit() { return usedCredit; }
public void setUsedCredit(BigDecimal usedCredit) { this.usedCredit = usedCredit; }
public BigDecimal getAvailableCredit() { return availableCredit; }
public void setAvailableCredit(BigDecimal availableCredit) { this.availableCredit = availableCredit; }
public boolean isOverdue() { return overdue; }
public void setOverdue(boolean overdue) { this.overdue = overdue; }
public boolean isExpiredTempCreditPending() { return expiredTempCreditPending; }
public void setExpiredTempCreditPending(boolean expiredTempCreditPending) { this.expiredTempCreditPending = expiredTempCreditPending; }
public String getAccountStatus() { return accountStatus; }
public void setAccountStatus(String accountStatus) { this.accountStatus = accountStatus; }
public String getAccountStatusDesc() { return accountStatusDesc; }
public void setAccountStatusDesc(String accountStatusDesc) { this.accountStatusDesc = accountStatusDesc; }
}