feat(finance): 添加付款退款功能并优化应付单界面

- 在付款管理页面增加退款按钮及申请退款功能
- 新增退款状态枚举及相关的业务逻辑处理
- 优化应付单列表展示,移除部分冗余字段显示
- 调整付款明细服务,支持批量插入和退款明细生成
- 修改付款计划查询逻辑,增强数据准确性
- 完善退款接口,支持通过ID申请退款操作
dev_1.0.0
chenhao 2025-12-10 18:46:24 +08:00
parent d3c4776bab
commit a9142c0e1b
13 changed files with 208 additions and 86 deletions

View File

@ -69,3 +69,11 @@ export function applyPaymentApi(data) {
})
}
// 申请退款
export function applyRefund(id) {
return request({
url: '/finance/payment/applyRefund/'+id,
method: 'get'
})
}

View File

@ -33,22 +33,22 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="合同编号" prop="orderCode">
<el-input
v-model="queryParams.orderCode"
placeholder="请输入合同编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="出入库单号" prop="inventoryCode">
<el-input
v-model="queryParams.inventoryCode"
placeholder="请输入出入库单号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<!-- <el-form-item label="合同编号" prop="orderCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.orderCode"-->
<!-- placeholder="请输入合同编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="出入库单号" prop="inventoryCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.inventoryCode"-->
<!-- placeholder="请输入出入库单号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="产品类型" prop="productType">
<el-select v-model="queryParams.productType" placeholder="请选择产品类型" clearable>
<el-option
@ -69,17 +69,27 @@
/>
</el-select>
</el-form-item>
<el-form-item label="生成时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
<el-form-item label="收票状态" prop="paymentStatus">
<el-select v-model="queryParams.paymentStatus" placeholder="请选择付款状态" clearable>
<el-option
v-for="dict in dict.type.payment_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="生成时间">-->
<!-- <el-date-picker-->
<!-- v-model="dateRange"-->
<!-- style="width: 240px"-->
<!-- value-format="yyyy-MM-dd HH:mm:ss"-->
<!-- type="daterange"-->
<!-- range-separator="-"-->
<!-- start-placeholder="开始日期"-->
<!-- end-placeholder="结束日期"-->
<!-- ></el-date-picker>-->
<!-- </el-form-item>-->
<el-form-item label="预计付款时间">
<el-date-picker
v-model="estimatedPaymentDateRange"
@ -117,48 +127,48 @@
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" />
<el-table-column label="项目名称" align="center" prop="projectName" width="150" />
<el-table-column label="应付单编号" align="center" prop="payableBillCode" width="150" />
<el-table-column label="生成时间" align="center" prop="createTime" width="180"/>
<!-- <el-table-column label="生成时间" align="center" prop="createTime" width="180"/>-->
<el-table-column label="预计付款时间" align="center" prop="estimatedPaymentTime" width="180"/>
<el-table-column label="预计付款金额" align="center" prop="totalPriceWithTax" width="120" />
<el-table-column label="该制造商是否有预付单" align="center" prop="hasAdvancePayment" width="150" />
<el-table-column label="预付金额" align="center" prop="advancePaymentAmount" width="120" />
<!-- <el-table-column label="预付金额" align="center" prop="advancePaymentAmount" width="120" />-->
<el-table-column label="制造商名称" align="center" prop="vendorName" width="150" />
<el-table-column label="合同编号" align="center" prop="orderCode" width="150" />
<el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150" />
<!-- <el-table-column label="合同编号" align="center" prop="orderCode" width="150" />-->
<!-- <el-table-column label="出入库单号" align="center" prop="inventoryCode" width="150" />-->
<el-table-column label="产品类型" align="center" prop="productType" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.product_type" :value="scope.row.productType"/>
</template>
</el-table-column>
<el-table-column label="含税总价" align="center" prop="totalPriceWithTax" width="120" />
<el-table-column label="未税总价" align="center" prop="totalPriceWithoutTax" width="120" />
<el-table-column label="税额" align="center" prop="taxAmount" width="120" />
<el-table-column label="付款状态" align="center" prop="paymentStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column label="生成付款单" align="center" width="120">
<template slot-scope="scope">
{{scope.row.unpaidAmount===scope.row.totalPriceWithTax?'未生成':scope.row.unpaidAmount===0?'全部生成':'部分生成'}}
</template>
</el-table-column>
<el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>
</template>
</el-table-column>
<el-table-column label="生成收票单" align="center" width="120">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
@click="handleGenerateInvoice(scope.row)"
>生成收票单</el-button>
</template>
</el-table-column>
<!-- <el-table-column label="未税总价" align="center" prop="totalPriceWithoutTax" width="120" />-->
<!-- <el-table-column label="税额" align="center" prop="taxAmount" width="120" />-->
<!-- <el-table-column label="付款状态" align="center" prop="paymentStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{ scope.row.unpaidAmount === scope.row.totalPriceWithTax ? '未生成' : scope.row.unpaidAmount === 0 ? '全部生成' : '部分生成' }}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="生成付款单" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- {{scope.row.unpaidAmount===scope.row.totalPriceWithTax?'未生成':scope.row.unpaidAmount===0?'全部生成':'部分生成'}}-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <dict-tag :options="dict.type.invoice_status" :value="scope.row.invoiceStatus"/>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column label="生成收票单" align="center" width="120">-->
<!-- <template slot-scope="scope">-->
<!-- <el-button-->
<!-- size="mini"-->
<!-- type="text"-->
<!-- @click="handleGenerateInvoice(scope.row)"-->
<!-- >生成收票单</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column label="未付款金额" align="center" prop="unpaidAmount" width="120" />
<el-table-column label="付款中金额" align="center" prop="payingAmount" width="120" />
<!-- <el-table-column label="付款中金额" align="center" prop="payingAmount" width="120" />-->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160" fixed="right">
<template slot-scope="scope">
<el-button

View File

@ -194,6 +194,13 @@
v-show="scope.row.approveStatus=='1'"
@click="handleReturn(scope.row)"
>退回</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-refresh-right"
v-if="scope.row.paymentStatus === '2' && scope.row.refundStatus !== 'REFUND_APPLIED' && scope.row.paymentBillType !== 'REFUND'"
@click="handleApplyRefund(scope.row)"
>退款</el-button>
</template>
</el-table-column>
</el-table>
@ -250,7 +257,7 @@
</template>
<script>
import { listPayment, getPayment, returnPayment, addPayment, applyPaymentApi } from "@/api/finance/payment";
import { listPayment, getPayment, returnPayment, addPayment, applyPaymentApi, applyRefund } from "@/api/finance/payment";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
@ -396,7 +403,16 @@ export default {
this.getList();
this.$modal.msgSuccess("退回成功");
}).catch(() => {});
}
},
/** 申请退款按钮操作 */
handleApplyRefund(row) {
this.$modal.confirm('是否确认对付款单编号为"' + row.paymentBillCode + '"的款项申请退款?').then(() => {
return applyRefund(row.id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("申请退款成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -197,20 +197,20 @@ public class OmsPaymentBillController extends BaseController
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
//
// /**
// * 申请退款
// */
// @PostMapping("/applyRefund")
// @ResponseBody
// public AjaxResult applyRefund(@RequestParam("id") Long id) {
// try {
// return omsPaymentBillService.applyRefund(id);
// } catch (Exception e) {
// logger.error("申请退款失败", e);
// return AjaxResult.error("操作失败:" + e.getMessage());
// }
// }
/**
* 退
*/
@GetMapping("/applyRefund/{id}")
@ResponseBody
public AjaxResult applyRefund(@PathVariable("id") Long id) {
try {
return omsPaymentBillService.applyRefund(id);
} catch (Exception e) {
logger.error("申请退款失败", e);
return AjaxResult.error("操作失败:" + e.getMessage());
}
}
//
// /**
// * 上传退款图

View File

@ -168,6 +168,21 @@ public class OmsPaymentBill extends BaseEntity
this.desc = desc;
}
}
@Getter
public enum RefundStatusEnum {
/** 应付单生成 */
REFUNDED("REFUND_APPLIED", "已申请退款"),
WAIT_REFUNDED("WAIT_REFUNDED", "未退款")
;
private final String code;
private final String desc;
RefundStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

View File

@ -1,6 +1,8 @@
package com.ruoyi.sip.mapper;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OmsPayablePaymentDetailMapper {
@ -8,4 +10,5 @@ public interface OmsPayablePaymentDetailMapper {
List<OmsPayablePaymentDetail> list(OmsPayablePaymentDetail omsPayablePaymentDetail);
void insertBatch(List<OmsPayablePaymentDetail> addList);
}

View File

@ -8,4 +8,7 @@ public interface IOmsPayablePaymentDetailService {
public int insertOmsPayablePaymentDetail(OmsPayablePaymentDetail omsPayablePaymentDetail);
public List<OmsPayablePaymentDetail> listByPayableBillId(Long payableBillId);
public List<OmsPayablePaymentDetail> listByPayableBillIdList(List<Long> payableBillId);
void applyRefund(String payableBillCode, String payableBillCode1);
}

View File

@ -78,7 +78,7 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
List<OmsPayablePaymentDetail> omsPayablePaymentDetails = omsPayablePaymentDetailService.listByPayableBillIdList(idList);
Map<Long, Map<String, BigDecimal>> planMap = omsPayablePaymentDetails.stream().collect(Collectors.groupingBy(OmsPayablePaymentDetail::getPayableBillId,
Collectors.groupingBy(
OmsPayablePaymentDetail::getPaymentStatus,
item->item.getPaymentStatus()==null?OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode(): item.getPaymentStatus(),
Collectors.reducing(
BigDecimal.ZERO,
detail -> (detail.getPaymentAmount() != null) ? detail.getPaymentAmount() : BigDecimal.ZERO,
@ -228,7 +228,7 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
omsPayableBill.setDetailList(omsPayablePaymentDetails);
Map<String, BigDecimal> decimalMap = omsPayablePaymentDetails.stream()
.collect(Collectors.groupingBy(
OmsPayablePaymentDetail::getPaymentStatus,
item->item.getPaymentStatus()==null?OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode(): item.getPaymentStatus(),
Collectors.reducing(
BigDecimal.ZERO,
detail -> (detail.getPaymentAmount() != null) ? detail.getPaymentAmount() : BigDecimal.ZERO,

View File

@ -1,6 +1,8 @@
package com.ruoyi.sip.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsFinAttachment;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.mapper.OmsPayablePaymentDetailMapper;
@ -9,6 +11,7 @@ import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -54,4 +57,28 @@ public class OmsPayablePaymentDetailServiceImpl implements IOmsPayablePaymentDet
omsPayablePaymentDetail.setPayableBillIdList(payableBillId);
return omsPayablePaymentDetailMapper.list(omsPayablePaymentDetail);
}
@Override
public void applyRefund(String originalCode, String payableBillCode) {
OmsPayablePaymentDetail omsPayablePaymentDetail = new OmsPayablePaymentDetail();
omsPayablePaymentDetail.setPaymentBillCode(originalCode);
List<OmsPayablePaymentDetail> list = omsPayablePaymentDetailMapper.list(omsPayablePaymentDetail);
if (CollUtil.isNotEmpty(list)){
List<OmsPayablePaymentDetail> addList=new ArrayList<>();
for (OmsPayablePaymentDetail payablePaymentDetail : list) {
OmsPayablePaymentDetail temp = new OmsPayablePaymentDetail();
BeanUtil.copyProperties(payablePaymentDetail,temp);
temp.setId(null);
temp.setPayableDetailType(OmsPayablePaymentDetail.PayableDetailTypeEnum.REFUND.getCode());
temp.setPaymentBillCode(payableBillCode);
temp.setCreateBy(ShiroUtils.getUserId().toString());
temp.setPaymentAmount(payablePaymentDetail.getPaymentAmount().negate());
temp.setPaymentRate(payablePaymentDetail.getPaymentRate().negate());
temp.setPaymentBillCode(payableBillCode);
temp.setRemark("退款");
addList.add( temp);
}
omsPayablePaymentDetailMapper.insertBatch(addList);
}
}
}

View File

@ -1,10 +1,15 @@
package com.ruoyi.sip.service.impl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -18,9 +23,23 @@ public class OmsPayablePaymentPlanServiceImpl implements IOmsPayablePaymentPlanS
@Autowired
private OmsPayablePaymentPlanMapper omsPayablePaymentPlanMapper;
@Autowired
private IOmsPayablePaymentDetailService omsPayablePaymentDetailService;
@Override
public List<OmsPayablePaymentPlan> selectOmsPayablePaymentPlanListByPayableBillId(Long payableBillId) {
return omsPayablePaymentPlanMapper.selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
List<OmsPayablePaymentPlan> omsPayablePaymentPlans = omsPayablePaymentPlanMapper.selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
if (CollUtil.isNotEmpty(omsPayablePaymentPlans)) {
List<OmsPayablePaymentDetail> omsPayablePaymentDetails = omsPayablePaymentDetailService.listByPayableBillIdList(omsPayablePaymentPlans.stream().map(OmsPayablePaymentPlan::getPayableBillId).collect(Collectors.toList()));
Map<Long, OmsPayablePaymentDetail> detailMap = omsPayablePaymentDetails.stream().collect(Collectors.toMap(OmsPayablePaymentDetail::getPaymentPlanId, Function.identity(),
(v1, v2) -> v1.getCreateTime().after(v2.getCreateTime()) ? v1 : v2));
for (OmsPayablePaymentPlan omsPayablePaymentPlan : omsPayablePaymentPlans) {
//找到最新的一条数据 如果不是退款 那么不允许再次勾选
OmsPayablePaymentDetail omsPayablePaymentDetail = detailMap.get(omsPayablePaymentPlan.getId());
if (omsPayablePaymentDetail != null && !OmsPayablePaymentDetail.PayableDetailTypeEnum.REFUND.getCode().equalsIgnoreCase(omsPayablePaymentDetail.getPayableDetailType()))
omsPayablePaymentPlan.setDetailId(omsPayablePaymentDetail.getId());
}
}
return omsPayablePaymentPlans;
}
@Override

View File

@ -17,6 +17,7 @@ import com.ruoyi.sip.domain.dto.PaymentBillDetailDTO;
import com.ruoyi.sip.flowable.domain.Todo;
import com.ruoyi.sip.flowable.service.TodoCommonTemplate;
import com.ruoyi.sip.service.IOmsFinAttachmentService;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import com.ruoyi.sip.service.IVendorInfoService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
@ -44,6 +45,8 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
private IVendorInfoService vendorInfoService;
@Autowired
private IOmsFinAttachmentService omsFinAttachmentService;
@Autowired
private IOmsPayablePaymentDetailService detailService;
/**
*
*
@ -304,7 +307,6 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
}
@Override
@Transactional
public AjaxResult applyRefund(Long originalPaymentId) {
// 1. 验证原始付款单
OmsPaymentBill originalBill = selectOmsPaymentBillById(originalPaymentId);
@ -314,7 +316,7 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
if (!OmsPaymentBill.PaymentStatusEnum.PAYMENT.getCode().equals(originalBill.getPaymentStatus())) {
return AjaxResult.error("只有已付款的订单才能申请退款");
}
if ("REFUND_APPLIED".equals(originalBill.getRefundStatus())) {
if (OmsPaymentBill.RefundStatusEnum.REFUNDED.getCode().equals(originalBill.getRefundStatus())) {
return AjaxResult.error("该付款单已申请过退款,请勿重复操作");
}
@ -332,19 +334,28 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
refundBill.setPayBankNumber(originalBill.getPayBankNumber());
refundBill.setPayBankOpenAddress(originalBill.getPayBankOpenAddress());
refundBill.setBankNumber(originalBill.getBankNumber());
refundBill.setPaymentBillCode(generatePaymentBillCode());
// 设置新属性
refundBill.setPaymentBillType(OmsPaymentBill.PaymentBillTypeEnum.REFUND.getCode());
refundBill.setPaymentStatus(OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode());
refundBill.setApproveStatus(ApproveStatusEnum.WAIT_COMMIT.getCode());
refundBill.setApproveStatus(ApproveStatusEnum.WAIT_APPROVE.getCode());
refundBill.setOriginalBillId(originalPaymentId);
refundBill.setPaymentTime(null);
refundBill.setPaymentMethod(originalBill.getPaymentMethod());
refundBill.setRemark("退款-关联原付款单:" + originalBill.getPaymentBillCode());
insertOmsPaymentBill(refundBill);
// 3. 更新原始付款单状态
originalBill.setRefundStatus("1");
originalBill.setRefundStatus(OmsPaymentBill.RefundStatusEnum.REFUNDED.getCode());
updateOmsPaymentBill(originalBill);
//4 创建付款明细
detailService.applyRefund(originalBill.getPayableBillCode(),refundBill.getPayableBillCode());
//5.todo 开始退款审批流程
return AjaxResult.success("退款申请已提交,新的退款单号为:" + refundBill.getPaymentBillCode());
}

View File

@ -23,27 +23,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into oms_payable_payment_detail (
<if test="paymentPlanId != null">payment_plan_id,</if>
<if test="payableBillId != null">payable_bill_id,</if>
<if test="paymentStatus != null">payment_status,</if>
<if test="paymentTime != null">payment_time,</if>
<if test="paymentAmount != null">payment_amount,</if>
<if test="paymentRate != null">payment_rate,</if>
<if test="paymentBillCode != null and paymentBillCode != ''">payment_bill_code,</if>
<if test="payableDetailType != null and payableDetailType != ''">payable_detail_type,</if>
<if test="remark != null and remark != ''">remark,</if>
create_by,
create_time
) values (
<if test="paymentPlanId != null">#{paymentPlanId},</if>
<if test="payableBillId != null">#{payableBillId},</if>
<if test="paymentStatus != null">#{paymentStatus},</if>
<if test="paymentTime != null">#{paymentTime},</if>
<if test="paymentAmount != null">#{paymentAmount},</if>
<if test="paymentRate != null">#{paymentRate},</if>
<if test="paymentBillCode != null and paymentBillCode != ''">#{paymentBillCode},</if>
<if test="payableDetailType != null and payableDetailType != ''">#{payableDetailType},</if>
<if test="remark != null and remark != ''">#{remark},</if>
#{createBy},
sysdate()
)
</insert>
<insert id="insertBatch">
insert into oms_payable_payment_detail (
payment_plan_id, payable_bill_id, payment_time, payment_amount, payment_rate,
payment_bill_code, payable_detail_type, remark, create_by, create_time )
values
<foreach item="item" collection="list" separator="," index="">
(#{item.paymentPlanId},#{item.payableBillId},#{item.paymentTime},#{item.paymentAmount},
#{item.paymentRate},#{item.paymentBillCode},#{item.payableDetailType},#{item.remark},
#{item.createBy},
sysdate())
</foreach>
</insert>
<select id="list" resultType="com.ruoyi.sip.domain.OmsPayablePaymentDetail">
SELECT

View File

@ -30,11 +30,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
t1.remark,
t1.create_time,
t1.create_by,
t1.update_time,
t2.id as detail_id
t1.update_time
FROM
oms_payable_payment_plan t1
left join oms_payable_payment_detail t2 on t1.id=t2.payment_plan_id
where t1.payable_bill_id = #{payableBillId}
order by t1.plan_payment_date
</select>