1、调整业务模块

This commit is contained in:
2026-09-02 03:22:51 +08:00
parent 80a15fc626
commit 252df72f87
19 changed files with 1354 additions and 177 deletions
+29 -8
View File
@@ -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);
},
+1 -1
View File
@@ -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;
+152 -39
View File
@@ -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>
+8 -3
View File
@@ -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) {
+44 -1
View File
@@ -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 {