From f3be74b7a49465677a2646fe11a3d7bb3dab351e Mon Sep 17 00:00:00 2001 From: luojiaquan <1583622118@qq.com> Date: Wed, 16 Sep 2026 11:37:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E4=BD=99=E9=A2=9D=EF=BC=8C?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E8=AE=B0=E5=BD=95=E6=B5=81=E6=B0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomerMoneyController.java | 29 ++ .../mapper/BmsCustomerMapper.java | 23 + .../service/impl/BmsCustomerServiceImpl.java | 6 + .../customermoney/domain/CustomerAccount.java | 12 +- .../domain/CustomerCreditFlow.java | 93 ++++ .../mapper/CustomerMoneyMapper.java | 12 + .../service/ICustomerMoneyService.java | 20 + .../impl/CustomerMoneyServiceImpl.java | 370 +++++++++++++- .../mapper/customer/BmsCustomerMapper.xml | 21 +- .../mapper/customer/CustomerMoneyMapper.xml | 111 ++++- .../customermoney/customermoney.js | 18 + .../customerinfo/customeredit/index.vue | 14 +- .../customerinfo/customermoney/index.vue | 457 +++++++++++++++++- 13 files changed, 1157 insertions(+), 29 deletions(-) create mode 100644 bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerCreditFlow.java diff --git a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/customer/customermoney/CustomerMoneyController.java b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/customer/customermoney/CustomerMoneyController.java index ccbe7aca..26058a3b 100644 --- a/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/customer/customermoney/CustomerMoneyController.java +++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/customer/customermoney/CustomerMoneyController.java @@ -17,6 +17,7 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.customer.customermoney.domain.CustomerBalanceFlow; +import com.ruoyi.customer.customermoney.domain.CustomerCreditFlow; import com.ruoyi.customer.customermoney.domain.CustomerReceipt; import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; @@ -118,4 +119,32 @@ public class CustomerMoneyController extends BaseController List list = customerMoneyService.selectFlowList(query); return getDataTable(list); } + + /** + * 查询额度流水列表 + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:list')") + @GetMapping("/credit/flow/list") + public TableDataInfo creditFlowList(CustomerCreditFlow query) + { + if (query == null || query.getCustomerId() == null || query.getCustomerId() == 0L) { + return getDataTable(Collections.emptyList()); + } + startPage(); + List list = customerMoneyService.selectCreditFlowList(query); + return getDataTable(list); + } + + /** + * 调整客户额度(信用额度/临时额度),调整过程留额度流水 + */ + @PreAuthorize("@ss.hasPermi('customer:credit:adjust')") + @Log(title = "客户额度调整", businessType = BusinessType.UPDATE) + @PostMapping("/credit/adjust") + public AjaxResult adjustCredit(@RequestBody CustomerCreditFlow flow) + { + flow.setCreateBy(getUsername()); + flow.setOperatorId(getUserId()); + return toAjax(customerMoneyService.adjustCredit(flow)); + } } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/mapper/BmsCustomerMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/mapper/BmsCustomerMapper.java index 9815bfd0..672e5ad8 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/mapper/BmsCustomerMapper.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/mapper/BmsCustomerMapper.java @@ -23,6 +23,15 @@ public interface BmsCustomerMapper */ public BmsCustomer selectBmsCustomerByCustomerId(Long customerId); + /** + * 只取客户档案物理额度列(credit_ed 在通用查询里是账户可用额度的子查询别名, + * 账户首次初始化时必须读取档案原始值) + * + * @param customerId 客户ID + * @return 仅含 creditEd/tempEd 的客户对象 + */ + public BmsCustomer selectRawCreditByCustomerId(Long customerId); + /** * 查询客户主列表 * @@ -103,6 +112,20 @@ public interface BmsCustomerMapper */ public int deleteBmsCustomerProfileAttachmentByCustomerId(Long customerId); + /** + * 同步客户档案额度字段(额度以客户余额账户为准,调整后回写档案) + * + * @param customerId 客户ID + * @param creditEd 信用额度 + * @param tempEd 临时额度 + * @param updateBy 更新人 + * @return 结果 + */ + public int updateCustomerCredit(@org.apache.ibatis.annotations.Param("customerId") Long customerId, + @org.apache.ibatis.annotations.Param("creditEd") java.math.BigDecimal creditEd, + @org.apache.ibatis.annotations.Param("tempEd") java.math.BigDecimal tempEd, + @org.apache.ibatis.annotations.Param("updateBy") String updateBy); + /** * 批量删除客户附件 * diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java index 5bf4ebdc..57525228 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/service/impl/BmsCustomerServiceImpl.java @@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import com.ruoyi.customer.customerinfo.mapper.BmsCustomerMapper; import com.ruoyi.customer.customerinfo.domain.BmsCustomer; import com.ruoyi.customer.customerinfo.service.IBmsCustomerService; +import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; /** * 客户主Service业务层处理 @@ -27,6 +28,9 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService @Autowired private BmsCustomerMapper bmsCustomerMapper; + @Autowired + private ICustomerMoneyService customerMoneyService; + /** * 查询客户主 * @@ -226,6 +230,8 @@ public class BmsCustomerServiceImpl implements IBmsCustomerService saveBmsCustomerAttachments(bmsCustomer.getCustomerId(), bmsCustomer.getAttachmentList()); saveBmsCustomerCompanies(bmsCustomer.getCustomerId(), bmsCustomer.getCompanyList()); saveBmsCustomerContacts(bmsCustomer.getCustomerId(), bmsCustomer.getContactList()); + // 新建客户即初始化默认CNY账户:余额0、总额度1000 CNY,写INIT流水 + customerMoneyService.initDefaultCnyAccount(bmsCustomer.getCustomerId()); } return rows; } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerAccount.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerAccount.java index 2e36641c..4f1b6bf2 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerAccount.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerAccount.java @@ -1,6 +1,8 @@ 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; @@ -35,9 +37,17 @@ public class CustomerAccount extends BaseEntity /** 总额度 */ private BigDecimal totalCredit; - /** 额外额度 */ + /** 额外额度(临时额度) */ private BigDecimal extraCredit; + /** 临时额度生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date extraEffectiveDate; + + /** 临时额度失效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date extraExpireDate; + /** 已用额度 */ private BigDecimal usedCredit; diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerCreditFlow.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerCreditFlow.java new file mode 100644 index 00000000..0bce0f0c --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerCreditFlow.java @@ -0,0 +1,93 @@ +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_flow + * + * @author ruoyi + * @date 2026-09-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomerCreditFlow extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 额度类型:信用额度(长期) */ + public static final String CREDIT_TYPE_CREDIT = "CREDIT"; + /** 额度类型:临时额度 */ + public static final String CREDIT_TYPE_TEMP = "TEMP"; + + /** 变动类型:初始化 */ + public static final String CHANGE_INIT = "INIT"; + /** 变动类型:手工调整 */ + public static final String CHANGE_ADJUST = "ADJUST"; + /** 变动类型:到期自动失效 */ + public static final String CHANGE_EXPIRE = "EXPIRE"; + + /** 调整方式:设置为 */ + public static final String MODE_SET = "SET"; + /** 调整方式:增加 */ + public static final String MODE_ADD = "ADD"; + /** 调整方式:减少 */ + public static final String MODE_SUB = "SUB"; + + /** 主键 */ + private Long id; + + /** 流水号 */ + private String flowNo; + + /** 客户ID */ + private Long customerId; + + /** 客户账户ID */ + private Long accountId; + + /** 额度类型:CREDIT信用额度 TEMP临时额度 */ + private String creditType; + + /** 变动类型:INIT初始化 ADJUST调整 EXPIRE到期失效 */ + private String changeType; + + /** 变动方向:1增加 -1减少 0持平 */ + private Integer direction; + + /** 变动前额度 */ + private BigDecimal beforeAmount; + + /** 变动额度(恒为非负数,方向看 direction) */ + private BigDecimal changeAmount; + + /** 变动后额度 */ + private BigDecimal afterAmount; + + /** 变动时已用额度快照 */ + private BigDecimal usedCredit; + + /** 生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date effectiveDate; + + /** 失效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date expireDate; + + /** 调整原因 */ + private String reason; + + /** 调整方式(仅ADJUST流水有值):SET/ADD/SUB */ + private String adjustMode; + + /** 操作人ID */ + private Long operatorId; + + /** 删除标志(0存在 1删除) */ + private String delFlag; +} diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/mapper/CustomerMoneyMapper.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/mapper/CustomerMoneyMapper.java index 0f7795fc..ec07a8b6 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/mapper/CustomerMoneyMapper.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/mapper/CustomerMoneyMapper.java @@ -4,6 +4,7 @@ import java.util.List; import org.apache.ibatis.annotations.Param; 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.domain.CustomerReceipt; /** @@ -33,6 +34,17 @@ public interface CustomerMoneyMapper /** 乐观锁更新余额 */ int updateAccountBalance(CustomerAccount account); + /** 乐观锁更新额度(信用额度/临时额度/有效期) */ + int updateAccountCredit(CustomerAccount account); + + /* ================= 额度流水 ================= */ + + /** 查询额度流水 */ + List selectCreditFlowList(CustomerCreditFlow query); + + /** 新增额度流水 */ + int insertCreditFlow(CustomerCreditFlow flow); + /* ================= 客户收款 ================= */ /** 查询收款单列表 */ diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/ICustomerMoneyService.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/ICustomerMoneyService.java index 096ad8a2..7772a19e 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/ICustomerMoneyService.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/ICustomerMoneyService.java @@ -3,6 +3,7 @@ package com.ruoyi.customer.customermoney.service; import java.util.List; 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.domain.CustomerReceipt; /** @@ -16,6 +17,15 @@ public interface ICustomerMoneyService /** 查询客户各币种账户 */ List selectAccountList(Long customerId); + /** + * 新建客户时初始化默认CNY账户(余额0,默认总额度1000 CNY),并写 INIT 额度流水。 + * 已存在CNY账户时不重复创建。 + * + * @param customerId 客户ID + * @return 1=已初始化或已存在 + */ + int initDefaultCnyAccount(Long customerId); + /** 查询收款单列表 */ List selectReceiptList(CustomerReceipt query); @@ -36,4 +46,14 @@ public interface ICustomerMoneyService /** 查询余额流水列表 */ List selectFlowList(CustomerBalanceFlow query); + + /** 查询额度流水列表 */ + List selectCreditFlowList(CustomerCreditFlow query); + + /** + * 调整客户额度(信用额度/临时额度) + * 事务内:锁定CNY账户(不存在则初始化)-> 校验(不能为负、不能低于已用额度) + * -> 乐观锁更新额度 -> 回写客户档案额度字段 -> 写额度流水 + */ + int adjustCredit(CustomerCreditFlow flow); } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java index 3889c536..35aea340 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java @@ -2,6 +2,7 @@ package com.ruoyi.customer.customermoney.service.impl; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; @@ -14,10 +15,12 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.customer.customerinfo.domain.BmsCustomer; import com.ruoyi.customer.customerinfo.domain.BmsCustomerAttachment; import com.ruoyi.customer.customerinfo.mapper.BmsCustomerMapper; 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.domain.CustomerReceipt; import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper; import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; @@ -37,6 +40,8 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService /** 本位币币种 */ private static final String BASE_CURRENCY = BmsExchangeRate.BASE_CURRENCY; + /** 新客户默认总额度(CNY) */ + private static final BigDecimal DEFAULT_CREDIT = new BigDecimal("1000"); /** 金额保留位数 */ private static final int MONEY_SCALE = 2; @@ -50,9 +55,25 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService private IBmsExchangeRateService exchangeRateService; @Override + @Transactional(rollbackFor = Exception.class) public List selectAccountList(Long customerId) { - return customerMoneyMapper.selectAccountListByCustomerId(customerId); + List list = customerMoneyMapper.selectAccountListByCustomerId(customerId); + // 查询时顺带处理临时额度到期自动失效(仅CNY账户),保证卡片展示为最新值 + for (int i = 0; i < list.size(); i++) + { + CustomerAccount account = list.get(i); + if (BASE_CURRENCY.equals(account.getCurrency()) && isTempCreditExpired(account)) + { + CustomerAccount locked = customerMoneyMapper.selectAccountForUpdate(customerId, BASE_CURRENCY); + expireTempCreditIfNeeded(locked); + if (locked != null) + { + list.set(i, locked); + } + } + } + return list; } @Override @@ -167,23 +188,8 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService ? 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(BASE_CURRENCY); - account.setBalance(BigDecimal.ZERO); - account.setFrozenBalance(BigDecimal.ZERO); - account.setTotalCredit(BigDecimal.ZERO); - account.setExtraCredit(BigDecimal.ZERO); - account.setUsedCredit(BigDecimal.ZERO); - account.setTax(BigDecimal.ZERO); - account.setVersion(0); - account.setCreateBy(username); - customerMoneyMapper.insertAccount(account); - } + // 1. 行锁锁定客户本位币(CNY)账户,不存在则按客户档案额度自动初始化。无论收什么币种,一律折算 CNY 入账 + CustomerAccount account = getOrInitCnyAccount(receipt.getCustomerId(), username); // 2. 本位币余额变动(预充值/混合增加余额;还款核销释放已用额度) BigDecimal beforeBalance = nz(account.getBalance()); @@ -260,6 +266,334 @@ public class CustomerMoneyServiceImpl implements ICustomerMoneyService return customerMoneyMapper.selectFlowList(query); } + @Override + public List selectCreditFlowList(CustomerCreditFlow query) + { + return customerMoneyMapper.selectCreditFlowList(query); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int adjustCredit(CustomerCreditFlow cmd) + { + if (cmd == null || cmd.getCustomerId() == null) + { + throw new ServiceException("客户ID不能为空"); + } + Long customerId = cmd.getCustomerId(); + String creditType = cmd.getCreditType(); + if (!CustomerCreditFlow.CREDIT_TYPE_CREDIT.equals(creditType) + && !CustomerCreditFlow.CREDIT_TYPE_TEMP.equals(creditType)) + { + throw new ServiceException("额度类型不正确"); + } + String adjustMode = cmd.getAdjustMode(); + if (!CustomerCreditFlow.MODE_SET.equals(adjustMode) + && !CustomerCreditFlow.MODE_ADD.equals(adjustMode) + && !CustomerCreditFlow.MODE_SUB.equals(adjustMode)) + { + throw new ServiceException("调整方式不正确"); + } + BigDecimal input = nz(cmd.getChangeAmount()); + if (input.compareTo(BigDecimal.ZERO) <= 0) + { + throw new ServiceException("调整金额必须大于0"); + } + if (!StringUtils.hasText(cmd.getReason())) + { + throw new ServiceException("请填写调整原因"); + } + String reason = cmd.getReason().trim(); + if (reason.length() > 255) + { + throw new ServiceException("调整原因不能超过255个字"); + } + String username = SecurityUtils.getUsername(); + Long userId = SecurityUtils.getUserId(); + boolean isTemp = CustomerCreditFlow.CREDIT_TYPE_TEMP.equals(creditType); + + // 1. 行锁锁定CNY账户(不存在则按客户档案额度初始化创建);先处理临时额度过期 + CustomerAccount account = getOrInitCnyAccount(customerId, username); + expireTempCreditIfNeeded(account); + + // 2. 计算调整结果 + BigDecimal before = isTemp ? nz(account.getExtraCredit()) : nz(account.getTotalCredit()); + BigDecimal otherCredit = isTemp ? nz(account.getTotalCredit()) : nz(account.getExtraCredit()); + BigDecimal after; + switch (adjustMode) + { + case CustomerCreditFlow.MODE_SET: + after = input; + break; + case CustomerCreditFlow.MODE_ADD: + after = before.add(input); + break; + default: + after = before.subtract(input); + break; + } + after = after.setScale(MONEY_SCALE, RoundingMode.HALF_UP); + if (after.compareTo(BigDecimal.ZERO) < 0) + { + throw new ServiceException("调整后额度不能为负数"); + } + if (otherCredit.add(after).compareTo(nz(account.getUsedCredit())) < 0) + { + throw new ServiceException("调整后授信总额度不能小于已用额度:" + + nz(account.getUsedCredit()).toPlainString()); + } + if (after.compareTo(before) == 0) + { + throw new ServiceException("额度未发生变化,无需调整"); + } + + // 3. 临时额度有效期校验(不允许预约未来生效,失效日必须晚于生效日) + Date effectiveDate = null; + Date expireDate = null; + if (isTemp) + { + LocalDate today = LocalDate.now(); + LocalDate eff = cmd.getEffectiveDate() != null ? toLocalDate(cmd.getEffectiveDate()) : today; + if (eff.isAfter(today)) + { + throw new ServiceException("生效日期不能晚于今天,需要未来生效请在生效当天再设置"); + } + effectiveDate = toSqlDate(eff); + if (cmd.getExpireDate() != null) + { + LocalDate exp = toLocalDate(cmd.getExpireDate()); + if (!exp.isAfter(eff)) + { + throw new ServiceException("失效日期必须晚于生效日期"); + } + expireDate = toSqlDate(exp); + } + account.setExtraCredit(after); + account.setExtraEffectiveDate(effectiveDate); + account.setExtraExpireDate(expireDate); + } + else + { + account.setTotalCredit(after); + } + account.setUpdateBy(username); + + // 4. 乐观锁更新额度 + int rows = customerMoneyMapper.updateAccountCredit(account); + if (rows == 0) + { + throw new ServiceException("账户额度已被其他操作变动,请刷新后重试"); + } + account.setVersion(account.getVersion() + 1); + + // 5. 回写客户档案额度字段(额度唯一数据源为账户,档案仅做同步展示) + bmsCustomerMapper.updateCustomerCredit(customerId, + nz(account.getTotalCredit()), nz(account.getExtraCredit()), username); + + // 6. 写额度流水 + CustomerCreditFlow flow = new CustomerCreditFlow(); + flow.setFlowNo(generateNo("EDLS")); + flow.setCustomerId(customerId); + flow.setAccountId(account.getId()); + flow.setCreditType(creditType); + flow.setChangeType(CustomerCreditFlow.CHANGE_ADJUST); + int direction = after.compareTo(before) > 0 ? 1 : -1; + flow.setDirection(direction); + flow.setBeforeAmount(before); + flow.setChangeAmount(after.subtract(before).abs()); + flow.setAfterAmount(after); + flow.setUsedCredit(nz(account.getUsedCredit())); + flow.setEffectiveDate(effectiveDate); + flow.setExpireDate(expireDate); + flow.setReason(reason); + flow.setAdjustMode(adjustMode); + flow.setOperatorId(userId); + flow.setCreateBy(username); + flow.setRemark(cmd.getRemark()); + flow.setDelFlag("0"); + customerMoneyMapper.insertCreditFlow(flow); + return 1; + } + + /* ================= 额度私有方法 ================= */ + + /** + * 新建客户时默认初始化CNY账户:余额0,总额度默认1000 CNY + * (客户档案已填信用额度且大于0时按档案值),并写 INIT 流水;已存在则跳过。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int initDefaultCnyAccount(Long customerId) + { + if (customerId == null) + { + throw new ServiceException("客户ID不能为空"); + } + String username = SecurityUtils.getUsername(); + CustomerAccount account = customerMoneyMapper.selectAccountForUpdate(customerId, BASE_CURRENCY); + if (account != null) + { + return 1; + } + BmsCustomer customer = bmsCustomerMapper.selectRawCreditByCustomerId(customerId); + if (customer == null) + { + throw new ServiceException("客户不存在"); + } + BigDecimal initCredit = nz(customer.getCreditEd()); + if (initCredit.compareTo(BigDecimal.ZERO) <= 0) + { + initCredit = DEFAULT_CREDIT; + } + 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"); + if (initTemp.compareTo(BigDecimal.ZERO) > 0) + { + writeInitFlow(account, CustomerCreditFlow.CREDIT_TYPE_TEMP, + initTemp, null, null, username, "新客户初始化临时额度"); + } + return 1; + } + + /** 行锁获取客户CNY账户,不存在则按客户档案当前额度初始化创建 */ + private CustomerAccount getOrInitCnyAccount(Long customerId, String username) + { + CustomerAccount account = customerMoneyMapper.selectAccountForUpdate(customerId, BASE_CURRENCY); + if (account == null) + { + BmsCustomer customer = bmsCustomerMapper.selectRawCreditByCustomerId(customerId); + if (customer == null) + { + throw new ServiceException("客户不存在"); + } + BigDecimal initCredit = nz(customer.getCreditEd()); + BigDecimal initTemp = nz(customer.getTempEd()); + account = createCnyAccount(customerId, initCredit, initTemp, username); + writeInitFlow(account, CustomerCreditFlow.CREDIT_TYPE_CREDIT, + initCredit, null, null, username, "首次创建CNY账户,从客户档案同步初始额度"); + if (initTemp.compareTo(BigDecimal.ZERO) > 0) + { + writeInitFlow(account, CustomerCreditFlow.CREDIT_TYPE_TEMP, + initTemp, null, null, username, "首次创建CNY账户,从客户档案同步临时额度"); + } + } + return account; + } + + /** 创建CNY账户(余额0,已用0) */ + private CustomerAccount createCnyAccount(Long customerId, BigDecimal totalCredit, + BigDecimal extraCredit, String username) + { + CustomerAccount account = new CustomerAccount(); + account.setCustomerId(customerId); + account.setCurrency(BASE_CURRENCY); + account.setBalance(BigDecimal.ZERO); + account.setFrozenBalance(BigDecimal.ZERO); + account.setTotalCredit(totalCredit); + account.setExtraCredit(extraCredit); + account.setUsedCredit(BigDecimal.ZERO); + account.setTax(BigDecimal.ZERO); + account.setVersion(0); + account.setCreateBy(username); + customerMoneyMapper.insertAccount(account); + return account; + } + + /** + * 账户首次初始化时写额度 INIT 流水(从客户档案额度同步) + */ + private void writeInitFlow(CustomerAccount account, String creditType, + BigDecimal initAmount, Date effectiveDate, Date expireDate, + String username, String remark) + { + CustomerCreditFlow flow = new CustomerCreditFlow(); + flow.setFlowNo(generateNo("EDLS")); + flow.setCustomerId(account.getCustomerId()); + flow.setAccountId(account.getId()); + flow.setCreditType(creditType); + flow.setChangeType(CustomerCreditFlow.CHANGE_INIT); + flow.setDirection(initAmount.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0); + flow.setBeforeAmount(BigDecimal.ZERO); + flow.setChangeAmount(initAmount); + flow.setAfterAmount(initAmount); + flow.setUsedCredit(nz(account.getUsedCredit())); + flow.setEffectiveDate(effectiveDate); + flow.setExpireDate(expireDate); + flow.setReason("账户初始化"); + flow.setOperatorId(SecurityUtils.getUserId()); + flow.setCreateBy(username); + flow.setRemark(remark); + flow.setDelFlag("0"); + customerMoneyMapper.insertCreditFlow(flow); + } + + /** 临时额度是否已过期:有失效日期且今天已晚于失效日(失效日当天仍有效) */ + private boolean isTempCreditExpired(CustomerAccount account) + { + return account != null + && nz(account.getExtraCredit()).compareTo(BigDecimal.ZERO) > 0 + && account.getExtraExpireDate() != null + && LocalDate.now().isAfter(toLocalDate(account.getExtraExpireDate())); + } + + /** + * 临时额度到期自动失效:额度清零 -> 回写客户档案 -> 写EXPIRE流水。 + * 入参必须是事务内行锁查出的账户。 + */ + private void expireTempCreditIfNeeded(CustomerAccount account) + { + if (account == null || !isTempCreditExpired(account)) + { + return; + } + String username = SecurityUtils.getUsername(); + BigDecimal before = nz(account.getExtraCredit()); + account.setExtraCredit(BigDecimal.ZERO); + account.setUpdateBy(username); + int rows = customerMoneyMapper.updateAccountCredit(account); + if (rows == 0) + { + throw new ServiceException("账户额度已被其他操作变动,请刷新后重试"); + } + account.setVersion(account.getVersion() + 1); + bmsCustomerMapper.updateCustomerCredit(account.getCustomerId(), + nz(account.getTotalCredit()), BigDecimal.ZERO, username); + + CustomerCreditFlow flow = new CustomerCreditFlow(); + flow.setFlowNo(generateNo("EDLS")); + flow.setCustomerId(account.getCustomerId()); + flow.setAccountId(account.getId()); + flow.setCreditType(CustomerCreditFlow.CREDIT_TYPE_TEMP); + 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.setOperatorId(SecurityUtils.getUserId()); + flow.setCreateBy(username); + flow.setRemark("系统在访问账户时检测到临时额度已过失效日期,自动置零"); + flow.setDelFlag("0"); + customerMoneyMapper.insertCreditFlow(flow); + } + + private LocalDate toLocalDate(Date date) + { + return date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate(); + } + + private Date toSqlDate(LocalDate localDate) + { + return java.sql.Date.valueOf(localDate); + } + /* ================= 私有方法 ================= */ /** 保存收款附件到客户附件表 */ diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/customer/BmsCustomerMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/customer/BmsCustomerMapper.xml index f5009886..7cae5ba2 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/customer/BmsCustomerMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/customer/BmsCustomerMapper.xml @@ -198,6 +198,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and (biz_type = 'customer' or biz_type is null or biz_type = '') + + + update bms_customer + set credit_ed = #{creditEd}, + temp_ed = #{tempEd}, + update_by = #{updateBy}, + update_time = now() + where customer_id = #{customerId} + + + + + update bms_customer_attachment set del_flag = '2' where attachment_id in @@ -287,12 +306,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" service_user_id = #{serviceUserId}, service_user = #{serviceUser}, settlement_type = #{settlementType}, - temp_ed = #{tempEd}, account_balance = #{accountBalance}, foreign_currency_balance = #{foreignCurrencyBalance}, weight_threshold = #{weightThreshold}, throw_ratio = #{throwRatio}, - credit_ed = #{creditEd}, product_ids = #{productIds}, remark = #{remark}, create_by = #{createBy}, diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/customer/CustomerMoneyMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/customer/CustomerMoneyMapper.xml index 1041fe83..c307ca04 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/customer/CustomerMoneyMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/customer/CustomerMoneyMapper.xml @@ -13,6 +13,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -25,7 +27,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select id, customer_id, currency, balance, frozen_balance, total_credit, extra_credit, used_credit, + select id, customer_id, currency, balance, frozen_balance, total_credit, 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 @@ -90,6 +93,112 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where id = #{id} and version = #{version} and del_flag = '0' + + + update customer_account + set total_credit = #{totalCredit}, + extra_credit = #{extraCredit}, + extra_effective_date = #{extraEffectiveDate}, + extra_expire_date = #{extraExpireDate}, + version = version + 1, + update_by = #{updateBy}, + update_time = now() + where id = #{id} and version = #{version} and del_flag = '0' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, flow_no, customer_id, account_id, credit_type, change_type, direction, + before_amount, change_amount, after_amount, used_credit, + effective_date, expire_date, reason, adjust_mode, operator_id, + create_by, create_time, update_by, update_time, remark, del_flag + from customer_credit_flow + + + + + + insert into customer_credit_flow + + flow_no, + customer_id, + account_id, + credit_type, + change_type, + direction, + before_amount, + change_amount, + after_amount, + used_credit, + effective_date, + expire_date, + reason, + adjust_mode, + operator_id, + create_by, + remark, + del_flag, + + + #{flowNo}, + #{customerId}, + #{accountId}, + #{creditType}, + #{changeType}, + #{direction}, + #{beforeAmount}, + #{changeAmount}, + #{afterAmount}, + #{usedCredit}, + #{effectiveDate}, + #{expireDate}, + #{reason}, + #{adjustMode}, + #{operatorId}, + #{createBy}, + #{remark}, + #{delFlag}, + + + diff --git a/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js b/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js index cf1a46e3..a9847d8e 100644 --- a/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js +++ b/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js @@ -58,3 +58,21 @@ export function listFlow(query) { params: query }) } + +/** 查询额度流水列表 */ +export function listCreditFlow(query) { + return request({ + url: '/customersupplier/customermoney/credit/flow/list', + method: 'get', + params: query + }) +} + +/** 调整客户额度(信用额度/临时额度),调整过程留额度流水 */ +export function adjustCredit(data) { + return request({ + url: '/customersupplier/customermoney/credit/adjust', + method: 'post', + data: data + }) +} diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue index cc9281a1..897096fd 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/customeredit/index.vue @@ -108,7 +108,11 @@ - + + + @@ -130,7 +134,11 @@ - + + + @@ -630,7 +638,7 @@ function buildEmptyForm() { foreignCurrencyBalance: null, weightThreshold: null, throwRatio: null, - creditEd: null, + creditEd: 1000, productIds: null, attachmentList: [], companyList: [], diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue index 82047fc9..644bedd6 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue @@ -11,6 +11,12 @@
客户编码:{{ customer.customerCode || route.query.customerCode || '-' }}
本位币账户 CNY + 额度调整
@@ -31,14 +37,23 @@
-
总额度
+
+ 总额度 + 调整 +
{{ formatMoney(currentAccount.totalCredit) }}
-
额外额度
+
+ 额外额度 + 调整 +
{{ formatMoney(currentAccount.extraCredit) }}
+
{{ tempCreditPeriodText }}
@@ -225,6 +240,100 @@ @pagination="getFlowList" /> + + + + + + + + + + + + + + + + + + + 搜索 + 重置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -402,6 +511,114 @@ 关 闭 + + + + +
当前{{ creditDialog.form.creditType === 'TEMP' ? '临时额度' : '信用额度' }}: + {{ formatMoney(creditBefore) }},已用额度:{{ formatMoney(currentAccount.usedCredit) }}
+
+ + + + + + 信用额度(长期) + 临时额度 + + + + + + + 设置为 + 增加 + 减少 + + + + + + + + + + + + + + + + + +
+
调整后{{ creditDialog.form.creditType === 'TEMP' ? '临时额度' : '信用额度' }}: + {{ formatMoney(creditAfter) }} +
+
调整后授信总额:{{ formatMoney(totalCreditAfter) }} + (可用 = 授信总额 - 已用 {{ formatMoney(currentAccount.usedCredit) }})
+ + +
+ + +
@@ -409,11 +626,12 @@ import axios from 'axios' import { saveAs } from 'file-saver' import { getCustomer } from "@/api/customersuppliermanagement/customerinfo/customer.js" -import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cancelReceipt, listFlow } from "@/api/customersuppliermanagement/customermoney/customermoney.js" +import { listAccount, listReceipt, getReceipt, addReceipt, confirmReceipt, cancelReceipt, listFlow, listCreditFlow, adjustCredit } from "@/api/customersuppliermanagement/customermoney/customermoney.js" import { convertToBase } from "@/api/data/exchangeRate" import { getToken } from "@/utils/auth" import { useRoute } from 'vue-router' +import { nextTick } from 'vue' const route = useRoute() const router = useRouter() @@ -472,9 +690,28 @@ function receiptTypeTag(v) { function receiptStatusTag(v) { return { PENDING: 'warning', CONFIRMED: 'success', CANCELLED: 'danger' }[v] || 'info' } -function bizTypeTag(v) { +const bizTypeTag = function(v) { return { RECEIPT: 'success', BILL_PAY: 'danger', REFUND: 'warning', ADJUST: 'info', FREEZE: 'info', UNFREEZE: '' }[v] || 'info' } + +/* ================= 额度相关枚举 ================= */ +const creditTypeOptions = [ + { value: 'CREDIT', label: '信用额度' }, + { value: 'TEMP', label: '临时额度' } +] +const creditChangeTypeOptions = [ + { value: 'INIT', label: '初始化' }, + { value: 'ADJUST', label: '手工调整' }, + { value: 'EXPIRE', label: '到期失效' } +] +const adjustModeOptions = [ + { value: 'SET', label: '设置为' }, + { value: 'ADD', label: '增加' }, + { value: 'SUB', label: '减少' } +] +function creditChangeTag(v) { + return { INIT: '', ADJUST: 'primary', EXPIRE: 'danger' }[v] || 'info' +} function formatMoney(val) { const num = Number(val) return isNaN(num) ? '0.00' : num.toFixed(2) @@ -506,6 +743,152 @@ const availableCredit = computed(() => { return formatMoney(total + extra - used) }) +/** 临时额度有效期文案 */ +const tempCreditPeriodText = computed(() => { + const a = currentAccount.value + const extra = Number(a.extraCredit) || 0 + if (extra <= 0) return '' + if (!a.extraExpireDate) return '长期有效' + const today = proxy.parseTime(new Date(), '{y}-{m}-{d}') + if (a.extraExpireDate < today) return '已过期' + return '有效期至 ' + a.extraExpireDate +}) + +/* ============ 额度调整弹窗 ============ */ +const creditFormRef = ref(null) +const creditSubmitLoading = ref(false) +const creditDialog = reactive({ + visible: false, + form: {} +}) +const creditRules = { + creditType: [{ required: true, message: '请选择额度类型', trigger: 'change' }], + adjustMode: [{ required: true, message: '请选择调整方式', trigger: 'change' }], + changeAmount: [{ required: true, message: '调整金额必须大于0', trigger: 'blur' }], + reason: [{ required: true, message: '请填写调整原因', trigger: 'blur' }] +} + +/** 禁止选择晚于今天的日期(不允许预约未来生效) */ +function afterToday(date) { + const today = new Date() + today.setHours(23, 59, 59, 999) + return date.getTime() > today.getTime() +} + +function defaultCreditForm(creditType) { + return { + customerId: route.query.customerId || null, + creditType: creditType || 'CREDIT', + adjustMode: 'ADD', + changeAmount: null, + reason: '', + effectiveDate: proxy.parseTime(new Date(), '{y}-{m}-{d}'), + expireDate: '' + } +} + +function openAdjustDialog(creditType) { + const customerId = Number(route.query.customerId) + if (!customerId) { + proxy.$modal.msgError('缺少客户ID,请从客户列表进入客户余额页') + return + } + resetCreditForm() + creditDialog.form = defaultCreditForm(creditType) + creditDialog.visible = true +} + +function resetCreditForm() { + creditDialog.form = defaultCreditForm() + proxy.resetForm('creditFormRef') +} + +/** 切换额度类型:清掉校验红框,临时额度默认生效日为今天 */ +function onCreditTypeChange() { + creditDialog.form.changeAmount = null + if (creditDialog.form.creditType === 'TEMP' && !creditDialog.form.effectiveDate) { + creditDialog.form.effectiveDate = proxy.parseTime(new Date(), '{y}-{m}-{d}') + } + nextTick(() => proxy.clearValidate('creditFormRef')) +} + +/** 当前被调整额度(调整前) */ +const creditBefore = computed(() => { + const a = currentAccount.value + return Number(creditDialog.form.creditType === 'TEMP' ? a.extraCredit : a.totalCredit) || 0 +}) + +/** 调整后被调额度 */ +const creditAfter = computed(() => { + const input = Number(creditDialog.form.changeAmount) || 0 + const mode = creditDialog.form.adjustMode + if (mode === 'SET') return input + return mode === 'SUB' ? creditBefore.value - input : creditBefore.value + input +}) + +/** 调整后授信总额(信用+临时) */ +const totalCreditAfter = computed(() => { + const a = currentAccount.value + const other = Number(creditDialog.form.creditType === 'TEMP' ? a.totalCredit : a.extraCredit) || 0 + return other + creditAfter.value +}) + +function submitAdjustCredit() { + creditFormRef.value.validate(valid => { + if (!valid) return + const f = creditDialog.form + const amount = Number(f.changeAmount) + if (!(amount > 0)) { + proxy.$modal.msgError('调整金额必须大于0') + return + } + if (f.creditType === 'TEMP') { + if (f.expireDate && f.effectiveDate && f.expireDate <= f.effectiveDate) { + proxy.$modal.msgError('失效日期必须晚于生效日期') + return + } + } + if (creditAfter.value < 0) { + proxy.$modal.msgError('调整后额度不能为负数') + return + } + const used = Number(currentAccount.value.usedCredit) || 0 + if (totalCreditAfter.value < used) { + proxy.$modal.msgError('调整后授信总额不能小于已用额度 ' + formatMoney(used)) + return + } + if (creditAfter.value === creditBefore.value) { + proxy.$modal.msgError('额度未发生变化,无需调整') + return + } + const payload = { + customerId: Number(f.customerId) || Number(route.query.customerId) || null, + creditType: f.creditType, + adjustMode: f.adjustMode, + changeAmount: amount, + reason: f.reason + } + if (!payload.customerId) { + proxy.$modal.msgError('缺少客户ID,请从客户列表进入客户余额页') + return + } + if (f.creditType === 'TEMP') { + payload.effectiveDate = f.effectiveDate || null + payload.expireDate = f.expireDate || null + } + creditSubmitLoading.value = true + adjustCredit(payload).then(() => { + proxy.$modal.msgSuccess('额度调整成功') + creditDialog.visible = false + loadCustomer() + loadAccounts() + getCreditFlowList() + }).finally(() => { + creditSubmitLoading.value = false + }) + }) +} + function loadCustomer() { const customerId = route.query.customerId if (!customerId) return @@ -752,6 +1135,38 @@ function resetFlowQuery() { getFlowList() } +/* ================= 额度流水 ================= */ +const creditFlowList = ref([]) +const creditFlowTotal = ref(0) +const creditFlowLoading = ref(false) +const creditFlowDateRange = ref([]) +const creditFlowQuery = reactive({ + pageNum: 1, + pageSize: 10, + customerId: route.query.customerId, + creditType: '', + changeType: '' +}) + +function getCreditFlowList() { + creditFlowLoading.value = true + const params = proxy.addDateRange({ ...creditFlowQuery }, creditFlowDateRange.value) + listCreditFlow(params).then(res => { + creditFlowList.value = res.rows || [] + creditFlowTotal.value = res.total || 0 + }).finally(() => { + creditFlowLoading.value = false + }) +} + +function resetCreditFlowQuery() { + creditFlowQuery.creditType = '' + creditFlowQuery.changeType = '' + creditFlowDateRange.value = [] + creditFlowQuery.pageNum = 1 + getCreditFlowList() +} + /* ================= 附件上传 ================= */ const uploadFileUrl = ref(baseUrl + "/common/upload") const uploadHeaders = ref({ Authorization: "Bearer " + getToken() }) @@ -848,6 +1263,8 @@ function initPage() { viewDialog.attachments = [] receiptDateRange.value = [] flowDateRange.value = [] + creditFlowDateRange.value = [] + creditDialog.visible = false receiptQuery.customerId = customerId receiptQuery.receiptType = '' receiptQuery.status = '' @@ -856,15 +1273,22 @@ function initPage() { flowQuery.bizType = '' flowQuery.direction = '' flowQuery.pageNum = 1 + creditFlowQuery.customerId = customerId + creditFlowQuery.creditType = '' + creditFlowQuery.changeType = '' + creditFlowQuery.pageNum = 1 receiptList.value = [] receiptTotal.value = 0 flowList.value = [] flowTotal.value = 0 + creditFlowList.value = [] + creditFlowTotal.value = 0 if (!customerId) return loadCustomer() loadAccounts() getReceiptList() getFlowList() + getCreditFlowList() } onMounted(initPage) @@ -916,6 +1340,31 @@ watch(() => route.query.customerId, (newId, oldId) => { color: var(--el-text-color-secondary); font-size: 13px; margin-bottom: 8px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; +} +.balance-sub { + margin-top: 6px; + font-size: 12px; + color: var(--el-text-color-secondary); + min-height: 16px; +} +.credit-tip { + margin-bottom: 12px; +} +.credit-preview { + margin-top: 4px; + padding: 10px 14px; + background: var(--el-fill-color-light); + border-radius: 4px; + font-size: 13px; + line-height: 1.8; +} +.credit-alert { + margin-top: 8px; + margin-bottom: 0; } .balance-value { font-size: 22px;