Compare commits
5 Commits
4852b3ffb1
...
9d590e03ec
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d590e03ec | |||
| 5641c23e39 | |||
| abfb425b4f | |||
| b52ab2eb40 | |||
| 137488dde6 |
|
|
@ -122,6 +122,11 @@ public class EduContractBo extends BaseEntity {
|
|||
*/
|
||||
private BigDecimal installmentThird;
|
||||
|
||||
/**
|
||||
* 分期数(2 或 3), isInstallment=Y 时必填, 用于生成缴费计划
|
||||
*/
|
||||
private Integer installmentCount;
|
||||
|
||||
/**
|
||||
* 实际总收费
|
||||
*/
|
||||
|
|
@ -133,10 +138,15 @@ public class EduContractBo extends BaseEntity {
|
|||
private Date settlementDate;
|
||||
|
||||
/**
|
||||
* 签约负责人
|
||||
* 签约负责人(姓名, 旧字段保留兼容)
|
||||
*/
|
||||
private String signPerson;
|
||||
|
||||
/**
|
||||
* 签约负责人ID(edu_staff.id)
|
||||
*/
|
||||
private Long signUserId;
|
||||
|
||||
/**
|
||||
* 合同是否已收回 Y/N
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package org.dromara.edu.application.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 缴费计划修改业务对象
|
||||
* <p>仅待收态可改应收金额/应付日期</p>
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@Data
|
||||
public class EduPaymentPlanBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 计划ID
|
||||
*/
|
||||
@NotNull(message = "计划ID不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 本期应收金额
|
||||
*/
|
||||
private BigDecimal planAmount;
|
||||
|
||||
/**
|
||||
* 本期应付日期
|
||||
*/
|
||||
private Date planDate;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.dromara.edu.application.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 缴费计划收款确认业务对象
|
||||
* <p>把一期计划从"待收"推进到"已收", 联动合同账本</p>
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@Data
|
||||
public class PaymentReceiveBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 计划ID
|
||||
*/
|
||||
@NotNull(message = "计划ID不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 实收金额
|
||||
*/
|
||||
@NotNull(message = "实收金额不能为空")
|
||||
private BigDecimal paidAmount;
|
||||
|
||||
/**
|
||||
* 支付方式 CASH/WECHAT/ALIPAY/BANK/CARD
|
||||
*/
|
||||
private String paymentMethod;
|
||||
|
||||
/**
|
||||
* 收款日期(留空取当前日期)
|
||||
*/
|
||||
private Date paidDate;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.dromara.edu.application.service.contract;
|
||||
|
||||
import org.dromara.edu.application.dto.EduPaymentPlanBo;
|
||||
import org.dromara.edu.application.dto.PaymentReceiveBo;
|
||||
import org.dromara.edu.interfaces.vo.EduContractPaymentPlanVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同缴费计划 业务层
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
public interface IEduPaymentPlanService {
|
||||
|
||||
/**
|
||||
* 生成缴费计划(合同创建/修改时调用, 幂等: 先删后建)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param installmentCount 分期数 2 或 3
|
||||
*/
|
||||
void generatePaymentPlan(Long contractId, int installmentCount);
|
||||
|
||||
/**
|
||||
* 查询合同的计划列表(按 installment_no 升序)
|
||||
*/
|
||||
List<EduContractPaymentPlanVo> selectPlanList(Long contractId);
|
||||
|
||||
/**
|
||||
* 修改单期计划(仅待收态可改应收金额/应付日期)
|
||||
*/
|
||||
int updatePlan(EduPaymentPlanBo bo);
|
||||
|
||||
/**
|
||||
* 收款确认: 联动合同账本(累加 paidAmount, 重算 paymentStatus, 写 PAY 流水)
|
||||
*/
|
||||
int receivePayment(PaymentReceiveBo bo);
|
||||
|
||||
/**
|
||||
* 批量作废未收期次(合同退费/作废时联动调用)
|
||||
* <p>仅把"待收"期置"已作废", "已收"期保留不动</p>
|
||||
*/
|
||||
int voidPendingPlans(Long contractId);
|
||||
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ import org.dromara.edu.infrastructure.persistence.mapper.EduContractMapper;
|
|||
import org.dromara.edu.infrastructure.persistence.mapper.EduContractPackageMapper;
|
||||
import org.dromara.edu.infrastructure.persistence.mapper.EduHoursLogMapper;
|
||||
import org.dromara.edu.interfaces.vo.EduContractVo;
|
||||
import org.dromara.edu.interfaces.vo.EduStaffVo;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -58,6 +59,8 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
private final EduHoursLogMapper hoursLogMapper;
|
||||
private final org.dromara.edu.application.service.service.IEduMilestoneService milestoneService;
|
||||
private final org.dromara.edu.infrastructure.persistence.mapper.EduContractMilestoneMapper contractMilestoneMapper;
|
||||
private final org.dromara.edu.application.service.contract.IEduPaymentPlanService paymentPlanService;
|
||||
private final org.dromara.edu.infrastructure.persistence.mapper.EduStaffMapper staffMapper;
|
||||
|
||||
/**
|
||||
* 分页查询合同列表
|
||||
|
|
@ -65,6 +68,8 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
@Override
|
||||
public TableDataInfo<EduContractVo> selectPageContractList(EduContractBo contract, PageQuery pageQuery) {
|
||||
Page<EduContractVo> page = contractMapper.selectPageContractList(pageQuery.build(), this.buildQueryWrapper(contract));
|
||||
// 翻译顾问/管家/签约负责人姓名
|
||||
fillStaffNames(page.getRecords());
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +91,10 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
.orderByDesc(EduContractLog::getLogTime)));
|
||||
// 回填里程碑(升学服务合同)
|
||||
vo.setMilestones(milestoneService.selectContractMilestoneList(id));
|
||||
// 回填缴费计划(分期合同)
|
||||
vo.setPaymentPlans(paymentPlanService.selectPlanList(id));
|
||||
// 翻译顾问/管家/签约负责人姓名
|
||||
fillStaffNames(java.util.Collections.singletonList(vo));
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +151,10 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
// 记流水
|
||||
recordLog(entity.getId(), EduConstants.ContractAction.CREATE,
|
||||
totalAmount, BigDecimal.ZERO, null, "创建合同");
|
||||
// 分期合同: 生成缴费计划(分期数取自入参 BO, 实体无此字段)
|
||||
if ("Y".equals(contract.getIsInstallment()) && contract.getInstallmentCount() != null) {
|
||||
paymentPlanService.generatePaymentPlan(entity.getId(), contract.getInstallmentCount());
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +196,10 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
entity.setTotalHours(totalHours);
|
||||
entity.setRemainHours(totalHours.subtract(nz(exist.getUsedHours())));
|
||||
entity.setGiftHours(giftHours);
|
||||
// 分期合同: 重建缴费计划(幂等先删后建, 分期数取自入参 BO)
|
||||
if ("Y".equals(contract.getIsInstallment()) && contract.getInstallmentCount() != null) {
|
||||
paymentPlanService.generatePaymentPlan(contract.getId(), contract.getInstallmentCount());
|
||||
}
|
||||
return contractMapper.updateById(entity);
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +221,8 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
update.setStatus(EduConstants.ContractStatus.VOIDED);
|
||||
recordLog(id, EduConstants.ContractAction.VOID,
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, null, "作废合同");
|
||||
// 联动作废未收期缴费计划(已收期保留为历史)
|
||||
paymentPlanService.voidPendingPlans(id);
|
||||
return contractMapper.updateById(update);
|
||||
}
|
||||
|
||||
|
|
@ -343,6 +362,8 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
bo.getRefundAmount().negate(), paidRemainHours.negate(), null,
|
||||
String.format("合同退费%s元, 剩余付费课时%s退清, 赠送课时%s不退",
|
||||
bo.getRefundAmount(), paidRemainHours, giftHours));
|
||||
// 联动作废未收期缴费计划(已收期保留为历史收款记录)
|
||||
paymentPlanService.voidPendingPlans(bo.getId());
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
|
@ -582,6 +603,32 @@ public class EduContractServiceImpl implements IEduContractService {
|
|||
return v == null ? nz(def) : v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量翻译合同上的人员ID为姓名(顾问/管家/签约负责人)
|
||||
* <p>收集所有非空 staff id, 一次 selectBatchIds 查出, 回填到 VO 的 *Name 字段</p>
|
||||
*/
|
||||
private void fillStaffNames(java.util.List<EduContractVo> voList) {
|
||||
if (voList == null || voList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
java.util.Set<Long> staffIds = new java.util.HashSet<>();
|
||||
for (EduContractVo vo : voList) {
|
||||
if (vo.getSalesId() != null) staffIds.add(vo.getSalesId());
|
||||
if (vo.getHousekeeperId() != null) staffIds.add(vo.getHousekeeperId());
|
||||
if (vo.getSignUserId() != null) staffIds.add(vo.getSignUserId());
|
||||
}
|
||||
if (staffIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
java.util.Map<Long, String> id2name = staffMapper.selectVoByIds(staffIds).stream()
|
||||
.collect(java.util.stream.Collectors.toMap(EduStaffVo::getId, EduStaffVo::getName, (a, b) -> a));
|
||||
for (EduContractVo vo : voList) {
|
||||
vo.setSalesName(id2name.get(vo.getSalesId()));
|
||||
vo.setHousekeeperName(id2name.get(vo.getHousekeeperId()));
|
||||
vo.setSignUserName(id2name.get(vo.getSignUserId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造查询条件
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
package org.dromara.edu.application.service.contract.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.edu.application.dto.EduPaymentPlanBo;
|
||||
import org.dromara.edu.application.dto.PaymentReceiveBo;
|
||||
import org.dromara.edu.application.service.contract.IEduPaymentPlanService;
|
||||
import org.dromara.edu.common.constant.EduConstants;
|
||||
import org.dromara.edu.common.exception.EduBusinessException;
|
||||
import org.dromara.edu.domain.model.contract.EduContract;
|
||||
import org.dromara.edu.domain.model.contract.EduContractLog;
|
||||
import org.dromara.edu.domain.model.contract.EduContractPaymentPlan;
|
||||
import org.dromara.edu.infrastructure.persistence.mapper.EduContractLogMapper;
|
||||
import org.dromara.edu.infrastructure.persistence.mapper.EduContractMapper;
|
||||
import org.dromara.edu.infrastructure.persistence.mapper.EduContractPaymentPlanMapper;
|
||||
import org.dromara.edu.interfaces.vo.EduContractPaymentPlanVo;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同缴费计划 业务实现
|
||||
* <p>分期合同的应收节点生成 + 收款联动合同账本 + 退费/作废联动作废</p>
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class EduPaymentPlanServiceImpl implements IEduPaymentPlanService {
|
||||
|
||||
private final EduContractPaymentPlanMapper paymentPlanMapper;
|
||||
private final EduContractMapper contractMapper;
|
||||
private final EduContractLogMapper contractLogMapper;
|
||||
|
||||
/**
|
||||
* 生成缴费计划(幂等: 先删后建)
|
||||
* <p>金额: 首期吸收尾差, 保证各期之和精确等于 totalAmount;
|
||||
* 日期: 首期=签约日, 第N期=签约日+(N-1)*30天</p>
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void generatePaymentPlan(Long contractId, int installmentCount) {
|
||||
EduContract contract = contractMapper.selectById(contractId);
|
||||
if (contract == null) {
|
||||
throw new EduBusinessException("CONTRACT_NOT_FOUND", "合同不存在");
|
||||
}
|
||||
if (installmentCount != 2 && installmentCount != 3) {
|
||||
throw new EduBusinessException("INSTALLMENT_COUNT_INVALID", "分期数必须为 2 或 3");
|
||||
}
|
||||
// 幂等: 先删后建
|
||||
paymentPlanMapper.delete(new LambdaQueryWrapper<EduContractPaymentPlan>()
|
||||
.eq(EduContractPaymentPlan::getContractId, contractId));
|
||||
|
||||
BigDecimal totalAmount = nz(contract.getTotalAmount());
|
||||
// base = totalAmount / N, 第2~N期取 base, 第1期吸收尾差
|
||||
BigDecimal base = totalAmount.divide(BigDecimal.valueOf(installmentCount), 2, RoundingMode.HALF_UP);
|
||||
BigDecimal firstAmount = totalAmount.subtract(base.multiply(BigDecimal.valueOf(installmentCount - 1)));
|
||||
|
||||
LocalDate signLocal = toLocalDate(contract.getSignDate());
|
||||
for (int i = 1; i <= installmentCount; i++) {
|
||||
EduContractPaymentPlan plan = new EduContractPaymentPlan();
|
||||
plan.setContractId(contractId);
|
||||
plan.setInstallmentNo(i);
|
||||
plan.setPlanAmount(i == 1 ? firstAmount : base);
|
||||
plan.setPlanDate(toDate(signLocal.plus(30L * (i - 1), ChronoUnit.DAYS)));
|
||||
plan.setPaidAmount(BigDecimal.ZERO);
|
||||
plan.setStatus(EduConstants.PaymentPlanStatus.PENDING);
|
||||
paymentPlanMapper.insert(plan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询合同的计划列表(按期次升序)
|
||||
*/
|
||||
@Override
|
||||
public List<EduContractPaymentPlanVo> selectPlanList(Long contractId) {
|
||||
return paymentPlanMapper.selectVoList(new LambdaQueryWrapper<EduContractPaymentPlan>()
|
||||
.eq(EduContractPaymentPlan::getContractId, contractId)
|
||||
.orderByAsc(EduContractPaymentPlan::getInstallmentNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改单期计划(仅待收态可改应收金额/应付日期)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updatePlan(EduPaymentPlanBo bo) {
|
||||
EduContractPaymentPlan exist = paymentPlanMapper.selectById(bo.getId());
|
||||
if (exist == null) {
|
||||
throw new EduBusinessException("PLAN_NOT_FOUND", "缴费计划不存在");
|
||||
}
|
||||
if (!EduConstants.PaymentPlanStatus.PENDING.equals(exist.getStatus())) {
|
||||
throw new EduBusinessException("PLAN_STATUS_NOT_EDITABLE", "仅待收态计划可修改");
|
||||
}
|
||||
EduContractPaymentPlan update = new EduContractPaymentPlan();
|
||||
update.setId(bo.getId());
|
||||
if (bo.getPlanAmount() != null) {
|
||||
update.setPlanAmount(bo.getPlanAmount());
|
||||
}
|
||||
if (bo.getPlanDate() != null) {
|
||||
update.setPlanDate(bo.getPlanDate());
|
||||
}
|
||||
update.setRemark(bo.getRemark());
|
||||
return paymentPlanMapper.updateById(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收款确认: 待收 → 已收, 联动合同账本
|
||||
* <p>1. 置该期已收, 记 paidAmount/paidDate/paymentMethod;
|
||||
* 2. 重算合同 paidAmount = SUM(已收期 paidAmount);
|
||||
* 3. 重算 paymentStatus: 全额=已支付(2), 否则部分支付(1);
|
||||
* 4. 写合同流水(action=RECEIVE_PAYMENT).</p>
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int receivePayment(PaymentReceiveBo bo) {
|
||||
EduContractPaymentPlan plan = paymentPlanMapper.selectById(bo.getId());
|
||||
if (plan == null) {
|
||||
throw new EduBusinessException("PLAN_NOT_FOUND", "缴费计划不存在");
|
||||
}
|
||||
if (!EduConstants.PaymentPlanStatus.PENDING.equals(plan.getStatus())) {
|
||||
throw new EduBusinessException("PLAN_STATUS_NOT_RECEIVABLE", "仅待收态计划可收款");
|
||||
}
|
||||
// 1. 更新该期
|
||||
EduContractPaymentPlan update = new EduContractPaymentPlan();
|
||||
update.setId(plan.getId());
|
||||
update.setStatus(EduConstants.PaymentPlanStatus.RECEIVED);
|
||||
update.setPaidAmount(bo.getPaidAmount());
|
||||
update.setPaymentMethod(bo.getPaymentMethod());
|
||||
update.setPaidDate(bo.getPaidDate() != null ? bo.getPaidDate() : new Date());
|
||||
update.setRemark(bo.getRemark());
|
||||
int rows = paymentPlanMapper.updateById(update);
|
||||
|
||||
// 2. 重算合同实收: SUM(已收期 paidAmount), 本期刚置为已收, 库里已为最新值
|
||||
List<EduContractPaymentPlan> all = paymentPlanMapper.selectList(
|
||||
new LambdaQueryWrapper<EduContractPaymentPlan>()
|
||||
.eq(EduContractPaymentPlan::getContractId, plan.getContractId()));
|
||||
BigDecimal receivedSum = all.stream()
|
||||
.filter(p -> EduConstants.PaymentPlanStatus.RECEIVED.equals(p.getStatus()))
|
||||
.map(p -> nz(p.getPaidAmount()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
// 3. 重算 paymentStatus 并回写合同
|
||||
EduContract contract = contractMapper.selectById(plan.getContractId());
|
||||
BigDecimal total = nz(contract.getTotalAmount());
|
||||
String paymentStatus = receivedSum.compareTo(total) >= 0 ? "2" : "1";
|
||||
EduContract contractUpd = new EduContract();
|
||||
contractUpd.setId(contract.getId());
|
||||
contractUpd.setPaidAmount(receivedSum);
|
||||
contractUpd.setPaymentStatus(paymentStatus);
|
||||
contractMapper.updateById(contractUpd);
|
||||
|
||||
// 4. 写合同流水
|
||||
EduContractLog log = new EduContractLog();
|
||||
log.setContractId(plan.getContractId());
|
||||
log.setAction(EduConstants.ContractAction.RECEIVE_PAYMENT);
|
||||
log.setAmount(bo.getPaidAmount());
|
||||
log.setOperatorId(LoginHelper.getUserId());
|
||||
log.setOperatorName(LoginHelper.getUsername());
|
||||
log.setReason("第" + plan.getInstallmentNo() + "期收款, 金额 " + bo.getPaidAmount()
|
||||
+ (bo.getPaymentMethod() != null ? ", 方式 " + bo.getPaymentMethod() : ""));
|
||||
log.setLogTime(new Date());
|
||||
contractLogMapper.insert(log);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量作废未收期次(合同退费/作废联动)
|
||||
* <p>仅把"待收"期置"已作废", "已收"期保留作为历史收款记录</p>
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int voidPendingPlans(Long contractId) {
|
||||
List<EduContractPaymentPlan> pending = paymentPlanMapper.selectList(
|
||||
new LambdaQueryWrapper<EduContractPaymentPlan>()
|
||||
.eq(EduContractPaymentPlan::getContractId, contractId)
|
||||
.eq(EduContractPaymentPlan::getStatus, EduConstants.PaymentPlanStatus.PENDING));
|
||||
int rows = 0;
|
||||
for (EduContractPaymentPlan p : pending) {
|
||||
EduContractPaymentPlan upd = new EduContractPaymentPlan();
|
||||
upd.setId(p.getId());
|
||||
upd.setStatus(EduConstants.PaymentPlanStatus.VOIDED);
|
||||
rows += paymentPlanMapper.updateById(upd);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 私有辅助
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private BigDecimal nz(BigDecimal v) {
|
||||
return v == null ? BigDecimal.ZERO : v;
|
||||
}
|
||||
|
||||
private LocalDate toLocalDate(Date date) {
|
||||
return date == null ? LocalDate.now() : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
private Date toDate(LocalDate localDate) {
|
||||
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -74,6 +74,17 @@ public final class EduConstants {
|
|||
private MilestoneStatus() {}
|
||||
}
|
||||
|
||||
/** 缴费计划状态 */
|
||||
public static final class PaymentPlanStatus {
|
||||
/** 待收 */
|
||||
public static final String PENDING = "0";
|
||||
/** 已收 */
|
||||
public static final String RECEIVED = "1";
|
||||
/** 已作废 */
|
||||
public static final String VOIDED = "4";
|
||||
private PaymentPlanStatus() {}
|
||||
}
|
||||
|
||||
/** 合同状态 */
|
||||
public static final class ContractStatus {
|
||||
public static final String DRAFT = "0";
|
||||
|
|
@ -97,6 +108,8 @@ public final class EduConstants {
|
|||
public static final String ADDON = "ADDON";
|
||||
public static final String EXPIRE = "EXPIRE";
|
||||
public static final String VOID = "VOID";
|
||||
/** 缴费计划收款确认 */
|
||||
public static final String RECEIVE_PAYMENT = "RECEIVE_PAYMENT";
|
||||
private ContractAction() {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,10 +158,15 @@ public class EduContract extends TenantEntity {
|
|||
private Date settlementDate;
|
||||
|
||||
/**
|
||||
* 签约负责人
|
||||
* 签约负责人(姓名, 旧字段保留兼容)
|
||||
*/
|
||||
private String signPerson;
|
||||
|
||||
/**
|
||||
* 签约负责人ID(edu_staff.id)
|
||||
*/
|
||||
private Long signUserId;
|
||||
|
||||
/**
|
||||
* 合同是否已收回 Y/N
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package org.dromara.edu.domain.model.contract;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 合同缴费计划 edu_contract_payment_plan
|
||||
* <p>分期合同生成的应收节点: 每期应收金额/应付日期/收款状态, 收款确认联动合同账本</p>
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("edu_contract_payment_plan")
|
||||
public class EduContractPaymentPlan extends TenantEntity {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private Long contractId;
|
||||
|
||||
/**
|
||||
* 期次号 1/2/3
|
||||
*/
|
||||
private Integer installmentNo;
|
||||
|
||||
/**
|
||||
* 本期应收金额
|
||||
*/
|
||||
private BigDecimal planAmount;
|
||||
|
||||
/**
|
||||
* 本期应付日期
|
||||
*/
|
||||
private Date planDate;
|
||||
|
||||
/**
|
||||
* 本期实收金额
|
||||
*/
|
||||
private BigDecimal paidAmount;
|
||||
|
||||
/**
|
||||
* 实际收款日期
|
||||
*/
|
||||
private Date paidDate;
|
||||
|
||||
/**
|
||||
* 本期支付方式 CASH/WECHAT/ALIPAY/BANK/CARD
|
||||
*/
|
||||
private String paymentMethod;
|
||||
|
||||
/**
|
||||
* 状态 0=待收 1=已收 4=已作废
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 删除标志 0=存在 1=删除
|
||||
*/
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.dromara.edu.infrastructure.persistence.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.edu.domain.model.contract.EduContractPaymentPlan;
|
||||
import org.dromara.edu.interfaces.vo.EduContractPaymentPlanVo;
|
||||
|
||||
/**
|
||||
* 合同缴费计划 数据层
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
public interface EduContractPaymentPlanMapper extends BaseMapperPlus<EduContractPaymentPlan, EduContractPaymentPlanVo> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package org.dromara.edu.interfaces.controller.contract;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.edu.application.dto.EduPaymentPlanBo;
|
||||
import org.dromara.edu.application.dto.PaymentReceiveBo;
|
||||
import org.dromara.edu.application.service.contract.IEduPaymentPlanService;
|
||||
import org.dromara.edu.interfaces.vo.EduContractPaymentPlanVo;
|
||||
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.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同缴费计划管理
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/edu/contract/payment-plan")
|
||||
public class EduPaymentPlanController extends BaseController {
|
||||
|
||||
private final IEduPaymentPlanService paymentPlanService;
|
||||
|
||||
/**
|
||||
* 查询某合同的缴费计划列表
|
||||
*/
|
||||
@SaCheckPermission("edu:contract:query")
|
||||
@GetMapping("/list/{contractId}")
|
||||
public R<List<EduContractPaymentPlanVo>> list(@PathVariable Long contractId) {
|
||||
return R.ok(paymentPlanService.selectPlanList(contractId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改单期计划(仅待收态可改应收金额/应付日期)
|
||||
*/
|
||||
@SaCheckPermission("edu:contract:edit")
|
||||
@Log(title = "缴费计划", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping
|
||||
public R<Void> edit(@Validated @RequestBody EduPaymentPlanBo bo) {
|
||||
return toAjax(paymentPlanService.updatePlan(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 收款确认(联动合同账本)
|
||||
*/
|
||||
@SaCheckPermission("edu:contract:activate")
|
||||
@Log(title = "缴费计划", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/receive")
|
||||
public R<Void> receive(@Validated @RequestBody PaymentReceiveBo bo) {
|
||||
return toAjax(paymentPlanService.receivePayment(bo));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package org.dromara.edu.interfaces.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.edu.domain.model.contract.EduContractPaymentPlan;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 合同缴费计划视图对象 edu_contract_payment_plan
|
||||
*
|
||||
* @author FIGS
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = EduContractPaymentPlan.class)
|
||||
public class EduContractPaymentPlanVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private Long contractId;
|
||||
|
||||
/**
|
||||
* 期次号 1/2/3
|
||||
*/
|
||||
private Integer installmentNo;
|
||||
|
||||
/**
|
||||
* 本期应收金额
|
||||
*/
|
||||
private BigDecimal planAmount;
|
||||
|
||||
/**
|
||||
* 本期应付日期
|
||||
*/
|
||||
private Date planDate;
|
||||
|
||||
/**
|
||||
* 本期实收金额
|
||||
*/
|
||||
private BigDecimal paidAmount;
|
||||
|
||||
/**
|
||||
* 实际收款日期
|
||||
*/
|
||||
private Date paidDate;
|
||||
|
||||
/**
|
||||
* 本期支付方式 CASH/WECHAT/ALIPAY/BANK/CARD
|
||||
*/
|
||||
private String paymentMethod;
|
||||
|
||||
/**
|
||||
* 状态 0=待收 1=已收 4=已作废
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
|
|
@ -51,6 +51,8 @@ public class EduContractVo implements Serializable {
|
|||
private BigDecimal actualTotalReceived;
|
||||
private Date settlementDate;
|
||||
private String signPerson;
|
||||
/** 签约负责人ID(edu_staff.id) */
|
||||
private Long signUserId;
|
||||
private String contractRecovered;
|
||||
private String remittanceName;
|
||||
private String needInvoice;
|
||||
|
|
@ -58,6 +60,12 @@ public class EduContractVo implements Serializable {
|
|||
private String status;
|
||||
private Long salesId;
|
||||
private Long housekeeperId;
|
||||
/** 课程顾问姓名(查询时翻译填充) */
|
||||
private String salesName;
|
||||
/** 管家姓名(查询时翻译填充) */
|
||||
private String housekeeperName;
|
||||
/** 签约负责人姓名(查询时翻译填充) */
|
||||
private String signUserName;
|
||||
private String signLocation;
|
||||
private String signImageUrl;
|
||||
private String description;
|
||||
|
|
@ -101,4 +109,9 @@ public class EduContractVo implements Serializable {
|
|||
*/
|
||||
private List<EduContractMilestoneVo> milestones;
|
||||
|
||||
/**
|
||||
* 缴费计划列表(分期合同详情)
|
||||
*/
|
||||
private List<EduContractPaymentPlanVo> paymentPlans;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,8 +296,9 @@ CREATE TABLE `edu_contract` (
|
|||
`payment_status` char(1) DEFAULT '0' COMMENT '支付状态 0=未支付 1=部分支付 2=已支付 3=已退费',
|
||||
`status` char(1) DEFAULT '0' COMMENT '合同状态 0=草稿 1=待生效 2=执行中 3=已结清 4=已退费 5=已转课 9=作废',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志 0=存在 1=删除',
|
||||
`sales_id` bigint DEFAULT NULL COMMENT '课程顾问ID',
|
||||
`housekeeper_id` bigint DEFAULT NULL COMMENT '管家ID',
|
||||
`sales_id` bigint DEFAULT NULL COMMENT '课程顾问ID(edu_staff.id)',
|
||||
`housekeeper_id` bigint DEFAULT NULL COMMENT '管家ID(edu_staff.id)',
|
||||
`sign_user_id` bigint DEFAULT NULL COMMENT '签约负责人ID(edu_staff.id)',
|
||||
`sign_location` varchar(255) DEFAULT NULL COMMENT '签约地点',
|
||||
`sign_image_url` varchar(1000) DEFAULT NULL COMMENT '合同扫描件URL',
|
||||
`description` varchar(2000) DEFAULT NULL COMMENT '合同说明',
|
||||
|
|
@ -401,6 +402,31 @@ CREATE TABLE `edu_contract_log` (
|
|||
KEY `idx_tenant_action` (`tenant_id`, `action`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同操作流水';
|
||||
|
||||
-- 5.5 合同缴费计划(分期付款时生成, 对标 edu_contract_milestone)
|
||||
DROP TABLE IF EXISTS `edu_contract_payment_plan`;
|
||||
CREATE TABLE `edu_contract_payment_plan` (
|
||||
`id` bigint NOT NULL COMMENT '主键',
|
||||
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID',
|
||||
`contract_id` bigint NOT NULL COMMENT '合同ID',
|
||||
`installment_no` int NOT NULL COMMENT '期次号 1/2/3',
|
||||
`plan_amount` decimal(14,2) NOT NULL COMMENT '本期应收金额',
|
||||
`plan_date` date NOT NULL COMMENT '本期应付日期',
|
||||
`paid_amount` decimal(14,2) NOT NULL DEFAULT 0.00 COMMENT '本期实收金额',
|
||||
`paid_date` date DEFAULT NULL COMMENT '实际收款日期',
|
||||
`payment_method` varchar(32) DEFAULT NULL COMMENT '本期支付方式 CASH/WECHAT/ALIPAY/BANK/CARD',
|
||||
`status` char(1) DEFAULT '0' COMMENT '状态 0=待收 1=已收 4=已作废',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志 0=存在 1=删除',
|
||||
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_tenant_contract_no` (`tenant_id`, `contract_id`, `installment_no`) USING BTREE,
|
||||
KEY `idx_tenant_contract` (`tenant_id`, `contract_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同缴费计划(分期)';
|
||||
|
||||
-- =====================================================================
|
||||
-- 六、排课消课上下文 schedule
|
||||
-- =====================================================================
|
||||
|
|
@ -604,7 +630,10 @@ INSERT INTO `edu_dict`(`id`,`tenant_id`,`dict_type`,`dict_label`,`dict_value`,`s
|
|||
(104,'000000','hours_biz_type','转入','TRANSFER_IN',5,'N','0'),
|
||||
(105,'000000','hours_biz_type','赠送','GIFT',6,'N','0'),
|
||||
(106,'000000','hours_biz_type','过期','EXPIRE',7,'N','0'),
|
||||
(107,'000000','hours_biz_type','手动调整','ADJUST',8,'N','0');
|
||||
(107,'000000','hours_biz_type','手动调整','ADJUST',8,'N','0'),
|
||||
(110,'000000','payment_plan_status','待收','0',1,'Y','0'),
|
||||
(111,'000000','payment_plan_status','已收','1',2,'N','0'),
|
||||
(112,'000000','payment_plan_status','已作废','4',3,'N','0');
|
||||
|
||||
-- =====================================================================
|
||||
-- 九、菜单与权限(注入 ruoyi 的 sys_menu)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user