去掉应收登记收款功能逻辑
This commit is contained in:
@@ -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;
|
||||
}
|
||||
-35
@@ -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);
|
||||
}
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
-275
@@ -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 >= #{receiptDateStart}</if>
|
||||
<if test="receiptDateEnd != null">and r.receipt_date <= #{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>
|
||||
Reference in New Issue
Block a user