去掉应收登记收款功能逻辑

This commit is contained in:
caojia
2026-09-16 17:57:34 +08:00
parent d2ed7fd4ea
commit e52f52cb35
22 changed files with 30 additions and 878 deletions
@@ -1,106 +0,0 @@
package com.ruoyi.web.controller.ar;
import java.util.List;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ar.domain.ArReceipt;
import com.ruoyi.ar.domain.ArReceiptWriteoff;
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
import com.ruoyi.ar.service.IArReceiptService;
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.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
/** 应收收款与核销管理。 */
@RestController
@RequestMapping("/ar/receipt")
@Validated
public class ArReceiptController extends BaseController
{
@Autowired
private IArReceiptService receiptService;
@PreAuthorize("@ss.hasPermi('ar:receipt:list')")
@GetMapping("/list")
public TableDataInfo list(ArReceiptQuery query)
{
startPage();
return getDataTable(receiptService.selectReceiptList(query));
}
@PreAuthorize("@ss.hasPermi('ar:receipt:list')")
@GetMapping("/{receiptId}/writeoffs")
public AjaxResult writeoffs(@PathVariable Long receiptId)
{
return success(receiptService.selectWriteoffs(receiptId));
}
@Log(title = "应收收款", businessType = BusinessType.INSERT)
@PreAuthorize("@ss.hasPermi('ar:receipt:add')")
@PostMapping
public AjaxResult add(@Valid @RequestBody ArReceipt receipt,
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
{
String requestId = requestId(headerRequestId);
return success(receiptService.registerReceipt(receipt, getUsername(), requestId)).put("request_id", requestId);
}
@Log(title = "应收收款", businessType = BusinessType.UPDATE)
@PreAuthorize("@ss.hasPermi('ar:receipt:cancel')")
@PutMapping("/{receiptId}/cancel")
public AjaxResult cancelReceipt(@PathVariable Long receiptId, @RequestBody(required = false) ArBillActionRequest request,
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
{
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
receiptService.cancelReceipt(receiptId, request == null ? null : request.getRemark(), getUsername(), requestId);
return success().put("request_id", requestId);
}
@Log(title = "应收收款核销", businessType = BusinessType.UPDATE)
@PreAuthorize("@ss.hasPermi('ar:receipt:writeoff')")
@PostMapping("/writeoff")
public AjaxResult writeoff(@Valid @RequestBody ArReceiptWriteoffRequest request,
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
{
String requestId = StringUtils.isNotEmpty(request.getRequestId()) ? request.getRequestId() : requestId(headerRequestId);
List<ArReceiptWriteoff> rows = receiptService.writeoff(request, getUsername(), requestId);
return success(rows).put("request_id", requestId);
}
@Log(title = "应收收款核销", businessType = BusinessType.UPDATE)
@PreAuthorize("@ss.hasPermi('ar:receipt:cancel')")
@PutMapping("/writeoff/{writeoffId}/cancel")
public AjaxResult cancel(@PathVariable Long writeoffId, @RequestBody(required = false) ArBillActionRequest request,
@RequestHeader(value = "X-Request-Id", required = false) String headerRequestId)
{
String requestId = requestId(request == null ? null : request.getRequestId(), headerRequestId);
receiptService.cancelWriteoff(writeoffId, request == null ? null : request.getRemark(), getUsername(), requestId);
return success().put("request_id", requestId);
}
private String requestId(String headerRequestId)
{
return StringUtils.isNotEmpty(headerRequestId) ? headerRequestId : IdUtils.fastSimpleUUID();
}
private String requestId(String bodyRequestId, String headerRequestId)
{
return StringUtils.isNotEmpty(bodyRequestId) ? bodyRequestId : requestId(headerRequestId);
}
}
@@ -10,8 +10,8 @@ import lombok.EqualsAndHashCode;
/**
* 应收账单余额核销 ar_balance_writeoff
*
* <p>用客户账户余额核销应收账单,与 ar_receipt_writeoff(收款核销)并列,
* 两者共享 ar_bill.written_off_amount / unwritten_amount 缓存。</p>
* <p>用客户账户余额核销应收账单,和客户账户余额流水联动,
* 共享 ar_bill.written_off_amount / unwritten_amount 缓存。</p>
*
* @author ruoyi
* @date 2026-09-16
@@ -1,33 +0,0 @@
package com.ruoyi.ar.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;
/** 应收真实收款。 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ArReceipt extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long receiptId;
private String receiptNo;
private Long customerId;
/** 客户名称,仅用于列表展示。 */
private String customerName;
/** 客户编码,仅用于列表展示。 */
private String customerCode;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date receiptDate;
private String currency;
private BigDecimal receiptAmount;
private BigDecimal unallocatedAmount;
private String paymentMethod;
private String bankReference;
private String status;
private String remark;
}
@@ -1,35 +0,0 @@
package com.ruoyi.ar.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/** 收款与账单的核销关系。 */
@Data
public class ArReceiptWriteoff implements Serializable
{
private static final long serialVersionUID = 1L;
private Long writeoffId;
private Long receiptId;
private Long arBillId;
/** 关联账单号,仅用于核销记录展示。 */
private String billNo;
/** 关联收款单号,仅用于核销记录展示。 */
private String receiptNo;
private String writeoffNo;
private BigDecimal writeoffAmount;
private String currency;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date writeoffDate;
private String status;
private String remark;
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String cancelBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date cancelTime;
}
@@ -1,16 +0,0 @@
package com.ruoyi.ar.domain.vo;
import java.util.Date;
import com.ruoyi.ar.domain.ArReceipt;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** 收款查询参数。 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ArReceiptQuery extends ArReceipt
{
private static final long serialVersionUID = 1L;
private Date receiptDateStart;
private Date receiptDateEnd;
}
@@ -1,35 +0,0 @@
package com.ruoyi.ar.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/** 批量收款核销参数。 */
@Data
public class ArReceiptWriteoffRequest
{
@NotNull(message = "收款ID不能为空")
private Long receiptId;
@NotEmpty(message = "至少选择一张账单")
@Valid
private List<Allocation> allocations;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date writeoffDate;
private String currency;
private String remark;
private String requestId;
@Data
public static class Allocation
{
@NotNull(message = "账单ID不能为空")
private Long arBillId;
@NotNull(message = "核销金额不能为空")
private BigDecimal amount;
}
}
@@ -1,22 +0,0 @@
package com.ruoyi.ar.enums;
/** 应收收款状态。 */
public enum ArReceiptStatus
{
REGISTERED("REGISTERED"),
PARTIAL_WRITTEN_OFF("PARTIAL_WRITTEN_OFF"),
WRITTEN_OFF("WRITTEN_OFF"),
CANCELLED("CANCELLED");
private final String code;
ArReceiptStatus(String code)
{
this.code = code;
}
public String getCode()
{
return code;
}
}
@@ -1,20 +0,0 @@
package com.ruoyi.ar.enums;
/** 收款核销状态。 */
public enum ArWriteoffStatus
{
EFFECTIVE("EFFECTIVE"),
CANCELLED("CANCELLED");
private final String code;
ArWriteoffStatus(String code)
{
this.code = code;
}
public String getCode()
{
return code;
}
}
@@ -1,30 +0,0 @@
package com.ruoyi.ar.mapper;
import java.math.BigDecimal;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.ar.domain.ArReceipt;
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
public interface ArReceiptMapper
{
List<ArReceipt> selectArReceiptList(ArReceiptQuery query);
ArReceipt selectArReceiptById(Long receiptId);
ArReceipt selectArReceiptByIdForUpdate(Long receiptId);
ArReceipt selectArReceiptByNo(String receiptNo);
ArReceipt selectArReceiptByReference(@Param("paymentMethod") String paymentMethod,
@Param("bankReference") String bankReference);
int insertArReceipt(ArReceipt receipt);
int updateBalanceAndStatus(@Param("receiptId") Long receiptId,
@Param("unallocatedAmount") BigDecimal unallocatedAmount,
@Param("status") String status, @Param("operator") String operator);
int updateCancelled(@Param("receiptId") Long receiptId, @Param("operator") String operator,
@Param("remark") String remark);
}
@@ -1,26 +0,0 @@
package com.ruoyi.ar.mapper;
import java.math.BigDecimal;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.ar.domain.ArReceiptWriteoff;
public interface ArReceiptWriteoffMapper
{
List<ArReceiptWriteoff> selectByBillId(Long arBillId);
List<ArReceiptWriteoff> selectByReceiptId(Long receiptId);
ArReceiptWriteoff selectById(Long writeoffId);
ArReceiptWriteoff selectByIdForUpdate(Long writeoffId);
int insert(ArReceiptWriteoff writeoff);
int updateCancelled(@Param("writeoffId") Long writeoffId, @Param("cancelBy") String cancelBy,
@Param("remark") String remark);
BigDecimal sumEffectiveByBillId(Long arBillId);
BigDecimal sumEffectiveByReceiptId(Long receiptId);
}
@@ -7,8 +7,7 @@ import com.ruoyi.ar.domain.ArBill;
/**
* 应收账单余额核销 Service接口
*
* <p>资金来源为客户账户余额(customer_account.balance),与收款核销
* (ar_receipt_writeoff)并列;核销时三方联动:账户余额扣减、已用额度释放、
* <p>资金来源为客户账户余额(customer_account.balance);核销时三方联动:账户余额扣减、已用额度释放、
* 账单已核销/未核销金额及状态更新。</p>
*
* @author ruoyi
@@ -1,22 +0,0 @@
package com.ruoyi.ar.service;
import java.util.List;
import com.ruoyi.ar.domain.ArReceipt;
import com.ruoyi.ar.domain.ArReceiptWriteoff;
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
public interface IArReceiptService
{
List<ArReceipt> selectReceiptList(ArReceiptQuery query);
List<ArReceiptWriteoff> selectWriteoffs(Long receiptId);
ArReceipt registerReceipt(ArReceipt receipt, String operator, String requestId);
void cancelReceipt(Long receiptId, String remark, String operator, String requestId);
List<ArReceiptWriteoff> writeoff(ArReceiptWriteoffRequest request, String operator, String requestId);
void cancelWriteoff(Long writeoffId, String remark, String operator, String requestId);
}
@@ -1,275 +0,0 @@
package com.ruoyi.ar.service.impl;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.ar.domain.ArBill;
import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.domain.ArReceipt;
import com.ruoyi.ar.domain.ArReceiptWriteoff;
import com.ruoyi.ar.domain.vo.ArReceiptQuery;
import com.ruoyi.ar.domain.vo.ArReceiptWriteoffRequest;
import com.ruoyi.ar.enums.ArBillStatus;
import com.ruoyi.ar.enums.ArLogResult;
import com.ruoyi.ar.enums.ArReceiptStatus;
import com.ruoyi.ar.enums.ArWriteoffStatus;
import com.ruoyi.ar.mapper.ArBillMapper;
import com.ruoyi.ar.mapper.ArBillLogMapper;
import com.ruoyi.ar.mapper.ArReceiptMapper;
import com.ruoyi.ar.mapper.ArReceiptWriteoffMapper;
import com.ruoyi.ar.service.IArBillLogService;
import com.ruoyi.ar.service.IArReceiptService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
@Service
public class ArReceiptServiceImpl implements IArReceiptService
{
@Autowired private ArReceiptMapper receiptMapper;
@Autowired private ArReceiptWriteoffMapper writeoffMapper;
@Autowired private ArBillMapper billMapper;
@Autowired private ArBillLogMapper logMapper;
@Autowired private IArBillLogService logService;
@Override
public List<ArReceipt> selectReceiptList(ArReceiptQuery query)
{
return receiptMapper.selectArReceiptList(query);
}
@Override
public List<ArReceiptWriteoff> selectWriteoffs(Long receiptId)
{
return writeoffMapper.selectByReceiptId(receiptId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ArReceipt registerReceipt(ArReceipt receipt, String operator, String requestId)
{
if (receipt == null || receipt.getCustomerId() == null || receipt.getReceiptDate() == null
|| receipt.getReceiptAmount() == null || receipt.getReceiptAmount().compareTo(BigDecimal.ZERO) <= 0)
{
throw failure(null, "RECEIPT", "REGISTER", "收款客户、日期和正数金额不能为空", operator, requestId);
}
if (StringUtils.isEmpty(receipt.getReceiptNo()))
{
receipt.setReceiptNo("RC" + DateUtils.dateTimeNow("yyyyMMddHHmmss") + IdUtils.fastSimpleUUID().substring(0, 6).toUpperCase());
}
if (StringUtils.isEmpty(receipt.getCurrency())) receipt.setCurrency("CNY");
if (StringUtils.isEmpty(receipt.getPaymentMethod())) receipt.setPaymentMethod("BANK");
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "RECEIPT");
if (previous != null && isNumber(previous.getObjectId()))
{
ArReceipt existed = receiptMapper.selectArReceiptById(Long.valueOf(previous.getObjectId()));
if (existed != null) return existed;
}
if (receiptMapper.selectArReceiptByNo(receipt.getReceiptNo()) != null)
{
throw failure(null, "RECEIPT", receipt.getReceiptNo(), "收款单号已存在", operator, requestId);
}
if (!StringUtils.isEmpty(receipt.getBankReference())
&& receiptMapper.selectArReceiptByReference(receipt.getPaymentMethod(), receipt.getBankReference()) != null)
{
throw failure(null, "RECEIPT", receipt.getReceiptNo(), "银行流水号已存在", operator, requestId);
}
receipt.setUnallocatedAmount(receipt.getReceiptAmount());
receipt.setStatus(ArReceiptStatus.REGISTERED.getCode());
receipt.setCreateBy(operator);
receipt.setUpdateBy(operator);
try
{
receiptMapper.insertArReceipt(receipt);
}
catch (DuplicateKeyException e)
{
ArReceipt existed = receiptMapper.selectArReceiptByNo(receipt.getReceiptNo());
if (existed != null) return existed;
throw e;
}
ArBillLog log = new ArBillLog();
log.setObjectType("RECEIPT"); log.setObjectId(String.valueOf(receipt.getReceiptId()));
log.setOperationType("RECEIPT"); log.setOperationDesc("登记应收收款");
log.setOperatorId(operator); log.setOperatorName(operator); log.setOperationSource("WEB");
log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
return receipt;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelReceipt(Long receiptId, String remark, String operator, String requestId)
{
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "RECEIPT");
if (previous != null && "CANCEL_RECEIPT".equals(previous.getOperationType())) return;
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(receiptId);
if (receipt == null) throw failure(null, "RECEIPT", String.valueOf(receiptId), "收款不存在", operator, requestId);
if (!ArReceiptStatus.REGISTERED.getCode().equals(receipt.getStatus())
|| nvl(receipt.getUnallocatedAmount()).compareTo(nvl(receipt.getReceiptAmount())) != 0)
{
throw failure(null, "RECEIPT", String.valueOf(receiptId), "收款已发生核销,不能直接取消", operator, requestId);
}
receiptMapper.updateCancelled(receiptId, operator, StringUtils.isEmpty(remark) ? "取消收款" : remark);
ArBillLog log = new ArBillLog();
log.setObjectType("RECEIPT"); log.setObjectId(String.valueOf(receiptId)); log.setOperationType("CANCEL_RECEIPT");
log.setOperationDesc(StringUtils.isEmpty(remark) ? "取消收款" : remark); log.setFromStatus(ArReceiptStatus.REGISTERED.getCode());
log.setToStatus(ArReceiptStatus.CANCELLED.getCode()); log.setOperatorId(operator); log.setOperatorName(operator);
log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<ArReceiptWriteoff> writeoff(ArReceiptWriteoffRequest request, String operator, String requestId)
{
if (request == null || request.getReceiptId() == null || request.getAllocations() == null || request.getAllocations().isEmpty())
{
throw failure(null, "WRITEOFF", "", "核销收款和账单不能为空", operator, requestId);
}
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "WRITEOFF");
if (previous != null && isNumber(previous.getObjectId()))
{
ArReceiptWriteoff existed = writeoffMapper.selectById(Long.valueOf(previous.getObjectId()));
if (existed != null) return writeoffMapper.selectByReceiptId(existed.getReceiptId());
}
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(request.getReceiptId());
if (receipt == null) throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款不存在", operator, requestId);
if (ArReceiptStatus.CANCELLED.getCode().equals(receipt.getStatus()))
{
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款已取消,不允许核销", operator, requestId);
}
String currency = StringUtils.isEmpty(request.getCurrency()) ? receipt.getCurrency() : request.getCurrency();
if (!currency.equalsIgnoreCase(receipt.getCurrency()))
{
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "收款与核销币种不一致", operator, requestId);
}
BigDecimal allocationTotal = BigDecimal.ZERO;
for (ArReceiptWriteoffRequest.Allocation allocation : request.getAllocations())
{
if (allocation.getAmount() == null || allocation.getAmount().compareTo(BigDecimal.ZERO) <= 0)
{
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "核销金额必须大于零", operator, requestId);
}
allocationTotal = allocationTotal.add(allocation.getAmount());
}
if (allocationTotal.compareTo(nvl(receipt.getUnallocatedAmount())) > 0)
{
throw failure(null, "WRITEOFF", String.valueOf(request.getReceiptId()), "核销金额超过收款未分配余额", operator, requestId);
}
List<ArReceiptWriteoff> result = new ArrayList<>();
BigDecimal remainingReceipt = nvl(receipt.getUnallocatedAmount());
Date writeoffDate = request.getWriteoffDate() == null ? new Date() : request.getWriteoffDate();
for (ArReceiptWriteoffRequest.Allocation allocation : request.getAllocations())
{
ArBill bill = billMapper.selectArBillByIdForUpdate(allocation.getArBillId());
if (bill == null) throw failure(null, "WRITEOFF", String.valueOf(allocation.getArBillId()), "账单不存在", operator, requestId);
ArBillStatus billStatus = ArBillStatus.of(bill.getStatus());
if (billStatus == ArBillStatus.VOID) throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单已作废,不允许核销", operator, requestId);
if (billStatus == null || !billStatus.canWriteOff())
{
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单尚未确认,不允许核销", operator, requestId);
}
if (!currency.equalsIgnoreCase(bill.getCurrency())) throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "收款与账单币种不一致", operator, requestId);
BigDecimal amount = allocation.getAmount();
if (amount.compareTo(nvl(bill.getUnwrittenAmount())) > 0)
{
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "核销金额超过账单未核销余额", operator, requestId);
}
ArReceiptWriteoff writeoff = new ArReceiptWriteoff();
writeoff.setReceiptId(receipt.getReceiptId()); writeoff.setArBillId(bill.getArBillId());
writeoff.setWriteoffNo("WO" + DateUtils.dateTimeNow("yyyyMMddHHmmss") + IdUtils.fastSimpleUUID().substring(0, 6).toUpperCase());
writeoff.setWriteoffAmount(amount); writeoff.setCurrency(currency); writeoff.setWriteoffDate(writeoffDate);
writeoff.setStatus(ArWriteoffStatus.EFFECTIVE.getCode()); writeoff.setRemark(request.getRemark()); writeoff.setCreateBy(operator);
writeoffMapper.insert(writeoff);
remainingReceipt = remainingReceipt.subtract(amount);
BigDecimal written = nvl(bill.getWrittenOffAmount()).add(amount);
BigDecimal unwritten = nvl(bill.getUnwrittenAmount()).subtract(amount);
String nextBillStatus = billStatus(written, nvl(bill.getTotalAmount()), bill.getStatus());
if (billMapper.updateArBillAmounts(bill.getArBillId(), written, unwritten,
nextBillStatus, operator, bill.getStatus()) != 1)
{
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单状态已发生变化,请刷新后重试", operator, requestId);
}
writeoffLog(writeoff, bill, operator, requestId);
result.add(writeoff);
}
receiptMapper.updateBalanceAndStatus(receipt.getReceiptId(), remainingReceipt,
remainingReceipt.compareTo(BigDecimal.ZERO) == 0 ? ArReceiptStatus.WRITTEN_OFF.getCode() : ArReceiptStatus.PARTIAL_WRITTEN_OFF.getCode(), operator);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelWriteoff(Long writeoffId, String remark, String operator, String requestId)
{
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, "WRITEOFF");
if (previous != null && "CANCEL_WRITEOFF".equals(previous.getOperationType())) return;
ArReceiptWriteoff writeoff = writeoffMapper.selectByIdForUpdate(writeoffId);
if (writeoff == null) throw failure(null, "WRITEOFF", String.valueOf(writeoffId), "核销记录不存在", operator, requestId);
if (!ArWriteoffStatus.EFFECTIVE.getCode().equals(writeoff.getStatus())) throw failure(writeoff.getArBillId(), "WRITEOFF", String.valueOf(writeoffId), "只有有效核销可以取消", operator, requestId);
ArReceipt receipt = receiptMapper.selectArReceiptByIdForUpdate(writeoff.getReceiptId());
ArBill bill = billMapper.selectArBillByIdForUpdate(writeoff.getArBillId());
if (receipt == null || bill == null) throw failure(writeoff.getArBillId(), "WRITEOFF", String.valueOf(writeoffId), "核销关联的收款或账单不存在", operator, requestId);
writeoffMapper.updateCancelled(writeoffId, operator, StringUtils.isEmpty(remark) ? "取消核销" : remark);
BigDecimal receiptUnallocated = nvl(receipt.getUnallocatedAmount()).add(writeoff.getWriteoffAmount());
receiptMapper.updateBalanceAndStatus(receipt.getReceiptId(), receiptUnallocated,
receiptUnallocated.compareTo(nvl(receipt.getReceiptAmount())) == 0 ? ArReceiptStatus.REGISTERED.getCode() : ArReceiptStatus.PARTIAL_WRITTEN_OFF.getCode(), operator);
BigDecimal written = nvl(bill.getWrittenOffAmount()).subtract(writeoff.getWriteoffAmount());
if (written.compareTo(BigDecimal.ZERO) < 0) written = BigDecimal.ZERO;
BigDecimal unwritten = nvl(bill.getTotalAmount()).subtract(written);
String nextBillStatus = billStatus(written, nvl(bill.getTotalAmount()), bill.getStatus());
if (billMapper.updateArBillAmounts(bill.getArBillId(), written, unwritten,
nextBillStatus, operator, bill.getStatus()) != 1)
{
throw failure(bill.getArBillId(), "WRITEOFF", String.valueOf(bill.getArBillId()), "账单状态已发生变化,请刷新后重试", operator, requestId);
}
ArBillLog log = new ArBillLog();
log.setArBillId(bill.getArBillId()); log.setObjectType("WRITEOFF"); log.setObjectId(String.valueOf(writeoffId));
log.setOperationType("CANCEL_WRITEOFF"); log.setOperationDesc(StringUtils.isEmpty(remark) ? "取消核销" : remark);
log.setFromStatus(ArWriteoffStatus.EFFECTIVE.getCode()); log.setToStatus(ArWriteoffStatus.CANCELLED.getCode()); log.setOperatorId(operator); log.setOperatorName(operator);
log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode()); logService.record(log);
}
private void writeoffLog(ArReceiptWriteoff writeoff, ArBill bill, String operator, String requestId)
{
ArBillLog log = new ArBillLog();
log.setArBillId(bill.getArBillId()); log.setObjectType("WRITEOFF"); log.setObjectId(String.valueOf(writeoff.getWriteoffId()));
log.setOperationType("WRITEOFF"); log.setOperationDesc("收款核销到账单"); log.setFromStatus(bill.getStatus());
log.setToStatus(billStatus(nvl(bill.getWrittenOffAmount()).add(writeoff.getWriteoffAmount()), nvl(bill.getTotalAmount()), bill.getStatus()));
log.setOperatorId(operator); log.setOperatorName(operator); log.setOperationSource("WEB"); log.setRequestId(requestId); log.setResult(ArLogResult.SUCCESS.getCode());
logService.record(log);
}
private String billStatus(BigDecimal written, BigDecimal total, String current)
{
if (written.compareTo(BigDecimal.ZERO) == 0)
{
return ArBillStatus.DRAFT.getCode().equals(current)
? ArBillStatus.DRAFT.getCode() : ArBillStatus.CONFIRMED.getCode();
}
return written.compareTo(total) >= 0
? ArBillStatus.WRITTEN_OFF.getCode() : ArBillStatus.PARTIAL_WRITTEN_OFF.getCode();
}
private ServiceException failure(Long billId, String objectType, String message, String operator, String requestId)
{
logService.recordFailure(billId, objectType, objectType, "RECEIPT", null, message, operator, requestId);
return new ServiceException(message);
}
private ServiceException failure(Long billId, String objectType, String objectId, String message, String operator, String requestId)
{
logService.recordFailure(billId, objectType, objectId,
"RECEIPT".equals(objectType) ? "RECEIPT" : "WRITEOFF", null, message, operator, requestId);
return new ServiceException(message);
}
private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
private boolean isNumber(String value) { if (StringUtils.isEmpty(value)) return false; try { Long.valueOf(value); return true; } catch (NumberFormatException e) { return false; } }
}
@@ -31,8 +31,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
case l.object_type
when 'BILL' then b.bill_no
when 'EXPORT' then coalesce(e.file_name, b.bill_no)
when 'RECEIPT' then r.receipt_no
when 'WRITEOFF' then w.writeoff_no
when 'BALANCE_WRITEOFF' then bw.writeoff_no
else null
end as object_display,
@@ -41,8 +39,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from ar_bill_log l
left join ar_bill b on b.ar_bill_id = l.ar_bill_id
left join ar_bill_export e on e.export_id = case when l.object_type = 'EXPORT' then cast(l.object_id as unsigned) end
left join ar_receipt r on r.receipt_id = case when l.object_type = 'RECEIPT' then cast(l.object_id as unsigned) end
left join ar_receipt_writeoff w on w.writeoff_id = case when l.object_type = 'WRITEOFF' then cast(l.object_id as unsigned) end
left join ar_balance_writeoff bw on bw.writeoff_id = case when l.object_type = 'BALANCE_WRITEOFF' then cast(l.object_id as unsigned) end
<where>
<if test="arBillId != null">and l.ar_bill_id = #{arBillId}</if>
@@ -1,97 +0,0 @@
<?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.ar.mapper.ArReceiptMapper">
<resultMap id="ArReceiptResult" type="ArReceipt">
<id property="receiptId" column="receipt_id"/>
<result property="receiptNo" column="receipt_no"/>
<result property="customerId" column="customer_id"/>
<result property="customerName" column="customer_name"/>
<result property="customerCode" column="customer_code"/>
<result property="receiptDate" column="receipt_date"/>
<result property="currency" column="currency"/>
<result property="receiptAmount" column="receipt_amount"/>
<result property="unallocatedAmount" column="unallocated_amount"/>
<result property="paymentMethod" column="payment_method"/>
<result property="bankReference" column="bank_reference"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectReceiptColumns">
select r.receipt_id, r.receipt_no, r.customer_id, c.customer_name, c.customer_code,
r.receipt_date, r.currency, r.receipt_amount, r.unallocated_amount,
r.payment_method, r.bank_reference, r.status, r.remark,
r.create_by, r.create_time, r.update_by, r.update_time
from ar_receipt r
left join bms_customer c on c.customer_id = r.customer_id and c.del_flag = '0'
</sql>
<!-- 加锁更新只读取收款主表,避免锁定展示关联表。 -->
<sql id="selectReceiptLockColumns">
select receipt_id, receipt_no, customer_id, receipt_date, currency, receipt_amount,
unallocated_amount, payment_method, bank_reference, status, remark,
create_by, create_time, update_by, update_time
from ar_receipt
</sql>
<select id="selectArReceiptList" parameterType="com.ruoyi.ar.domain.vo.ArReceiptQuery" resultMap="ArReceiptResult">
<include refid="selectReceiptColumns"/>
<where>
<if test="receiptNo != null and receiptNo != ''">and r.receipt_no like concat('%', #{receiptNo}, '%')</if>
<if test="customerId != null">and r.customer_id = #{customerId}</if>
<if test="currency != null and currency != ''">and r.currency = #{currency}</if>
<if test="paymentMethod != null and paymentMethod != ''">and r.payment_method = #{paymentMethod}</if>
<if test="status != null and status != ''">and r.status = #{status}</if>
<if test="receiptDateStart != null">and r.receipt_date &gt;= #{receiptDateStart}</if>
<if test="receiptDateEnd != null">and r.receipt_date &lt;= #{receiptDateEnd}</if>
</where>
order by r.receipt_date desc, r.receipt_id desc
</select>
<select id="selectArReceiptById" parameterType="long" resultMap="ArReceiptResult">
<include refid="selectReceiptColumns"/> where r.receipt_id = #{receiptId}
</select>
<select id="selectArReceiptByIdForUpdate" parameterType="long" resultMap="ArReceiptResult">
<include refid="selectReceiptLockColumns"/> where receipt_id = #{receiptId} for update
</select>
<select id="selectArReceiptByNo" parameterType="string" resultMap="ArReceiptResult">
<include refid="selectReceiptColumns"/> where r.receipt_no = #{receiptNo}
</select>
<select id="selectArReceiptByReference" resultMap="ArReceiptResult">
<include refid="selectReceiptColumns"/>
where r.payment_method = #{paymentMethod} and r.bank_reference = #{bankReference}
limit 1
</select>
<insert id="insertArReceipt" parameterType="ArReceipt" useGeneratedKeys="true" keyProperty="receiptId">
insert into ar_receipt (
receipt_no, customer_id, receipt_date, currency, receipt_amount, unallocated_amount,
payment_method, bank_reference, status, remark, create_by, create_time, update_by, update_time
) values (
#{receiptNo}, #{customerId}, #{receiptDate}, #{currency}, #{receiptAmount}, #{unallocatedAmount},
#{paymentMethod}, #{bankReference}, #{status}, #{remark}, #{createBy}, sysdate(), #{updateBy}, sysdate()
)
</insert>
<update id="updateBalanceAndStatus">
update ar_receipt
set unallocated_amount = #{unallocatedAmount}, status = #{status},
update_by = #{operator}, update_time = sysdate()
where receipt_id = #{receiptId}
</update>
<update id="updateCancelled">
update ar_receipt set status = 'CANCELLED', remark = #{remark},
update_by = #{operator}, update_time = sysdate()
where receipt_id = #{receiptId}
</update>
</mapper>
@@ -1,81 +0,0 @@
<?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.ar.mapper.ArReceiptWriteoffMapper">
<resultMap id="ArReceiptWriteoffResult" type="ArReceiptWriteoff">
<id property="writeoffId" column="writeoff_id"/>
<result property="receiptId" column="receipt_id"/>
<result property="arBillId" column="ar_bill_id"/>
<result property="billNo" column="bill_no"/>
<result property="receiptNo" column="receipt_no"/>
<result property="writeoffNo" column="writeoff_no"/>
<result property="writeoffAmount" column="writeoff_amount"/>
<result property="currency" column="currency"/>
<result property="writeoffDate" column="writeoff_date"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="cancelBy" column="cancel_by"/>
<result property="cancelTime" column="cancel_time"/>
</resultMap>
<sql id="selectWriteoffColumns">
select w.writeoff_id, w.receipt_id, w.ar_bill_id, b.bill_no, r.receipt_no,
w.writeoff_no, w.writeoff_amount, w.currency, w.writeoff_date, w.status,
w.remark, w.create_by, w.create_time, w.cancel_by, w.cancel_time
from ar_receipt_writeoff w
left join ar_bill b on b.ar_bill_id = w.ar_bill_id
left join ar_receipt r on r.receipt_id = w.receipt_id
</sql>
<!-- 加锁更新只读取核销关系主表,展示字段不参与锁定。 -->
<sql id="selectWriteoffLockColumns">
select writeoff_id, receipt_id, ar_bill_id, writeoff_no, writeoff_amount, currency,
writeoff_date, status, remark, create_by, create_time, cancel_by, cancel_time
from ar_receipt_writeoff
</sql>
<select id="selectByBillId" parameterType="long" resultMap="ArReceiptWriteoffResult">
<include refid="selectWriteoffColumns"/> where w.ar_bill_id = #{arBillId} order by w.create_time desc
</select>
<select id="selectByReceiptId" parameterType="long" resultMap="ArReceiptWriteoffResult">
<include refid="selectWriteoffColumns"/> where w.receipt_id = #{receiptId} order by w.create_time desc
</select>
<select id="selectById" parameterType="long" resultMap="ArReceiptWriteoffResult">
<include refid="selectWriteoffColumns"/> where w.writeoff_id = #{writeoffId}
</select>
<select id="selectByIdForUpdate" parameterType="long" resultMap="ArReceiptWriteoffResult">
<include refid="selectWriteoffLockColumns"/> where writeoff_id = #{writeoffId} for update
</select>
<insert id="insert" parameterType="ArReceiptWriteoff" useGeneratedKeys="true" keyProperty="writeoffId">
insert into ar_receipt_writeoff (
receipt_id, ar_bill_id, writeoff_no, writeoff_amount, currency, writeoff_date,
status, remark, create_by, create_time
) values (
#{receiptId}, #{arBillId}, #{writeoffNo}, #{writeoffAmount}, #{currency}, #{writeoffDate},
#{status}, #{remark}, #{createBy}, sysdate()
)
</insert>
<update id="updateCancelled">
update ar_receipt_writeoff
set status = 'CANCELLED', remark = #{remark}, cancel_by = #{cancelBy}, cancel_time = sysdate()
where writeoff_id = #{writeoffId} and status = 'EFFECTIVE'
</update>
<select id="sumEffectiveByBillId" parameterType="long" resultType="java.math.BigDecimal">
select coalesce(sum(writeoff_amount), 0) from ar_receipt_writeoff
where ar_bill_id = #{arBillId} and status = 'EFFECTIVE'
</select>
<select id="sumEffectiveByReceiptId" parameterType="long" resultType="java.math.BigDecimal">
select coalesce(sum(writeoff_amount), 0) from ar_receipt_writeoff
where receipt_id = #{receiptId} and status = 'EFFECTIVE'
</select>
</mapper>
+2 -2
View File
@@ -5,8 +5,8 @@
-- 3) 菜单与授权:客户账单(2435) / 账单详情(2436) / 余额核销(2437) / 反核销(2438)
-- =====================================================================
-- 余额核销单:用客户账户余额核销应收账单(与 ar_receipt_writeoff 收款核销并列,
-- 两者共享 ar_bill.written_off_amount / unwritten_amount 缓存字段)
-- 余额核销单:用客户账户余额核销应收账单,
-- 与客户账户余额流水配合,共享 ar_bill.written_off_amount / unwritten_amount 缓存字段
CREATE TABLE IF NOT EXISTS ar_balance_writeoff (
writeoff_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '余额核销ID',
writeoff_no VARCHAR(50) NOT NULL COMMENT '核销单号(BWO开头)',
+2 -47
View File
@@ -98,51 +98,6 @@ CREATE TABLE IF NOT EXISTS ar_bill_detail_fee (
CONSTRAINT fk_ar_detail_fee_detail FOREIGN KEY (detail_id) REFERENCES ar_bill_detail (detail_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收账单费用快照';
CREATE TABLE IF NOT EXISTS ar_receipt (
receipt_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '收款ID',
receipt_no VARCHAR(50) NOT NULL COMMENT '收款单号',
customer_id BIGINT(20) NOT NULL COMMENT '付款客户ID',
receipt_date DATE NOT NULL COMMENT '到账日期',
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
receipt_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '到账总额',
unallocated_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '未分配金额缓存',
payment_method VARCHAR(30) NOT NULL COMMENT '到账方式',
bank_reference VARCHAR(100) DEFAULT NULL COMMENT '银行流水号或第三方交易号',
status VARCHAR(30) NOT NULL COMMENT 'REGISTERED/PARTIAL_WRITTEN_OFF/WRITTEN_OFF/CANCELLED',
remark VARCHAR(500) DEFAULT NULL COMMENT '收款备注或取消原因',
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
create_time DATETIME NOT NULL COMMENT '创建时间',
update_by VARCHAR(64) NOT NULL COMMENT '更新人',
update_time DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (receipt_id),
UNIQUE KEY uk_ar_receipt_no (receipt_no),
UNIQUE KEY uk_ar_payment_reference (payment_method, bank_reference),
KEY idx_ar_receipt_customer_date (customer_id, receipt_date),
KEY idx_ar_receipt_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收收款表';
CREATE TABLE IF NOT EXISTS ar_receipt_writeoff (
writeoff_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '核销ID',
receipt_id BIGINT(20) NOT NULL COMMENT '收款ID',
ar_bill_id BIGINT(20) NOT NULL COMMENT '应收账单ID',
writeoff_no VARCHAR(50) NOT NULL COMMENT '核销单号',
writeoff_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '本次核销金额',
currency VARCHAR(10) NOT NULL COMMENT '币种',
writeoff_date DATE NOT NULL COMMENT '核销日期',
status VARCHAR(30) NOT NULL COMMENT 'EFFECTIVE/CANCELLED',
remark VARCHAR(500) DEFAULT NULL COMMENT '核销备注或取消原因',
create_by VARCHAR(64) NOT NULL COMMENT '创建人',
create_time DATETIME NOT NULL COMMENT '创建时间',
cancel_by VARCHAR(64) DEFAULT NULL COMMENT '取消人',
cancel_time DATETIME DEFAULT NULL COMMENT '取消时间',
PRIMARY KEY (writeoff_id),
UNIQUE KEY uk_ar_writeoff_no (writeoff_no),
KEY idx_ar_writeoff_receipt (receipt_id, status),
KEY idx_ar_writeoff_bill (ar_bill_id, status),
CONSTRAINT fk_ar_writeoff_receipt FOREIGN KEY (receipt_id) REFERENCES ar_receipt (receipt_id),
CONSTRAINT fk_ar_writeoff_bill FOREIGN KEY (ar_bill_id) REFERENCES ar_bill (ar_bill_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应收收款核销表';
CREATE TABLE IF NOT EXISTS ar_bill_export (
export_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '导出ID',
ar_bill_id BIGINT(20) NOT NULL COMMENT '应收账单ID',
@@ -167,9 +122,9 @@ CREATE TABLE IF NOT EXISTS ar_bill_export (
CREATE TABLE IF NOT EXISTS ar_bill_log (
log_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID',
ar_bill_id BIGINT(20) DEFAULT NULL COMMENT '账单ID',
object_type VARCHAR(40) NOT NULL COMMENT 'BILL/RECEIPT/WRITEOFF/EXPORT/BALANCE_WRITEOFF',
object_type VARCHAR(40) NOT NULL COMMENT 'BILL/EXPORT/BALANCE_WRITEOFF',
object_id VARCHAR(64) NOT NULL COMMENT '通用对象ID',
operation_type VARCHAR(40) NOT NULL COMMENT '操作类型:CONFIRM/VOID/EXPORT/RECEIPT/CANCEL_RECEIPT/WRITEOFF/CANCEL_WRITEOFF/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF',
operation_type VARCHAR(40) NOT NULL COMMENT '操作类型:CONFIRM/VOID/EXPORT/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF',
operation_desc VARCHAR(500) DEFAULT NULL COMMENT '操作说明',
from_status VARCHAR(30) DEFAULT NULL COMMENT '变更前状态',
to_status VARCHAR(30) DEFAULT NULL COMMENT '变更后状态',
@@ -1,6 +1,6 @@
-- 应收日志兼容余额核销:仅更新字段注释,不改变字段类型和历史数据
ALTER TABLE ar_bill_log
MODIFY COLUMN object_type VARCHAR(40) NOT NULL
COMMENT 'BILL/RECEIPT/WRITEOFF/EXPORT/BALANCE_WRITEOFF',
COMMENT 'BILL/EXPORT/BALANCE_WRITEOFF',
MODIFY COLUMN operation_type VARCHAR(40) NOT NULL
COMMENT '操作类型:CONFIRM/VOID/EXPORT/RECEIPT/CANCEL_RECEIPT/WRITEOFF/CANCEL_WRITEOFF/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF';
COMMENT '操作类型:CONFIRM/VOID/EXPORT/BALANCE_WRITEOFF/CANCEL_BALANCE_WRITEOFF';
+2 -18
View File
@@ -2,7 +2,7 @@
SET NAMES utf8mb4;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2400, N'应收管理', 0, 10, 'ar', NULL, '', '', 1, 0, 'M', '0', '0', '', 'money', 'admin', sysdate(), N'应收账单、收款与核销'
SELECT 2400, N'应收管理', 0, 10, 'ar', NULL, '', '', 1, 0, 'M', '0', '0', '', 'money', 'admin', sysdate(), N'应收账单、余额核销与日志'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2400);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
@@ -24,22 +24,6 @@ INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component,
SELECT 2406, N'导出账单', 2401, 5, '', NULL, 1, 0, 'F', '0', '0', 'ar:bill:export', '#', 'admin', sysdate()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2406);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2410, N'应收收款', 2400, 2, 'receipt', 'ar/receipt/index', 'ArReceipt', 1, 0, 'C', '0', '0', 'ar:receipt:list', 'money', 'admin', sysdate(), N'收款登记与核销'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2410);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
SELECT 2411, N'收款登记', 2410, 1, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:add', '#', 'admin', sysdate()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2411);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
SELECT 2412, N'批量核销', 2410, 2, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:writeoff', '#', 'admin', sysdate()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2412);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
SELECT 2413, N'取消核销', 2410, 3, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:cancel', '#', 'admin', sysdate()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2413);
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
SELECT 2414, N'取消收款', 2410, 4, '', NULL, 1, 0, 'F', '0', '0', 'ar:receipt:cancel', '#', 'admin', sysdate()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2414);
INSERT INTO sys_menu (
menu_id, menu_name, parent_id, order_num, path, component,
is_frame, is_cache, menu_type, visible, status, perms, icon,
@@ -53,5 +37,5 @@ FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2420);
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, m.menu_id FROM (SELECT 2400 AS menu_id UNION SELECT 2401 UNION SELECT 2402 UNION SELECT 2403 UNION SELECT 2404 UNION SELECT 2405 UNION SELECT 2406 UNION SELECT 2410 UNION SELECT 2411 UNION SELECT 2412 UNION SELECT 2413 UNION SELECT 2414 UNION SELECT 2420) m
SELECT 1, m.menu_id FROM (SELECT 2400 AS menu_id UNION SELECT 2401 UNION SELECT 2402 UNION SELECT 2403 UNION SELECT 2404 UNION SELECT 2405 UNION SELECT 2406 UNION SELECT 2420) m
WHERE NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);
@@ -0,0 +1,16 @@
-- 应收收款独立模块下线
-- 业务闭环改为:客户收款登记/审核 -> 客户余额 -> 余额核销账单。
-- 本脚本会永久删除 ar_receipt、ar_receipt_writeoff 及其历史数据,执行前请确认已完成备份。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 先删除子表,再删除收款主表
DROP TABLE IF EXISTS ar_receipt_writeoff;
DROP TABLE IF EXISTS ar_receipt;
-- 删除应收管理下独立“应收收款”菜单及按钮权限
DELETE FROM sys_role_menu WHERE menu_id IN (2410, 2411, 2412, 2413, 2414);
DELETE FROM sys_menu WHERE menu_id IN (2410, 2411, 2412, 2413, 2414);
SET FOREIGN_KEY_CHECKS = 1;
+3 -3
View File
@@ -10,7 +10,7 @@
import { listArBillLogs } from '@/api/ar/bill'
const tableRef = ref()
const searchColumns = [
{ label: '对象类型', prop: 'objectType', component: 'select', options: [{ label: '应收账单', value: 'BILL' }, { label: '账单导出', value: 'EXPORT' }, { label: '应收收款', value: 'RECEIPT' }, { label: '收款核销', value: 'WRITEOFF' }, { label: '余额核销', value: 'BALANCE_WRITEOFF' }], width: '130px' },
{ label: '对象类型', prop: 'objectType', component: 'select', options: [{ label: '应收账单', value: 'BILL' }, { label: '账单导出', value: 'EXPORT' }, { label: '余额核销', value: 'BALANCE_WRITEOFF' }], width: '130px' },
{ label: '操作类型', prop: 'operationType', component: 'input', width: '140px' }, { label: '结果', prop: 'result', component: 'select', options: [{ label: '成功', value: 'SUCCESS' }, { label: '失败', value: 'FAILED' }], width: '120px' }
]
const columns = [
@@ -20,8 +20,8 @@ const columns = [
{ label: '结果', prop: 'result', width: 90, slot: 'result' }, { label: '时间', prop: 'createTime', width: 170, parseTime: true }
]
const objectTypeLabels = { BILL: '应收账单', EXPORT: '账单导出', RECEIPT: '应收收款', WRITEOFF: '收款核销', BALANCE_WRITEOFF: '余额核销' }
const operationLabels = { CONFIRM: '确认账单', VOID: '作废账单', EXPORT: '导出账单', RECEIPT: '登记收款', CANCEL_RECEIPT: '取消收款', WRITEOFF: '收款核销', CANCEL_WRITEOFF: '取消核销', BALANCE_WRITEOFF: '余额核销', CANCEL_BALANCE_WRITEOFF: '取消余额核销' }
const objectTypeLabels = { BILL: '应收账单', EXPORT: '账单导出', BALANCE_WRITEOFF: '余额核销' }
const operationLabels = { CONFIRM: '确认账单', VOID: '作废账单', EXPORT: '导出账单', BALANCE_WRITEOFF: '余额核销', CANCEL_BALANCE_WRITEOFF: '取消余额核销' }
const statusLabels = { DRAFT: '草稿', CONFIRMED: '已确认', PARTIAL_WRITTEN_OFF: '部分核销', WRITTEN_OFF: '已核销', VOID: '已作废', REGISTERED: '已登记', CANCELLED: '已取消', EFFECTIVE: '有效' }
function objectTypeText(value) { return objectTypeLabels[value] || value || '-' }
function operationText(value) { return operationLabels[value] || value || '-' }