新加额度池配置功能
This commit is contained in:
+10
-9
@@ -31,28 +31,29 @@ public interface ICustomerAccountApi
|
||||
CustomerAccount getAccount(Long customerId, String currency);
|
||||
|
||||
/**
|
||||
* 事务内行锁获取客户CNY账户,不存在则按客户档案额度自动初始化(不处理临时额度过期)。
|
||||
* 调用方拿到后如需最新临时额度,可再调 {@link #expireTempCreditIfNeeded}。
|
||||
* 事务内行锁获取客户CNY账户,不存在则按客户档案额度自动初始化(不处理授信过期)。
|
||||
* 调用方拿到后如需最新额度,可再调 {@link #expireDueCreditsIfNeeded}。
|
||||
*/
|
||||
CustomerAccount lockCnyAccount(Long customerId);
|
||||
|
||||
/**
|
||||
* 临时额度到期自动失效(额度清零 + 回写客户档案 + EXPIRE 额度流水)。
|
||||
* 入参必须是同一事务内行锁查出的账户;未过期时不做任何操作。
|
||||
* 授信(信用额度/临时额度)到期自动失效:过期项额度清零 + 释放角色额度池
|
||||
* + 回写客户档案 + 写 EXPIRE 额度流水。
|
||||
* 入参必须是同一事务内行锁查出的账户;均未过期时不做任何操作。
|
||||
*/
|
||||
void expireTempCreditIfNeeded(CustomerAccount account);
|
||||
void expireDueCreditsIfNeeded(CustomerAccount account);
|
||||
|
||||
/**
|
||||
* 扫描所有临时额度已到期(extra_credit>0 且失效日期早于今天)的CNY账户,
|
||||
* 逐户独立事务物化失效(行锁 -> 清零 -> 回写档案 -> EXPIRE流水)。
|
||||
* 扫描所有授信已到期(信用或临时额度>0 且失效日期早于今天)的CNY账户,
|
||||
* 逐户独立事务物化失效(行锁 -> 清零 -> 释放角色池 -> 回写档案 -> EXPIRE流水)。
|
||||
* 单户失败仅记录日志不影响其他户;定时任务每日调用,应用启动后也会执行一次。
|
||||
*
|
||||
* @return 实际发生失效的账户数
|
||||
*/
|
||||
int expireAllDueTempCredits();
|
||||
int expireAllDueCredits();
|
||||
|
||||
/**
|
||||
* 新建客户时初始化默认CNY账户(余额0,默认总额度1000 CNY,档案有值按档案),
|
||||
* 新建客户时初始化默认CNY账户(余额0,信用额度默认0,档案有值按档案),
|
||||
* 写 INIT 额度流水;CNY账户已存在时不重复创建。
|
||||
*/
|
||||
int initDefaultCnyAccount(Long customerId);
|
||||
|
||||
+83
-27
@@ -49,8 +49,8 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
|
||||
/** 本位币币种 */
|
||||
private static final String BASE_CURRENCY = BmsExchangeRate.BASE_CURRENCY;
|
||||
/** 新客户默认总额度(CNY) */
|
||||
private static final BigDecimal DEFAULT_CREDIT = new BigDecimal("1000");
|
||||
/** 新客户默认总额度(CNY):默认0,信用额度一律通过「申请-审批」分配 */
|
||||
private static final BigDecimal DEFAULT_CREDIT = BigDecimal.ZERO;
|
||||
/** 金额保留位数 */
|
||||
private static final int MONEY_SCALE = 2;
|
||||
|
||||
@@ -60,6 +60,10 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
@Autowired
|
||||
private BmsCustomerMapper bmsCustomerMapper;
|
||||
|
||||
/** 角色额度池台账:授信到期释放角色池 */
|
||||
@Autowired
|
||||
private com.ruoyi.customer.customermoney.service.ICreditQuotaService creditQuotaService;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@@ -113,16 +117,16 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
throw new ServiceException("客户不存在");
|
||||
}
|
||||
BigDecimal initCredit = nz(customer.getCreditEd());
|
||||
if (initCredit.compareTo(BigDecimal.ZERO) <= 0)
|
||||
if (initCredit.compareTo(BigDecimal.ZERO) < 0)
|
||||
{
|
||||
initCredit = DEFAULT_CREDIT;
|
||||
initCredit = BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal initTemp = nz(customer.getTempEd());
|
||||
account = createCnyAccount(customerId, initCredit, initTemp, username);
|
||||
// 档案额度与实际初始额度保持一致
|
||||
bmsCustomerMapper.updateCustomerCredit(customerId, initCredit, initTemp, username);
|
||||
writeInitFlow(account, CustomerCreditFlow.CREDIT_TYPE_CREDIT,
|
||||
initCredit, null, null, username, "新客户默认初始化额度1000 CNY");
|
||||
initCredit, null, null, username, "新客户初始化账户,默认信用额度0(需通过额度申请审批分配)");
|
||||
if (initTemp.compareTo(BigDecimal.ZERO) > 0)
|
||||
{
|
||||
writeInitFlow(account, CustomerCreditFlow.CREDIT_TYPE_TEMP,
|
||||
@@ -132,9 +136,9 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expireAllDueTempCredits()
|
||||
public int expireAllDueCredits()
|
||||
{
|
||||
List<Long> customerIds = customerMoneyMapper.selectExpiredTempCreditCustomerIds();
|
||||
List<Long> customerIds = customerMoneyMapper.selectExpiredCreditCustomerIds();
|
||||
if (customerIds == null || customerIds.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
@@ -142,17 +146,18 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
int expired = 0;
|
||||
for (Long customerId : customerIds)
|
||||
{
|
||||
// 每户独立事务:行锁 -> 失效清零 -> 回写档案 -> EXPIRE流水,单户失败不影响其他户
|
||||
// 每户独立事务:行锁 -> 失效清零 -> 释放角色池 -> 回写档案 -> EXPIRE流水,单户失败不影响其他户
|
||||
try
|
||||
{
|
||||
Boolean changed = requireNewTxTemplate.execute(status -> {
|
||||
CustomerAccount locked = customerMoneyMapper.selectAccountForUpdate(customerId, BASE_CURRENCY);
|
||||
if (locked == null || !isTempCreditExpired(locked))
|
||||
if (locked == null
|
||||
|| (!isTempCreditExpired(locked) && !isCreditExpired(locked)))
|
||||
{
|
||||
// 可能被其他节点/入口抢先处理
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
expireTempCreditIfNeeded(locked);
|
||||
expireDueCreditsIfNeeded(locked);
|
||||
return Boolean.TRUE;
|
||||
});
|
||||
if (Boolean.TRUE.equals(changed))
|
||||
@@ -162,25 +167,39 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("客户{}临时额度到期自动失效失败", customerId, e);
|
||||
log.error("客户{}授信到期自动失效失败", customerId, e);
|
||||
}
|
||||
}
|
||||
log.info("临时额度到期批量失效完成,扫描{}户,实际失效{}户", customerIds.size(), expired);
|
||||
log.info("授信到期批量失效完成,扫描{}户,实际失效{}户", customerIds.size(), expired);
|
||||
return expired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireTempCreditIfNeeded(CustomerAccount account)
|
||||
public void expireDueCreditsIfNeeded(CustomerAccount account)
|
||||
{
|
||||
if (account == null || !isTempCreditExpired(account))
|
||||
if (account == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
boolean creditExpired = isCreditExpired(account);
|
||||
boolean tempExpired = isTempCreditExpired(account);
|
||||
if (!creditExpired && !tempExpired)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 定时任务场景无登录态,操作人回落为 system(人工触发的入口仍记录实际操作人)
|
||||
String username = currentUsername();
|
||||
Long userId = currentUserId();
|
||||
BigDecimal before = nz(account.getExtraCredit());
|
||||
account.setExtraCredit(BigDecimal.ZERO);
|
||||
BigDecimal beforeCredit = nz(account.getTotalCredit());
|
||||
BigDecimal beforeExtra = nz(account.getExtraCredit());
|
||||
if (creditExpired)
|
||||
{
|
||||
account.setTotalCredit(BigDecimal.ZERO);
|
||||
}
|
||||
if (tempExpired)
|
||||
{
|
||||
account.setExtraCredit(BigDecimal.ZERO);
|
||||
}
|
||||
account.setUpdateBy(username);
|
||||
int rows = customerMoneyMapper.updateAccountCredit(account);
|
||||
if (rows == 0)
|
||||
@@ -189,25 +208,49 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
}
|
||||
account.setVersion(account.getVersion() + 1);
|
||||
bmsCustomerMapper.updateCustomerCredit(account.getCustomerId(),
|
||||
nz(account.getTotalCredit()), BigDecimal.ZERO, username);
|
||||
nz(account.getTotalCredit()), nz(account.getExtraCredit()), username);
|
||||
|
||||
// 释放对应角色额度池(无台账背书的历史额度释放金额为0,不影响池余额)
|
||||
if (creditExpired)
|
||||
{
|
||||
creditQuotaService.releaseAll(account.getCustomerId(),
|
||||
CustomerCreditFlow.CREDIT_TYPE_CREDIT, username);
|
||||
writeExpireFlow(account, CustomerCreditFlow.CREDIT_TYPE_CREDIT, beforeCredit,
|
||||
account.getCreditEffectiveDate(), account.getCreditExpireDate(),
|
||||
username, userId, "信用额度到期自动失效");
|
||||
}
|
||||
if (tempExpired)
|
||||
{
|
||||
creditQuotaService.releaseAll(account.getCustomerId(),
|
||||
CustomerCreditFlow.CREDIT_TYPE_TEMP, username);
|
||||
writeExpireFlow(account, CustomerCreditFlow.CREDIT_TYPE_TEMP, beforeExtra,
|
||||
account.getExtraEffectiveDate(), account.getExtraExpireDate(),
|
||||
username, userId, "临时额度到期自动失效");
|
||||
}
|
||||
}
|
||||
|
||||
/** 到期失效写 EXPIRE 额度流水 */
|
||||
private void writeExpireFlow(CustomerAccount account, String creditType, BigDecimal before,
|
||||
Date effectiveDate, Date expireDate,
|
||||
String username, Long userId, String reason)
|
||||
{
|
||||
CustomerCreditFlow flow = new CustomerCreditFlow();
|
||||
flow.setFlowNo(generateNo("EDLS"));
|
||||
flow.setCustomerId(account.getCustomerId());
|
||||
flow.setAccountId(account.getId());
|
||||
flow.setCreditType(CustomerCreditFlow.CREDIT_TYPE_TEMP);
|
||||
flow.setCreditType(creditType);
|
||||
flow.setChangeType(CustomerCreditFlow.CHANGE_EXPIRE);
|
||||
flow.setDirection(-1);
|
||||
flow.setBeforeAmount(before);
|
||||
flow.setChangeAmount(before);
|
||||
flow.setAfterAmount(BigDecimal.ZERO);
|
||||
flow.setUsedCredit(nz(account.getUsedCredit()));
|
||||
flow.setEffectiveDate(account.getExtraEffectiveDate());
|
||||
flow.setExpireDate(account.getExtraExpireDate());
|
||||
flow.setReason("临时额度到期自动失效");
|
||||
flow.setEffectiveDate(effectiveDate);
|
||||
flow.setExpireDate(expireDate);
|
||||
flow.setReason(reason);
|
||||
flow.setOperatorId(userId);
|
||||
flow.setCreateBy(username);
|
||||
flow.setRemark("系统在访问账户时检测到临时额度已过失效日期,自动置零");
|
||||
flow.setRemark("系统在访问账户或定时任务中检测到额度已过失效日期,自动置零");
|
||||
flow.setDelFlag("0");
|
||||
customerMoneyMapper.insertCreditFlow(flow);
|
||||
}
|
||||
@@ -473,7 +516,7 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
if (BASE_CURRENCY.equals(currency))
|
||||
{
|
||||
CustomerAccount account = getOrInitCnyAccount(customerId, username);
|
||||
expireTempCreditIfNeeded(account);
|
||||
expireDueCreditsIfNeeded(account);
|
||||
return account;
|
||||
}
|
||||
return customerMoneyMapper.selectAccountForUpdate(customerId, currency);
|
||||
@@ -488,7 +531,7 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
if (BASE_CURRENCY.equals(currency))
|
||||
{
|
||||
account = getOrInitCnyAccount(customerId, username);
|
||||
expireTempCreditIfNeeded(account);
|
||||
expireDueCreditsIfNeeded(account);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -676,21 +719,24 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
BigDecimal rawExtra = account != null ? nz(account.getExtraCredit()) : BigDecimal.ZERO;
|
||||
BigDecimal usedCredit = account != null ? nz(account.getUsedCredit()) : BigDecimal.ZERO;
|
||||
|
||||
// 临时额度已过期则视为0(仅内存计算,不物化DB;由定时任务/真实资金入口落库)
|
||||
// 已过期的信用/临时额度视为0(仅内存计算,不物化DB;由定时任务/真实资金入口落库)
|
||||
boolean creditExpiredPending = isCreditExpired(account);
|
||||
boolean expiredPending = isTempCreditExpired(account);
|
||||
BigDecimal effectiveTotal = creditExpiredPending ? BigDecimal.ZERO : totalCredit;
|
||||
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.setEffectiveTotalCredit(effectiveTotal);
|
||||
vo.setExtraCredit(rawExtra);
|
||||
vo.setEffectiveExtraCredit(effectiveExtra);
|
||||
vo.setUsedCredit(usedCredit);
|
||||
BigDecimal available = totalCredit.add(effectiveExtra).subtract(usedCredit);
|
||||
BigDecimal available = effectiveTotal.add(effectiveExtra).subtract(usedCredit);
|
||||
vo.setAvailableCredit(available);
|
||||
boolean overdue = available.compareTo(BigDecimal.ZERO) < 0;
|
||||
vo.setOverdue(overdue);
|
||||
vo.setExpiredTempCreditPending(expiredPending);
|
||||
vo.setExpiredTempCreditPending(expiredPending || creditExpiredPending);
|
||||
|
||||
boolean disabled = "1".equals(cust.getIsEnable());
|
||||
if (disabled && overdue)
|
||||
@@ -779,6 +825,16 @@ public class CustomerAccountApiImpl implements ICustomerAccountApi
|
||||
.atZone(java.time.ZoneId.systemDefault()).toLocalDate());
|
||||
}
|
||||
|
||||
/** 信用额度是否已过期:口径同临时额度(失效日当天仍有效) */
|
||||
private boolean isCreditExpired(CustomerAccount account)
|
||||
{
|
||||
return account != null
|
||||
&& nz(account.getTotalCredit()).compareTo(BigDecimal.ZERO) > 0
|
||||
&& account.getCreditExpireDate() != null
|
||||
&& LocalDate.now().isAfter(account.getCreditExpireDate().toInstant()
|
||||
.atZone(java.time.ZoneId.systemDefault()).toLocalDate());
|
||||
}
|
||||
|
||||
private String resolveCurrency(String currency)
|
||||
{
|
||||
return StringUtils.hasText(currency) ? currency.trim().toUpperCase() : BASE_CURRENCY;
|
||||
|
||||
+9
-1
@@ -34,9 +34,17 @@ public class CustomerAccount extends BaseEntity
|
||||
/** 冻结余额 */
|
||||
private BigDecimal frozenBalance;
|
||||
|
||||
/** 总额度 */
|
||||
/** 总额度(信用额度,默认0;配置有效期后到期次日自动清零) */
|
||||
private BigDecimal totalCredit;
|
||||
|
||||
/** 信用额度生效日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date creditEffectiveDate;
|
||||
|
||||
/** 信用额度失效日期(空=长期;到期次日自动清零) */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date creditExpireDate;
|
||||
|
||||
/** 额外额度(临时额度) */
|
||||
private BigDecimal extraCredit;
|
||||
|
||||
|
||||
+6
@@ -79,6 +79,9 @@ public class CustomerCreditApply extends BaseEntity
|
||||
/** 审批人ID */
|
||||
private Long approveUserId;
|
||||
|
||||
/** 审批占用的额度池角色ID(NULL=超管审批不占池;调减/到期释放时不消费池) */
|
||||
private Long approveRoleId;
|
||||
|
||||
/** 审批时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date approveTime;
|
||||
@@ -99,4 +102,7 @@ public class CustomerCreditApply extends BaseEntity
|
||||
|
||||
/** 客户名称 */
|
||||
private String customerName;
|
||||
|
||||
/** 额度池角色名称(关联展示) */
|
||||
private String approveRoleName;
|
||||
}
|
||||
|
||||
+3
@@ -101,6 +101,9 @@ public class CustomerCreditFlow extends BaseEntity
|
||||
/** 操作人ID */
|
||||
private Long operatorId;
|
||||
|
||||
/** 额度池角色ID(ADJUST 审批调整时记录本次占用/释放的角色池;NULL=超管/系统) */
|
||||
private Long roleId;
|
||||
|
||||
/** 删除标志(0存在 1删除) */
|
||||
private String delFlag;
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.ruoyi.customer.customermoney.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 客户授信额度分配台账 customer_credit_grant
|
||||
* 审批通过的净增额度占一行(占用角色额度池);调减/到期按 FIFO 核销行余额。
|
||||
* 角色池已用 = SUM(remaining WHERE role_id=? AND status='0')
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CustomerCreditGrant extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 占用中 */
|
||||
public static final String STATUS_ACTIVE = "0";
|
||||
/** 已释放 */
|
||||
public static final String STATUS_RELEASED = "1";
|
||||
|
||||
/** 台账ID */
|
||||
private Long grantId;
|
||||
|
||||
/** 客户ID */
|
||||
private Long customerId;
|
||||
|
||||
/** 客户账户ID */
|
||||
private Long accountId;
|
||||
|
||||
/** 占用的额度池角色ID(NULL=超管审批/不占池) */
|
||||
private Long roleId;
|
||||
|
||||
/** 审批人ID */
|
||||
private Long approveUserId;
|
||||
|
||||
/** 额度申请单ID */
|
||||
private Long applyId;
|
||||
|
||||
/** 额度流水ID */
|
||||
private Long flowId;
|
||||
|
||||
/** 额度类型:CREDIT信用额度 TEMP临时额度 */
|
||||
private String creditType;
|
||||
|
||||
/** 原始占用池金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 仍占用池的待释放余额 */
|
||||
private BigDecimal remaining;
|
||||
|
||||
/** 生效日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date effectiveDate;
|
||||
|
||||
/** 失效日期 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date expireDate;
|
||||
|
||||
/** 状态:0占用中 1已释放 */
|
||||
private String status;
|
||||
|
||||
/** 全部释放时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date releaseTime;
|
||||
|
||||
/** 删除标志 */
|
||||
private String delFlag;
|
||||
|
||||
/* -------- 展示字段 -------- */
|
||||
|
||||
/** 客户编码 */
|
||||
private String customerCode;
|
||||
|
||||
/** 客户名称 */
|
||||
private String customerName;
|
||||
|
||||
/** 角色名称 */
|
||||
private String roleName;
|
||||
|
||||
/** 审批人用户名 */
|
||||
private String approveBy;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.customer.customermoney.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 角色可分配授信额度池 sys_role_credit_quota
|
||||
* 一个角色一行;没有记录视为额度0(该角色用户不能审批任何增量授信)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SysRoleCreditQuota extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 角色ID */
|
||||
private Long roleId;
|
||||
|
||||
/** 可分配额度总额(CNY) */
|
||||
private BigDecimal totalQuota;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
private Integer version;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.customer.customermoney.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerCreditGrant;
|
||||
import com.ruoyi.customer.customermoney.domain.SysRoleCreditQuota;
|
||||
import com.ruoyi.customer.customermoney.vo.RoleCreditQuotaVo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 角色额度池 / 授信分配台账 Mapper
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
public interface CreditQuotaMapper
|
||||
{
|
||||
/* ================= 角色额度池 ================= */
|
||||
|
||||
/** 全部角色的额度池(左连接,未配置额度的角色总额度按0返回;used 由台账 remaining 实时求和) */
|
||||
List<RoleCreditQuotaVo> selectRoleQuotaList();
|
||||
|
||||
/** 指定用户名下正常角色的额度池(sys_user_role 关联) */
|
||||
List<RoleCreditQuotaVo> selectRoleQuotaListByUserId(@Param("userId") Long userId);
|
||||
|
||||
/** 行锁取额度池配置(不存在返回null) */
|
||||
SysRoleCreditQuota selectQuotaForUpdate(@Param("roleId") Long roleId);
|
||||
|
||||
/** 角色池已分配额(占用中台账 remaining 合计;调用前需先持有该池行锁) */
|
||||
BigDecimal sumActiveRemaining(@Param("roleId") Long roleId);
|
||||
|
||||
/** 新建额度池配置 */
|
||||
int insertQuota(SysRoleCreditQuota quota);
|
||||
|
||||
/** 修改额度池总额度(乐观锁) */
|
||||
int updateQuota(SysRoleCreditQuota quota);
|
||||
|
||||
/* ================= 授信分配台账 ================= */
|
||||
|
||||
/** 台账查询(带客户名/角色名) */
|
||||
List<CustomerCreditGrant> selectGrantList(CustomerCreditGrant query);
|
||||
|
||||
/** 行锁取某客户某额度类型的占用中台账(FIFO:按 grant_id 升序) */
|
||||
List<CustomerCreditGrant> selectActiveGrantsForUpdate(@Param("customerId") Long customerId,
|
||||
@Param("creditType") String creditType);
|
||||
|
||||
/** 新增台账 */
|
||||
int insertGrant(CustomerCreditGrant grant);
|
||||
|
||||
/** 更新台账剩余余额/状态(乐观条件 remaining 不变,防并发核销) */
|
||||
int updateGrantRemaining(CustomerCreditGrant grant);
|
||||
}
|
||||
+2
-2
@@ -29,10 +29,10 @@ public interface CustomerMoneyMapper
|
||||
List<CustomerAccount> selectAccountListByCustomerId(@Param("customerId") Long customerId);
|
||||
|
||||
/**
|
||||
* 扫描临时额度已到期的CNY账户客户ID(extra_credit>0 且失效日期早于今天;失效日当天仍有效)。
|
||||
* 扫描授信已到期的CNY账户客户ID(信用或临时额度失效日早于今天;失效日当天仍有效)。
|
||||
* 供定时任务批量物化失效使用,结果集不加锁,逐户处理时再行锁。
|
||||
*/
|
||||
List<Long> selectExpiredTempCreditCustomerIds();
|
||||
List<Long> selectExpiredCreditCustomerIds();
|
||||
|
||||
/** 新增账户 */
|
||||
int insertAccount(CustomerAccount account);
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.ruoyi.customer.customermoney.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerAccount;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerCreditGrant;
|
||||
import com.ruoyi.customer.customermoney.vo.RoleCreditQuotaVo;
|
||||
|
||||
/**
|
||||
* 角色额度池 Service
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
public interface ICreditQuotaService
|
||||
{
|
||||
/** 全部角色额度池(管理员配置用) */
|
||||
List<RoleCreditQuotaVo> listRoleQuotas();
|
||||
|
||||
/** 当前用户可使用的角色额度池(审批选池用;超管返回空列表由调用方放行) */
|
||||
List<RoleCreditQuotaVo> myPools(Long userId);
|
||||
|
||||
/** 设置角色可分配额度总额(不能小于该池已分配额) */
|
||||
void setRoleQuota(Long roleId, BigDecimal totalQuota, String remark);
|
||||
|
||||
/** 额度池占用台账查询 */
|
||||
List<CustomerCreditGrant> listGrants(CustomerCreditGrant query);
|
||||
|
||||
/**
|
||||
* 审批净增额度:占用角色额度池并写台账。
|
||||
* roleId 为 null(超管审批)时台账角色为空、不占用任何池。
|
||||
* 必须在客户账户行锁之后调用;内部对角色池加行锁防并发超配。
|
||||
*/
|
||||
void consume(CustomerAccount account, String creditType, BigDecimal amount,
|
||||
Date effectiveDate, Date expireDate, Long roleId,
|
||||
Long applyId, Long flowId, String username, Long userId);
|
||||
|
||||
/**
|
||||
* 审批调减:按 FIFO 核销该客户该类型的占用台账,释放对应角色池。
|
||||
* 历史无台账背书的额度(建档直配/超管授予)核销时不释放池。
|
||||
* 返回实际从池中释放的金额(可能小于 amount)。
|
||||
*/
|
||||
BigDecimal releaseFifo(Long customerId, String creditType, BigDecimal amount, String username);
|
||||
|
||||
/**
|
||||
* 到期自动失效:释放该客户该类型全部占用台账,返回释放总额。
|
||||
*/
|
||||
BigDecimal releaseAll(Long customerId, String creditType, String username);
|
||||
}
|
||||
+3
-2
@@ -19,7 +19,7 @@ public interface ICustomerMoneyService
|
||||
List<CustomerAccount> selectAccountList(Long customerId);
|
||||
|
||||
/**
|
||||
* 新建客户时初始化默认CNY账户(余额0,默认总额度1000 CNY),并写 INIT 额度流水。
|
||||
* 新建客户时初始化默认CNY账户(余额0,信用额度默认0),并写 INIT 额度流水。
|
||||
* 已存在CNY账户时不重复创建。
|
||||
*
|
||||
* @param customerId 客户ID
|
||||
@@ -60,8 +60,9 @@ public interface ICustomerMoneyService
|
||||
/**
|
||||
* 审批通过额度调整申请:PENDING -> APPROVED。
|
||||
* 事务内重新锁定CNY账户并按审批时的实际额度计算/校验,通过后调整额度、回写客户档案、写额度流水。
|
||||
* 净增额度需占用 approveRoleId 对应角色额度池(超管传 null 不占池;净减/到期释放不占池)。
|
||||
*/
|
||||
int approveCreditApply(Long applyId, String approveRemark);
|
||||
int approveCreditApply(Long applyId, String approveRemark, Long approveRoleId);
|
||||
|
||||
/** 审批驳回:PENDING -> REJECTED,驳回原因必填 */
|
||||
int rejectCreditApply(Long applyId, String rejectReason);
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
package com.ruoyi.customer.customermoney.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import com.ruoyi.common.core.domain.entity.SysRole;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerAccount;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerCreditGrant;
|
||||
import com.ruoyi.customer.customermoney.domain.SysRoleCreditQuota;
|
||||
import com.ruoyi.customer.customermoney.mapper.CreditQuotaMapper;
|
||||
import com.ruoyi.customer.customermoney.service.ICreditQuotaService;
|
||||
import com.ruoyi.customer.customermoney.vo.RoleCreditQuotaVo;
|
||||
import com.ruoyi.system.mapper.SysRoleMapper;
|
||||
|
||||
/**
|
||||
* 角色额度池 Service实现
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
@Service
|
||||
public class CreditQuotaServiceImpl implements ICreditQuotaService
|
||||
{
|
||||
private static final int MONEY_SCALE = 2;
|
||||
|
||||
@Autowired
|
||||
private CreditQuotaMapper creditQuotaMapper;
|
||||
|
||||
@Autowired
|
||||
private SysRoleMapper sysRoleMapper;
|
||||
|
||||
@Override
|
||||
public List<RoleCreditQuotaVo> listRoleQuotas()
|
||||
{
|
||||
List<RoleCreditQuotaVo> list = creditQuotaMapper.selectRoleQuotaList();
|
||||
list.forEach(this::fillAvailable);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleCreditQuotaVo> myPools(Long userId)
|
||||
{
|
||||
if (userId == null)
|
||||
{
|
||||
throw new ServiceException("无法获取当前登录用户");
|
||||
}
|
||||
List<RoleCreditQuotaVo> list = creditQuotaMapper.selectRoleQuotaListByUserId(userId);
|
||||
list.forEach(this::fillAvailable);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setRoleQuota(Long roleId, BigDecimal totalQuota, String remark)
|
||||
{
|
||||
if (roleId == null)
|
||||
{
|
||||
throw new ServiceException("角色ID不能为空");
|
||||
}
|
||||
SysRole role = sysRoleMapper.selectRoleById(roleId);
|
||||
if (role == null || "2".equals(role.getDelFlag()))
|
||||
{
|
||||
throw new ServiceException("角色不存在或已删除");
|
||||
}
|
||||
BigDecimal total = nz(totalQuota).setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
if (total.compareTo(BigDecimal.ZERO) < 0)
|
||||
{
|
||||
throw new ServiceException("可分配额度总额不能为负数");
|
||||
}
|
||||
String username = SecurityUtils.getUsername();
|
||||
|
||||
SysRoleCreditQuota quota = creditQuotaMapper.selectQuotaForUpdate(roleId);
|
||||
if (quota == null)
|
||||
{
|
||||
SysRoleCreditQuota newQuota = new SysRoleCreditQuota();
|
||||
newQuota.setRoleId(roleId);
|
||||
newQuota.setTotalQuota(total);
|
||||
newQuota.setVersion(0);
|
||||
newQuota.setCreateBy(username);
|
||||
newQuota.setRemark(StringUtils.hasText(remark) ? remark.trim() : null);
|
||||
creditQuotaMapper.insertQuota(newQuota);
|
||||
return;
|
||||
}
|
||||
BigDecimal used = nz(creditQuotaMapper.sumActiveRemaining(roleId));
|
||||
if (total.compareTo(used) < 0)
|
||||
{
|
||||
throw new ServiceException("可分配额度总额不能小于该角色已分配额度:" + used.toPlainString() + " CNY");
|
||||
}
|
||||
quota.setTotalQuota(total);
|
||||
quota.setUpdateBy(username);
|
||||
quota.setRemark(StringUtils.hasText(remark) ? remark.trim() : null);
|
||||
if (creditQuotaMapper.updateQuota(quota) == 0)
|
||||
{
|
||||
throw new ServiceException("额度池已被其他操作变动,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CustomerCreditGrant> listGrants(CustomerCreditGrant query)
|
||||
{
|
||||
return creditQuotaMapper.selectGrantList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void consume(CustomerAccount account, String creditType, BigDecimal amount,
|
||||
Date effectiveDate, Date expireDate, Long roleId,
|
||||
Long applyId, Long flowId, String username, Long userId)
|
||||
{
|
||||
if (account == null || account.getId() == null)
|
||||
{
|
||||
throw new ServiceException("客户账户不存在,无法分配额度");
|
||||
}
|
||||
BigDecimal delta = nz(amount).setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
if (delta.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CustomerCreditGrant grant = new CustomerCreditGrant();
|
||||
grant.setCustomerId(account.getCustomerId());
|
||||
grant.setAccountId(account.getId());
|
||||
grant.setApproveUserId(userId);
|
||||
grant.setApplyId(applyId);
|
||||
grant.setFlowId(flowId);
|
||||
grant.setCreditType(creditType);
|
||||
grant.setAmount(delta);
|
||||
grant.setRemaining(delta);
|
||||
grant.setEffectiveDate(effectiveDate);
|
||||
grant.setExpireDate(expireDate);
|
||||
grant.setStatus(CustomerCreditGrant.STATUS_ACTIVE);
|
||||
grant.setCreateBy(username);
|
||||
|
||||
if (roleId == null)
|
||||
{
|
||||
// 超管审批:不占用任何角色池
|
||||
creditQuotaMapper.insertGrant(grant);
|
||||
return;
|
||||
}
|
||||
// 校验角色归属与状态
|
||||
SysRole role = sysRoleMapper.selectRoleById(roleId);
|
||||
if (role == null || "2".equals(role.getDelFlag()) || !"0".equals(role.getStatus()))
|
||||
{
|
||||
throw new ServiceException("额度池角色不存在或已停用");
|
||||
}
|
||||
List<Long> myRoleIds = sysRoleMapper.selectRoleListByUserId(userId)
|
||||
.stream().filter(id -> id != null && id.equals(roleId)).collect(Collectors.toList());
|
||||
if (myRoleIds.isEmpty())
|
||||
{
|
||||
throw new ServiceException("不能使用不属于自己的角色额度池");
|
||||
}
|
||||
// 池行锁 -> 已分配实时汇总 -> 容量校验,防并发超配
|
||||
SysRoleCreditQuota quota = creditQuotaMapper.selectQuotaForUpdate(roleId);
|
||||
if (quota == null)
|
||||
{
|
||||
throw new ServiceException("角色【" + role.getRoleName() + "】未配置可分配额度总额,请联系管理员");
|
||||
}
|
||||
BigDecimal used = nz(creditQuotaMapper.sumActiveRemaining(roleId));
|
||||
BigDecimal available = nz(quota.getTotalQuota()).subtract(used);
|
||||
if (available.compareTo(delta) < 0)
|
||||
{
|
||||
throw new ServiceException("角色【" + role.getRoleName() + "】额度池不足:总额度 "
|
||||
+ nz(quota.getTotalQuota()).toPlainString() + ",已分配 " + used.toPlainString()
|
||||
+ ",剩余 " + available.toPlainString() + ",本次需分配 " + delta.toPlainString());
|
||||
}
|
||||
grant.setRoleId(roleId);
|
||||
creditQuotaMapper.insertGrant(grant);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public BigDecimal releaseFifo(Long customerId, String creditType, BigDecimal amount, String username)
|
||||
{
|
||||
BigDecimal need = nz(amount);
|
||||
if (need.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
List<CustomerCreditGrant> grants = creditQuotaMapper.selectActiveGrantsForUpdate(customerId, creditType);
|
||||
if (grants.isEmpty())
|
||||
{
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
lockQuotaRows(grants);
|
||||
|
||||
BigDecimal released = BigDecimal.ZERO;
|
||||
Date now = new Date();
|
||||
for (CustomerCreditGrant grant : grants)
|
||||
{
|
||||
if (need.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
BigDecimal remaining = nz(grant.getRemaining());
|
||||
if (remaining.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BigDecimal take = remaining.min(need);
|
||||
BigDecimal after = remaining.subtract(take).setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
grant.setRemaining(after);
|
||||
grant.setUpdateBy(username);
|
||||
if (after.compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
grant.setStatus(CustomerCreditGrant.STATUS_RELEASED);
|
||||
grant.setReleaseTime(now);
|
||||
}
|
||||
else
|
||||
{
|
||||
grant.setStatus(CustomerCreditGrant.STATUS_ACTIVE);
|
||||
grant.setReleaseTime(null);
|
||||
}
|
||||
creditQuotaMapper.updateGrantRemaining(grant);
|
||||
need = need.subtract(take);
|
||||
released = released.add(take);
|
||||
}
|
||||
return released.setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public BigDecimal releaseAll(Long customerId, String creditType, String username)
|
||||
{
|
||||
List<CustomerCreditGrant> grants = creditQuotaMapper.selectActiveGrantsForUpdate(customerId, creditType);
|
||||
if (grants.isEmpty())
|
||||
{
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
lockQuotaRows(grants);
|
||||
|
||||
BigDecimal released = BigDecimal.ZERO;
|
||||
Date now = new Date();
|
||||
for (CustomerCreditGrant grant : grants)
|
||||
{
|
||||
BigDecimal remaining = nz(grant.getRemaining());
|
||||
if (remaining.compareTo(BigDecimal.ZERO) <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
grant.setRemaining(BigDecimal.ZERO);
|
||||
grant.setStatus(CustomerCreditGrant.STATUS_RELEASED);
|
||||
grant.setReleaseTime(now);
|
||||
grant.setUpdateBy(username);
|
||||
creditQuotaMapper.updateGrantRemaining(grant);
|
||||
released = released.add(remaining);
|
||||
}
|
||||
return released.setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对台账涉及的非空角色池按 role_id 升序加行锁,
|
||||
* 保证与池占用(consume)互斥且避免多角色交叉死锁。
|
||||
*/
|
||||
private void lockQuotaRows(List<CustomerCreditGrant> grants)
|
||||
{
|
||||
List<Long> roleIds = grants.stream()
|
||||
.map(CustomerCreditGrant::getRoleId)
|
||||
.filter(id -> id != null)
|
||||
.distinct()
|
||||
.sorted(Comparator.naturalOrder())
|
||||
.collect(Collectors.toList());
|
||||
for (Long roleId : roleIds)
|
||||
{
|
||||
// 池配置理论上一定存在(占用时已校验);若被误删则空过,不影响台账核销
|
||||
creditQuotaMapper.selectQuotaForUpdate(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillAvailable(RoleCreditQuotaVo vo)
|
||||
{
|
||||
BigDecimal total = nz(vo.getTotalQuota());
|
||||
BigDecimal used = nz(vo.getUsedQuota());
|
||||
vo.setTotalQuota(total);
|
||||
vo.setUsedQuota(used);
|
||||
vo.setAvailableQuota(total.subtract(used).setScale(MONEY_SCALE, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
private BigDecimal nz(BigDecimal value)
|
||||
{
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
}
|
||||
+70
-29
@@ -28,6 +28,7 @@ import com.ruoyi.customer.customermoney.domain.CustomerCreditFlow;
|
||||
import com.ruoyi.customer.customermoney.domain.CustomerReceipt;
|
||||
import com.ruoyi.customer.customermoney.mapper.CustomerCreditApplyMapper;
|
||||
import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper;
|
||||
import com.ruoyi.customer.customermoney.service.ICreditQuotaService;
|
||||
import com.ruoyi.customer.customermoney.service.ICustomerMoneyService;
|
||||
import com.ruoyi.system.domain.BmsExchangeRate;
|
||||
import com.ruoyi.system.service.IBmsExchangeRateService;
|
||||
@@ -64,19 +65,24 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
@Autowired
|
||||
private ICustomerAccountApi customerAccountApi;
|
||||
|
||||
/** 角色额度池:审批增量占用/调减与到期释放 */
|
||||
@Autowired
|
||||
private ICreditQuotaService creditQuotaService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CustomerAccount> selectAccountList(Long customerId)
|
||||
{
|
||||
List<CustomerAccount> list = customerMoneyMapper.selectAccountListByCustomerId(customerId);
|
||||
// 查询时顺带处理临时额度到期自动失效(仅CNY账户),保证卡片展示为最新值
|
||||
// 查询时顺带处理授信到期自动失效(仅CNY账户),保证卡片展示为最新值
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
{
|
||||
CustomerAccount account = list.get(i);
|
||||
if (BASE_CURRENCY.equals(account.getCurrency()) && isTempCreditExpired(account))
|
||||
if (BASE_CURRENCY.equals(account.getCurrency())
|
||||
&& (isTempCreditExpired(account) || isCreditExpired(account)))
|
||||
{
|
||||
CustomerAccount locked = customerAccountApi.lockCnyAccount(customerId);
|
||||
customerAccountApi.expireTempCreditIfNeeded(locked);
|
||||
customerAccountApi.expireDueCreditsIfNeeded(locked);
|
||||
if (locked != null)
|
||||
{
|
||||
list.set(i, locked);
|
||||
@@ -331,10 +337,11 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
|
||||
/**
|
||||
* 执行额度调整(额度申请审批通过后调用)。
|
||||
* 锁CNY账户 -> 按审批时实际额度计算并校验 -> 乐观锁更新 -> 回写客户档案 -> 写额度流水。
|
||||
* 锁CNY账户 -> 按审批时实际额度计算并校验 -> 乐观锁更新 -> 回写客户档案 -> 写额度流水 -> 角色额度池占用/释放。
|
||||
* roleId 为本次审批选择的额度池角色(超管/净减时可为null);applyId 为审批申请单ID。
|
||||
* 返回已落库的额度流水(含ID)。
|
||||
*/
|
||||
private CustomerCreditFlow executeAdjustCredit(CustomerCreditFlow cmd)
|
||||
private CustomerCreditFlow executeAdjustCredit(CustomerCreditFlow cmd, Long roleId, Long applyId)
|
||||
{
|
||||
String username = SecurityUtils.getUsername();
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
@@ -365,18 +372,18 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
}
|
||||
after = after.setScale(MONEY_SCALE, RoundingMode.HALF_UP);
|
||||
|
||||
// 3. 临时额度有效期处理(不允许预约未来生效,失效日必须晚于生效日)
|
||||
// 3. 有效期处理:信用/临时同口径——不允许预约未来生效;信用额度失效日期必填,临时额度空=长期
|
||||
Date effectiveDate = null;
|
||||
Date expireDate = null;
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate eff = cmd.getEffectiveDate() != null ? toLocalDate(cmd.getEffectiveDate()) : today;
|
||||
effectiveDate = toSqlDate(eff);
|
||||
if (cmd.getExpireDate() != null)
|
||||
{
|
||||
expireDate = toSqlDate(toLocalDate(cmd.getExpireDate()));
|
||||
}
|
||||
if (isTemp)
|
||||
{
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate eff = cmd.getEffectiveDate() != null ? toLocalDate(cmd.getEffectiveDate()) : today;
|
||||
effectiveDate = toSqlDate(eff);
|
||||
if (cmd.getExpireDate() != null)
|
||||
{
|
||||
expireDate = toSqlDate(toLocalDate(cmd.getExpireDate()));
|
||||
}
|
||||
account.setExtraCredit(after);
|
||||
account.setExtraEffectiveDate(effectiveDate);
|
||||
account.setExtraExpireDate(expireDate);
|
||||
@@ -384,6 +391,8 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
else
|
||||
{
|
||||
account.setTotalCredit(after);
|
||||
account.setCreditEffectiveDate(effectiveDate);
|
||||
account.setCreditExpireDate(expireDate);
|
||||
}
|
||||
account.setUpdateBy(username);
|
||||
|
||||
@@ -417,10 +426,29 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
flow.setReason(reason);
|
||||
flow.setAdjustMode(adjustMode);
|
||||
flow.setOperatorId(userId);
|
||||
// 非超管审批且未指定角色池的净增申请不允许通过(在 consume 中强校验)
|
||||
flow.setRoleId(SecurityUtils.isAdmin(userId) ? null : roleId);
|
||||
flow.setCreateBy(username);
|
||||
flow.setRemark(cmd.getRemark());
|
||||
flow.setDelFlag("0");
|
||||
customerMoneyMapper.insertCreditFlow(flow);
|
||||
|
||||
// 7. 角色额度池占用/释放(账户行锁已持有;池锁在台账服务内部按角色加锁)
|
||||
BigDecimal netDelta = after.subtract(before);
|
||||
if (netDelta.compareTo(BigDecimal.ZERO) > 0)
|
||||
{
|
||||
Long poolRoleId = SecurityUtils.isAdmin(userId) ? null : roleId;
|
||||
if (poolRoleId == null && !SecurityUtils.isAdmin(userId))
|
||||
{
|
||||
throw new ServiceException("请选择本次额度分配占用的角色额度池");
|
||||
}
|
||||
creditQuotaService.consume(account, creditType, netDelta,
|
||||
effectiveDate, expireDate, poolRoleId, applyId, flow.getId(), username, userId);
|
||||
}
|
||||
else if (netDelta.compareTo(BigDecimal.ZERO) < 0)
|
||||
{
|
||||
creditQuotaService.releaseFifo(customerId, creditType, netDelta.negate(), username);
|
||||
}
|
||||
return flow;
|
||||
}
|
||||
|
||||
@@ -477,7 +505,7 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int approveCreditApply(Long applyId, String approveRemark)
|
||||
public int approveCreditApply(Long applyId, String approveRemark, Long approveRoleId)
|
||||
{
|
||||
if (applyId == null)
|
||||
{
|
||||
@@ -505,7 +533,7 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
cmd.setRemark(apply.getRemark());
|
||||
cmd.setEffectiveDate(apply.getEffectiveDate());
|
||||
cmd.setExpireDate(apply.getExpireDate());
|
||||
CustomerCreditFlow flow = executeAdjustCredit(cmd);
|
||||
CustomerCreditFlow flow = executeAdjustCredit(cmd, approveRoleId, applyId);
|
||||
|
||||
CustomerCreditApply update = new CustomerCreditApply();
|
||||
update.setApplyId(applyId);
|
||||
@@ -513,6 +541,8 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
update.setFlowId(flow.getId());
|
||||
update.setApproveBy(username);
|
||||
update.setApproveUserId(userId);
|
||||
// 超管审批不占池;非超管净减时 roleId 为 null 也正常(只释放不占用)
|
||||
update.setApproveRoleId(SecurityUtils.isAdmin(userId) ? null : approveRoleId);
|
||||
update.setApproveRemark(approveRemark);
|
||||
int rows = creditApplyMapper.updateApproved(update);
|
||||
if (rows == 0)
|
||||
@@ -628,7 +658,7 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
boolean isTemp = CustomerCreditFlow.CREDIT_TYPE_TEMP.equals(creditType);
|
||||
|
||||
CustomerAccount account = customerAccountApi.lockCnyAccount(cmd.getCustomerId());
|
||||
customerAccountApi.expireTempCreditIfNeeded(account);
|
||||
customerAccountApi.expireDueCreditsIfNeeded(account);
|
||||
|
||||
BigDecimal before = isTemp ? nz(account.getExtraCredit()) : nz(account.getTotalCredit());
|
||||
BigDecimal otherCredit = isTemp ? nz(account.getTotalCredit()) : nz(account.getExtraCredit());
|
||||
@@ -660,21 +690,23 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
throw new ServiceException("额度未发生变化,无需调整");
|
||||
}
|
||||
|
||||
if (isTemp)
|
||||
if (!isTemp && cmd.getExpireDate() == null)
|
||||
{
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate eff = cmd.getEffectiveDate() != null ? toLocalDate(cmd.getEffectiveDate()) : today;
|
||||
if (eff.isAfter(today))
|
||||
// 信用额度必须设置失效日期(到期自动清零);临时额度不填=长期
|
||||
throw new ServiceException("请选择信用额度失效日期");
|
||||
}
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate eff = cmd.getEffectiveDate() != null ? toLocalDate(cmd.getEffectiveDate()) : today;
|
||||
if (eff.isAfter(today))
|
||||
{
|
||||
throw new ServiceException("生效日期不能晚于今天,需要未来生效请在生效当天再提交");
|
||||
}
|
||||
if (cmd.getExpireDate() != null)
|
||||
{
|
||||
LocalDate exp = toLocalDate(cmd.getExpireDate());
|
||||
if (!exp.isAfter(eff))
|
||||
{
|
||||
throw new ServiceException("生效日期不能晚于今天,需要未来生效请在生效当天再提交");
|
||||
}
|
||||
if (cmd.getExpireDate() != null)
|
||||
{
|
||||
LocalDate exp = toLocalDate(cmd.getExpireDate());
|
||||
if (!exp.isAfter(eff))
|
||||
{
|
||||
throw new ServiceException("失效日期必须晚于生效日期");
|
||||
}
|
||||
throw new ServiceException("失效日期必须晚于生效日期");
|
||||
}
|
||||
}
|
||||
return account;
|
||||
@@ -701,6 +733,15 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService
|
||||
&& LocalDate.now().isAfter(toLocalDate(account.getExtraExpireDate()));
|
||||
}
|
||||
|
||||
/** 信用额度是否已过期:口径同临时额度(失效日当天仍有效) */
|
||||
private boolean isCreditExpired(CustomerAccount account)
|
||||
{
|
||||
return account != null
|
||||
&& nz(account.getTotalCredit()).compareTo(BigDecimal.ZERO) > 0
|
||||
&& account.getCreditExpireDate() != null
|
||||
&& LocalDate.now().isAfter(toLocalDate(account.getCreditExpireDate()));
|
||||
}
|
||||
|
||||
private LocalDate toLocalDate(Date date)
|
||||
{
|
||||
return date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
|
||||
|
||||
+7
-7
@@ -10,11 +10,11 @@ import org.springframework.stereotype.Component;
|
||||
import com.ruoyi.customer.customermoney.api.ICustomerAccountApi;
|
||||
|
||||
/**
|
||||
* 客户临时额度到期自动失效任务。
|
||||
* 客户授信(信用额度/临时额度)到期自动失效任务。
|
||||
*
|
||||
* <p>每天 01:30 扫描临时额度已到期(extra_credit>0 且失效日期早于今天)的CNY账户,
|
||||
* 逐户独立事务物化失效:额度清零 -> 回写客户档案 -> 写 EXPIRE 额度流水。
|
||||
* 失效日当天临时额度仍有效,故严格按「早于今天」判断。</p>
|
||||
* <p>每天 01:30 扫描授信已到期(信用或临时额度>0 且失效日期早于今天)的CNY账户,
|
||||
* 逐户独立事务物化失效:额度清零 -> 释放角色额度池 -> 回写客户档案 -> 写 EXPIRE 额度流水。
|
||||
* 失效日当天额度仍有效,故严格按「早于今天」判断。</p>
|
||||
*
|
||||
* <p>除惰性触发(访问资金接口时即查即失效)外,本任务保证展示口径一致:
|
||||
* 客户列表可用额度是账户表现算,不经过惰性入口,需由本任务把到期状态落库。
|
||||
@@ -51,12 +51,12 @@ public class TempCreditExpireTask implements ApplicationRunner
|
||||
{
|
||||
try
|
||||
{
|
||||
int count = customerAccountApi.expireAllDueTempCredits();
|
||||
log.info("客户临时额度到期失效任务执行完成,失效账户数={}", count);
|
||||
int count = customerAccountApi.expireAllDueCredits();
|
||||
log.info("客户授信到期失效任务执行完成,失效账户数={}", count);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("客户临时额度到期失效任务执行异常", e);
|
||||
log.error("客户授信到期失效任务执行异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -44,6 +44,8 @@ public class CustomerAccountStatusVO implements Serializable
|
||||
private BigDecimal frozenBalance;
|
||||
/** 授信总额度(CNY) */
|
||||
private BigDecimal totalCredit;
|
||||
/** 授信总额度实际有效值(过期未物化时为0,接口计算得出,不物化DB) */
|
||||
private BigDecimal effectiveTotalCredit;
|
||||
/** 临时额度原值(CNY,DB 中 extra_credit 字段) */
|
||||
private BigDecimal extraCredit;
|
||||
/** 临时额度实际有效值(过期时为0,接口计算得出,不物化DB) */
|
||||
@@ -78,6 +80,8 @@ public class CustomerAccountStatusVO implements Serializable
|
||||
public void setFrozenBalance(BigDecimal frozenBalance) { this.frozenBalance = frozenBalance; }
|
||||
public BigDecimal getTotalCredit() { return totalCredit; }
|
||||
public void setTotalCredit(BigDecimal totalCredit) { this.totalCredit = totalCredit; }
|
||||
public BigDecimal getEffectiveTotalCredit() { return effectiveTotalCredit; }
|
||||
public void setEffectiveTotalCredit(BigDecimal effectiveTotalCredit) { this.effectiveTotalCredit = effectiveTotalCredit; }
|
||||
public BigDecimal getExtraCredit() { return extraCredit; }
|
||||
public void setExtraCredit(BigDecimal extraCredit) { this.extraCredit = extraCredit; }
|
||||
public BigDecimal getEffectiveExtraCredit() { return effectiveExtraCredit; }
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.customer.customermoney.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 角色额度池视图(角色管理/审批选池用)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-20
|
||||
*/
|
||||
@Data
|
||||
public class RoleCreditQuotaVo
|
||||
{
|
||||
/** 角色ID */
|
||||
private Long roleId;
|
||||
|
||||
/** 角色名称 */
|
||||
private String roleName;
|
||||
|
||||
/** 权限字符 */
|
||||
private String roleKey;
|
||||
|
||||
/** 角色状态(0正常 1停用) */
|
||||
private String status;
|
||||
|
||||
/** 可分配额度总额;未配置记录时为0 */
|
||||
private BigDecimal totalQuota;
|
||||
|
||||
/** 已分配(占用中台账余额合计) */
|
||||
private BigDecimal usedQuota;
|
||||
|
||||
/** 剩余可分配 */
|
||||
private BigDecimal availableQuota;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?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.ruoyi.customer.customermoney.mapper.CreditQuotaMapper">
|
||||
|
||||
<!-- ================= 角色额度池 ================= -->
|
||||
|
||||
<select id="selectRoleQuotaList" resultType="com.ruoyi.customer.customermoney.vo.RoleCreditQuotaVo">
|
||||
select r.role_id as roleId,
|
||||
r.role_name as roleName,
|
||||
r.role_key as roleKey,
|
||||
r.status as status,
|
||||
coalesce(q.total_quota, 0) as totalQuota,
|
||||
(select coalesce(sum(g.remaining), 0)
|
||||
from customer_credit_grant g
|
||||
where g.role_id = r.role_id and g.status = '0' and g.del_flag = '0') as usedQuota,
|
||||
q.remark as remark
|
||||
from sys_role r
|
||||
left join sys_role_credit_quota q on q.role_id = r.role_id
|
||||
where r.del_flag = '0'
|
||||
order by r.role_sort, r.role_id
|
||||
</select>
|
||||
|
||||
<select id="selectRoleQuotaListByUserId"
|
||||
resultType="com.ruoyi.customer.customermoney.vo.RoleCreditQuotaVo">
|
||||
select r.role_id as roleId,
|
||||
r.role_name as roleName,
|
||||
r.role_key as roleKey,
|
||||
r.status as status,
|
||||
coalesce(q.total_quota, 0) as totalQuota,
|
||||
(select coalesce(sum(g.remaining), 0)
|
||||
from customer_credit_grant g
|
||||
where g.role_id = r.role_id and g.status = '0' and g.del_flag = '0') as usedQuota,
|
||||
q.remark as remark
|
||||
from sys_user_role ur
|
||||
inner join sys_role r on r.role_id = ur.role_id and r.del_flag = '0' and r.status = '0'
|
||||
left join sys_role_credit_quota q on q.role_id = r.role_id
|
||||
where ur.user_id = #{userId}
|
||||
order by r.role_sort, r.role_id
|
||||
</select>
|
||||
|
||||
<select id="selectQuotaForUpdate"
|
||||
resultType="com.ruoyi.customer.customermoney.domain.SysRoleCreditQuota">
|
||||
select role_id as roleId, total_quota as totalQuota, version, remark
|
||||
from sys_role_credit_quota
|
||||
where role_id = #{roleId}
|
||||
for update
|
||||
</select>
|
||||
|
||||
<select id="sumActiveRemaining" resultType="java.math.BigDecimal">
|
||||
select coalesce(sum(remaining), 0)
|
||||
from customer_credit_grant
|
||||
where role_id = #{roleId} and status = '0' and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertQuota" parameterType="com.ruoyi.customer.customermoney.domain.SysRoleCreditQuota">
|
||||
insert into sys_role_credit_quota (role_id, total_quota, version, create_by, create_time, remark)
|
||||
values (#{roleId}, #{totalQuota}, 0, #{createBy}, sysdate(), #{remark})
|
||||
on duplicate key update total_quota = values(total_quota),
|
||||
remark = values(remark),
|
||||
update_by = values(create_by),
|
||||
update_time = now()
|
||||
</insert>
|
||||
|
||||
<update id="updateQuota" parameterType="com.ruoyi.customer.customermoney.domain.SysRoleCreditQuota">
|
||||
update sys_role_credit_quota
|
||||
set total_quota = #{totalQuota},
|
||||
version = version + 1,
|
||||
update_by = #{updateBy},
|
||||
update_time = now(),
|
||||
remark = #{remark}
|
||||
where role_id = #{roleId} and version = #{version}
|
||||
</update>
|
||||
|
||||
<!-- ================= 授信分配台账 ================= -->
|
||||
|
||||
<resultMap type="com.ruoyi.customer.customermoney.domain.CustomerCreditGrant" id="CustomerCreditGrantResult">
|
||||
<id property="grantId" column="grant_id" />
|
||||
<result property="customerId" column="customer_id" />
|
||||
<result property="accountId" column="account_id" />
|
||||
<result property="roleId" column="role_id" />
|
||||
<result property="approveUserId" column="approve_user_id" />
|
||||
<result property="applyId" column="apply_id" />
|
||||
<result property="flowId" column="flow_id" />
|
||||
<result property="creditType" column="credit_type" />
|
||||
<result property="amount" column="amount" />
|
||||
<result property="remaining" column="remaining" />
|
||||
<result property="effectiveDate" column="effective_date" />
|
||||
<result property="expireDate" column="expire_date" />
|
||||
<result property="status" column="status" />
|
||||
<result property="releaseTime" column="release_time" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="customerCode" column="customer_code" />
|
||||
<result property="customerName" column="customer_name" />
|
||||
<result property="roleName" column="role_name" />
|
||||
<result property="approveBy" column="approve_by" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="grantVo">
|
||||
select g.grant_id, g.customer_id, g.account_id, g.role_id, g.approve_user_id, g.apply_id, g.flow_id,
|
||||
g.credit_type, g.amount, g.remaining, g.effective_date, g.expire_date, g.status, g.release_time,
|
||||
g.create_by, g.create_time, g.update_by, g.update_time, g.remark, g.del_flag,
|
||||
c.customer_code, c.customer_name,
|
||||
r.role_name,
|
||||
u.user_name as approve_by
|
||||
from customer_credit_grant g
|
||||
left join bms_customer c on c.customer_id = g.customer_id
|
||||
left join sys_role r on r.role_id = g.role_id
|
||||
left join sys_user u on u.user_id = g.approve_user_id
|
||||
</sql>
|
||||
|
||||
<select id="selectGrantList"
|
||||
parameterType="com.ruoyi.customer.customermoney.domain.CustomerCreditGrant"
|
||||
resultMap="CustomerCreditGrantResult">
|
||||
<include refid="grantVo"/>
|
||||
<where>
|
||||
g.del_flag = '0'
|
||||
<if test="customerId != null"> and g.customer_id = #{customerId}</if>
|
||||
<if test="roleId != null"> and g.role_id = #{roleId}</if>
|
||||
<if test="creditType != null and creditType != ''"> and g.credit_type = #{creditType}</if>
|
||||
<if test="status != null and status != ''"> and g.status = #{status}</if>
|
||||
</where>
|
||||
order by g.grant_id desc
|
||||
</select>
|
||||
|
||||
<select id="selectActiveGrantsForUpdate"
|
||||
resultMap="CustomerCreditGrantResult">
|
||||
select grant_id, customer_id, account_id, role_id, approve_user_id, apply_id, flow_id,
|
||||
credit_type, amount, remaining, effective_date, expire_date, status,
|
||||
create_by, create_time, update_by, update_time, del_flag
|
||||
from customer_credit_grant
|
||||
where customer_id = #{customerId}
|
||||
and credit_type = #{creditType}
|
||||
and status = '0'
|
||||
and del_flag = '0'
|
||||
order by grant_id
|
||||
for update
|
||||
</select>
|
||||
|
||||
<insert id="insertGrant" parameterType="com.ruoyi.customer.customermoney.domain.CustomerCreditGrant"
|
||||
useGeneratedKeys="true" keyProperty="grantId">
|
||||
insert into customer_credit_grant (
|
||||
customer_id, account_id, role_id, approve_user_id, apply_id, flow_id,
|
||||
credit_type, amount, remaining, effective_date, expire_date, status,
|
||||
create_by, create_time, remark, del_flag
|
||||
) values (
|
||||
#{customerId}, #{accountId}, #{roleId}, #{approveUserId}, #{applyId}, #{flowId},
|
||||
#{creditType}, #{amount}, #{remaining}, #{effectiveDate}, #{expireDate}, #{status},
|
||||
#{createBy}, sysdate(), #{remark}, '0'
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateGrantRemaining"
|
||||
parameterType="com.ruoyi.customer.customermoney.domain.CustomerCreditGrant">
|
||||
update customer_credit_grant
|
||||
set remaining = #{remaining},
|
||||
status = #{status},
|
||||
release_time = #{releaseTime},
|
||||
update_by = #{updateBy},
|
||||
update_time = now()
|
||||
where grant_id = #{grantId}
|
||||
</update>
|
||||
</mapper>
|
||||
+7
-2
@@ -21,6 +21,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="applyTime" column="apply_time" />
|
||||
<result property="approveBy" column="approve_by" />
|
||||
<result property="approveUserId" column="approve_user_id"/>
|
||||
<result property="approveRoleId" column="approve_role_id"/>
|
||||
<result property="approveRoleName" column="approve_role_name"/>
|
||||
<result property="approveTime" column="approve_time" />
|
||||
<result property="approveRemark" column="approve_remark" />
|
||||
<result property="flowId" column="flow_id" />
|
||||
@@ -38,11 +40,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select a.apply_id, a.apply_no, a.customer_id, a.account_id, a.credit_type, a.adjust_mode,
|
||||
a.change_amount, a.effective_date, a.expire_date, a.reason, a.status,
|
||||
a.apply_by, a.apply_user_id, a.apply_time,
|
||||
a.approve_by, a.approve_user_id, a.approve_time, a.approve_remark, a.flow_id,
|
||||
a.approve_by, a.approve_user_id, a.approve_role_id, a.approve_time, a.approve_remark, a.flow_id,
|
||||
a.create_by, a.create_time, a.update_by, a.update_time, a.remark, a.del_flag,
|
||||
c.customer_code, c.customer_name
|
||||
c.customer_code, c.customer_name,
|
||||
r.role_name as approve_role_name
|
||||
from customer_credit_apply a
|
||||
left join bms_customer c on c.customer_id = a.customer_id
|
||||
left join sys_role r on r.role_id = a.approve_role_id
|
||||
</sql>
|
||||
|
||||
<select id="selectApplyList"
|
||||
@@ -98,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
flow_id = #{flowId},
|
||||
approve_by = #{approveBy},
|
||||
approve_user_id = #{approveUserId},
|
||||
approve_role_id = #{approveRoleId},
|
||||
approve_time = sysdate(),
|
||||
approve_remark = #{approveRemark},
|
||||
update_by = #{approveBy},
|
||||
|
||||
@@ -12,6 +12,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="balance" column="balance" />
|
||||
<result property="frozenBalance" column="frozen_balance" />
|
||||
<result property="totalCredit" column="total_credit" />
|
||||
<result property="creditEffectiveDate" column="credit_effective_date" />
|
||||
<result property="creditExpireDate" column="credit_expire_date" />
|
||||
<result property="extraCredit" column="extra_credit" />
|
||||
<result property="extraEffectiveDate" column="extra_effective_date" />
|
||||
<result property="extraExpireDate" column="extra_expire_date" />
|
||||
@@ -27,7 +29,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectCustomerAccountVo">
|
||||
select id, customer_id, currency, balance, frozen_balance, total_credit, extra_credit,
|
||||
select id, customer_id, currency, balance, frozen_balance, total_credit,
|
||||
credit_effective_date, credit_expire_date, extra_credit,
|
||||
extra_effective_date, extra_expire_date, used_credit,
|
||||
tax, version, create_by, create_time, update_by, update_time, remark, del_flag
|
||||
from customer_account
|
||||
@@ -49,15 +52,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where customer_id = #{customerId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<!-- 扫描已到期临时额度的CNY账户(失效日当天仍有效,故严格小于今天) -->
|
||||
<select id="selectExpiredTempCreditCustomerIds" resultType="java.lang.Long">
|
||||
select customer_id
|
||||
<!-- 扫描已到期授信的CNY账户(信用或临时额度失效日早于今天;失效日当天仍有效,故严格小于今天) -->
|
||||
<select id="selectExpiredCreditCustomerIds" resultType="java.lang.Long">
|
||||
select distinct customer_id
|
||||
from customer_account
|
||||
where del_flag = '0'
|
||||
and currency = 'CNY'
|
||||
and extra_credit > 0
|
||||
and extra_expire_date is not null
|
||||
and extra_expire_date < CURDATE()
|
||||
and (
|
||||
(total_credit > 0 and credit_expire_date is not null and credit_expire_date < CURDATE())
|
||||
or
|
||||
(extra_credit > 0 and extra_expire_date is not null and extra_expire_date < CURDATE())
|
||||
)
|
||||
</select>
|
||||
|
||||
<insert id="insertAccount" parameterType="com.ruoyi.customer.customermoney.domain.CustomerAccount"
|
||||
@@ -104,10 +109,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where id = #{id} and version = #{version} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<!-- 乐观锁更新额度(信用/临时额度及临时额度有效期) -->
|
||||
<!-- 乐观锁更新额度(信用/临时额度及各自有效期) -->
|
||||
<update id="updateAccountCredit">
|
||||
update customer_account
|
||||
set total_credit = #{totalCredit},
|
||||
credit_effective_date = #{creditEffectiveDate},
|
||||
credit_expire_date = #{creditExpireDate},
|
||||
extra_credit = #{extraCredit},
|
||||
extra_effective_date = #{extraEffectiveDate},
|
||||
extra_expire_date = #{extraExpireDate},
|
||||
@@ -138,6 +145,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="reason" column="reason" />
|
||||
<result property="adjustMode" column="adjust_mode" />
|
||||
<result property="operatorId" column="operator_id" />
|
||||
<result property="roleId" column="role_id" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
@@ -149,7 +157,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<sql id="selectCustomerCreditFlowVo">
|
||||
select id, flow_no, customer_id, account_id, credit_type, change_type, biz_type, biz_id, biz_no, direction,
|
||||
before_amount, change_amount, after_amount, used_credit,
|
||||
effective_date, expire_date, reason, adjust_mode, operator_id,
|
||||
effective_date, expire_date, reason, adjust_mode, operator_id, role_id,
|
||||
create_by, create_time, update_by, update_time, remark, del_flag
|
||||
from customer_credit_flow
|
||||
</sql>
|
||||
@@ -192,6 +200,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="reason != null">reason,</if>
|
||||
<if test="adjustMode != null">adjust_mode,</if>
|
||||
<if test="operatorId != null">operator_id,</if>
|
||||
<if test="roleId != null">role_id,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="delFlag != null">del_flag,</if>
|
||||
@@ -215,6 +224,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="reason != null">#{reason},</if>
|
||||
<if test="adjustMode != null">#{adjustMode},</if>
|
||||
<if test="operatorId != null">#{operatorId},</if>
|
||||
<if test="roleId != null">#{roleId},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="delFlag != null">#{delFlag},</if>
|
||||
|
||||
Reference in New Issue
Block a user