feat(finance): 添加合并收票单功能并优化付款单逻辑

- 在应付单页面增加“合并并发起收票单”按钮及弹窗组件
- 新增收票单相关接口与服务实现,支持合并多个应付单发起收票
- 优化付款单含税金额与无税金额的计算逻辑
- 补充付款明细查询接口,支持通过付款单编号获取关联应付单信息
- 调整部分字段命名以提升语义清晰度(如 paymentRatio 改为 paymentRate)
- 更新收票计划相关接口路径,统一归入 /finance/ticket/plan 路由下
dev_1.0.0
chenhao 2025-12-11 11:02:11 +08:00
parent a9142c0e1b
commit bd830115d4
11 changed files with 221 additions and 39 deletions

View File

@ -43,10 +43,19 @@ export function mergeAndInitiatePayment(data) {
})
}
// 合并并发起收票
export function mergeAndInitiateReceipt(data) {
return request({
url: '/finance/payable/mergeAndInitiateReceipt',
method: 'post',
data: data
})
}
// [PLACEHOLDER] 查询收票计划列表 - Endpoint to be confirmed by user
export function getReceivingTicketPlan(payableBillId) {
return request({
url: `/finance/payable/ticket-plan/${payableBillId}`,
url: `/finance/ticket/plan/${payableBillId}`,
method: 'get'
})
}
@ -54,7 +63,7 @@ export function getReceivingTicketPlan(payableBillId) {
// [PLACEHOLDER] 更新收票计划 - Endpoint to be confirmed by user
export function updateReceivingTicketPlan(payableBillId, data) {
return request({
url: `/finance/payable/ticket-plan/${payableBillId}`,
url: `/finance/ticket/plan/${payableBillId}`,
method: 'post',
data: data
})

View File

@ -114,7 +114,7 @@
</el-button>
</el-col>
<el-col :span="1.5" >
<el-button type="primary" plain icon="el-icon-plus" v-hasPermi="['inventory:inner:add']">
<el-button type="primary" plain icon="el-icon-plus" @click="handleMergeAndInitiateReceipt" v-hasPermi="['inventory:inner:add']">
合并并发起收票单
</el-button>
</el-col>
@ -210,17 +210,21 @@
<!-- 合并付款单弹窗 -->
<merge-payment-dialog :visible.sync="isMergePaymentDialogOpen" :payable-orders="selectedPayableRows" @confirm="confirmMergePayment" />
<!-- 合并收票单弹窗 -->
<merge-receipt-dialog :visible.sync="isMergeReceiptDialogOpen" :payable-orders="selectedPayableRows" @confirm="confirmMergeReceipt" />
</div>
</template>
<script>
import { listPayable, mergeAndInitiatePayment } from "@/api/finance/payable";
import { listPayable, mergeAndInitiatePayment, mergeAndInitiateReceipt } from "@/api/finance/payable";
import EditForm from './components/EditForm.vue';
import MergePaymentDialog from './components/MergePaymentDialog.vue';
import MergeReceiptDialog from './components/MergeReceiptDialog.vue';
export default {
name: "Payable",
components: { EditForm, MergePaymentDialog },
components: { EditForm, MergePaymentDialog, MergeReceiptDialog },
dicts: ['product_type', 'payment_status', 'invoice_status'],
data() {
return {
@ -261,7 +265,9 @@ export default {
//
selectedPayableRows: [],
//
isMergePaymentDialogOpen: false
isMergePaymentDialogOpen: false,
//
isMergeReceiptDialogOpen: false
};
},
created() {
@ -351,6 +357,28 @@ export default {
this.isMergePaymentDialogOpen = false;
this.getList(); // Refresh the list
});
},
/** 合并并发起收票单按钮操作 */
handleMergeAndInitiateReceipt() {
if (this.selectedPayableRows.length === 0) {
this.$modal.msgWarning("请选择至少一条应付单进行合并操作");
return;
}
let vendorLength = new Set(this.selectedPayableRows.map(item=>item.vendorCode)).size;
if (vendorLength > 1) {
this.$modal.msgWarning("请选择同一家供应商的应付单进行合并操作");
return;
}
this.isMergeReceiptDialogOpen = true;
},
/** 确认合并收票单操作 */
confirmMergeReceipt(receiptData) {
mergeAndInitiateReceipt(receiptData).then(() => {
this.$modal.msgSuccess("合并收票单发起成功");
this.isMergeReceiptDialogOpen = false;
this.getList(); // Refresh the list
});
}
}
};

View File

@ -109,7 +109,7 @@
<el-table-column property="payableBillCode" label="采购应付单编号"></el-table-column>
<el-table-column property="totalPriceWithTax" label="含税总价"></el-table-column>
<el-table-column property="paymentAmount" label="本次付款金额"></el-table-column>
<el-table-column property="paymentRatio" label="本次付款比例"></el-table-column>
<el-table-column property="paymentRate" label="本次付款比例"></el-table-column>
</el-table>
</div>
</div>

View File

@ -22,6 +22,7 @@ import com.ruoyi.sip.domain.OmsInvoiceReceiptBill;
import org.springframework.ui.ModelMap;
import com.ruoyi.sip.domain.dto.MergedPaymentDataDto;
import com.ruoyi.sip.domain.dto.MergedReceiptDataDto;
/**
* Controller
@ -155,4 +156,16 @@ public class OmsPayableBillController extends BaseController
{
return toAjax(omsPayableBillService.mergeAndInitiatePayment(dto));
}
/**
*
*/
@RequiresPermissions("inventory:inner:add") // Using permission from button in vue file
@Log(title = "合并并发起收票单", businessType = BusinessType.INSERT)
@PostMapping("/mergeAndInitiateReceipt")
@ResponseBody
public AjaxResult mergeAndInitiateReceipt(@RequestBody MergedReceiptDataDto dto)
{
return toAjax(omsPayableBillService.mergeAndInitiateReceipt(dto));
}
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.sip.mapper;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -11,4 +12,6 @@ public interface OmsPayablePaymentDetailMapper {
List<OmsPayablePaymentDetail> list(OmsPayablePaymentDetail omsPayablePaymentDetail);
void insertBatch(List<OmsPayablePaymentDetail> addList);
List<PaymentBillPayableDetailDTO> listPayableByPaymentCode(List<String> paymentBillCodeList);
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.ruoyi.sip.domain.OmsPayableBill;
import com.ruoyi.sip.domain.dto.MergedPaymentDataDto;
import com.ruoyi.sip.domain.dto.MergedReceiptDataDto;
/**
* Service
@ -69,5 +70,13 @@ public interface IOmsPayableBillService
*/
public int mergeAndInitiatePayment(MergedPaymentDataDto dto);
/**
*
*
* @param dto
* @return
*/
public int mergeAndInitiateReceipt(MergedReceiptDataDto dto);
OmsPayableBill query(Long id);
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.sip.service;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
import java.util.List;
@ -11,4 +12,5 @@ public interface IOmsPayablePaymentDetailService {
void applyRefund(String payableBillCode, String payableBillCode1);
List<PaymentBillPayableDetailDTO> listPayableByPaymentCode(String paymentBillCode);
}

View File

@ -1,34 +1,29 @@
package com.ruoyi.sip.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.domain.dto.MergedPaymentDataDto;
import com.ruoyi.sip.domain.dto.MergedReceiptDataDto;
import com.ruoyi.sip.domain.dto.PayableOrderDto;
import com.ruoyi.sip.domain.dto.PayableOrderReceiptDto;
import com.ruoyi.sip.mapper.OmsPayableBillMapper;
import com.ruoyi.sip.mapper.OmsPayablePaymentPlanMapper;
import com.ruoyi.sip.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.ApproveStatusEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.mapper.OmsPayableBillMapper;
import com.ruoyi.sip.service.IOmsPayableBillService;
import com.ruoyi.sip.service.IVendorInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.sip.mapper.OmsPayablePaymentPlanMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.sip.domain.dto.MergedPaymentDataDto;
import com.ruoyi.sip.domain.dto.PayableOrderDto;
import com.ruoyi.sip.service.IOmsPaymentBillService;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
/**
* Service
@ -52,6 +47,12 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
@Autowired
private IVendorInfoService vendorInfoService;
@Autowired
private IOmsTicketBillService omsTicketBillService;
@Autowired
private IOmsPayableTicketDetailService omsPayableTicketDetailService;
/**
*
*
@ -185,17 +186,42 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
@Override
@Transactional
public int mergeAndInitiatePayment(MergedPaymentDataDto dto) {
// 1. 创建付款单
// 1. Calculate Tax Totals
BigDecimal totalWithoutTax = BigDecimal.ZERO;
Map<Long, OmsPayableBill> billMap = new java.util.HashMap<>();
// Fetch bills once
for (PayableOrderDto order : dto.getPayableOrders()) {
OmsPayableBill bill = omsPayableBillMapper.selectOmsPayableBillById(order.getId());
billMap.put(order.getId(), bill);
// todo 此处存疑 税额的计算到底应该怎么计算
for (OmsPayablePaymentPlan plan : order.getPaymentPlans()) {
if (bill.getTotalPriceWithTax() != null && bill.getTotalPriceWithTax().compareTo(BigDecimal.ZERO) != 0) {
// ratio = planAmount / totalWithTax
BigDecimal ratio = plan.getPlanAmount().divide(bill.getTotalPriceWithTax(), 10, java.math.RoundingMode.HALF_UP);
// planWithoutTax = totalWithoutTax * ratio
BigDecimal planWithoutTax = bill.getTotalPriceWithoutTax().multiply(ratio).setScale(2, java.math.RoundingMode.HALF_UP);
totalWithoutTax = totalWithoutTax.add(planWithoutTax);
}
}
}
// 2. 创建付款单
OmsPaymentBill paymentBill = new OmsPaymentBill();
OmsPayableBill firstPayableBill = omsPayableBillMapper.selectOmsPayableBillById(dto.getPayableOrders().get(0).getId());
paymentBill.setPaymentBillType(dto.getPaymentBillType());
paymentBill.setVendorCode(firstPayableBill.getVendorCode());
paymentBill.setPaymentTime(dto.getEstimatedPaymentTime());
paymentBill.setTotalPriceWithTax(dto.getTotalMergePaymentAmount());
// Set Calculated Tax Info
paymentBill.setTotalPriceWithoutTax(totalWithoutTax);
paymentBill.setTaxAmount(dto.getTotalMergePaymentAmount().subtract(totalWithoutTax));
paymentBill.setPaymentBillType(OmsPaymentBill.PaymentBillTypeEnum.FROM_PAYABLE.getCode());
omsPaymentBillService.insertOmsPaymentBill(paymentBill);
// 2. 创建付款明细
// 3. 创建付款明细
for (PayableOrderDto payableOrderDto : dto.getPayableOrders()) {
for (OmsPayablePaymentPlan paymentPlanDto : payableOrderDto.getPaymentPlans()) {
OmsPayablePaymentDetail detail = new OmsPayablePaymentDetail();
@ -221,6 +247,63 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
return 1;
}
@Override
@Transactional
public int mergeAndInitiateReceipt(MergedReceiptDataDto dto) {
// 1. Calculate Tax Totals
BigDecimal totalWithoutTax = BigDecimal.ZERO;
Map<Long, OmsPayableBill> billMap = new java.util.HashMap<>();
// Fetch bills once
for (PayableOrderReceiptDto order : dto.getPayableOrders()) {
OmsPayableBill bill = omsPayableBillMapper.selectOmsPayableBillById(order.getId());
billMap.put(order.getId(), bill);
for (OmsPayableTicketPlan plan : order.getTicketPlans()) {
if (bill.getTotalPriceWithTax() != null && bill.getTotalPriceWithTax().compareTo(BigDecimal.ZERO) != 0) {
BigDecimal ratio = plan.getPlanAmount().divide(bill.getTotalPriceWithTax(), 10, java.math.RoundingMode.HALF_UP);
BigDecimal planWithoutTax = bill.getTotalPriceWithoutTax().multiply(ratio).setScale(2, java.math.RoundingMode.HALF_UP);
totalWithoutTax = totalWithoutTax.add(planWithoutTax);
}
}
}
// 2. 创建收票单
OmsTicketBill ticketBill = new OmsTicketBill();
OmsPayableBill firstPayableBill = billMap.get(dto.getPayableOrders().get(0).getId());
ticketBill.setTicketBillType(dto.getTicketBillType());
ticketBill.setVendorCode(firstPayableBill.getVendorCode());
ticketBill.setVendorName(firstPayableBill.getVendorName());
ticketBill.setTicketTime(dto.getTicketTime());
ticketBill.setTotalPriceWithTax(dto.getTotalMergeTicketAmount());
// Set Calculated Tax Info
ticketBill.setTotalPriceWithoutTax(totalWithoutTax);
ticketBill.setTaxAmount(dto.getTotalMergeTicketAmount().subtract(totalWithoutTax));
ticketBill.setTicketBillType(OmsTicketBill.TicketBillTypeEnum.FROM_PAYABLE.getCode());
ticketBill.setTicketStatus(OmsTicketBill.TicketStatusEnum.WAIT_TICKET.getCode());
omsTicketBillService.insertOmsTicketBill(ticketBill);
// 3. 创建收票明细
for (PayableOrderReceiptDto payableOrderDto : dto.getPayableOrders()) {
for (OmsPayableTicketPlan ticketPlanDto : payableOrderDto.getTicketPlans()) {
OmsPayableTicketDetail detail = new OmsPayableTicketDetail();
detail.setPayableBillId(payableOrderDto.getId());
detail.setTicketPlanId(ticketPlanDto.getId());
detail.setTicketBillCode(ticketBill.getTicketBillCode());
detail.setTicketBillId(ticketBill.getId());
detail.setPaymentAmount(ticketPlanDto.getPlanAmount());
detail.setPaymentRate(ticketPlanDto.getPlanRate());
detail.setPaymentTime(ticketPlanDto.getPlanTicketDate());
detail.setCreateBy(ShiroUtils.getLoginName());
detail.setPayableDetailType(OmsPayableTicketDetail.PayableDetailTypeEnum.TICKET.getCode());
omsPayableTicketDetailService.insertOmsPayableTicketDetail(detail);
}
}
return 1;
}
@Override
public OmsPayableBill query(Long id) {
OmsPayableBill omsPayableBill = selectOmsPayableBillById(id);

View File

@ -5,12 +5,16 @@ 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.domain.dto.PaymentBillPayableDetailDTO;
import com.ruoyi.sip.mapper.OmsPayablePaymentDetailMapper;
import com.ruoyi.sip.service.IOmsFinAttachmentService;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -81,4 +85,19 @@ public class OmsPayablePaymentDetailServiceImpl implements IOmsPayablePaymentDet
omsPayablePaymentDetailMapper.insertBatch(addList);
}
}
@Override
public List<PaymentBillPayableDetailDTO> listPayableByPaymentCode(String paymentBillCode) {
if (StringUtils.isEmpty(paymentBillCode)){
return Collections.emptyList();
}
List<PaymentBillPayableDetailDTO> paymentBillPayableDetailDTOS = omsPayablePaymentDetailMapper.listPayableByPaymentCode(Collections.singletonList(paymentBillCode));
for (PaymentBillPayableDetailDTO paymentBillPayableDetailDTO : paymentBillPayableDetailDTOS) {
paymentBillPayableDetailDTO.setPaymentRate(paymentBillPayableDetailDTO.getPaymentAmount()
.divide(paymentBillPayableDetailDTO.getTotalPriceWithTax(),4,java.math.RoundingMode.HALF_UP)
.multiply(new BigDecimal("100")));
}
return paymentBillPayableDetailDTOS;
}
}

View File

@ -14,16 +14,15 @@ import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.sip.domain.*;
import com.ruoyi.sip.domain.dto.PaymentBillDetailDTO;
import com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO;
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 com.ruoyi.sip.service.*;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsPaymentBillMapper;
import com.ruoyi.sip.service.IOmsPaymentBillService;
import com.ruoyi.common.core.text.Convert;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -47,6 +46,9 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
private IOmsFinAttachmentService omsFinAttachmentService;
@Autowired
private IOmsPayablePaymentDetailService detailService;
@Autowired
@Lazy
private IOmsPayableBillService payableBillService;
/**
*
*
@ -149,8 +151,8 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService , TodoC
@Override
public PaymentBillDetailDTO query(Long id) {
PaymentBillDetailDTO paymentBillDetailDTO = omsPaymentBillMapper.selectPaymentBillDetail(id);
List<PaymentBillPayableDetailDTO> paymentBillPayableDetailDTOS = detailService.listPayableByPaymentCode(paymentBillDetailDTO.getPaymentBillCode());
paymentBillDetailDTO.setPayableDetails(paymentBillPayableDetailDTOS);
return paymentBillDetailDTO;
}

View File

@ -75,5 +75,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
</where>
</select>
<select id="listPayableByPaymentCode" resultType="com.ruoyi.sip.domain.dto.PaymentBillPayableDetailDTO">
select t1.payment_amount, t2.payable_bill_code, t4.project_name, t4.project_code, t2.total_price_with_tax
from (SELECT sum(payment_amount) payment_amount,
payable_bill_id
FROM oms_payable_payment_detail
WHERE payment_bill_code in
<foreach item="item" collection="list" separator="," open="(" close=")" index="">
#{item}
</foreach>
group by payable_bill_id) t1
left join oms_payable_bill t2 on t1.payable_bill_id = t2.id
left join project_order_info t3 on t2.order_code = t3.order_code
left join project_info t4 on t3.project_id = t4.id
</select>
</mapper>