1、调整业务模块
This commit is contained in:
@@ -247,13 +247,14 @@
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="recalculateLine(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="含税金额" min-width="130">
|
||||
<el-table-column label="不含税单价" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.amountWithTax"
|
||||
v-model="row.unitPriceNoTax"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
@@ -262,6 +263,9 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="不含税金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.amountNoTax) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税率" min-width="110">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
@@ -281,6 +285,9 @@
|
||||
<el-table-column label="税额" min-width="120">
|
||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.totalAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" :disabled="readonly" maxlength="200" />
|
||||
@@ -374,6 +381,7 @@
|
||||
<span v-else-if="column.prop === 'transportType'">{{
|
||||
transportTypeLabel(row.transportType)
|
||||
}}</span>
|
||||
<span v-else-if="column.dateTime">{{ formatDateTime(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -492,7 +500,9 @@ const emptyLine = () => ({
|
||||
unit: '吨',
|
||||
quantity: 0,
|
||||
unitPriceNoTax: 0,
|
||||
amountNoTax: 0,
|
||||
amountWithTax: 0,
|
||||
totalAmount: 0,
|
||||
taxRate: 0,
|
||||
taxAmount: 0,
|
||||
remark: '',
|
||||
@@ -598,7 +608,7 @@ export default {
|
||||
return this.form.sheets.reduce(
|
||||
(sheetTotal, sheet) =>
|
||||
sheetTotal +
|
||||
sheet.lines.reduce((lineTotal, line) => lineTotal + Number(line.amountWithTax || 0), 0),
|
||||
sheet.lines.reduce((lineTotal, line) => lineTotal + Number(line.totalAmount || 0), 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
@@ -714,11 +724,11 @@ export default {
|
||||
const item = this.findInvoiceItem(line);
|
||||
if (item) {
|
||||
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||
this.recalculateLine(line);
|
||||
} else if (clearInvalid) {
|
||||
line.goodsName = '';
|
||||
line.taxRate = 0;
|
||||
}
|
||||
this.recalculateLine(line);
|
||||
},
|
||||
handleGoodsCategoryChange(line) {
|
||||
const names = this.invoiceItemNames(line.goodsCategory);
|
||||
@@ -1067,9 +1077,14 @@ export default {
|
||||
sheet.lines.push(emptyLine());
|
||||
},
|
||||
recalculateLine(row) {
|
||||
const amount = Number(row.amountWithTax || 0);
|
||||
const quantity = Number(row.quantity || 0);
|
||||
const unitPrice = Number(row.unitPriceNoTax || 0);
|
||||
const amountNoTax = Number((quantity * unitPrice).toFixed(2));
|
||||
const rate = Number(row.taxRate || 0) / 100;
|
||||
row.taxAmount = rate ? Number((amount - amount / (1 + rate)).toFixed(2)) : 0;
|
||||
row.amountNoTax = amountNoTax;
|
||||
row.taxAmount = rate ? Number((amountNoTax * rate).toFixed(2)) : 0;
|
||||
row.totalAmount = Number((amountNoTax + row.taxAmount).toFixed(2));
|
||||
row.amountWithTax = row.totalAmount;
|
||||
},
|
||||
validateBusiness() {
|
||||
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
||||
@@ -1082,7 +1097,7 @@ export default {
|
||||
for (const line of sheet.lines) {
|
||||
if (!line.goodsCategory || !line.goodsName) throw new Error('请完整填写商品和服务信息');
|
||||
if (
|
||||
[line.quantity, line.unitPriceNoTax, line.amountWithTax, line.taxRate].some(
|
||||
[line.quantity, line.unitPriceNoTax, line.amountNoTax, line.taxRate].some(
|
||||
Number.isNaN
|
||||
)
|
||||
) {
|
||||
@@ -1121,7 +1136,9 @@ export default {
|
||||
unit: line.unit,
|
||||
quantity: line.quantity,
|
||||
unitPriceNoTax: line.unitPriceNoTax,
|
||||
amountWithTax: line.amountWithTax,
|
||||
amountNoTax: line.amountNoTax,
|
||||
totalAmount: line.totalAmount,
|
||||
amountWithTax: line.totalAmount,
|
||||
taxRate: line.taxRate,
|
||||
taxAmount: line.taxAmount,
|
||||
remark: line.remark,
|
||||
@@ -1169,6 +1186,10 @@ export default {
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatDateTime(value) {
|
||||
if (!value) return '-';
|
||||
return this.$dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
|
||||
@@ -654,7 +654,7 @@ export default {
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
settlementId: item.id,
|
||||
allocatedInvoiceAmount: 0,
|
||||
allocatedInvoiceAmount: rows.length === 1 ? Number(item.settlementAmount || 0) : 0,
|
||||
}));
|
||||
this.form.projectName = first.projectName;
|
||||
this.form.deptName = first.deptName;
|
||||
|
||||
@@ -140,23 +140,14 @@
|
||||
></el-col>
|
||||
<el-col v-if="billPayment" :span="6"
|
||||
><el-form-item label="汇票单号" prop="billLedgerId"
|
||||
><el-select
|
||||
v-model="form.billLedgerId"
|
||||
><el-input
|
||||
v-model="form.billNo"
|
||||
readonly
|
||||
:disabled="readonly"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="loadBillOptions"
|
||||
:loading="billLoading"
|
||||
placeholder="请选择可用汇票"
|
||||
@change="handleBillChange"
|
||||
><el-option
|
||||
v-for="item in billOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.billNo}|余额${formatMoney(item.availableBalance)}|${
|
||||
item.maturityDate
|
||||
}`"
|
||||
:value="item.id" /></el-select></el-form-item
|
||||
><template #append
|
||||
><el-button :disabled="readonly" @click="openBillDialog">选择</el-button></template
|
||||
></el-input></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="6"
|
||||
><el-form-item label="收款方"
|
||||
@@ -501,6 +492,54 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs></div
|
||||
></el-dialog>
|
||||
<el-dialog v-model="billDialogVisible" title="选择汇票" width="80%" append-to-body>
|
||||
<div class="payment-form-page__reference-search">
|
||||
<el-form :model="billQuery" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="payment-form-page__reference-search-fields">
|
||||
<el-form-item label="汇票筛选">
|
||||
<el-input
|
||||
v-model="billQuery.keyword"
|
||||
clearable
|
||||
placeholder="请输入汇票号、出票单位或收票单位"
|
||||
@keyup.enter="handleBillSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="payment-form-page__reference-search-actions">
|
||||
<el-button type="primary" @click="handleBillSearch">搜索</el-button>
|
||||
<el-button @click="resetBillSearch">清空</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-table v-loading="billPage.loading" :data="billRows" border>
|
||||
<el-table-column prop="billNo" label="汇票单号" min-width="180" />
|
||||
<el-table-column prop="issuerName" label="出票单位" min-width="160" />
|
||||
<el-table-column prop="receiverName" label="收票单位" min-width="160" />
|
||||
<el-table-column prop="faceAmount" label="票面金额" min-width="120">
|
||||
<template #default="{ row }">{{ formatMoney(row.faceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="availableBalance" label="可用余额" min-width="120">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="maturityDate" label="到期日期" min-width="120" />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="selectBill(row)">选择</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="payment-form-page__reference-pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="billPage.current"
|
||||
v-model:page-size="billPage.size"
|
||||
:total="billPage.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadBillPage"
|
||||
@size-change="handleBillSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="attachmentDocumentPreviewVisible"
|
||||
:title="attachmentPreviewFile.name || '附件预览'"
|
||||
@@ -552,6 +591,7 @@ import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/paymentApplication';
|
||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||
@@ -674,6 +714,17 @@ export default {
|
||||
contractRows: [],
|
||||
billOptions: [],
|
||||
billLoading: false,
|
||||
billDialogVisible: false,
|
||||
billQuery: {
|
||||
keyword: '',
|
||||
},
|
||||
billRows: [],
|
||||
billPage: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
},
|
||||
receiptAccountOptions: [],
|
||||
receiptAccountLoading: false,
|
||||
firstContractPayment: false,
|
||||
@@ -696,7 +747,7 @@ export default {
|
||||
zoom: true,
|
||||
},
|
||||
paymentTypeOptions: api.paymentTypeOptions,
|
||||
paymentMethodOptions: api.paymentMethodOptions,
|
||||
paymentMethodOptions: [],
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
@@ -740,7 +791,7 @@ export default {
|
||||
return this.hasPermission(code);
|
||||
},
|
||||
billPayment() {
|
||||
return this.form.paymentMethod !== 'bank_transfer';
|
||||
return this.isBillPaymentMethod();
|
||||
},
|
||||
paymentAmountBase() {
|
||||
if (this.form.paymentType === 'project_advance') return 0;
|
||||
@@ -976,6 +1027,7 @@ export default {
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
await this.loadPaymentMethodOptions();
|
||||
if (this.recordId) {
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
@@ -1020,6 +1072,37 @@ export default {
|
||||
? paymentType
|
||||
: 'project_advance';
|
||||
},
|
||||
async loadPaymentMethodOptions() {
|
||||
try {
|
||||
const data = this.unwrapData(await getDictionary({ code: 'pay_method' }));
|
||||
const records = Array.isArray(data) ? data : data?.records || [];
|
||||
this.paymentMethodOptions = records
|
||||
.map(item => ({
|
||||
...item,
|
||||
label: item.dictValue || item.label || item.name || item.dictKey || item.value,
|
||||
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||
}))
|
||||
.filter(item => item.label && item.value);
|
||||
} catch (error) {
|
||||
this.paymentMethodOptions = [];
|
||||
this.$message.warning('付款方式字典加载失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
isBillPaymentMethod(value = this.form.paymentMethod) {
|
||||
const selected = this.paymentMethodOptions.find(
|
||||
item => String(item.value) === String(value)
|
||||
);
|
||||
const searchableText = [
|
||||
selected?.dictKey,
|
||||
selected?.dictValue,
|
||||
selected?.label,
|
||||
selected?.name,
|
||||
selected?.value,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
return searchableText.includes('汇票');
|
||||
},
|
||||
getTransferredPreSettlementIds() {
|
||||
const payloadIds = (this.transferPayload?.sourcePreSettlements || []).map(
|
||||
item => item.preSettlementId || item.id
|
||||
@@ -1566,14 +1649,13 @@ export default {
|
||||
}
|
||||
this.contractPaymentRatioExceeded = exceeded;
|
||||
},
|
||||
validateRequiredAttachments() {
|
||||
if (!this.missingAttachmentMaterials.length) return true;
|
||||
notifyMissingAttachments() {
|
||||
if (!this.missingAttachmentMaterials.length) return;
|
||||
this.$message.warning(
|
||||
`请先上传付款申请所需材料:${this.missingAttachmentMaterials
|
||||
`付款申请所需材料未上传,请核对:${this.missingAttachmentMaterials
|
||||
.map(item => item.label)
|
||||
.join('、')}`
|
||||
);
|
||||
return false;
|
||||
},
|
||||
handleTypeChange() {
|
||||
this.form.invoices = [];
|
||||
@@ -1648,20 +1730,10 @@ export default {
|
||||
try {
|
||||
const response = await formalApi.getContractOptions('', projectId);
|
||||
this.contractOptions = (this.unwrapData(response) || []).filter(
|
||||
item => String(item.projectId || '') === String(projectId)
|
||||
item =>
|
||||
String(item.projectId || '') === String(projectId) &&
|
||||
String(item.contractCategory || '').trim() === '承运商合同'
|
||||
);
|
||||
if (
|
||||
this.form.contractId &&
|
||||
this.form.contractName &&
|
||||
!this.contractOptions.some(item => String(item.id) === String(this.form.contractId))
|
||||
) {
|
||||
this.contractOptions.unshift({
|
||||
id: this.form.contractId,
|
||||
contractNo: this.form.contractNo,
|
||||
contractName: this.form.contractName,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.contractOptions = [];
|
||||
this.$message.warning('项目合同加载失败,请稍后重试');
|
||||
@@ -1807,6 +1879,8 @@ export default {
|
||||
this.form.billLedgerId = null;
|
||||
this.form.billNo = '';
|
||||
this.billOptions = [];
|
||||
this.billRows = [];
|
||||
this.billDialogVisible = false;
|
||||
return;
|
||||
}
|
||||
await this.loadBillOptions();
|
||||
@@ -1823,10 +1897,49 @@ export default {
|
||||
this.billLoading = false;
|
||||
}
|
||||
},
|
||||
handleBillChange(id) {
|
||||
const selected = this.billOptions.find(item => String(item.id) === String(id));
|
||||
this.form.billNo = selected?.billNo || '';
|
||||
this.$refs.formRef?.validateField('billLedgerId').catch(() => {});
|
||||
async openBillDialog() {
|
||||
if (this.readonly || !this.billPayment) return;
|
||||
this.billDialogVisible = true;
|
||||
this.billQuery.keyword = '';
|
||||
this.billPage.current = 1;
|
||||
await this.loadBillPage();
|
||||
},
|
||||
async loadBillPage() {
|
||||
if (!this.billPayment) return;
|
||||
this.billPage.loading = true;
|
||||
try {
|
||||
const response = await billLedgerApi.getAvailablePage(
|
||||
this.billPage.current,
|
||||
this.billPage.size,
|
||||
String(this.billQuery.keyword || '').trim() || undefined,
|
||||
this.form.deptId,
|
||||
this.form.billLedgerId
|
||||
);
|
||||
const data = this.unwrapData(response) || {};
|
||||
this.billRows = data.records || [];
|
||||
this.billPage.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.billPage.loading = false;
|
||||
}
|
||||
},
|
||||
handleBillSearch() {
|
||||
this.billPage.current = 1;
|
||||
this.loadBillPage();
|
||||
},
|
||||
resetBillSearch() {
|
||||
this.billQuery.keyword = '';
|
||||
this.handleBillSearch();
|
||||
},
|
||||
handleBillSizeChange() {
|
||||
this.billPage.current = 1;
|
||||
this.loadBillPage();
|
||||
},
|
||||
selectBill(row) {
|
||||
this.form.billLedgerId = row.id;
|
||||
this.form.billNo = row.billNo || '';
|
||||
this.billOptions = [row];
|
||||
this.billDialogVisible = false;
|
||||
this.$nextTick(() => this.$refs.formRef?.validateField('billLedgerId').catch(() => {}));
|
||||
},
|
||||
referenceParams(type) {
|
||||
const { settlementNo, projectName, contractName } = this.referenceQuery;
|
||||
@@ -2200,7 +2313,7 @@ export default {
|
||||
this.$message.warning('请选择所属项目');
|
||||
return false;
|
||||
}
|
||||
if (validateMaterials && !this.validateRequiredAttachments()) return false;
|
||||
if (validateMaterials) this.notifyMissingAttachments();
|
||||
try {
|
||||
this.validateInvoices();
|
||||
this.validatePaymentRecords();
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<basic-container class="receipt-claim-batch-form-page">
|
||||
<div class="archive-page-form__title">批量认领收款流水</div>
|
||||
<el-form ref="formRef" :model="form" class="receipt-claim-batch-form-page__form">
|
||||
<section-card title="收款信息">
|
||||
<el-table v-loading="loading" :data="flows" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
prop="receiptNoticeNo"
|
||||
label="认领通知单"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="payerName" label="付款人" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="收款金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.receiptAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transactionTime" label="交易时间" min-width="170" />
|
||||
<el-table-column
|
||||
prop="counterpartyName"
|
||||
label="对方户名"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="counterpartyBank"
|
||||
label="对方开户行"
|
||||
min-width="170"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="counterpartyAccount"
|
||||
label="对方账号"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="detailSerialNo"
|
||||
label="明细流水号"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="已认领金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.claimedAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="summary" label="摘要" min-width="180" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-row :gutter="32" class="receipt-claim-batch-form-page__claim-info">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="认领人">
|
||||
<el-input v-model="claimerName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="认领日期">
|
||||
<el-input v-model="claimDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="认领人部门">
|
||||
<el-input v-model="claimerDeptName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section-card>
|
||||
|
||||
<section-card title="结算信息">
|
||||
<div class="receipt-claim-batch-form-page__settlement-toolbar">
|
||||
<el-button type="primary" @click="openSettlementDialog">选择结算单</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="allocationRows"
|
||||
border
|
||||
show-summary
|
||||
:summary-method="summaryMethod"
|
||||
:span-method="settlementSpanMethod"
|
||||
empty-text="请先选择应收正式结算单"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="结算单号" min-width="180">
|
||||
<template #default="{ row }">{{ row.settlement.formalSettlementNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{
|
||||
formatMoney(row.settlement.settlementAmount)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{
|
||||
formatMoney(row.settlement.claimedReceiptAmount)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="认领通知单" min-width="180">
|
||||
<template #default="{ row }">{{ row.flow.receiptNoticeNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.flow.receiptAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊收款金额" min-width="180" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.settlement.allocations[row.flow.id]"
|
||||
:min="0"
|
||||
:max="maxAllocation(row)"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
@change="handleAllocationChange(row.settlement)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件信息">
|
||||
<vehicle-attachment-table v-model="form.attachments" />
|
||||
</section-card>
|
||||
|
||||
<section-card title="备注">
|
||||
<el-form-item class="receipt-claim-batch-form-page__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</section-card>
|
||||
|
||||
<div class="receipt-claim-batch-form-page__actions">
|
||||
<el-button :disabled="submitting" @click="goBack">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitClaim">确认</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-dialog
|
||||
v-model="settlementDialog.visible"
|
||||
title="选择应收正式结算单"
|
||||
width="86%"
|
||||
append-to-body
|
||||
>
|
||||
<div class="receipt-claim-batch-form-page__dialog-search">
|
||||
<el-input
|
||||
v-model="settlementDialog.keyword"
|
||||
clearable
|
||||
placeholder="结算单号、项目或合同"
|
||||
@keyup.enter="loadSettlementCandidates"
|
||||
/>
|
||||
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
||||
</div>
|
||||
<el-table v-loading="settlementDialog.loading" :data="settlementDialog.rows" border>
|
||||
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.claimedReceiptAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="selectSettlement(row)">选择</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
|
||||
const emptyForm = () => ({ attachments: [], remark: '' });
|
||||
|
||||
export default {
|
||||
name: 'ReceiptFlowBatchForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
data() {
|
||||
return {
|
||||
form: emptyForm(),
|
||||
flows: [],
|
||||
settlements: [],
|
||||
loading: false,
|
||||
submitting: false,
|
||||
claimerName: '',
|
||||
claimerDeptName: '',
|
||||
claimDate: '',
|
||||
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
this.claimerName = userInfo.realName || userInfo.userName || '';
|
||||
this.claimerDeptName = userInfo.deptName || userInfo.dept_name || '';
|
||||
this.claimDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
this.initialize();
|
||||
},
|
||||
computed: {
|
||||
allocationRows() {
|
||||
return this.settlements.flatMap(settlement => this.flows.map(flow => ({ settlement, flow })));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
const ids = [
|
||||
...new Set(
|
||||
String(this.$route.query.ids || '')
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
),
|
||||
];
|
||||
if (!ids.length) {
|
||||
this.$message.error('缺少批量认领流水');
|
||||
this.goBack();
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const details = await Promise.all(ids.map(id => api.getDetail(id)));
|
||||
this.flows = details.map(item => this.unwrapData(item)).filter(Boolean);
|
||||
if (!this.flows.length) throw new Error('没有可认领的收款流水');
|
||||
const first = this.flows[0];
|
||||
const sameParty = this.flows.every(
|
||||
row =>
|
||||
String(row.payerName || '').trim() === String(first.payerName || '').trim() &&
|
||||
String(row.counterpartyAccount || '').trim() ===
|
||||
String(first.counterpartyAccount || '').trim()
|
||||
);
|
||||
if (!sameParty) throw new Error('批量认领所选流水的付款人、对方账号必须一致');
|
||||
} catch (error) {
|
||||
this.$message.error(error?.message || '批量认领流水加载失败');
|
||||
this.goBack();
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async openSettlementDialog() {
|
||||
this.settlementDialog.visible = true;
|
||||
await this.loadSettlementCandidates();
|
||||
},
|
||||
async loadSettlementCandidates() {
|
||||
if (!this.flows.length) return;
|
||||
this.settlementDialog.loading = true;
|
||||
try {
|
||||
this.settlementDialog.rows =
|
||||
this.unwrapData(
|
||||
await api.getSettlementCandidates(this.settlementDialog.keyword, this.flows[0].id)
|
||||
) || [];
|
||||
} finally {
|
||||
this.settlementDialog.loading = false;
|
||||
}
|
||||
},
|
||||
selectSettlement(row) {
|
||||
this.settlementDialog.selection = [row];
|
||||
this.confirmSettlements();
|
||||
},
|
||||
confirmSettlements() {
|
||||
const rows = this.settlementDialog.selection;
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请至少选择一张应收正式结算单');
|
||||
return;
|
||||
}
|
||||
const item = rows[0];
|
||||
this.settlements = [
|
||||
{
|
||||
...item,
|
||||
allocations: this.flows.reduce((result, flow) => {
|
||||
result[flow.id] = 0;
|
||||
return result;
|
||||
}, {}),
|
||||
},
|
||||
];
|
||||
this.settlementDialog.visible = false;
|
||||
},
|
||||
settlementRemaining(row) {
|
||||
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
|
||||
},
|
||||
settlementAllocatedTotal(row) {
|
||||
return this.flows.reduce((total, flow) => total + Number(row.allocations?.[flow.id] || 0), 0);
|
||||
},
|
||||
flowAllocatedTotal(flowId) {
|
||||
return this.settlements.reduce(
|
||||
(total, row) => total + Number(row.allocations?.[flowId] || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
maxAllocation(row) {
|
||||
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
|
||||
const otherTotal = this.settlementAllocatedTotal(row.settlement) - current;
|
||||
return Math.max(this.settlementRemaining(row.settlement) - otherTotal, 0);
|
||||
},
|
||||
handleAllocationChange(settlement) {
|
||||
const total = this.settlementAllocatedTotal(settlement);
|
||||
if (total > this.settlementRemaining(settlement) + 0.001) {
|
||||
this.$message.warning(`结算单${settlement.formalSettlementNo}的分摊金额不能超过可认领金额`);
|
||||
}
|
||||
},
|
||||
summaryMethod({ columns }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) return '合计';
|
||||
if (index === 6) return this.formatMoney(this.allocatedTotal());
|
||||
return '';
|
||||
});
|
||||
},
|
||||
settlementSpanMethod({ rowIndex, columnIndex }) {
|
||||
if (![1, 2, 3].includes(columnIndex)) return [1, 1];
|
||||
if (!this.flows.length) return [1, 1];
|
||||
if (rowIndex % this.flows.length === 0) return [this.flows.length, 1];
|
||||
return [0, 0];
|
||||
},
|
||||
allocatedTotal() {
|
||||
return this.settlements.reduce((total, row) => total + this.settlementAllocatedTotal(row), 0);
|
||||
},
|
||||
async submitClaim() {
|
||||
if (!this.settlements.length) {
|
||||
this.$message.warning('请至少选择一张应收正式结算单');
|
||||
return;
|
||||
}
|
||||
for (const row of this.settlements) {
|
||||
if (this.settlementAllocatedTotal(row) > this.settlementRemaining(row) + 0.001) {
|
||||
this.$message.warning(`结算单${row.formalSettlementNo}的累计认领金额不能超过结算总金额`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const flow of this.flows) {
|
||||
const total = this.flowAllocatedTotal(flow.id);
|
||||
const remaining = Math.max(
|
||||
Number(flow.receiptAmount || 0) - Number(flow.claimedAmount || 0),
|
||||
0
|
||||
);
|
||||
if (total > remaining + 0.001) {
|
||||
this.$message.warning(`流水${flow.receiptNoticeNo}的分摊金额不能超过剩余可认领金额`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const requests = this.flows
|
||||
.map(flow => ({
|
||||
flow,
|
||||
settlements: this.settlements
|
||||
.map(row => ({
|
||||
settlementId: row.id,
|
||||
allocatedReceiptAmount: Number(row.allocations?.[flow.id] || 0),
|
||||
}))
|
||||
.filter(row => row.allocatedReceiptAmount > 0),
|
||||
}))
|
||||
.filter(item => item.settlements.length);
|
||||
if (!requests.length) {
|
||||
this.$message.warning('没有需要入库的分摊收款金额');
|
||||
return;
|
||||
}
|
||||
this.submitting = true;
|
||||
try {
|
||||
for (const item of requests) {
|
||||
await api.claim({
|
||||
flowId: item.flow.id,
|
||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||
remark: this.form.remark,
|
||||
settlements: item.settlements,
|
||||
});
|
||||
}
|
||||
this.$message.success(`批量认领成功,共${requests.length}条流水`);
|
||||
this.goBack();
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/receipt-flow');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.receipt-claim-batch-form-page__form {
|
||||
padding-bottom: 72px;
|
||||
}
|
||||
.receipt-claim-batch-form-page__claim-info {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.receipt-claim-batch-form-page__form :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-claim-batch-form-page__settlement-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
.receipt-claim-batch-form-page__notice-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 8px;
|
||||
}
|
||||
.receipt-claim-batch-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 230px;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background: #fff;
|
||||
border-top: 1px solid #eff1f7;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.receipt-claim-batch-form-page__remark :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.receipt-claim-batch-form-page__dialog-search {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.receipt-claim-batch-form-page__dialog-search .el-input {
|
||||
width: 360px;
|
||||
}
|
||||
.receipt-claim-batch-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.receipt-claim-batch-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.receipt-claim-batch-form-page__actions {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<basic-container class="receipt-claim-form-page">
|
||||
<receipt-flow-batch-form v-if="batchMode" />
|
||||
<basic-container v-else class="receipt-claim-form-page">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@@ -182,6 +183,7 @@
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||
import ReceiptFlowBatchForm from './receipt-flow-batch-form.vue';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
@@ -206,7 +208,7 @@ const emptyForm = () => ({
|
||||
|
||||
export default {
|
||||
name: 'ReceiptFlowForm',
|
||||
components: { VehicleAttachmentTable },
|
||||
components: { VehicleAttachmentTable, ReceiptFlowBatchForm },
|
||||
data() {
|
||||
return {
|
||||
Search,
|
||||
@@ -222,6 +224,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
batchMode() {
|
||||
return String(this.$route.query.batch || '') === '1';
|
||||
},
|
||||
flowId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
@@ -239,7 +244,7 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
if (!this.batchMode) this.initialize();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
|
||||
@@ -60,8 +60,22 @@
|
||||
>
|
||||
手动同步流水
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasPermission('receipt_flow_claim')"
|
||||
type="primary"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="openBatchClaim"
|
||||
>
|
||||
批量认领
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
border
|
||||
@selection-change="selectedRows = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" align="center" />
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
@@ -256,6 +270,7 @@ export default {
|
||||
claimStatusOptions: api.claimStatusOptions,
|
||||
columns: receiptFlowTableColumns,
|
||||
rows: [],
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
syncing: false,
|
||||
searchExpanded: false,
|
||||
@@ -293,6 +308,7 @@ export default {
|
||||
})
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.selectedRows = [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -342,6 +358,32 @@ export default {
|
||||
query: { mode: 'add', id: row.id },
|
||||
});
|
||||
},
|
||||
openBatchClaim() {
|
||||
if (!this.selectedRows.length) {
|
||||
this.$message.warning('请先勾选收款流水');
|
||||
return;
|
||||
}
|
||||
const first = this.selectedRows[0];
|
||||
const payerName = String(first.payerName || '').trim();
|
||||
const counterpartyAccount = String(first.counterpartyAccount || '').trim();
|
||||
const sameParty = this.selectedRows.every(
|
||||
row =>
|
||||
String(row.payerName || '').trim() === payerName &&
|
||||
String(row.counterpartyAccount || '').trim() === counterpartyAccount
|
||||
);
|
||||
if (!sameParty) {
|
||||
this.$message.warning('批量认领所选流水的付款人、对方账号必须一致');
|
||||
return;
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/payment/receipt-flow/form',
|
||||
query: {
|
||||
mode: 'add',
|
||||
batch: '1',
|
||||
ids: this.selectedRows.map(row => row.id).join(','),
|
||||
},
|
||||
});
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
},
|
||||
@@ -398,6 +440,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.receipt-flow-page__pagination {
|
||||
|
||||
Reference in New Issue
Block a user