feat(finance): 新增付款计划同步至发票计划功能

- 在后端服务中实现 syncPaymentToTicketPlan 方法,用于将付款计划同步到发票计划
- 添加对应的 REST 接口 /sync/{payableBillId} 支持前端调用
- 前端页面新增“同步至发票计划”按钮,并绑定相应处理逻辑
- 提供 API 函数 syncToTicketPlan 以支持前后端通信
- 优化计算公式显示,确保数值精度和展示正确性
- 增强数据一致性校验,防止已执行的数据不匹配导致错误同步
dev_1.0.0
chenhao 2025-12-11 20:47:05 +08:00
parent 4cc2d3beb1
commit 2f51b56298
7 changed files with 121 additions and 4 deletions

View File

@ -68,3 +68,11 @@ export function updateReceivingTicketPlan(payableBillId, data) {
data: data
})
}
// 同步付款计划至发票计划
export function syncToTicketPlan(payableBillId) {
return request({
url: `/finance/payable/plan/sync/${payableBillId}`,
method: 'post'
})
}

View File

@ -81,7 +81,7 @@
<span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{
totalPlannedAmount.toFixed(2)
}}</el-tag></span>
<span>计划付款比例: <el-tag type="info">{{ this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4)*100 }}%</el-tag></span>
<span>计划付款比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">
@ -339,7 +339,9 @@ export default {
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
if (order && order.paymentPlans && order.unpaidAmount >= 0) {
const currentAmount = this.calculateOrderCurrentPaymentAmount(orderId);
return this.$calc.mul(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ),100);
console.log(this.$calc.div(currentAmount ,order.totalPriceWithTax,4 ))
console.log(11111)
return this.$calc.mul((this.$calc.div(currentAmount ,order.totalPriceWithTax,4 )),100);
}
return 0;
},

View File

@ -81,7 +81,7 @@
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{
totalPlannedAmount.toFixed(2)
}}</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4)*100 }}%</el-tag></span>
<span>计划收票比例: <el-tag type="info">{{ this.$calc.mul(this.$calc.div(totalPlannedAmount,totalPayableAmountWithTax,4),100) }}%</el-tag></span>
</div>
</div>
<div slot="footer" class="dialog-footer">

View File

@ -6,6 +6,10 @@
style="margin-bottom: 10px;">
保存付款计划
</el-button>
<el-button v-if="isEditing" type="primary" size="mini" @click="handleSyncToTicketPlan"
style="margin-bottom: 10px; margin-left: 10px;">
同步至发票计划
</el-button>
<el-button v-else type="primary" size="mini" @click="isEditing=true"
style="margin-bottom: 10px;">
编辑
@ -86,7 +90,7 @@
</template>
<script>
import {getPaymentPlan, updatePaymentPlan} from "@/api/finance/payable";
import {getPaymentPlan, updatePaymentPlan, syncToTicketPlan} from "@/api/finance/payable";
import {isNumberStr} from "@/utils";
export default {
@ -219,6 +223,13 @@ export default {
this.fetchPaymentPlans(this.payableData.id); // Re-fetch using the correct method and ID
});
},
handleSyncToTicketPlan() {
this.$modal.confirm('是否确认同步付款计划至发票计划?').then(() => {
return syncToTicketPlan(this.payableData.id);
}).then(() => {
this.$modal.msgSuccess("同步成功");
}).catch(() => {});
},
handleAddPaymentPlanRow() {
this.paymentPlans.push({

View File

@ -32,4 +32,10 @@ public class OmsPayablePlanController extends BaseController {
paymentPlanService.updatePaymentPlans(payableBillId, paymentPlanList);
return AjaxResult.success();
}
@PostMapping("/sync/{payableBillId}")
public AjaxResult syncPaymentToTicketPlan(@PathVariable("payableBillId") Long payableBillId) {
paymentPlanService.syncPaymentToTicketPlan(payableBillId);
return AjaxResult.success();
}
}

View File

@ -19,4 +19,12 @@ public interface IOmsPayablePaymentPlanService {
* @param paymentPlanList
*/
public void updatePaymentPlans(Long payableBillId, List<OmsPayablePaymentPlan> paymentPlanList);
/**
*
*
* @param payableBillId ID
* @return
*/
public void syncPaymentToTicketPlan(Long payableBillId);
}

View File

@ -1,15 +1,20 @@
package com.ruoyi.sip.service.impl;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.ArrayList;
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.domain.OmsPayableTicketPlan;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import com.ruoyi.sip.service.IOmsPayableTicketPlanService;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -25,6 +30,9 @@ public class OmsPayablePaymentPlanServiceImpl implements IOmsPayablePaymentPlanS
@Autowired
private IOmsPayablePaymentDetailService omsPayablePaymentDetailService;
@Autowired
private IOmsPayableTicketPlanService ticketPlanService;
@Override
public List<OmsPayablePaymentPlan> selectOmsPayablePaymentPlanListByPayableBillId(Long payableBillId) {
List<OmsPayablePaymentPlan> omsPayablePaymentPlans = omsPayablePaymentPlanMapper.selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
@ -74,4 +82,78 @@ public class OmsPayablePaymentPlanServiceImpl implements IOmsPayablePaymentPlanS
omsPayablePaymentPlanMapper.deleteOmsPayablePaymentPlanById(idToDelete);
}
}
@Override
@Transactional
public void syncPaymentToTicketPlan(Long payableBillId) {
List<OmsPayableTicketPlan> ticketPlans = ticketPlanService.listByPayableBillId(payableBillId);
List<OmsPayablePaymentPlan> paymentPlans = selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
boolean hasExecutedTicket = ticketPlans.stream().anyMatch(p -> p.getDetailId() != null);
if (!hasExecutedTicket) {
List<OmsPayableTicketPlan> newPlans = convertPaymentToTicket(paymentPlans, payableBillId);
ticketPlanService.updateTicketPlan(payableBillId, newPlans);
} else {
List<OmsPayableTicketPlan> executedTickets = ticketPlans.stream()
.filter(p -> p.getDetailId() != null).collect(Collectors.toList());
List<OmsPayablePaymentPlan> executedPayments = paymentPlans.stream()
.filter(p -> p.getDetailId() != null).collect(Collectors.toList());
List<OmsPayablePaymentPlan> unmatchedExecutedPayments = new ArrayList<>(executedPayments);
for (OmsPayableTicketPlan ticket : executedTickets) {
boolean found = false;
Iterator<OmsPayablePaymentPlan> it = unmatchedExecutedPayments.iterator();
while (it.hasNext()) {
OmsPayablePaymentPlan payment = it.next();
if (compareDates(ticket.getPlanTicketDate(), payment.getPlanPaymentDate()) == 0 &&
ticket.getPlanAmount().compareTo(payment.getPlanAmount()) == 0) {
it.remove();
found = true;
break;
}
}
if (!found) {
throw new ServiceException("因付款计划表与开票计划表中已付款/开票数据已不一致,所以无法关联开票计划");
}
}
List<OmsPayablePaymentPlan> unexecutedPayments = paymentPlans.stream()
.filter(p -> p.getDetailId() == null).collect(Collectors.toList());
List<OmsPayablePaymentPlan> toConvert = new ArrayList<>();
toConvert.addAll(unmatchedExecutedPayments);
toConvert.addAll(unexecutedPayments);
List<OmsPayableTicketPlan> newPlans = convertPaymentToTicket(toConvert, payableBillId);
List<OmsPayableTicketPlan> finalPlans = new ArrayList<>(executedTickets);
finalPlans.addAll(newPlans);
ticketPlanService.updateTicketPlan(payableBillId, finalPlans);
}
}
private List<OmsPayableTicketPlan> convertPaymentToTicket(List<OmsPayablePaymentPlan> paymentPlans, Long payableBillId) {
List<OmsPayableTicketPlan> list = new ArrayList<>();
for (OmsPayablePaymentPlan p : paymentPlans) {
OmsPayableTicketPlan t = new OmsPayableTicketPlan();
t.setPayableBillId(payableBillId);
t.setPlanTicketDate(p.getPlanPaymentDate());
t.setPlanAmount(p.getPlanAmount());
t.setPlanRate(p.getPlanRate());
t.setDetailId(null);
t.setRemark(p.getRemark());
list.add(t);
}
return list;
}
private int compareDates(java.util.Date d1, java.util.Date d2) {
if (d1 == null && d2 == null) return 0;
if (d1 == null) return -1;
if (d2 == null) return 1;
return d1.compareTo(d2);
}
}