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 new file mode 100644 index 00000000..b04ba43f --- /dev/null +++ b/bms-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/customer/customermoney/CustomerMoneyController.java @@ -0,0 +1,121 @@ +package com.ruoyi.web.controller.customer.customermoney; + +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +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.CustomerReceipt; +import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; + +/** + * 客户余额/收款/流水 Controller + * + * @author ruoyi + * @date 2026-09-11 + */ +@RestController +@RequestMapping("/customersupplier/customermoney") +public class CustomerMoneyController extends BaseController +{ + @Autowired + private ICustomerMoneyService customerMoneyService; + + /** + * 查询客户各币种账户余额 + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:list')") + @GetMapping("/account/list/{customerId}") + public AjaxResult accountList(@PathVariable("customerId") Long customerId) + { + return success(customerMoneyService.selectAccountList(customerId)); + } + + /** + * 查询收款记录列表 + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:list')") + @GetMapping("/receipt/list") + public TableDataInfo receiptList(CustomerReceipt query) + { + // customerId 为空或为 0,直接返回空数据 + if (query == null || query.getCustomerId() == null || query.getCustomerId() == 0L) { + return getDataTable(Collections.emptyList()); + } + + startPage(); + List list = customerMoneyService.selectReceiptList(query); + return getDataTable(list); + } + /** + * 获取收款单详情(含收款附件) + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:query')") + @GetMapping("/receipt/{id}") + public AjaxResult getReceipt(@PathVariable("id") Long id) + { + return success(customerMoneyService.selectReceiptById(id)); + } + + /** + * 登记收款(默认待确认,同时保存收款凭证附件) + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:add')") + @Log(title = "客户收款", businessType = BusinessType.INSERT) + @PostMapping("/receipt") + public AjaxResult addReceipt(@RequestBody CustomerReceipt receipt) + { + receipt.setCreateBy(getUsername()); + return toAjax(customerMoneyService.insertReceipt(receipt)); + } + + /** + * 审核通过收款单:确认后变动客户余额并生成余额流水 + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:edit')") + @Log(title = "客户收款审核", businessType = BusinessType.UPDATE) + @PutMapping("/receipt/confirm/{id}") + public AjaxResult confirmReceipt(@PathVariable("id") Long id) + { + return toAjax(customerMoneyService.confirmReceipt(id)); + } + + /** + * 取消收款单(仅待确认可取消) + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:edit')") + @Log(title = "客户收款取消", businessType = BusinessType.UPDATE) + @PutMapping("/receipt/cancel/{id}") + public AjaxResult cancelReceipt(@PathVariable("id") Long id) + { + return toAjax(customerMoneyService.cancelReceipt(id)); + } + + /** + * 查询余额流水列表 + */ + @PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:list')") + @GetMapping("/flow/list") + public TableDataInfo flowList(CustomerBalanceFlow query) + { + // customerId 为空或为 0,直接返回空数据 + if (query == null || query.getCustomerId() == null || query.getCustomerId() == 0L) { + return getDataTable(Collections.emptyList()); + } + startPage(); + List list = customerMoneyService.selectFlowList(query); + return getDataTable(list); + } +} diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/domain/BmsCustomerAttachment.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/domain/BmsCustomerAttachment.java index 455fd8ab..e92dfe1e 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/domain/BmsCustomerAttachment.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customerinfo/domain/BmsCustomerAttachment.java @@ -47,6 +47,9 @@ public class BmsCustomerAttachment extends BaseEntity @Excel(name = "附件类型", readConverterExp = "0=附件资料,1=客户Logo,2=合同文件,3=其他") private String attachmentType; + /** 关联业务ID(如收款单ID,attachment_type=3 时使用) */ + private Long bizId; + /** 是否Logo(0否 1是) */ private String isLogo; 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 new file mode 100644 index 00000000..2e36641c --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerAccount.java @@ -0,0 +1,52 @@ +package com.ruoyi.customer.customermoney.domain; + +import java.math.BigDecimal; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 客户余额账户 customer_account + * + * @author ruoyi + * @date 2026-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomerAccount extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 客户ID */ + private Long customerId; + + /** 币种,如 CNY/USD */ + private String currency; + + /** 账户余额 */ + private BigDecimal balance; + + /** 冻结余额 */ + private BigDecimal frozenBalance; + + /** 总额度 */ + private BigDecimal totalCredit; + + /** 额外额度 */ + private BigDecimal extraCredit; + + /** 已用额度 */ + private BigDecimal usedCredit; + + /** 税金 */ + private BigDecimal tax; + + /** 乐观锁版本号 */ + private Integer version; + + /** 删除标志(0存在 1删除) */ + private String delFlag; +} diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerBalanceFlow.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerBalanceFlow.java new file mode 100644 index 00000000..c0b3a952 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerBalanceFlow.java @@ -0,0 +1,80 @@ +package com.ruoyi.customer.customermoney.domain; + +import java.math.BigDecimal; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 客户余额流水 customer_balance_flow + * + * @author ruoyi + * @date 2026-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomerBalanceFlow extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 业务类型:收款 */ + public static final String BIZ_RECEIPT = "RECEIPT"; + /** 业务类型:账单支付 */ + public static final String BIZ_BILL_PAY = "BILL_PAY"; + /** 业务类型:退款 */ + public static final String BIZ_REFUND = "REFUND"; + /** 业务类型:手工调整 */ + public static final String BIZ_ADJUST = "ADJUST"; + /** 业务类型:冻结 */ + public static final String BIZ_FREEZE = "FREEZE"; + /** 业务类型:解冻 */ + public static final String BIZ_UNFREEZE = "UNFREEZE"; + + /** 主键 */ + private Long id; + + /** 流水号 */ + private String flowNo; + + /** 客户ID */ + private Long customerId; + + /** 客户账户ID */ + private Long accountId; + + /** 币种 */ + private String currency; + + /** 业务类型:RECEIPT/BILL_PAY/REFUND/ADJUST/FREEZE/UNFREEZE */ + private String bizType; + + /** 关联业务ID */ + private Long bizId; + + /** 关联业务单号 */ + private String bizNo; + + /** 变动方向:1增加,-1减少 */ + private Integer direction; + + /** 变动金额 */ + private BigDecimal amount; + + /** 变动前余额 */ + private BigDecimal beforeBalance; + + /** 变动后余额 */ + private BigDecimal afterBalance; + + /** 变动前冻结余额 */ + private BigDecimal beforeFrozenBalance; + + /** 变动后冻结余额 */ + private BigDecimal afterFrozenBalance; + + /** 操作人ID */ + private Long operatorId; + + /** 删除标志(0存在 1删除) */ + private String delFlag; +} diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerReceipt.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerReceipt.java new file mode 100644 index 00000000..99ddc9e7 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/domain/CustomerReceipt.java @@ -0,0 +1,99 @@ +package com.ruoyi.customer.customermoney.domain; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.customer.customerinfo.domain.BmsCustomerAttachment; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 客户收款 customer_receipt + * + * @author ruoyi + * @date 2026-09-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomerReceipt extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 状态:待确认 */ + public static final String STATUS_PENDING = "PENDING"; + /** 状态:已确认 */ + public static final String STATUS_CONFIRMED = "CONFIRMED"; + /** 状态:已取消 */ + public static final String STATUS_CANCELLED = "CANCELLED"; + + /** 收款类型:预充值 */ + public static final String TYPE_PREPAY = "PREPAY"; + /** 收款类型:还款核销 */ + public static final String TYPE_REPAY = "REPAY"; + /** 收款类型:混合 */ + public static final String TYPE_MIXED = "MIXED"; + + /** 主键 */ + private Long id; + + /** 收款单号 */ + private String receiptNo; + + /** 客户ID */ + private Long customerId; + + /** 客户账户ID(预留) */ + private Long accountId; + + /** 收款类型:PREPAY预充值/REPAY还款核销/MIXED混合 */ + private String receiptType; + + /** 收款金额 */ + private BigDecimal amount; + + /** 已核销金额 */ + private BigDecimal allocatedAmount; + + /** 待核销金额 */ + private BigDecimal unallocatedAmount; + + /** 币种 */ + private String currency; + + /** 收款方式:BANK_TRANSFER/ALIPAY/WECHAT/CASH/OTHER */ + private String payMethod; + + /** 银行流水号/支付平台流水号 */ + private String bankTransactionNo; + + /** 收款日期 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date receivedAt; + + /** 状态:PENDING待确认/CONFIRMED已确认/CANCELLED已取消 */ + private String status; + + /** 确认时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date confirmedAt; + + /** 确认人 */ + private Long confirmedBy; + + /** 开票状态:NONE未开/PART部分/ALL全部 */ + private String invoiceStatus; + + /** 乐观锁版本号 */ + private Integer version; + + /** 删除标志(0存在 1删除) */ + private String delFlag; + + /** 收款附件列表(存 bms_customer_attachment,attachment_type='3',biz_id 关联本单) */ + private List attachmentList; + + /** 收款附件数量(列表查询统计) */ + private Integer attachmentCount; +} 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 new file mode 100644 index 00000000..0f7795fc --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/mapper/CustomerMoneyMapper.java @@ -0,0 +1,60 @@ +package com.ruoyi.customer.customermoney.mapper; + +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.CustomerReceipt; + +/** + * 客户余额/收款/流水 Mapper + * + * @author ruoyi + * @date 2026-09-11 + */ +public interface CustomerMoneyMapper +{ + /* ================= 客户余额账户 ================= */ + + /** 按客户+币种查询账户 */ + CustomerAccount selectAccountByCustomerCurrency(@Param("customerId") Long customerId, + @Param("currency") String currency); + + /** 按客户+币种查询账户(行锁,审核事务内使用) */ + CustomerAccount selectAccountForUpdate(@Param("customerId") Long customerId, + @Param("currency") String currency); + + /** 查询客户全部币种账户 */ + List selectAccountListByCustomerId(@Param("customerId") Long customerId); + + /** 新增账户 */ + int insertAccount(CustomerAccount account); + + /** 乐观锁更新余额 */ + int updateAccountBalance(CustomerAccount account); + + /* ================= 客户收款 ================= */ + + /** 查询收款单列表 */ + List selectReceiptList(CustomerReceipt query); + + /** 按ID查询收款单 */ + CustomerReceipt selectReceiptById(@Param("id") Long id); + + /** 新增收款单 */ + int insertReceipt(CustomerReceipt receipt); + + /** 审核通过:PENDING -> CONFIRMED(带状态+乐观锁条件) */ + int confirmReceipt(CustomerReceipt receipt); + + /** 取消收款单:PENDING -> CANCELLED */ + int cancelReceipt(@Param("id") Long id, @Param("updateBy") String updateBy); + + /* ================= 余额流水 ================= */ + + /** 查询流水列表 */ + List selectFlowList(CustomerBalanceFlow query); + + /** 新增流水 */ + int insertFlow(CustomerBalanceFlow 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 new file mode 100644 index 00000000..096ad8a2 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/ICustomerMoneyService.java @@ -0,0 +1,39 @@ +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.CustomerReceipt; + +/** + * 客户余额/收款/流水 Service接口 + * + * @author ruoyi + * @date 2026-09-11 + */ +public interface ICustomerMoneyService +{ + /** 查询客户各币种账户 */ + List selectAccountList(Long customerId); + + /** 查询收款单列表 */ + List selectReceiptList(CustomerReceipt query); + + /** 查询收款单详情(含附件) */ + CustomerReceipt selectReceiptById(Long id); + + /** 登记收款(状态默认 PENDING,同时保存附件) */ + int insertReceipt(CustomerReceipt receipt); + + /** + * 审核通过收款单:PENDING -> CONFIRMED + * 事务内:锁定账户(不存在则初始化)-> 变动余额/已用额度 -> 写余额流水 + */ + int confirmReceipt(Long id); + + /** 取消收款单(仅待确认可取消) */ + int cancelReceipt(Long id); + + /** 查询余额流水列表 */ + List selectFlowList(CustomerBalanceFlow query); +} 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 new file mode 100644 index 00000000..df105d25 --- /dev/null +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/customer/customermoney/service/impl/CustomerMoneyServiceImpl.java @@ -0,0 +1,253 @@ +package com.ruoyi.customer.customermoney.service.impl; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +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.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.CustomerReceipt; +import com.ruoyi.customer.customermoney.mapper.CustomerMoneyMapper; +import com.ruoyi.customer.customermoney.service.ICustomerMoneyService; + +/** + * 客户余额/收款/流水 Service实现 + * + * @author ruoyi + * @date 2026-09-11 + */ +@Service +public class CustomerMoneyServiceImpl implements ICustomerMoneyService +{ + private static final DateTimeFormatter NO_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + + @Autowired + private CustomerMoneyMapper customerMoneyMapper; + + @Autowired + private BmsCustomerMapper bmsCustomerMapper; + + @Override + public List selectAccountList(Long customerId) + { + return customerMoneyMapper.selectAccountListByCustomerId(customerId); + } + + @Override + public List selectReceiptList(CustomerReceipt query) + { + return customerMoneyMapper.selectReceiptList(query); + } + + @Override + public CustomerReceipt selectReceiptById(Long id) + { + CustomerReceipt receipt = customerMoneyMapper.selectReceiptById(id); + if (receipt != null) + { + BmsCustomerAttachment query = new BmsCustomerAttachment(); + query.setBizId(id); + receipt.setAttachmentList(bmsCustomerMapper.selectBmsCustomerAttachmentList(query)); + } + return receipt; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int insertReceipt(CustomerReceipt receipt) + { + if (receipt.getCustomerId() == null) + { + throw new ServiceException("客户ID不能为空"); + } + if (receipt.getAmount() == null || receipt.getAmount().compareTo(BigDecimal.ZERO) <= 0) + { + throw new ServiceException("收款金额必须大于0"); + } + if (!StringUtils.hasText(receipt.getReceiptType())) + { + receipt.setReceiptType(CustomerReceipt.TYPE_PREPAY); + } + if (!StringUtils.hasText(receipt.getCurrency())) + { + receipt.setCurrency("CNY"); + } + receipt.setReceiptNo(generateNo("SK")); + receipt.setStatus(CustomerReceipt.STATUS_PENDING); + receipt.setAllocatedAmount(BigDecimal.ZERO); + receipt.setUnallocatedAmount(receipt.getAmount()); + if (!StringUtils.hasText(receipt.getInvoiceStatus())) + { + receipt.setInvoiceStatus("NONE"); + } + receipt.setVersion(0); + customerMoneyMapper.insertReceipt(receipt); + + // 收款附件:复用客户附件表,类型挂"其他(3)",biz_id 关联收款单 + saveReceiptAttachments(receipt); + return 1; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int confirmReceipt(Long id) + { + CustomerReceipt receipt = customerMoneyMapper.selectReceiptById(id); + if (receipt == null) + { + throw new ServiceException("收款单不存在"); + } + if (!CustomerReceipt.STATUS_PENDING.equals(receipt.getStatus())) + { + throw new ServiceException("仅待确认的收款单可以审核"); + } + + String currency = StringUtils.hasText(receipt.getCurrency()) ? receipt.getCurrency() : "CNY"; + String username = SecurityUtils.getUsername(); + Long userId = SecurityUtils.getUserId(); + + // 1. 行锁锁定客户币种账户,不存在则自动初始化 + CustomerAccount account = customerMoneyMapper.selectAccountForUpdate(receipt.getCustomerId(), currency); + if (account == null) + { + account = new CustomerAccount(); + account.setCustomerId(receipt.getCustomerId()); + account.setCurrency(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); + } + + // 2. 变动余额(预充值/混合增加余额;还款核销释放已用额度) + BigDecimal amount = nz(receipt.getAmount()); + BigDecimal beforeBalance = nz(account.getBalance()); + BigDecimal afterBalance = beforeBalance.add(amount); + BigDecimal frozenBalance = nz(account.getFrozenBalance()); + + account.setBalance(afterBalance); + if (CustomerReceipt.TYPE_REPAY.equals(receipt.getReceiptType())) + { + account.setUsedCredit(nz(account.getUsedCredit()).subtract(amount).max(BigDecimal.ZERO)); + } + account.setUpdateBy(username); + + int accountRows = customerMoneyMapper.updateAccountBalance(account); + if (accountRows == 0) + { + throw new ServiceException("账户余额已被其他操作变动,请刷新后重试"); + } + + // 3. 写余额流水 + CustomerBalanceFlow flow = new CustomerBalanceFlow(); + flow.setFlowNo(generateNo("LS")); + flow.setCustomerId(receipt.getCustomerId()); + flow.setAccountId(account.getId()); + flow.setCurrency(currency); + flow.setBizType(CustomerBalanceFlow.BIZ_RECEIPT); + flow.setBizId(receipt.getId()); + flow.setBizNo(receipt.getReceiptNo()); + flow.setDirection(1); + flow.setAmount(amount); + flow.setBeforeBalance(beforeBalance); + flow.setAfterBalance(afterBalance); + flow.setBeforeFrozenBalance(frozenBalance); + flow.setAfterFrozenBalance(frozenBalance); + flow.setOperatorId(userId); + flow.setCreateBy(username); + flow.setRemark("收款单审核通过:" + receipt.getReceiptNo()); + customerMoneyMapper.insertFlow(flow); + + // 4. 收款单置为已确认 + receipt.setStatus(CustomerReceipt.STATUS_CONFIRMED); + receipt.setConfirmedAt(new Date()); + receipt.setConfirmedBy(userId); + receipt.setUpdateBy(username); + int receiptRows = customerMoneyMapper.confirmReceipt(receipt); + if (receiptRows == 0) + { + throw new ServiceException("收款单状态已变更,请刷新后重试"); + } + return receiptRows; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int cancelReceipt(Long id) + { + CustomerReceipt receipt = customerMoneyMapper.selectReceiptById(id); + if (receipt == null) + { + throw new ServiceException("收款单不存在"); + } + if (!CustomerReceipt.STATUS_PENDING.equals(receipt.getStatus())) + { + throw new ServiceException("仅待确认的收款单可以取消"); + } + return customerMoneyMapper.cancelReceipt(id, SecurityUtils.getUsername()); + } + + @Override + public List selectFlowList(CustomerBalanceFlow query) + { + return customerMoneyMapper.selectFlowList(query); + } + + /* ================= 私有方法 ================= */ + + /** 保存收款附件到客户附件表 */ + private void saveReceiptAttachments(CustomerReceipt receipt) + { + if (CollectionUtils.isEmpty(receipt.getAttachmentList())) + { + return; + } + int sort = 0; + for (BmsCustomerAttachment attachment : receipt.getAttachmentList()) + { + attachment.setAttachmentId(null); + attachment.setCustomerId(receipt.getCustomerId()); + attachment.setBizId(receipt.getId()); + attachment.setAttachmentType("3"); + if (!StringUtils.hasText(attachment.getIsLogo())) + { + attachment.setIsLogo("0"); + } + if (attachment.getSortOrder() == null) + { + attachment.setSortOrder(sort); + } + attachment.setDelFlag("0"); + bmsCustomerMapper.insertBmsCustomerAttachment(attachment); + sort++; + } + } + + /** 生成业务单号:前缀 + 时间戳(毫秒) + 3位随机数 */ + private String generateNo(String prefix) + { + return prefix + LocalDateTime.now().format(NO_FORMATTER) + + ThreadLocalRandom.current().nextInt(100, 1000); + } + + private BigDecimal nz(BigDecimal value) + { + return value == null ? BigDecimal.ZERO : value; + } +} 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 bafef326..669cb861 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 @@ -36,41 +36,50 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select customer_id, customer_code, customer_name, customer_code_dw, credit_code, customer_type, is_enable, sales_user_id, sales_user, ed, zq, settlement_user_id, settlement_user, service_user_id, service_user, settlement_type, temp_ed, account_balance, foreign_currency_balance, weight_threshold, throw_ratio, credit_ed, product_ids, remark, create_by, create_time, update_by, update_time, del_flag from bms_customer + select c.customer_id, c.customer_code, c.customer_name, c.customer_code_dw, c.credit_code, c.customer_type, + c.is_enable, c.sales_user_id, c.sales_user, c.ed, c.zq, c.settlement_user_id, c.settlement_user, + c.service_user_id, c.service_user, c.settlement_type, c.temp_ed, + (select coalesce(sum(ca.balance), 0) from customer_account ca + where ca.customer_id = c.customer_id and ca.currency = 'CNY' and ca.del_flag = '0') as account_balance, + (select coalesce(sum(ca.balance), 0) from customer_account ca + where ca.customer_id = c.customer_id and ca.currency != 'CNY' and ca.del_flag = '0') as foreign_currency_balance, + c.weight_threshold, c.throw_ratio, c.credit_ed, c.product_ids, c.remark, + c.create_by, c.create_time, c.update_by, c.update_time, c.del_flag + from bms_customer c - + @@ -82,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -93,7 +103,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select attachment_id, customer_id, file_name, file_path, file_size, file_type, file_extension, attachment_type, is_logo, sort_order, remark, create_by, create_time, update_by, update_time, del_flag from bms_customer_attachment + select attachment_id, customer_id, file_name, file_path, file_size, file_type, file_extension, attachment_type, biz_id, is_logo, sort_order, remark, create_by, create_time, update_by, update_time, del_flag from bms_customer_attachment + + where customer_id = #{customerId} and currency = #{currency} and del_flag = '0' + + + + + + + + insert into customer_account + + customer_id, + currency, + balance, + frozen_balance, + total_credit, + extra_credit, + used_credit, + tax, + version, + create_by, + remark, + del_flag, + + + #{customerId}, + #{currency}, + #{balance}, + #{frozenBalance}, + #{totalCredit}, + #{extraCredit}, + #{usedCredit}, + #{tax}, + #{version}, + #{createBy}, + #{remark}, + '0', + + + + + update customer_account + set balance = #{balance}, + frozen_balance = #{frozenBalance}, + used_credit = #{usedCredit}, + version = version + 1, + update_by = #{updateBy}, + update_time = now() + where id = #{id} and version = #{version} and del_flag = '0' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select r.id, r.receipt_no, r.customer_id, r.account_id, r.receipt_type, r.amount, r.allocated_amount, + r.unallocated_amount, r.currency, r.pay_method, r.bank_transaction_no, r.received_at, r.status, + r.confirmed_at, r.confirmed_by, r.invoice_status, r.version, r.create_by, r.create_time, + r.update_by, r.update_time, r.remark, r.del_flag, + (select count(1) from bms_customer_attachment a + where a.biz_id = r.id and a.attachment_type = '3' and a.del_flag = '0') as attachment_count + from customer_receipt r + + + + + + + + insert into customer_receipt + + receipt_no, + customer_id, + account_id, + receipt_type, + amount, + allocated_amount, + unallocated_amount, + currency, + pay_method, + bank_transaction_no, + received_at, + status, + confirmed_at, + confirmed_by, + invoice_status, + version, + create_by, + remark, + del_flag, + + + #{receiptNo}, + #{customerId}, + #{accountId}, + #{receiptType}, + #{amount}, + #{allocatedAmount}, + #{unallocatedAmount}, + #{currency}, + #{payMethod}, + #{bankTransactionNo}, + #{receivedAt}, + #{status}, + #{confirmedAt}, + #{confirmedBy}, + #{invoiceStatus}, + #{version}, + #{createBy}, + #{remark}, + '0', + + + + + update customer_receipt + set status = 'CONFIRMED', + confirmed_at = #{confirmedAt}, + confirmed_by = #{confirmedBy}, + version = version + 1, + update_by = #{updateBy}, + update_time = now() + where id = #{id} and status = 'PENDING' and version = #{version} and del_flag = '0' + + + + update customer_receipt + set status = 'CANCELLED', + update_by = #{updateBy}, + update_time = now() + where id = #{id} and status = 'PENDING' and del_flag = '0' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, flow_no, customer_id, account_id, currency, biz_type, biz_id, biz_no, direction, amount, + before_balance, after_balance, before_frozen_balance, after_frozen_balance, operator_id, + create_by, create_time, update_by, update_time, remark, del_flag + from customer_balance_flow + + + + + + insert into customer_balance_flow + + flow_no, + customer_id, + account_id, + currency, + biz_type, + biz_id, + biz_no, + direction, + amount, + before_balance, + after_balance, + before_frozen_balance, + after_frozen_balance, + operator_id, + create_by, + remark, + del_flag, + + + #{flowNo}, + #{customerId}, + #{accountId}, + #{currency}, + #{bizType}, + #{bizId}, + #{bizNo}, + #{direction}, + #{amount}, + #{beforeBalance}, + #{afterBalance}, + #{beforeFrozenBalance}, + #{afterFrozenBalance}, + #{operatorId}, + #{createBy}, + #{remark}, + '0', + + + diff --git a/bms-back/sql/customer+supplier/customerye.sql b/bms-back/sql/customer+supplier/customerye.sql new file mode 100644 index 00000000..0410e9ce --- /dev/null +++ b/bms-back/sql/customer+supplier/customerye.sql @@ -0,0 +1,90 @@ +DROP TABLE IF EXISTS `customer_account`; +CREATE TABLE `customer_account` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `customer_id` BIGINT(20) NOT NULL COMMENT '客户ID', + `currency` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,如 CNY/USD', + `balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额', + `frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额', + `total_credit` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '总额度', + `extra_credit` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '额外额度', + `used_credit` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '已用额度', + `tax` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '税金', + `version` INT(11) NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', + `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_customer_currency` (`customer_id`, `currency`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='客户余额表'; + + +DROP TABLE IF EXISTS `customer_receipt`; +CREATE TABLE `customer_receipt` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `receipt_no` VARCHAR(64) NOT NULL COMMENT '收款单号', + `customer_id` BIGINT(20) NOT NULL COMMENT '客户ID', + `account_id` BIGINT(20) DEFAULT NULL COMMENT '客户账户ID', + `receipt_type` VARCHAR(32) NOT NULL DEFAULT 'PREPAY' COMMENT '收款类型:PREPAY预充值/REPAY还款核销/MIXED混合', + `amount` DECIMAL(18,2) NOT NULL COMMENT '收款金额', + `allocated_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '已核销金额', + `unallocated_amount` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '待核销金额', + `currency` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,如 CNY/USD', + `pay_method` VARCHAR(32) DEFAULT NULL COMMENT '收款方式:BANK_TRANSFER/ALIPAY/WECHAT/CASH/OTHER', + `bank_transaction_no` VARCHAR(64) DEFAULT NULL COMMENT '银行流水号/支付平台流水号', + `received_at` DATETIME NOT NULL COMMENT '收款日期', + `status` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING待确认/CONFIRMED已确认/CANCELLED已取消', + `confirmed_at` DATETIME DEFAULT NULL COMMENT '确认时间', + `confirmed_by` BIGINT(20) DEFAULT NULL COMMENT '确认人', + `invoice_status` VARCHAR(16) NOT NULL DEFAULT 'NONE' COMMENT '开票状态:NONE未开/PART部分/ALL全部', + `version` INT(11) NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', + `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_receipt_no` (`receipt_no`), + KEY `idx_customer_time` (`customer_id`, `received_at`), + KEY `idx_account` (`account_id`), + KEY `idx_status` (`status`), + KEY `idx_bank_transaction_no` (`bank_transaction_no`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='客户收款表'; + + +CREATE TABLE `customer_balance_flow` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `flow_no` VARCHAR(64) NOT NULL COMMENT '流水号', + `customer_id` BIGINT(20) NOT NULL COMMENT '客户ID', + `account_id` BIGINT(20) NOT NULL COMMENT '客户账户ID', + `currency` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种', + `biz_type` VARCHAR(32) NOT NULL COMMENT '业务类型:RECEIPT收款/BILL_PAY账单支付/REFUND退款/ADJUST手工调整/FREEZE冻结/UNFREEZE解冻', + `biz_id` BIGINT(20) DEFAULT NULL COMMENT '关联业务ID,如收款单ID、账单ID', + `biz_no` VARCHAR(64) DEFAULT NULL COMMENT '关联业务单号', + `direction` TINYINT(1) NOT NULL COMMENT '变动方向:1增加,-1减少', + `amount` DECIMAL(18,2) NOT NULL COMMENT '变动金额', + `before_balance` DECIMAL(18,2) NOT NULL COMMENT '变动前余额', + `after_balance` DECIMAL(18,2) NOT NULL COMMENT '变动后余额', + `before_frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动前冻结余额', + `after_frozen_balance` DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '变动后冻结余额', + `operator_id` BIGINT(20) DEFAULT NULL COMMENT '操作人', + `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', + `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_flow_no` (`flow_no`), + KEY `idx_customer_time` (`customer_id`, `create_time`), + KEY `idx_account_time` (`account_id`, `create_time`), + KEY `idx_biz` (`biz_type`, `biz_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='余额流水表'; + +-- 收款单附件复用客户附件表:attachment_type='3'(其他),biz_id 关联 customer_receipt.id +ALTER TABLE `bms_customer_attachment` + ADD COLUMN `biz_id` BIGINT(20) DEFAULT NULL COMMENT '关联业务ID(如收款单ID,attachment_type=3时使用)' AFTER `attachment_type`, + ADD KEY `idx_biz_id` (`biz_id`); diff --git a/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js b/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js new file mode 100644 index 00000000..cf1a46e3 --- /dev/null +++ b/bms-front/src/api/customersuppliermanagement/customermoney/customermoney.js @@ -0,0 +1,60 @@ +import request from '@/utils/request' + +/** 查询客户各币种账户余额 */ +export function listAccount(customerId) { + return request({ + url: '/customersupplier/customermoney/account/list/' + customerId, + method: 'get' + }) +} + +/** 查询收款记录列表 */ +export function listReceipt(query) { + return request({ + url: '/customersupplier/customermoney/receipt/list', + method: 'get', + params: query + }) +} + +/** 查询收款单详情(含附件) */ +export function getReceipt(id) { + return request({ + url: '/customersupplier/customermoney/receipt/' + id, + method: 'get' + }) +} + +/** 登记收款 */ +export function addReceipt(data) { + return request({ + url: '/customersupplier/customermoney/receipt', + method: 'post', + data: data + }) +} + +/** 审核通过收款单 */ +export function confirmReceipt(id) { + return request({ + url: '/customersupplier/customermoney/receipt/confirm/' + id, + method: 'put' + }) +} + +/** 取消收款单 */ +export function cancelReceipt(id) { + return request({ + url: '/customersupplier/customermoney/receipt/cancel/' + id, + method: 'put' + }) +} + +/** 查询余额流水列表 */ +export function listFlow(query) { + return request({ + url: '/customersupplier/customermoney/flow/list', + method: 'get', + params: query + }) +} diff --git a/bms-front/src/router/index.js b/bms-front/src/router/index.js index 830d7d1b..cfca5c30 100644 --- a/bms-front/src/router/index.js +++ b/bms-front/src/router/index.js @@ -158,6 +158,20 @@ export const dynamicRoutes = [ meta: { title: '修改生成配置', activeMenu: '/tool/gen' } } ] + }, + { + path: '/customersupplier/customer-money', + component: Layout, + hidden: true, + permissions: ['customersupplier:customerinfo:list'], + children: [ + { + path: 'index', + component: () => import('@/views/customersuppliermanagement/customerinfo/customermoney/index.vue'), + name: 'CustomerMoney', + meta: { title: '客户余额', activeMenu: '/customersupplier/customerinfo' } + } + ] } ] diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue new file mode 100644 index 00000000..0c8e5831 --- /dev/null +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/customermoney/index.vue @@ -0,0 +1,838 @@ + + + + + diff --git a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue index b6c2b5c6..5474fd43 100644 --- a/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue +++ b/bms-front/src/views/customersuppliermanagement/customerinfo/index.vue @@ -115,7 +115,7 @@ 修改 + + 客户余额 + - - + + + + + + @@ -186,7 +200,7 @@ @@ -356,13 +370,17 @@ - - + + + + - - + + + + @@ -798,6 +816,12 @@ function formatFileSize(bytes) { return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] } +/** 格式化余额(数据源 customer_account 余额表) */ +function formatBalance(val) { + const num = Number(val) + return isNaN(num) ? '0.00' : num.toFixed(2) +} + /** 附件上传前校验 */ function handleBeforeAttachmentUpload(file) { const ext = (file.name.split('.').pop() || "").toLowerCase() @@ -959,8 +983,10 @@ function resetQuery() { } /** 多选框选中数据 */ +const selectedRows = ref([]) function handleSelectionChange(selection) { ids.value = selection.map(item => item.customerId) + selectedRows.value = selection single.value = selection.length != 1 multiple.value = !selection.length } @@ -996,14 +1022,16 @@ function handleUpdate(row) { function submitForm() { proxy.$refs["customerRef"].validate(valid => { if (valid) { + // 账户余额/外币余额以 customer_account 余额表为准(收款审核自动变动),提交时不写回主表冗余列 + const payload = { ...form.value, accountBalance: null, foreignCurrencyBalance: null } if (form.value.customerId != null) { - updateCustomer(form.value).then(() => { + updateCustomer(payload).then(() => { proxy.$modal.msgSuccess("修改成功") open.value = false getList() }) } else { - addCustomer(form.value).then(() => { + addCustomer(payload).then(() => { proxy.$modal.msgSuccess("新增成功") open.value = false getList() @@ -1024,12 +1052,30 @@ function handleDelete(row) { }).catch(() => {}) } -// /** 导出按钮操作 */ -// function handleExport() { -// proxy.download('system/customer/export', { -// ...queryParams.value -// }, `customer_${new Date().getTime()}.xlsx`) -// } +/** 跳转客户余额页面(操作列传 row;工具栏按钮不传参时取勾选行) */ +function handleCustomerMoney(row) { + const target = (row && row.customerId) ? row : selectedRows.value[0] + if (!target || !target.customerId) { + proxy.$modal.msgWarning("请先勾选一个客户") + return + } + proxy.$router.push({ + path: '/customersupplier/customer-money/index', + query: { + customerId: target.customerId, + customerName: target.customerName, + customerCode: target.customerCode + } + }) +} + + +/** 导出按钮操作 */ +function handleExport() { + proxy.download('/customersupplier/customerinfo/export', { + ...queryParams.value + }, `customer_${new Date().getTime()}.xlsx`) +} /* ============ 公司主体子表 ============ */ const companyDialogVisible = ref(false) diff --git a/bms-front/src/views/customersuppliermanagement/supplierinfo/index.vue b/bms-front/src/views/customersuppliermanagement/supplierinfo/index.vue index c7e8de74..936840f9 100644 --- a/bms-front/src/views/customersuppliermanagement/supplierinfo/index.vue +++ b/bms-front/src/views/customersuppliermanagement/supplierinfo/index.vue @@ -233,6 +233,25 @@ + + +
+ 新增联系人 +
+ + + + + + + + + +
+
@@ -615,6 +634,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 否 + 是 + + + + + + + + + + + + + + + + + @@ -848,6 +979,7 @@ function reset() { channelIds: null, attachmentList: [], companyList: [], + contactList: [], remark: null, createBy: null, createTime: null, @@ -896,6 +1028,9 @@ function handleUpdate(row) { if (!form.value.companyList) { form.value.companyList = [] } + if (!form.value.contactList) { + form.value.contactList = [] + } open.value = true title.value = "修改供应商" }) @@ -1047,6 +1182,86 @@ function submitCompanyForm() { proxy.$modal.msgSuccess(companyEditingIndex.value >= 0 ? "修改成功" : "新增成功") } +/* ============ 联系人子表 ============ */ +const contactDialogVisible = ref(false) +const contactDialogTitle = ref("") +const contactForm = ref({}) +const contactEditingIndex = ref(-1) +const contactRules = { + contactName: [{ required: true, message: '联系人姓名不能为空', trigger: 'blur' }], + phone: [{ required: true, message: '手机号不能为空', trigger: 'blur' }] +} + +/** 重置联系人表单 */ +function resetContactForm() { + contactForm.value = { + contactId: null, + supplierId: form.value.supplierId || null, + contactName: '', + position: '', + department: '', + phone: '', + landline: '', + email: '', + wechat: '', + qq: '', + country: '中国', + province: '', + city: '', + district: '', + contactAddress: '', + postalCode: '', + isDefault: '0', + contactType: '0', + sortOrder: 0, + remark: '' + } + proxy.resetForm("contactRef") +} + +/** 新增联系人(本地子表,跟随主表一起提交) */ +function handleAddContact() { + resetContactForm() + contactEditingIndex.value = -1 + contactDialogTitle.value = "新增联系人" + contactDialogVisible.value = true +} + +/** 编辑联系人 */ +function handleEditContact(row, index) { + resetContactForm() + contactForm.value = { ...row } + contactEditingIndex.value = index + contactDialogTitle.value = "修改联系人" + contactDialogVisible.value = true +} + +/** 删除联系人(仅从子表移除;未入库的不调后端,已入库的在主表保存后由后端增量处理) */ +function handleDeleteContact(index) { + proxy.$modal.confirm('是否确认删除该条联系人记录?').then(() => { + form.value.contactList.splice(index, 1) + proxy.$modal.msgSuccess("删除成功") + }).catch(() => {}) +} + +/** 提交联系人表单(本地保存,跟随主表提交一起入库) */ +function submitContactForm() { + if (!contactForm.value.contactName) { + proxy.$modal.msgWarning("联系人姓名不能为空") + return + } + if (!Array.isArray(form.value.contactList)) { + form.value.contactList = [] + } + if (contactEditingIndex.value >= 0) { + form.value.contactList.splice(contactEditingIndex.value, 1, { ...contactForm.value }) + } else { + form.value.contactList.push({ ...contactForm.value }) + } + contactDialogVisible.value = false + proxy.$modal.msgSuccess(contactEditingIndex.value >= 0 ? "修改成功" : "新增成功") +} + getUserList() getList() \ No newline at end of file