feat(finance): 新增付款单功能并优化应付单详情展示

- 新增付款单新增表单组件,支持付款单类型、制造商、付款时间等字段录入
- 优化应付单编辑表单,将弹窗改为抽屉展示,增强用户体验
- 应付单详情页增加付款明细表格展示及统计功能
- 调整付款单生成逻辑,支持从应付单直接发起付款操作
- 完善应付单状态显示,明确展示未生成、部分生成、全部生成状态
- 后端增加应付单查询接口及付款明细关联查询功能
- 优化付款单退回按钮显示条件,仅在审批通过状态下可操作
- 重构合并付款弹窗,调整默认付款单类型及展示逻辑
dev_1.0.0
chenhao 2025-12-09 16:35:45 +08:00
parent 05865dc2ef
commit a1ea52a934
22 changed files with 518 additions and 135 deletions

View File

@ -9,6 +9,14 @@ export function listPayable(query) {
})
}
// 查询采购应付单详情
export function getPayable(id) {
return request({
url: '/finance/payable/' + id,
method: 'get'
})
}
// 查询付款计划列表
export function getPaymentPlan(payableBillId) {
return request({

View File

@ -27,4 +27,14 @@ export function returnPayment(id) {
url: '/finance/payment/returnPayment/' + id,
method: 'delete'
})
}
}
// 新增付款单
export function addPayment(data) {
return request({
url: '/finance/payment/add',
method: 'post',
data: data,
needLoading: true
})
}

View File

@ -1,5 +1,5 @@
<template>
<el-dialog title="修改采购应付单" :visible.sync="internalVisible" width="70%" @close="handleClose">
<el-drawer title="修改采购应付单" :visible.sync="internalVisible" size="70%" @close="handleClose">
<div class="dialog-body">
<!-- Part 1: Details -->
<div>
@ -7,78 +7,103 @@
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>应付单编号:</strong> {{ data.payableBillCode }}</div>
<div class="detail-item"><strong>应付单编号:</strong> {{ formData.payableBillCode }}</div>
</el-col>
<el-col :span="16">
<div class="detail-item"><strong>生成时间:</strong> {{ data.createTime }}</div>
<div class="detail-item"><strong>生成时间:</strong> {{ formData.createTime }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>制造商名称:</strong> {{ data.vendorName }}</div>
<div class="detail-item"><strong>制造商名称:</strong> {{ formData.vendorName }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>项目编号:</strong> {{ data.projectCode }}</div>
<div class="detail-item"><strong>项目编号:</strong> {{ formData.projectCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>项目名称:</strong> {{ data.projectName }}</div>
<div class="detail-item"><strong>项目名称:</strong> {{ formData.projectName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>合同编号:</strong> {{ data.orderCode }}</div>
<div class="detail-item"><strong>合同编号:</strong> {{ formData.orderCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>出入库单号:</strong> {{ data.inventoryCode }}</div>
<div class="detail-item"><strong>出入库单号:</strong> {{ formData.inventoryCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item" style="display: flex"><strong>产品类型:</strong>
<dict-tag :options="dict.type.product_type" :value="data.productType"/>
<dict-tag :options="dict.type.product_type" :value="formData.productType"/>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>含税总价:</strong> {{ data.totalPriceWithTax }}</div>
<div class="detail-item"><strong>含税总价:</strong> {{ formData.totalPriceWithTax }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>未税总价:</strong> {{ data.totalPriceWithoutTax }}</div>
<div class="detail-item"><strong>未税总价:</strong> {{ formData.totalPriceWithoutTax }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>税额:</strong> {{ data.taxAmount }}</div>
<div class="detail-item"><strong>税额:</strong> {{ formData.taxAmount }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未付款金额:</strong> {{ data.unpaidAmount }}</div>
<div class="detail-item"><strong>未付款金额:</strong> {{ formData.unpaidAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已付款金额:</strong> {{ data.paidAmount }}</div>
<div class="detail-item"><strong>已付款金额:</strong> {{ formData.paidAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>付款中金额:</strong> {{ data.payingAmount }}</div>
<div class="detail-item"><strong>付款中金额:</strong> {{ formData.payingAmount }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>未收票金额:</strong> {{ data.unInvoicedAmount }}</div>
<div class="detail-item"><strong>未收票金额:</strong> {{ formData.unInvoicedAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>已收票金额:</strong> {{ data.invoicedAmount }}</div>
<div class="detail-item"><strong>已收票金额:</strong> {{ formData.invoicedAmount }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>收票中金额:</strong> {{ data.invoicingAmount }}</div>
<div class="detail-item"><strong>收票中金额:</strong> {{ formData.invoicingAmount }}</div>
</el-col>
</el-row>
</div>
</div>
<!-- Part 2: Tabs -->
<div style="margin-top: 20px;">
<div style="padding: 20px">
<el-tabs v-model="activeTab">
<el-tab-pane label="明细" name="details">
<!-- Content for 明细 tab will be added later -->
明细内容
<el-divider content-position="left">采购付款单</el-divider>
<el-table :data="formData.detailList" style="width: 100%" show-summary :summary-method="getSummaries">
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column prop="payableDetailType" label="付款通道">
<template slot-scope="scope">
<dict-tag :options="dict.type.payable_detail_type" :value="scope.row.payableDetailType"/>
</template>
</el-table-column>
<el-table-column prop="actualPaymentTime" label="实际付款时间">
<template slot-scope="scope">
{{ scope.row.actualPaymentTime || '-' }}
</template>
</el-table-column>
<el-table-column prop="paymentAmount" label="本次付款金额"></el-table-column>
<el-table-column prop="paymentRate" label="本次付款比例"></el-table-column>
<el-table-column prop="paymentStatus" label="付款状态">
<template slot-scope="scope">
<dict-tag :options="dict.type.payment_status" :value="scope.row.paymentStatus"/>
</template>
</el-table-column>
<el-table-column prop="paymentBillCode" label="采购付款单编号"></el-table-column>
<el-table-column label="回执单/退款图">
<template slot-scope="scope">
{{ scope.row.receiptAttachmentId || scope.row.refundProofAttachmentId }}
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="付款计划" name="paymentPlan">
<PaymentPlan :isInitEdit=true :payableData="data"/>
@ -86,20 +111,21 @@
</el-tabs>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
<!-- <div slot="footer" class="dialog-footer">-->
<!-- <el-button @click="handleClose"> </el-button>-->
<!-- <el-button type="primary" @click="handleSubmit"> </el-button>-->
<!-- </div>-->
</el-dialog>
</el-drawer>
</template>
<script>
import PaymentPlan from './PaymentPlan.vue';
import { getPayable } from "@/api/finance/payable";
export default {
name: "EditForm",
dicts: ['product_type'],
dicts: ['product_type','payment_status','payable_detail_type'],
components: {
PaymentPlan
},
@ -112,21 +138,63 @@ export default {
type: Object,
default: () => ({})
}
}, data() {
},
data() {
return {
internalVisible: this.visible, // Local copy of the visible prop
activeTab: 'details',
formData: {}
};
},
watch: {
visible(newVal) {
this.internalVisible = newVal; // Sync prop to local data
if (newVal && this.data.id) {
this.getDetails();
}
},
internalVisible(newVal) {
if (!newVal) {
this.formData = {};
}
this.$emit('update:visible', newVal); // Emit changes to parent
}
},
methods: {
getDetails() {
getPayable(this.data.id).then(res => {
this.formData = res.data;
});
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
let paymentAmountSum = 0;
if (data && data.length > 0) {
paymentAmountSum = data.reduce((acc, item) => {
const value = Number(item.paymentAmount);
return acc + (isNaN(value) ? 0 : value);
}, 0);
}
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
} else if (column.property === 'paymentAmount') {
sums[index] = paymentAmountSum.toFixed(2);
} else if (column.property === 'paymentRate') {
if (this.formData.totalPriceWithTax && this.formData.totalPriceWithTax > 0) {
const ratio = this.$calc.div(paymentAmountSum , this.formData.totalPriceWithTax,4);
sums[index] = (ratio * 100).toFixed(2) + '%';
} else {
sums[index] = '0.00%';
}
} else {
sums[index] = '';
}
});
return sums;
},
handleClose() {
this.internalVisible = false; // Close dialog locally
},
@ -147,10 +215,11 @@ export default {
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
font-size: 14px;
}
.dialog-body {
max-height: 70vh;
overflow-y: auto;
}
</style>

View File

@ -5,9 +5,9 @@
<el-row>
<el-col :span="8">
<el-form-item label="付款单类型" prop="paymentBillType">
<el-select v-model="form.paymentBillType" placeholder="请选择付款单类型" clearable>
<el-select disabled v-model="form.paymentBillType" placeholder="请选择付款单类型" clearable>
<el-option
v-for="dict in paymentBillTypeOptions"
v-for="dict in dict.type.payment_bill_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -77,10 +77,11 @@
</el-table>
<div class="total-info">
<span>合并应付总金额 (含税): <el-tag type="info">{{ totalPayableAmountWithTax.toFixed(2) }}</el-tag></span>
<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>
</div>
</div>
<div slot="footer" class="dialog-footer">
@ -113,7 +114,7 @@ import {getPaymentPlan} from "@/api/finance/payable";
export default {
name: "MergePaymentDialog",
components: {PaymentPlanSelector: PaymentPlan},
dicts: ['payment_status'], // Add dicts for dict-tag
dicts: ['payment_status','payment_bill_type'], // Add dicts for dict-tag
props: {
visible: {
type: Boolean,
@ -130,14 +131,10 @@ export default {
planTitle: '',
choosePayable: {},
form: {
paymentBillType: '1', // Default to a type, or make it dynamic
paymentBillType: 'FROM_PAYABLE', // Default to a type, or make it dynamic
vendorName: '',
estimatedPaymentTime: null,
},
paymentBillTypeOptions: [
{label: '普通付款单', value: '1'},
{label: '紧急付款单', value: '2'},
],
payableOrdersWithPlans: [], // Each order will now have its own paymentPlans array
isPaymentPlanSelectorOpen: false,
currentPayableOrderIndexForPlan: -1, // Index of the order in payableOrdersWithPlans
@ -192,7 +189,7 @@ export default {
this.form.vendorName = '';
this.form.estimatedPaymentTime = null;
}
this.form.paymentBillType = '1'; // Default
this.form.paymentBillType = 'FROM_PAYABLE'; // Default
// Initialize payableOrdersWithPlans
this.payableOrdersWithPlans = this.payableOrders.map(order => ({
@ -307,7 +304,7 @@ export default {
},
resetForm() {
this.form = {
paymentBillType: '1',
paymentBillType: 'FROM_PAYABLE',
vendorName: '',
estimatedPaymentTime: null,
};

View File

@ -11,16 +11,14 @@
编辑
</el-button>
<el-table :data="paymentPlans" border @selection-change="selectPlan" ref="paymentPlanTable">
<el-table-column type="selection" width="50" align="center"/>
<el-table-column v-show="false" prop="id" width="50" align="center"/>
<el-table-column type="selection" width="50" align="center" :selectable="selectableRow"/>
<el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
<el-table-column label="预计付款时间" align="center" width="200">
<template slot-scope="scope">
<el-date-picker v-model="scope.row.planPaymentDate" type="date" style="width: 180px"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择日期"
:readonly="!scope.row.detailId"
:disabled="!isEditing || scope.row.status === 'paid'"></el-date-picker>
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="预计付款金额" align="center" width="230">
@ -33,7 +31,7 @@
:readonly="!scope.row.detailId"
:max="totalPriceWithTax"
@change="handleAmountChange(scope.row)"
:disabled="!isEditing || scope.row.status === 'paid'"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
@ -47,26 +45,26 @@
:max="100"
:readonly="!scope.row.detailId"
@change="handleRateChange(scope.row)"
:disabled="!isEditing || scope.row.status === 'paid'"
:disabled="!isEditing || isNumberStr(scope.row.detailId)"
></el-input-number>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
<el-input v-model="scope.row.remark" placeholder="请输入备注"
:disabled="!isEditing || scope.row.detailId"></el-input>
:disabled="!isEditing || isNumberStr(scope.row.detailId)"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template slot-scope="scope">
<el-button v-if="isEditing && !scope.row.detailId"
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddPaymentPlanRow"
>增加下行
</el-button>
<el-button v-if="isEditing && !scope.row.detailId"
<el-button v-if="isEditing && !isNumberStr(scope.row.detailId)"
size="mini"
type="text"
icon="el-icon-delete"
@ -78,17 +76,18 @@
</el-table-column>
</el-table>
<div class="total-info">
<span>应付单未付款金额: <el-tag type="info">{{ totalUnpaidAmount.toFixed(2) }}</el-tag></span>
<span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{
totalPlannedAmount.toFixed(2)
}}</el-tag></span>
</div>
<!-- <div class="total-info">-->
<!-- <span>应付单未付款金额: <el-tag type="info">{{ totalUnpaidAmount.toFixed(2) }}</el-tag></span>-->
<!-- <span style="margin-left: 20px;">计划付款总金额: <el-tag type="success">{{-->
<!-- totalPlannedAmount.toFixed(2)-->
<!-- }}</el-tag></span>-->
<!-- </div>-->
</div>
</template>
<script>
import {getPaymentPlan, updatePaymentPlan} from "@/api/finance/payable";
import {isNumberStr} from "@/utils";
export default {
name: "PaymentPlanSelector",
@ -148,8 +147,11 @@ export default {
}
},
methods: {
isNumberStr,
selectableRow(row, index){
return !row.detailId;
},
selectPlan( selection){
console.log(selection)
this.selectedPlan=selection
},
fetchPaymentPlans(payableId) {

View File

@ -140,11 +140,7 @@
</el-table-column>
<el-table-column label="生成付款单" align="center" width="120">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
@click="handleGeneratePayment(scope.row)"
>生成付款单</el-button>
{{scope.row.unpaidAmount===scope.row.totalPriceWithTax?'未生成':scope.row.unpaidAmount===0?'全部生成':'部分生成'}}
</template>
</el-table-column>
<el-table-column label="收票状态" align="center" prop="invoiceStatus" width="120">
@ -171,7 +167,15 @@
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['finance:payable:edit']"
>修改</el-button>
>查看详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-show="scope.row.unpaidAmount!==0"
@click="handleGeneratedPayment(scope.row)"
v-hasPermi="['finance:payable:edit']"
>生成付款单</el-button>
<el-button
size="mini"
type="text"
@ -312,6 +316,10 @@ export default {
handleSelectionChange(selection) {
this.selectedPayableRows = selection;
},
handleGeneratedPayment(row) {
this.selectedPayableRows=[row]
this.handleMergeAndInitiatePayment()
},
/** 合并并发起付款单按钮操作 */
handleMergeAndInitiatePayment() {
if (this.selectedPayableRows.length === 0) {

View File

@ -0,0 +1,146 @@
<template>
<el-dialog title="新增付款单" :visible.sync="internalVisible" width="500px" @close="handleClose">
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="付款单类型" prop="paymentBillType">
<el-select v-model="form.paymentBillType" disabled placeholder="请选择付款单类型">
<el-option
v-for="dict in dicts.payment_bill_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="制造商名称" prop="vendorCode">
<el-select v-model="form.vendorCode" placeholder="请选择制造商" style="width:100%" >
<el-option v-for="item in vendorOptions" :key="item.vendorCode" :label="item.vendorName" :value="item.vendorCode"></el-option>
</el-select>
</el-form-item>
<el-form-item label="预计付款时间" prop="paymentTime">
<el-date-picker
v-model="form.paymentTime"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
></el-date-picker>
</el-form-item>
<el-form-item label="含税总价" prop="totalPriceWithTax">
<el-input-number v-model="form.totalPriceWithTax" @change="handlePriceChange" :precision="2"
:step="1"></el-input-number>
</el-form-item>
<el-form-item label="税率" prop="taxRate">
<el-input-number v-model="form.taxRate" @change="handlePriceChange" :precision="2" :step="0.01"
:max="1"></el-input-number>
</el-form-item>
<el-form-item label="未税总价" prop="totalPriceWithoutTax">
<el-input v-model="form.totalPriceWithoutTax" :disabled="true"/>
</el-form-item>
<el-form-item label="税额" prop="taxAmount">
<el-input v-model="form.taxAmount" :disabled="true"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</el-dialog>
</template>
<script>
import {listAllVendor} from "@/api/base/vendor";
export default {
name: "AddForm",
props: {
visible: {
type: Boolean,
default: false
},
dicts: {
type: Object,
default: () => ({})
}
},
data() {
return {
internalVisible: this.visible,
vendorOptions: [],
form: {
paymentBillType:'PRE_PAYMENT',
vendorName: null,
paymentTime: null,
totalPriceWithTax: 0,
taxRate: 0.13, // Default tax rate
totalPriceWithoutTax: 0,
taxAmount: 0
},
rules: {
paymentBillType: [{required: true, message: "付款单类型不能为空", trigger: "change"}],
vendorName: [{required: true, message: "制造商名称不能为空", trigger: "blur"}],
paymentTime: [{required: true, message: "预计付款时间不能为空", trigger: "change"}],
totalPriceWithTax: [{required: true, message: "含税总价不能为空", trigger: "blur"}]
}
};
},
watch: {
visible(newVal) {
this.internalVisible = newVal;
if (newVal) {
this.resetForm();
this.getVendorList();
}
},
internalVisible(newVal) {
this.$emit("update:visible", newVal);
}
},
methods: {
/** 获取厂商列表 */
getVendorList() {
return listAllVendor().then(res => {
this.vendorOptions = res.data;
})
},
handlePriceChange() {
const taxed = this.form.totalPriceWithTax || 0;
const rate = this.form.taxRate || 0;
const unTaxed = this.$calc.div(taxed, this.$calc.add(1, rate));
const taxAmount = this.$calc.sub(taxed, unTaxed);
this.form.totalPriceWithoutTax = unTaxed;
this.form.taxAmount = taxAmount;
},
handleClose() {
this.internalVisible = false;
},
handleSubmit() {
this.$refs.form.validate(valid => {
if (valid) {
// Here you would typically emit an event to the parent component
// to handle the form submission, e.g., call an API.
this.form.preResidueAmount=0;
this.$emit("submit", this.form);
}
});
},
resetForm() {
if (this.$refs.form) {
this.$refs.form.resetFields();
}
this.form = {
paymentBillType: 'PRE_PAYMENT',
vendorName: null,
paymentTime: null,
totalPriceWithTax: 0,
taxRate: 0.13,
totalPriceWithoutTax: 0,
taxAmount: 0
};
this.handlePriceChange();
}
}
};
</script>

View File

@ -12,83 +12,83 @@
<div class="details-container">
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>采购-付款单编号:</strong> {{ detail.paymentBillCode }}</div>
<div class="detail-item">采购-付款单编号: {{ detail.paymentBillCode }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>预计付款时间:</strong> {{ detail.paymentTime }}</div>
<div class="detail-item">预计付款时间: {{ detail.paymentTime }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>制造商名称:</strong> {{ detail.vendorName }}</div>
<div class="detail-item">制造商名称: {{ detail.vendorName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>含税总价:</strong> {{ detail.totalPriceWithTax }}</div>
<div class="detail-item">含税总价: {{ detail.totalPriceWithTax }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>未税总价:</strong> {{ detail.totalPriceWithoutTax }}</div>
<div class="detail-item">未税总价: {{ detail.totalPriceWithoutTax }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>税额:</strong> {{ detail.taxAmount }}</div>
<div class="detail-item">税额: {{ detail.taxAmount }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>付款单类型:</strong>
<div class="detail-item">付款单类型:
<dict-tag :options="dict.type.payment_bill_type" :value="detail.paymentBillType"/>
</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>预付单剩余额度:</strong> {{ detail.preResidueAmount || '-' }}</div>
<div class="detail-item">预付单剩余额度: {{ detail.preResidueAmount || '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>财务付款时间:</strong> {{ detail.actualPaymentTime || '-'}}</div>
<div class="detail-item">财务付款时间: {{ detail.actualPaymentTime || '-'}}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>支付方式:</strong> {{ detail.paymentMethod || '-'}}</div>
<div class="detail-item">支付方式: {{ detail.paymentMethod || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>回执单/退款图:</strong> {{ detail.receiptAttachmentId }} /
<div class="detail-item">回执单/退款图: {{ detail.receiptAttachmentId }} /
{{ detail.refundProofAttachmentId }}
</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>付款状态:</strong> {{ detail.paymentStatus }}</div>
<div class="detail-item">付款状态: {{ detail.paymentStatus }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>备注:</strong> {{ detail.remark }}</div>
<div class="detail-item">备注: {{ detail.remark }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>上传人姓名:</strong> {{ detail.createBy }}</div>
<div class="detail-item">上传人姓名: {{ detail.createBy }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>账户名称:</strong> {{ detail.payName }}</div>
<div class="detail-item">账户名称: {{ detail.payName }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>银行账号:</strong> {{ detail.payBankNumber }}</div>
<div class="detail-item">银行账号: {{ detail.payBankNumber }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>银行开户行:</strong> {{ detail.payBankOpenAddress }}</div>
<div class="detail-item">银行开户行: {{ detail.payBankOpenAddress }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>银行行号:</strong> {{ detail.bankNumber }}</div>
<div class="detail-item">银行行号: {{ detail.bankNumber }}</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="detail-item"><strong>审批节点:</strong> {{ detail.approveNode|| '-' }}</div>
<div class="detail-item">审批节点: {{ detail.approveNode|| '-' }}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>审批状态:</strong> {{ detail.approveStatus || '-'}}</div>
<div class="detail-item">审批状态: {{ detail.approveStatus || '-'}}</div>
</el-col>
<el-col :span="8">
<div class="detail-item"><strong>审批通过时间:</strong> {{ detail.approveTime || '-'}}</div>
<div class="detail-item">审批通过时间: {{ detail.approveTime || '-'}}</div>
</el-col>
</el-row>
</div>

View File

@ -175,6 +175,7 @@
size="mini"
type="text"
icon="el-icon-refresh-left"
v-show="scope.row.approveStatus=='1'"
@click="handleReturn(scope.row)"
>退回</el-button>
</template>
@ -191,18 +192,22 @@
<!-- 详情抽屉 -->
<detail-drawer :visible.sync="detailOpen" :detail="detailData"></detail-drawer>
<!-- 新增弹窗 -->
<add-form :visible.sync="addOpen" :dicts="dict.type" @submit="handleAddSubmit"></add-form>
</div>
</template>
<script>
import { listPayment, getPayment, returnPayment } from "@/api/finance/payment";
import { listPayment, getPayment, returnPayment, addPayment } from "@/api/finance/payment";
import { addDateRange } from "@/utils/ruoyi";
import DetailDrawer from "./components/DetailDrawer.vue";
import AddForm from "./components/AddForm.vue";
export default {
name: "Payment",
components: {
DetailDrawer
DetailDrawer,
AddForm
},
dicts:['payment_bill_type','approve_status','payment_status'],
data() {
@ -237,6 +242,8 @@ export default {
//
detailOpen: false,
detailData: null,
//
addOpen: false,
};
},
created() {
@ -273,8 +280,18 @@ export default {
},
/** 新增按钮操作 */
handleAdd() {
//
console.log("handleAdd");
this.addOpen = true;
},
/** 新增提交 */
handleAddSubmit(form) {
addPayment(form).then(response => {
this.$modal.msgSuccess("新增成功");
this.addOpen = false;
this.getList();
}).catch(error => {
console.error("新增付款单失败", error);
this.$modal.msgError("新增失败");
});
},
/** 详情按钮操作 */
handleDetail(row) {

View File

@ -99,6 +99,14 @@ public class OmsPayableBillController extends BaseController
{
return toAjax(omsPayableBillService.insertOmsPayableBill(omsPayableBill));
}
@RequiresPermissions("finance:payable:query")
@Log(title = "采购应付单", businessType = BusinessType.INSERT)
@GetMapping("/{id}")
@ResponseBody
public AjaxResult query(@PathVariable("id") Long id)
{
return AjaxResult.success(omsPayableBillService.query(id));
}
/**
*

View File

@ -82,7 +82,7 @@ public class OmsPaymentBillController extends BaseController
@Log(title = "采购付款单", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(OmsPaymentBill omsPaymentBill)
public AjaxResult addSave(@RequestBody OmsPaymentBill omsPaymentBill)
{
return toAjax(omsPaymentBillService.insertOmsPaymentBill(omsPaymentBill));
}

View File

@ -2,6 +2,8 @@ package com.ruoyi.sip.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -80,4 +82,15 @@ public class OmsPayableBill extends BaseEntity
private Date estimatedPaymentTimeEnd;
private Date estimatedPaymentTimeStart;
private BigDecimal unpaidAmount;
private BigDecimal paidAmount;
private BigDecimal payingAmount;
private List<OmsPayablePaymentDetail> detailList;
}

View File

@ -2,9 +2,11 @@ package com.ruoyi.sip.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.Getter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class OmsPayablePaymentDetail extends BaseEntity {
@ -13,11 +15,38 @@ public class OmsPayablePaymentDetail extends BaseEntity {
private Long id;
private Long paymentPlanId;
private Long payableBillId;
private Integer paymentStatus;
private List<Long> payableBillIdList;
private String payableDetailType;
private Date paymentTime;
private BigDecimal paymentAmount;
private BigDecimal paymentRate;
private String paymentBillCode;
private Date actualPaymentTime;
private String paymentStatus;
/** 回执单附件ID */
private Long receiptAttachmentId;
/** 退款图附件ID */
private Long refundProofAttachmentId;
@Getter
public enum PayableDetailTypeEnum {
/** 待确认 */
APPLY_PAYMENT("1", "申请付款"),
/** 已确认 */
PREPAY_WRITE_OFF("2", "预付核销"),
REFUND("3", "退款"),
;
private final String code;
private final String desc;
PayableDetailTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
}

View File

@ -1,45 +1,17 @@
package com.ruoyi.sip.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class MergedPaymentDataDto {
private String paymentBillType;
private Date estimatedPaymentTime;
private List<PayableOrderDto> payableOrders;
private BigDecimal totalMergePaymentAmount;
// Getters and Setters
public String getPaymentBillType() {
return paymentBillType;
}
public void setPaymentBillType(String paymentBillType) {
this.paymentBillType = paymentBillType;
}
public Date getEstimatedPaymentTime() {
return estimatedPaymentTime;
}
public void setEstimatedPaymentTime(Date estimatedPaymentTime) {
this.estimatedPaymentTime = estimatedPaymentTime;
}
public List<PayableOrderDto> getPayableOrders() {
return payableOrders;
}
public void setPayableOrders(List<PayableOrderDto> payableOrders) {
this.payableOrders = payableOrders;
}
public BigDecimal getTotalMergePaymentAmount() {
return totalMergePaymentAmount;
}
public void setTotalMergePaymentAmount(BigDecimal totalMergePaymentAmount) {
this.totalMergePaymentAmount = totalMergePaymentAmount;
}
}

View File

@ -5,4 +5,7 @@ import java.util.List;
public interface OmsPayablePaymentDetailMapper {
public int insertOmsPayablePaymentDetail(OmsPayablePaymentDetail omsPayablePaymentDetail);
List<OmsPayablePaymentDetail> list(OmsPayablePaymentDetail omsPayablePaymentDetail);
}

View File

@ -68,4 +68,6 @@ public interface IOmsPayableBillService
* @return
*/
public int mergeAndInitiatePayment(MergedPaymentDataDto dto);
OmsPayableBill query(Long id);
}

View File

@ -2,6 +2,10 @@ package com.ruoyi.sip.service;
import com.ruoyi.sip.domain.OmsPayablePaymentDetail;
import java.util.List;
public interface IOmsPayablePaymentDetailService {
public int insertOmsPayablePaymentDetail(OmsPayablePaymentDetail omsPayablePaymentDetail);
public List<OmsPayablePaymentDetail> listByPayableBillId(Long payableBillId);
public List<OmsPayablePaymentDetail> listByPayableBillIdList(List<Long> payableBillId);
}

View File

@ -1,12 +1,19 @@
package com.ruoyi.sip.service.impl;
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;
@ -20,7 +27,6 @@ import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.sip.domain.dto.MergedPaymentDataDto;
import com.ruoyi.sip.domain.dto.PayableOrderDto;
import com.ruoyi.sip.domain.dto.PaymentPlanDto;
import com.ruoyi.sip.service.IOmsPaymentBillService;
import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
@ -65,7 +71,30 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
*/
@Override
public List<OmsPayableBill> selectOmsPayableBillList(OmsPayableBill omsPayableBill) {
return omsPayableBillMapper.selectOmsPayableBillList(omsPayableBill);
List<OmsPayableBill> omsPayableBills = omsPayableBillMapper.selectOmsPayableBillList(omsPayableBill);
PageUtils.clearPage();
if (CollUtil.isNotEmpty(omsPayableBills)){
List<Long> idList = omsPayableBills.stream().map(OmsPayableBill::getId).distinct().collect(Collectors.toList());
List<OmsPayablePaymentDetail> omsPayablePaymentDetails = omsPayablePaymentDetailService.listByPayableBillIdList(idList);
Map<Long, Map<String, BigDecimal>> planMap = omsPayablePaymentDetails.stream().collect(Collectors.groupingBy(OmsPayablePaymentDetail::getPayableBillId,
Collectors.groupingBy(
OmsPayablePaymentDetail::getPaymentStatus,
Collectors.reducing(
BigDecimal.ZERO,
detail -> (detail.getPaymentAmount() != null) ? detail.getPaymentAmount() : BigDecimal.ZERO,
BigDecimal::add
))));
omsPayableBills.forEach(bill -> {
bill.setPaidAmount(planMap.getOrDefault(bill.getId(), Collections.emptyMap()).getOrDefault(OmsPaymentBill.PaymentStatusEnum.PAYMENT.getCode(), BigDecimal.ZERO));
bill.setPayingAmount(planMap.getOrDefault(bill.getId(), Collections.emptyMap()).getOrDefault(OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode(), BigDecimal.ZERO));
bill.setUnpaidAmount(bill.getTotalPriceWithTax().subtract(bill.getPaidAmount()).subtract(bill.getPayingAmount()));
});
}
return omsPayableBills;
}
/**
@ -159,20 +188,11 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
// 1. 创建付款单
OmsPaymentBill paymentBill = new OmsPaymentBill();
OmsPayableBill firstPayableBill = omsPayableBillMapper.selectOmsPayableBillById(dto.getPayableOrders().get(0).getId());
VendorInfo vendorInfo = vendorInfoService.selectVendorInfoByVendorCode(firstPayableBill.getVendorCode());
paymentBill.setPaymentBillType(dto.getPaymentBillType());
paymentBill.setVendorCode(firstPayableBill.getVendorCode());
paymentBill.setPaymentTime(dto.getEstimatedPaymentTime());
paymentBill.setTotalPriceWithTax(dto.getTotalMergePaymentAmount());
paymentBill.setVendorCode(firstPayableBill.getVendorCode());
paymentBill.setCreateBy(ShiroUtils.getUserId().toString());
paymentBill.setPaymentBillType(OmsPaymentBill.PaymentBillTypeEnum.FROM_PAYABLE.getCode());
paymentBill.setPayName(vendorInfo.getPayName());
paymentBill.setPayBankNumber(vendorInfo.getPayBankNumber());
paymentBill.setPayBankOpenAddress(vendorInfo.getPayBankOpenAddress());
paymentBill.setBankNumber(vendorInfo.getBankNumber());
paymentBill.setPaymentStatus(OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode());
paymentBill.setApproveStatus(ApproveStatusEnum.WAIT_APPROVE.getCode());
omsPaymentBillService.insertOmsPaymentBill(paymentBill);
// 2. 创建付款明细
@ -187,6 +207,7 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
detail.setPaymentTime(paymentPlanDto.getPlanPaymentDate());
detail.setRemark(paymentPlanDto.getRemark());
detail.setCreateBy(ShiroUtils.getLoginName());
detail.setPayableDetailType(OmsPayablePaymentDetail.PayableDetailTypeEnum.APPLY_PAYMENT.getCode());
omsPayablePaymentDetailService.insertOmsPayablePaymentDetail(detail);
}
@ -199,5 +220,29 @@ public class OmsPayableBillServiceImpl implements IOmsPayableBillService {
return 1;
}
@Override
public OmsPayableBill query(Long id) {
OmsPayableBill omsPayableBill = selectOmsPayableBillById(id);
List<OmsPayablePaymentDetail> omsPayablePaymentDetails = omsPayablePaymentDetailService.listByPayableBillId(id);
omsPayableBill.setDetailList(omsPayablePaymentDetails);
Map<String, BigDecimal> decimalMap = omsPayablePaymentDetails.stream()
.collect(Collectors.groupingBy(
OmsPayablePaymentDetail::getPaymentStatus,
Collectors.reducing(
BigDecimal.ZERO,
detail -> (detail.getPaymentAmount() != null) ? detail.getPaymentAmount() : BigDecimal.ZERO,
BigDecimal::add
)
));
omsPayableBill.setPaidAmount(decimalMap.getOrDefault(OmsPaymentBill.PaymentStatusEnum.PAYMENT.getCode(), BigDecimal.ZERO));
omsPayableBill.setPayingAmount(decimalMap.getOrDefault(OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode(), BigDecimal.ZERO));
BigDecimal decimal = omsPayableBill.getTotalPriceWithTax().subtract(omsPayableBill.getPaidAmount()).subtract(omsPayableBill.getPayingAmount());
omsPayableBill.setUnpaidAmount(decimal);
return omsPayableBill;
}
}

View File

@ -6,6 +6,9 @@ import com.ruoyi.sip.service.IOmsPayablePaymentDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
public class OmsPayablePaymentDetailServiceImpl implements IOmsPayablePaymentDetailService {
@ -16,4 +19,18 @@ public class OmsPayablePaymentDetailServiceImpl implements IOmsPayablePaymentDet
public int insertOmsPayablePaymentDetail(OmsPayablePaymentDetail omsPayablePaymentDetail) {
return omsPayablePaymentDetailMapper.insertOmsPayablePaymentDetail(omsPayablePaymentDetail);
}
@Override
public List<OmsPayablePaymentDetail> listByPayableBillId(Long payableBillId) {
OmsPayablePaymentDetail omsPayablePaymentDetail = new OmsPayablePaymentDetail();
omsPayablePaymentDetail.setPayableBillId(payableBillId);
return omsPayablePaymentDetailMapper.list(omsPayablePaymentDetail);
}
@Override
public List<OmsPayablePaymentDetail> listByPayableBillIdList(List<Long> payableBillId) {
OmsPayablePaymentDetail omsPayablePaymentDetail = new OmsPayablePaymentDetail();
omsPayablePaymentDetail.setPayableBillIdList(payableBillId);
return omsPayablePaymentDetailMapper.list(omsPayablePaymentDetail);
}
}

View File

@ -6,9 +6,13 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.ApproveStatusEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.sip.domain.OmsPayableBill;
import com.ruoyi.sip.domain.VendorInfo;
import com.ruoyi.sip.domain.dto.PaymentBillDetailDTO;
import com.ruoyi.sip.service.IVendorInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.sip.mapper.OmsPaymentBillMapper;
@ -30,6 +34,8 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService
@Autowired
private OmsPaymentBillMapper omsPaymentBillMapper;
@Autowired
private IVendorInfoService vendorInfoService;
/**
*
*
@ -63,6 +69,14 @@ public class OmsPaymentBillServiceImpl implements IOmsPaymentBillService
@Override
public int insertOmsPaymentBill(OmsPaymentBill omsPaymentBill)
{
VendorInfo vendorInfo = vendorInfoService.selectVendorInfoByVendorCode(omsPaymentBill.getVendorCode());
omsPaymentBill.setCreateBy(ShiroUtils.getUserId().toString());
omsPaymentBill.setPayName(vendorInfo.getPayName());
omsPaymentBill.setPayBankNumber(vendorInfo.getPayBankNumber());
omsPaymentBill.setPayBankOpenAddress(vendorInfo.getPayBankOpenAddress());
omsPaymentBill.setBankNumber(vendorInfo.getBankNumber());
omsPaymentBill.setPaymentStatus(OmsPaymentBill.PaymentStatusEnum.WAIT_PAYMENT.getCode());
omsPaymentBill.setApproveStatus(ApproveStatusEnum.WAIT_COMMIT.getCode());
omsPaymentBill.setPaymentBillCode(generatePaymentBillCode());
omsPaymentBill.setCreateTime(DateUtils.getNowDate());
return omsPaymentBillMapper.insertOmsPaymentBill(omsPaymentBill);

View File

@ -101,8 +101,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectOmsPayableBillById" parameterType="Long" resultMap="OmsPayableBillResult">
<include refid="selectOmsPayableBillVo"/>
where id = #{id}
<include refid="selectOmsPayableBillRelationVo"/>
where t1.id = #{id}
</select>
<select id="selectMaxCodeByPrefix" resultType="java.lang.Integer">
select ifnull(max(SUBSTR(payable_bill_code FROM LENGTH(#{prefix}) + 1 FOR 4)), 0)

View File

@ -44,5 +44,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
sysdate()
)
</insert>
<select id="list" resultType="com.ruoyi.sip.domain.OmsPayablePaymentDetail">
SELECT
t1.*,t2.payment_bill_code,t2.actual_payment_time,t2.payment_status,t2.refund_proof_attachment_id,t2.receipt_attachment_id
FROM
oms_payable_payment_detail t1
LEFT JOIN oms_payment_bill t2 ON t1.payment_bill_code = t2.payment_bill_code
<where>
<if test="payableBillId != null">
and t1.payable_bill_id = #{payableBillId}
</if>
<if test="payableBillIdList != null and payableBillIdList.size>0">
and t1.payable_bill_id in
<foreach item="item" collection="payableBillIdList" separator="," open="(" close=")" index="">
#{item}
</foreach>
</if>
</where>
</select>
</mapper>