feat(finance): 新增付款计划同步至发票计划功能
- 在后端服务中实现 syncPaymentToTicketPlan 方法,用于将付款计划同步到发票计划
- 添加对应的 REST 接口 /sync/{payableBillId} 支持前端调用
- 前端页面新增“同步至发票计划”按钮,并绑定相应处理逻辑
- 提供 API 函数 syncToTicketPlan 以支持前后端通信
- 优化计算公式显示,确保数值精度和展示正确性
- 增强数据一致性校验,防止已执行的数据不匹配导致错误同步
dev_1.0.0
parent
4cc2d3beb1
commit
2f51b56298
|
|
@ -68,3 +68,11 @@ export function updateReceivingTicketPlan(payableBillId, data) {
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步付款计划至发票计划
|
||||||
|
export function syncToTicketPlan(payableBillId) {
|
||||||
|
return request({
|
||||||
|
url: `/finance/payable/plan/sync/${payableBillId}`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{
|
<span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{
|
||||||
totalPlannedAmount.toFixed(2)
|
totalPlannedAmount.toFixed(2)
|
||||||
}}</el-tag></span>
|
}}</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>
|
</div>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
|
|
@ -339,7 +339,9 @@ export default {
|
||||||
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
|
const order = this.payableOrdersWithPlans.find(o => o.id === orderId);
|
||||||
if (order && order.paymentPlans && order.unpaidAmount >= 0) {
|
if (order && order.paymentPlans && order.unpaidAmount >= 0) {
|
||||||
const currentAmount = this.calculateOrderCurrentPaymentAmount(orderId);
|
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;
|
return 0;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{
|
<span style="margin-left: 20px;">计划收票总金额: <el-tag type="success">{{
|
||||||
totalPlannedAmount.toFixed(2)
|
totalPlannedAmount.toFixed(2)
|
||||||
}}</el-tag></span>
|
}}</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>
|
</div>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@
|
||||||
style="margin-bottom: 10px;">
|
style="margin-bottom: 10px;">
|
||||||
保存付款计划
|
保存付款计划
|
||||||
</el-button>
|
</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"
|
<el-button v-else type="primary" size="mini" @click="isEditing=true"
|
||||||
style="margin-bottom: 10px;">
|
style="margin-bottom: 10px;">
|
||||||
编辑
|
编辑
|
||||||
|
|
@ -86,7 +90,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {getPaymentPlan, updatePaymentPlan} from "@/api/finance/payable";
|
import {getPaymentPlan, updatePaymentPlan, syncToTicketPlan} from "@/api/finance/payable";
|
||||||
import {isNumberStr} from "@/utils";
|
import {isNumberStr} from "@/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -219,6 +223,13 @@ export default {
|
||||||
this.fetchPaymentPlans(this.payableData.id); // Re-fetch using the correct method and ID
|
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() {
|
handleAddPaymentPlanRow() {
|
||||||
this.paymentPlans.push({
|
this.paymentPlans.push({
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,10 @@ public class OmsPayablePlanController extends BaseController {
|
||||||
paymentPlanService.updatePaymentPlans(payableBillId, paymentPlanList);
|
paymentPlanService.updatePaymentPlans(payableBillId, paymentPlanList);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/sync/{payableBillId}")
|
||||||
|
public AjaxResult syncPaymentToTicketPlan(@PathVariable("payableBillId") Long payableBillId) {
|
||||||
|
paymentPlanService.syncPaymentToTicketPlan(payableBillId);
|
||||||
|
return AjaxResult.success();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,12 @@ public interface IOmsPayablePaymentPlanService {
|
||||||
* @param paymentPlanList 付款计划列表
|
* @param paymentPlanList 付款计划列表
|
||||||
*/
|
*/
|
||||||
public void updatePaymentPlans(Long payableBillId, List<OmsPayablePaymentPlan> paymentPlanList);
|
public void updatePaymentPlans(Long payableBillId, List<OmsPayablePaymentPlan> paymentPlanList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步付款计划至发票计划
|
||||||
|
*
|
||||||
|
* @param payableBillId 应付单ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public void syncPaymentToTicketPlan(Long payableBillId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
package com.ruoyi.sip.service.impl;
|
package com.ruoyi.sip.service.impl;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import com.ruoyi.common.utils.ShiroUtils;
|
import com.ruoyi.common.utils.ShiroUtils;
|
||||||
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
|
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
|
||||||
|
import com.ruoyi.sip.domain.OmsPayableTicketPlan;
|
||||||
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -25,6 +30,9 @@ public class OmsPayablePaymentPlanServiceImpl implements IOmsPayablePaymentPlanS
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IOmsPayablePaymentDetailService omsPayablePaymentDetailService;
|
private IOmsPayablePaymentDetailService omsPayablePaymentDetailService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IOmsPayableTicketPlanService ticketPlanService;
|
||||||
@Override
|
@Override
|
||||||
public List<OmsPayablePaymentPlan> selectOmsPayablePaymentPlanListByPayableBillId(Long payableBillId) {
|
public List<OmsPayablePaymentPlan> selectOmsPayablePaymentPlanListByPayableBillId(Long payableBillId) {
|
||||||
List<OmsPayablePaymentPlan> omsPayablePaymentPlans = omsPayablePaymentPlanMapper.selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
|
List<OmsPayablePaymentPlan> omsPayablePaymentPlans = omsPayablePaymentPlanMapper.selectOmsPayablePaymentPlanListByPayableBillId(payableBillId);
|
||||||
|
|
@ -74,4 +82,78 @@ public class OmsPayablePaymentPlanServiceImpl implements IOmsPayablePaymentPlanS
|
||||||
omsPayablePaymentPlanMapper.deleteOmsPayablePaymentPlanById(idToDelete);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue