调整收付款问题
This commit is contained in:
@@ -51,9 +51,9 @@
|
|||||||
<vehicle-attachment-upload
|
<vehicle-attachment-upload
|
||||||
:model-value="rows"
|
:model-value="rows"
|
||||||
:file-types="attachmentFileTypes"
|
:file-types="attachmentFileTypes"
|
||||||
:max-size="500"
|
:max-size="maxSize"
|
||||||
:show-tip="true"
|
:show-tip="true"
|
||||||
tip="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过500M"
|
:tip="`支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过${maxSize}MB`"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
button-text="上传附件"
|
button-text="上传附件"
|
||||||
@update:model-value="handleUploadChange"
|
@update:model-value="handleUploadChange"
|
||||||
@@ -149,6 +149,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
maxSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 500,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const receiptFlowTableColumns = [
|
export const receiptFlowTableColumns = [
|
||||||
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150 },
|
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true },
|
||||||
{ prop: 'payerName', label: '付款人', minWidth: 130 },
|
{ prop: 'payerName', label: '付款人', minWidth: 130 },
|
||||||
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
|
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
|
||||||
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
|
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
|
||||||
|
|||||||
@@ -10,8 +10,18 @@ export const preSettlementTableColumns = [
|
|||||||
{ label: '原币结算金额', prop: 'settlementAmount', minWidth: 140, money: true, precision: 2 },
|
{ label: '原币结算金额', prop: 'settlementAmount', minWidth: 140, money: true, precision: 2 },
|
||||||
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 150, money: true, precision: 2 },
|
{ label: '本位币结算金额', prop: 'localSettlementAmount', minWidth: 150, money: true, precision: 2 },
|
||||||
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110, precision: 2 },
|
{ label: '结算汇率', prop: 'exchangeRate', minWidth: 110, precision: 2 },
|
||||||
{ label: '申请预付金额', prop: 'advanceAppliedAmount', minWidth: 140, money: true },
|
{
|
||||||
{ label: '已付款金额', prop: 'advancePaidAmount', minWidth: 130, money: true },
|
label: '申请预付金额(审核中的)',
|
||||||
|
prop: 'advanceAppliedAmount',
|
||||||
|
minWidth: 190,
|
||||||
|
money: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已付款金额(审核通过的)',
|
||||||
|
prop: 'advancePaidAmount',
|
||||||
|
minWidth: 200,
|
||||||
|
money: true,
|
||||||
|
},
|
||||||
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
{ label: '审核状态', prop: 'approvalStatusName', minWidth: 110, status: true },
|
||||||
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
{ label: '当前节点', prop: 'currentNode', minWidth: 130 },
|
||||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 130 },
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="附件信息">
|
<section-card title="附件信息">
|
||||||
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
|
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" :max-size="10" />
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<div class="bill-payment-form-page__actions">
|
<div class="bill-payment-form-page__actions">
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
@edit="openEdit"
|
@edit="openEdit"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@flow="openFlow"
|
@flow="openFlow"
|
||||||
|
@approve="handleApprove"
|
||||||
|
@return="handleReturn"
|
||||||
@void="handleVoid"
|
@void="handleVoid"
|
||||||
@page-change="handlePageChange"
|
@page-change="handlePageChange"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@@ -126,6 +128,17 @@ export default {
|
|||||||
this.flowDialog.row = row;
|
this.flowDialog.row = row;
|
||||||
this.flowDialog.visible = true;
|
this.flowDialog.visible = true;
|
||||||
},
|
},
|
||||||
|
async handleApprove(row) {
|
||||||
|
await api.approve({ id: row.id });
|
||||||
|
this.$message.success('审批通过');
|
||||||
|
this.loadTable();
|
||||||
|
},
|
||||||
|
async handleReturn(row) {
|
||||||
|
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
|
||||||
|
await api.returnBill({ id: row.id, reason: value });
|
||||||
|
this.$message.success('已驳回');
|
||||||
|
this.loadTable();
|
||||||
|
},
|
||||||
async handleVoid(row) {
|
async handleVoid(row) {
|
||||||
const { value } = await this.$prompt('请输入作废原因', '作废汇票付款');
|
const { value } = await this.$prompt('请输入作废原因', '作废汇票付款');
|
||||||
await api.voidBill({ id: row.id, reason: value });
|
await api.voidBill({ id: row.id, reason: value });
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="bill-payment-table__links">
|
<div class="bill-payment-table__links">
|
||||||
<el-link
|
<el-link
|
||||||
@@ -55,6 +55,18 @@
|
|||||||
@click="$emit('flow', row)"
|
@click="$emit('flow', row)"
|
||||||
>流程</el-link
|
>流程</el-link
|
||||||
>
|
>
|
||||||
|
<el-link
|
||||||
|
v-if="row.approvalStatus === 'reviewing' && hasPermission('bill_payment_approve')"
|
||||||
|
type="primary"
|
||||||
|
@click="$emit('approve', row)"
|
||||||
|
>通过</el-link
|
||||||
|
>
|
||||||
|
<el-link
|
||||||
|
v-if="row.approvalStatus === 'reviewing' && hasPermission('bill_payment_approve')"
|
||||||
|
type="danger"
|
||||||
|
@click="$emit('return', row)"
|
||||||
|
>驳回</el-link
|
||||||
|
>
|
||||||
<el-link
|
<el-link
|
||||||
v-if="row.approvalStatus === 'approved' && hasPermission('bill_payment_void')"
|
v-if="row.approvalStatus === 'approved' && hasPermission('bill_payment_void')"
|
||||||
type="danger"
|
type="danger"
|
||||||
@@ -90,7 +102,18 @@ export default {
|
|||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
page: { type: Object, required: true },
|
page: { type: Object, required: true },
|
||||||
},
|
},
|
||||||
emits: ['add', 'view', 'edit', 'delete', 'flow', 'void', 'page-change', 'size-change'],
|
emits: [
|
||||||
|
'add',
|
||||||
|
'view',
|
||||||
|
'edit',
|
||||||
|
'delete',
|
||||||
|
'flow',
|
||||||
|
'approve',
|
||||||
|
'return',
|
||||||
|
'void',
|
||||||
|
'page-change',
|
||||||
|
'size-change',
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return { columns: billPaymentTableColumns };
|
return { columns: billPaymentTableColumns };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -188,7 +188,10 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="开票内容"><template #extra><el-button v-if="!readonly" type="primary" @click="addSheet">添加</el-button></template>
|
<section-card title="开票内容"
|
||||||
|
><template #extra
|
||||||
|
><el-button v-if="!readonly" type="primary" @click="addSheet">添加</el-button></template
|
||||||
|
>
|
||||||
<div v-for="(sheet, sheetIndex) in form.sheets" :key="sheet.localId" class="invoice-sheet">
|
<div v-for="(sheet, sheetIndex) in form.sheets" :key="sheet.localId" class="invoice-sheet">
|
||||||
<div class="invoice-sheet__header">
|
<div class="invoice-sheet__header">
|
||||||
<span>第{{ sheetIndex + 1 }}张</span>
|
<span>第{{ sheetIndex + 1 }}张</span>
|
||||||
@@ -247,17 +250,6 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="不含税单价" min-width="130">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.unitPriceNoTax"
|
|
||||||
:disabled="readonly"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="含税金额" min-width="130">
|
<el-table-column label="含税金额" min-width="130">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -362,7 +354,11 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link v-if="column.link && row[column.prop]" type="primary">
|
<el-link
|
||||||
|
v-if="column.link && row[column.prop]"
|
||||||
|
type="primary"
|
||||||
|
@click.stop="handleDetailLink(row, column)"
|
||||||
|
>
|
||||||
{{ row[column.prop] }}
|
{{ row[column.prop] }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<span
|
<span
|
||||||
@@ -371,9 +367,13 @@
|
|||||||
Number(row[column.prop]) === -1
|
Number(row[column.prop]) === -1
|
||||||
"
|
"
|
||||||
></span>
|
></span>
|
||||||
<span v-else-if="column.feeItemKeys">{{ formatMoney(getFeeItemValue(row, column.feeItemKeys)) }}</span>
|
<span v-else-if="column.feeItemKeys">{{
|
||||||
|
formatMoney(getFeeItemValue(row, column.feeItemKeys))
|
||||||
|
}}</span>
|
||||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||||
<span v-else-if="column.prop === 'transportType'">{{ transportTypeLabel(row.transportType) }}</span>
|
<span v-else-if="column.prop === 'transportType'">{{
|
||||||
|
transportTypeLabel(row.transportType)
|
||||||
|
}}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -400,17 +400,15 @@
|
|||||||
<div class="invoice-form-page__actions">
|
<div class="invoice-form-page__actions">
|
||||||
<el-button @click="goBack">取消</el-button>
|
<el-button @click="goBack">取消</el-button>
|
||||||
<el-button v-if="!readonly" type="primary" plain @click="syncReferenceData">同步</el-button>
|
<el-button v-if="!readonly" type="primary" plain @click="syncReferenceData">同步</el-button>
|
||||||
<el-button
|
<el-button v-if="!readonly && canSave" type="primary" plain @click="saveDraft"
|
||||||
v-if="!readonly && canSave"
|
>保存</el-button
|
||||||
type="primary"
|
>
|
||||||
plain
|
|
||||||
@click="saveDraft"
|
|
||||||
>保存</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!readonly && canSave && hasPermission('invoice_application_submit')"
|
v-if="!readonly && canSave && hasPermission('invoice_application_submit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="submitForm"
|
@click="submitForm"
|
||||||
>提交</el-button>
|
>提交</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@@ -468,6 +466,8 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/invoiceApplication';
|
import * as api from '@/api/payment/invoiceApplication';
|
||||||
import { getAll as getInvoiceItems } from '@/api/base/invoice-item';
|
import { getAll as getInvoiceItems } from '@/api/base/invoice-item';
|
||||||
|
import { getList as getWaybillList } from '@/api/business/waybill-manage';
|
||||||
|
import { getList as getReceivablePayableDetailList } from '@/api/settlement/receivable-payable-detail';
|
||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication';
|
import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
@@ -662,7 +662,8 @@ export default {
|
|||||||
if (this.settlementIds.length) {
|
if (this.settlementIds.length) {
|
||||||
await Promise.all([this.loadReceiverInformation(), this.loadSettlementDetails(false)]);
|
await Promise.all([this.loadReceiverInformation(), this.loadSettlementDetails(false)]);
|
||||||
}
|
}
|
||||||
if (this.readonly) this.$nextTick(() => this.$refs.formRef?.clearValidate('departmentEmails'));
|
if (this.readonly)
|
||||||
|
this.$nextTick(() => this.$refs.formRef?.clearValidate('departmentEmails'));
|
||||||
this.$nextTick(this.restoreDetailSelection);
|
this.$nextTick(this.restoreDetailSelection);
|
||||||
},
|
},
|
||||||
async loadInvoiceItems() {
|
async loadInvoiceItems() {
|
||||||
@@ -683,8 +684,7 @@ export default {
|
|||||||
transportTypeLabel(value) {
|
transportTypeLabel(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
return (
|
return (
|
||||||
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
this.transportTypeOptions.find(item => String(item.value) === String(value))?.label || value
|
||||||
value
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
invoiceItemNames(categoryName) {
|
invoiceItemNames(categoryName) {
|
||||||
@@ -840,7 +840,7 @@ export default {
|
|||||||
...item,
|
...item,
|
||||||
formalSettlementId: item.id,
|
formalSettlementId: item.id,
|
||||||
settlementId: item.id,
|
settlementId: item.id,
|
||||||
allocatedInvoiceAmount: 0,
|
allocatedInvoiceAmount: rows.length === 1 ? Number(item.availableInvoiceAmount || 0) : 0,
|
||||||
}));
|
}));
|
||||||
this.form.projectName = first.projectName;
|
this.form.projectName = first.projectName;
|
||||||
this.form.deptName = first.deptName;
|
this.form.deptName = first.deptName;
|
||||||
@@ -906,9 +906,7 @@ export default {
|
|||||||
const invoiceInfoEmails = [
|
const invoiceInfoEmails = [
|
||||||
...this.receiverInvoiceOptions.flatMap(item => this.parseEmails(item.email)),
|
...this.receiverInvoiceOptions.flatMap(item => this.parseEmails(item.email)),
|
||||||
];
|
];
|
||||||
this.departmentEmailOptions = invoiceInfoEmails
|
this.departmentEmailOptions = invoiceInfoEmails.filter(Boolean).reduce((emails, email) => {
|
||||||
.filter(Boolean)
|
|
||||||
.reduce((emails, email) => {
|
|
||||||
if (!emails.some(item => item.toLowerCase() === email.toLowerCase())) emails.push(email);
|
if (!emails.some(item => item.toLowerCase() === email.toLowerCase())) emails.push(email);
|
||||||
return emails;
|
return emails;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -962,6 +960,56 @@ export default {
|
|||||||
if (this.readonly) return;
|
if (this.readonly) return;
|
||||||
this.selectedDetailIds = rows.map(item => item.formalSettlementDetailId || item.id);
|
this.selectedDetailIds = rows.map(item => item.formalSettlementDetailId || item.id);
|
||||||
},
|
},
|
||||||
|
handleDetailLink(row, column) {
|
||||||
|
if (column.prop === 'documentNo') {
|
||||||
|
this.openPayableDetail(row);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (column.prop === 'waybillNo') this.openWaybillDetail(row);
|
||||||
|
},
|
||||||
|
async openPayableDetail(row) {
|
||||||
|
let detailId = row.sourceDetailId || '';
|
||||||
|
if (!detailId && row.documentNo) {
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(
|
||||||
|
await getReceivablePayableDetailList(1, 1, {
|
||||||
|
documentNo: row.documentNo,
|
||||||
|
settlementType: 'payable',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
detailId = (Array.isArray(data) ? data : data?.records || [])[0]?.id || '';
|
||||||
|
} catch {
|
||||||
|
detailId = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!detailId) {
|
||||||
|
this.$message.info('当前记录未找到对应的应付明细');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/settlement/payable-detail',
|
||||||
|
query: { detailId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async openWaybillDetail(row) {
|
||||||
|
let waybillId = row.waybillId || '';
|
||||||
|
if (!waybillId && row.waybillNo) {
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(await getWaybillList(1, 1, { waybillNo: row.waybillNo }));
|
||||||
|
waybillId = (Array.isArray(data) ? data : data?.records || [])[0]?.id || '';
|
||||||
|
} catch {
|
||||||
|
waybillId = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!waybillId) {
|
||||||
|
this.$message.info('当前记录未找到对应的运单详情');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/business/waybill-manage',
|
||||||
|
query: { detailId: waybillId },
|
||||||
|
});
|
||||||
|
},
|
||||||
restoreDetailSelection() {
|
restoreDetailSelection() {
|
||||||
if (this.readonly || !this.$refs.detailTable) return;
|
if (this.readonly || !this.$refs.detailTable) return;
|
||||||
const selected = new Set(this.selectedDetailIds.map(String));
|
const selected = new Set(this.selectedDetailIds.map(String));
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
v-loading="invoiceDialog.loading"
|
v-loading="invoiceDialog.loading"
|
||||||
:data="invoiceDialog.rows"
|
:data="invoiceDialog.rows"
|
||||||
border
|
border
|
||||||
|
empty-text="暂无票据数据"
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
@current-change="invoiceDialog.current = $event"
|
@current-change="invoiceDialog.current = $event"
|
||||||
@row-dblclick="confirmInvoice"
|
@row-dblclick="confirmInvoice"
|
||||||
@@ -188,6 +189,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<div class="receipt-form-page__dialog-pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="invoiceDialog.page.current"
|
||||||
|
v-model:page-size="invoiceDialog.page.size"
|
||||||
|
:total="invoiceDialog.page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="refreshInvoicePoolPage"
|
||||||
|
@size-change="handleInvoicePoolSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="confirmInvoice()">确定</el-button>
|
<el-button type="primary" @click="confirmInvoice()">确定</el-button>
|
||||||
@@ -247,67 +259,12 @@
|
|||||||
import { Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/invoiceReceipt';
|
import * as api from '@/api/payment/invoiceReceipt';
|
||||||
|
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
const invoicePoolMockTemplates = [
|
const mockInvoiceTypes = ['增值税专用发票', '增值税普通发票'];
|
||||||
{
|
const mockTaxRates = [3, 6, 9, 13];
|
||||||
id: 90000001,
|
const mockBankNames = ['中国工商银行', '中国农业银行', '中国银行', '中国建设银行', '招商银行'];
|
||||||
invoiceNo: '25312000000000000101',
|
|
||||||
invoiceDate: '2026-08-25',
|
|
||||||
invoiceType: '增值税专用发票',
|
|
||||||
taxRate: 9,
|
|
||||||
invoiceAmount: 109000,
|
|
||||||
taxAmount: 9000,
|
|
||||||
receiverName: '华东供应链管理有限公司',
|
|
||||||
issuerName: '上海安捷运输有限公司',
|
|
||||||
bankName: '中国工商银行上海分行',
|
|
||||||
bankAccount: '1001000100000000019',
|
|
||||||
issuingBank: '中国工商银行上海分行营业部',
|
|
||||||
phone: '021-66880001',
|
|
||||||
customerEmails: 'finance@huadong.example.com',
|
|
||||||
departmentEmails: 'transport@huadong.example.com',
|
|
||||||
attachmentsJson: '[]',
|
|
||||||
kingdeeBillNo: 'KD-FP-20260825001',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 90000002,
|
|
||||||
invoiceNo: '25312000000000000102',
|
|
||||||
invoiceDate: '2026-08-26',
|
|
||||||
invoiceType: '增值税普通发票',
|
|
||||||
taxRate: 6,
|
|
||||||
invoiceAmount: 53000,
|
|
||||||
taxAmount: 3000,
|
|
||||||
receiverName: '华东供应链管理有限公司',
|
|
||||||
issuerName: '江苏联运物流有限公司',
|
|
||||||
bankName: '中国建设银行南京分行',
|
|
||||||
bankAccount: '3201000100000000026',
|
|
||||||
issuingBank: '中国建设银行南京城南支行',
|
|
||||||
phone: '025-66880002',
|
|
||||||
customerEmails: 'finance@huadong.example.com',
|
|
||||||
departmentEmails: 'logistics@huadong.example.com',
|
|
||||||
attachmentsJson: '[]',
|
|
||||||
kingdeeBillNo: 'KD-FP-20260826002',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 90000003,
|
|
||||||
invoiceNo: '25312000000000000103',
|
|
||||||
invoiceDate: '2026-08-27',
|
|
||||||
invoiceType: '增值税专用发票',
|
|
||||||
taxRate: 3,
|
|
||||||
invoiceAmount: 20600,
|
|
||||||
taxAmount: 600,
|
|
||||||
receiverName: '华东供应链管理有限公司',
|
|
||||||
issuerName: '浙江通达物流有限公司',
|
|
||||||
bankName: '招商银行杭州分行',
|
|
||||||
bankAccount: '5719000100000000033',
|
|
||||||
issuingBank: '招商银行杭州高新支行',
|
|
||||||
phone: '0571-66880003',
|
|
||||||
customerEmails: 'finance@huadong.example.com',
|
|
||||||
departmentEmails: 'settlement@huadong.example.com',
|
|
||||||
attachmentsJson: '[]',
|
|
||||||
kingdeeBillNo: 'KD-FP-20260827003',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const randomDigits = length =>
|
const randomDigits = length =>
|
||||||
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
Array.from({ length }, () => Math.floor(Math.random() * 10)).join('');
|
||||||
@@ -319,28 +276,59 @@ const formatMockDate = date => {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createInvoicePoolMockRows = () =>
|
const customerName = customer =>
|
||||||
invoicePoolMockTemplates.map((template, index) => {
|
customer.fullName || customer.shortName || customer.customerCode || `客商${customer.id}`;
|
||||||
|
|
||||||
|
const customerEmail = (customer, index) => {
|
||||||
|
const prefix = String(customer.customerCode || customer.id || `customer${index + 1}`)
|
||||||
|
.replace(/[^a-zA-Z0-9_-]/g, '')
|
||||||
|
.toLowerCase();
|
||||||
|
return `finance@${prefix || `customer${index + 1}`}.example.com`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInvoicePoolMockRows = (customers, counterpartyName, departmentEmail) => {
|
||||||
|
const rows = [];
|
||||||
|
const baseId = Date.now();
|
||||||
|
customers.forEach((customer, customerIndex) => {
|
||||||
|
const name = customerName(customer);
|
||||||
|
['issuer', 'receiver'].forEach((role, roleIndex) => {
|
||||||
|
const count = 3 + Math.floor(Math.random() * 5);
|
||||||
|
for (let index = 0; index < count; index += 1) {
|
||||||
|
const rowIndex = rows.length;
|
||||||
const invoiceDateValue = new Date();
|
const invoiceDateValue = new Date();
|
||||||
invoiceDateValue.setDate(invoiceDateValue.getDate() - Math.floor(Math.random() * 15));
|
invoiceDateValue.setDate(invoiceDateValue.getDate() - Math.floor(Math.random() * 90));
|
||||||
const invoiceDate = formatMockDate(invoiceDateValue);
|
const invoiceDate = formatMockDate(invoiceDateValue);
|
||||||
|
const taxRate = mockTaxRates[(customerIndex + roleIndex + index) % mockTaxRates.length];
|
||||||
const invoiceAmount = Math.floor(10000 + Math.random() * 190000);
|
const invoiceAmount = Math.floor(10000 + Math.random() * 190000);
|
||||||
const taxAmount = Number(
|
const taxAmount = Number(
|
||||||
(
|
((invoiceAmount * Number(taxRate)) / (100 + Number(taxRate))).toFixed(2)
|
||||||
(invoiceAmount * Number(template.taxRate || 0)) /
|
|
||||||
(100 + Number(template.taxRate || 0))
|
|
||||||
).toFixed(2)
|
|
||||||
);
|
);
|
||||||
return {
|
const bankName = mockBankNames[(customerIndex + index) % mockBankNames.length];
|
||||||
...template,
|
rows.push({
|
||||||
id: Date.now() + index * 100 + Math.floor(Math.random() * 100),
|
id: baseId + rowIndex,
|
||||||
invoiceNo: `25${randomDigits(18)}`,
|
invoiceNo: `25${randomDigits(18)}`,
|
||||||
invoiceDate,
|
invoiceDate,
|
||||||
|
invoiceType: mockInvoiceTypes[(customerIndex + index) % mockInvoiceTypes.length],
|
||||||
|
taxRate,
|
||||||
invoiceAmount,
|
invoiceAmount,
|
||||||
taxAmount,
|
taxAmount,
|
||||||
kingdeeBillNo: `KD-FP-${invoiceDate.replaceAll('-', '')}${randomDigits(3)}`,
|
receiverName: role === 'receiver' ? name : counterpartyName,
|
||||||
};
|
issuerName: role === 'issuer' ? name : counterpartyName,
|
||||||
|
bankName,
|
||||||
|
bankAccount: randomDigits(19),
|
||||||
|
issuingBank: `${bankName}营业部`,
|
||||||
|
phone: customer.contactPhone || '',
|
||||||
|
customerEmails: customerEmail(customer, customerIndex),
|
||||||
|
departmentEmails: departmentEmail || '',
|
||||||
|
attachmentsJson: '[]',
|
||||||
|
kingdeeBillNo: `KD-FP-${invoiceDate.replaceAll('-', '')}${randomDigits(5)}`,
|
||||||
|
kingdeeStatus: 'unsynced',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
};
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -380,9 +368,18 @@ export default {
|
|||||||
Search,
|
Search,
|
||||||
form: emptyForm(),
|
form: emptyForm(),
|
||||||
invoicePoolMockRows: [],
|
invoicePoolMockRows: [],
|
||||||
|
invoicePoolMockLoaded: false,
|
||||||
customerEmailOptions: [],
|
customerEmailOptions: [],
|
||||||
departmentEmailOptions: [],
|
departmentEmailOptions: [],
|
||||||
invoiceDialog: { visible: false, loading: false, keyword: '', rows: [], current: null },
|
invoiceDialog: {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
keyword: '',
|
||||||
|
sourceRows: [],
|
||||||
|
rows: [],
|
||||||
|
current: null,
|
||||||
|
page: { current: 1, size: 10, total: 0 },
|
||||||
|
},
|
||||||
settlementDialog: {
|
settlementDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -447,7 +444,6 @@ export default {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.invoicePoolMockRows = createInvoicePoolMockRows();
|
|
||||||
const userInfo = this.$store.getters.userInfo || {};
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
const userEmail = userInfo.email || '';
|
const userEmail = userInfo.email || '';
|
||||||
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
||||||
@@ -499,22 +495,72 @@ export default {
|
|||||||
async openInvoiceDialog() {
|
async openInvoiceDialog() {
|
||||||
this.invoiceDialog.visible = true;
|
this.invoiceDialog.visible = true;
|
||||||
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
||||||
if (!this.recordId) this.invoicePoolMockRows = createInvoicePoolMockRows();
|
|
||||||
await this.loadInvoicePool();
|
await this.loadInvoicePool();
|
||||||
},
|
},
|
||||||
async loadInvoicePool() {
|
async loadInvoicePool() {
|
||||||
this.invoiceDialog.loading = true;
|
this.invoiceDialog.loading = true;
|
||||||
|
this.invoiceDialog.page.current = 1;
|
||||||
|
this.invoiceDialog.current = null;
|
||||||
try {
|
try {
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.invoiceDialog.rows = this.filterInvoicePoolMockRows(this.invoiceDialog.keyword);
|
if (!this.invoicePoolMockLoaded) await this.loadInvoicePoolMockRows();
|
||||||
|
this.invoiceDialog.sourceRows = this.filterInvoicePoolMockRows(
|
||||||
|
this.invoiceDialog.keyword
|
||||||
|
);
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword));
|
const data = this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword));
|
||||||
this.invoiceDialog.rows = Array.isArray(data) ? data : data?.records || [];
|
this.invoiceDialog.sourceRows = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
} finally {
|
} finally {
|
||||||
this.invoiceDialog.loading = false;
|
this.invoiceDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadInvoicePoolMockRows() {
|
||||||
|
const pageSize = 500;
|
||||||
|
const firstData = this.unwrapData(await getCustomerList(1, pageSize, {}));
|
||||||
|
const firstRows = Array.isArray(firstData) ? firstData : firstData?.records || [];
|
||||||
|
const total = Number(firstData?.total || firstRows.length);
|
||||||
|
const pageCount = Math.ceil(total / pageSize);
|
||||||
|
const remainingPages = await Promise.all(
|
||||||
|
Array.from({ length: Math.max(pageCount - 1, 0) }, (_, index) =>
|
||||||
|
getCustomerList(index + 2, pageSize, {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const customers = [
|
||||||
|
...firstRows,
|
||||||
|
...remainingPages.flatMap(response => {
|
||||||
|
const data = this.unwrapData(response);
|
||||||
|
return Array.isArray(data) ? data : data?.records || [];
|
||||||
|
}),
|
||||||
|
].filter(
|
||||||
|
(customer, index, rows) =>
|
||||||
|
rows.findIndex(item => String(item.id) === String(customer.id)) === index
|
||||||
|
);
|
||||||
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
|
const counterpartyName = userInfo.deptName || userInfo.dept_name || '本企业';
|
||||||
|
this.invoicePoolMockRows = createInvoicePoolMockRows(
|
||||||
|
customers,
|
||||||
|
counterpartyName,
|
||||||
|
userInfo.email || ''
|
||||||
|
);
|
||||||
|
this.invoicePoolMockLoaded = true;
|
||||||
|
},
|
||||||
|
refreshInvoicePoolPage() {
|
||||||
|
const page = this.invoiceDialog.page;
|
||||||
|
const total = this.invoiceDialog.sourceRows.length;
|
||||||
|
const maxPage = Math.max(Math.ceil(total / page.size), 1);
|
||||||
|
if (page.current > maxPage) page.current = maxPage;
|
||||||
|
const start = (page.current - 1) * page.size;
|
||||||
|
page.total = total;
|
||||||
|
this.invoiceDialog.current = null;
|
||||||
|
this.invoiceDialog.rows = this.invoiceDialog.sourceRows.slice(start, start + page.size);
|
||||||
|
},
|
||||||
|
handleInvoicePoolSizeChange() {
|
||||||
|
this.invoiceDialog.page.current = 1;
|
||||||
|
this.refreshInvoicePoolPage();
|
||||||
|
},
|
||||||
filterInvoicePoolMockRows(keyword) {
|
filterInvoicePoolMockRows(keyword) {
|
||||||
const normalizedKeyword = String(keyword || '')
|
const normalizedKeyword = String(keyword || '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -781,6 +827,11 @@ export default {
|
|||||||
.receipt-form-page__dialog-search .el-input {
|
.receipt-form-page__dialog-search .el-input {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
.receipt-form-page__dialog-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
.receipt-form-page :deep(.el-table) {
|
.receipt-form-page :deep(.el-table) {
|
||||||
--el-table-border-color: #eff1f7;
|
--el-table-border-color: #eff1f7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,9 +238,19 @@
|
|||||||
<el-table-column label="匹配金额(含税)" min-width="150">
|
<el-table-column label="匹配金额(含税)" min-width="150">
|
||||||
<template #default="{ row }">{{ formatMoney(row.matchedAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.matchedAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="!readonly" label="操作" width="100">
|
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||||
<template #default="{ $index }">
|
<template #default="{ row, $index }">
|
||||||
<el-link type="danger" @click="form.invoices.splice($index, 1)">删除</el-link>
|
<div class="payment-form-page__invoice-actions">
|
||||||
|
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
|
||||||
|
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
|
||||||
|
<el-link
|
||||||
|
v-if="!readonly"
|
||||||
|
type="danger"
|
||||||
|
@click="form.invoices.splice($index, 1)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -333,7 +343,7 @@
|
|||||||
>
|
>
|
||||||
<div class="payment-form-page__attachment-upload">
|
<div class="payment-form-page__attachment-upload">
|
||||||
<vehicle-attachment-upload
|
<vehicle-attachment-upload
|
||||||
v-model="form.attachments"
|
:model-value="form.attachments"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:limit="20"
|
:limit="20"
|
||||||
@@ -341,7 +351,7 @@
|
|||||||
:file-types="attachmentFileTypes"
|
:file-types="attachmentFileTypes"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
button-text="上传附件"
|
button-text="上传附件"
|
||||||
@change="normalizeAttachments"
|
@update:model-value="normalizeAttachments"
|
||||||
@success="handleManualAttachmentUpload"
|
@success="handleManualAttachmentUpload"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -598,7 +608,7 @@ const emptyForm = () => ({
|
|||||||
settlementAmount: 0,
|
settlementAmount: 0,
|
||||||
payableAmount: 0,
|
payableAmount: 0,
|
||||||
billType: '',
|
billType: '',
|
||||||
paymentRatio: 50,
|
paymentRatio: null,
|
||||||
appliedAmount: 0,
|
appliedAmount: 0,
|
||||||
paymentMethod: 'bank_transfer',
|
paymentMethod: 'bank_transfer',
|
||||||
billLedgerId: null,
|
billLedgerId: null,
|
||||||
@@ -661,6 +671,8 @@ export default {
|
|||||||
receiptAccountLoading: false,
|
receiptAccountLoading: false,
|
||||||
firstContractPayment: false,
|
firstContractPayment: false,
|
||||||
contractSignatureFileAvailable: false,
|
contractSignatureFileAvailable: false,
|
||||||
|
contractPaymentRatioLimit: null,
|
||||||
|
contractPaymentRatioExceeded: false,
|
||||||
payeeCustomerLevel: '',
|
payeeCustomerLevel: '',
|
||||||
attachmentRuleLoading: false,
|
attachmentRuleLoading: false,
|
||||||
attachmentImagePreviewVisible: false,
|
attachmentImagePreviewVisible: false,
|
||||||
@@ -794,6 +806,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'form.paymentRatio'(value) {
|
'form.paymentRatio'(value) {
|
||||||
|
this.checkContractPaymentRatio(value);
|
||||||
if (this.amountSyncing || !this.paymentAmountBase) return;
|
if (this.amountSyncing || !this.paymentAmountBase) return;
|
||||||
this.amountSyncing = true;
|
this.amountSyncing = true;
|
||||||
this.form.appliedAmount = Number(
|
this.form.appliedAmount = Number(
|
||||||
@@ -1013,7 +1026,8 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
isValidReferenceId(id) {
|
isValidReferenceId(id) {
|
||||||
return id !== null && id !== undefined && String(id).trim() !== '-1';
|
const value = String(id ?? '').trim();
|
||||||
|
return value !== '' && value !== '-1' && value !== '0';
|
||||||
},
|
},
|
||||||
async loadTransferredPreSettlements() {
|
async loadTransferredPreSettlements() {
|
||||||
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
||||||
@@ -1246,6 +1260,44 @@ export default {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
invoiceAttachment(row = {}) {
|
||||||
|
const value = row.attachmentJson;
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'object') return Array.isArray(value) ? value[0] || {} : value;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed[0] || {} : parsed || {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invoiceAttachmentUrl(row) {
|
||||||
|
return this.attachmentFileUrl(this.invoiceAttachment(row));
|
||||||
|
},
|
||||||
|
invoiceAttachmentName(row) {
|
||||||
|
return (
|
||||||
|
this.attachmentFileName(this.invoiceAttachment(row)) || `${row.invoiceNo || '发票'}.pdf`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
viewInvoiceAttachment(row) {
|
||||||
|
const attachment = this.invoiceAttachment(row);
|
||||||
|
if (!this.attachmentFileUrl(attachment)) {
|
||||||
|
this.$message.warning('当前发票暂无可查看附件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.previewAttachment(
|
||||||
|
{ ...attachment, name: this.invoiceAttachmentName(row) },
|
||||||
|
[attachment]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
downloadInvoiceAttachment(row) {
|
||||||
|
const url = this.invoiceAttachmentUrl(row);
|
||||||
|
if (!url) {
|
||||||
|
this.$message.warning('当前发票暂无可下载附件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFileByUrl(url, this.invoiceAttachmentName(row));
|
||||||
|
},
|
||||||
attachmentFileName(file) {
|
attachmentFileName(file) {
|
||||||
return String(file?.originalName || file?.name || file?.fileName || '').trim();
|
return String(file?.originalName || file?.name || file?.fileName || '').trim();
|
||||||
},
|
},
|
||||||
@@ -1282,14 +1334,14 @@ export default {
|
|||||||
this.attachmentFileExtension(file) === 'pdf'
|
this.attachmentFileExtension(file) === 'pdf'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
previewAttachment(file) {
|
previewAttachment(file, files = this.form.attachments) {
|
||||||
const url = this.attachmentFileUrl(file);
|
const url = this.attachmentFileUrl(file);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
this.$message.warning('附件地址为空,无法预览');
|
this.$message.warning('附件地址为空,无法预览');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.isAttachmentImage(file)) {
|
if (this.isAttachmentImage(file)) {
|
||||||
this.attachmentImagePreviewUrls = (this.form.attachments || [])
|
this.attachmentImagePreviewUrls = (files || [])
|
||||||
.filter(item => this.isAttachmentImage(item) && this.attachmentFileUrl(item))
|
.filter(item => this.isAttachmentImage(item) && this.attachmentFileUrl(item))
|
||||||
.map(item => this.attachmentFileUrl(item));
|
.map(item => this.attachmentFileUrl(item));
|
||||||
this.attachmentImagePreviewIndex = Math.max(
|
this.attachmentImagePreviewIndex = Math.max(
|
||||||
@@ -1384,22 +1436,29 @@ export default {
|
|||||||
if (imported.length) this.form.attachments = [...this.form.attachments, ...imported];
|
if (imported.length) this.form.attachments = [...this.form.attachments, ...imported];
|
||||||
},
|
},
|
||||||
async loadCurrentSettlementDetails() {
|
async loadCurrentSettlementDetails() {
|
||||||
if (this.form.paymentType === 'project_advance') return [];
|
if (
|
||||||
const requests = [];
|
this.form.paymentType === 'progress_advance' &&
|
||||||
if (this.isValidReferenceId(this.form.preSettlementId)) {
|
this.isValidReferenceId(this.form.preSettlementId)
|
||||||
requests.push(preApi.getDetail(this.form.preSettlementId));
|
) {
|
||||||
|
const response = await preApi.getDetail(this.form.preSettlementId);
|
||||||
|
const detail = this.unwrapData(response);
|
||||||
|
return detail?.id ? [detail] : [];
|
||||||
}
|
}
|
||||||
if (this.isValidReferenceId(this.form.settlementId)) {
|
if (
|
||||||
requests.push(formalApi.getDetail(this.form.settlementId));
|
this.form.paymentType === 'settlement_payment' &&
|
||||||
|
this.isValidReferenceId(this.form.settlementId)
|
||||||
|
) {
|
||||||
|
const response = await formalApi.getDetail(this.form.settlementId);
|
||||||
|
const detail = this.unwrapData(response);
|
||||||
|
return detail?.id ? [detail] : [];
|
||||||
}
|
}
|
||||||
if (!requests.length) return [];
|
return [];
|
||||||
const responses = await Promise.all(requests);
|
|
||||||
return responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
|
||||||
},
|
},
|
||||||
async loadAttachmentRuleData(settlementRows = []) {
|
async loadAttachmentRuleData(settlementRows = []) {
|
||||||
this.attachmentRuleLoading = true;
|
this.attachmentRuleLoading = true;
|
||||||
this.firstContractPayment = false;
|
this.firstContractPayment = false;
|
||||||
this.contractSignatureFileAvailable = false;
|
this.contractSignatureFileAvailable = false;
|
||||||
|
this.resetContractPaymentRatioLimit();
|
||||||
try {
|
try {
|
||||||
const settlementMaterials = [
|
const settlementMaterials = [
|
||||||
ATTACHMENT_MATERIALS.weighingSlip,
|
ATTACHMENT_MATERIALS.weighingSlip,
|
||||||
@@ -1417,6 +1476,7 @@ export default {
|
|||||||
const contractResponse = await getContractDetail(this.form.contractId);
|
const contractResponse = await getContractDetail(this.form.contractId);
|
||||||
const contract = this.unwrapData(contractResponse) || {};
|
const contract = this.unwrapData(contractResponse) || {};
|
||||||
if (this.form.paymentType === 'project_advance') {
|
if (this.form.paymentType === 'project_advance') {
|
||||||
|
this.applyContractPaymentRatioLimit(contract);
|
||||||
const contractFiles = this.parse(contract.contractFileJson);
|
const contractFiles = this.parse(contract.contractFileJson);
|
||||||
this.contractSignatureFileAvailable = contractFiles.some(file =>
|
this.contractSignatureFileAvailable = contractFiles.some(file =>
|
||||||
Boolean(this.attachmentFileUrl(file))
|
Boolean(this.attachmentFileUrl(file))
|
||||||
@@ -1453,6 +1513,41 @@ export default {
|
|||||||
this.attachmentRuleLoading = false;
|
this.attachmentRuleLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
resetContractPaymentRatioLimit() {
|
||||||
|
this.contractPaymentRatioLimit = null;
|
||||||
|
this.contractPaymentRatioExceeded = false;
|
||||||
|
},
|
||||||
|
applyContractPaymentRatioLimit(contract = {}) {
|
||||||
|
const [firstRatio] = this.parse(contract.paymentRatioJson);
|
||||||
|
const ratioLimitValue = firstRatio?.ratioLimit;
|
||||||
|
const ratioLimit = Number(ratioLimitValue);
|
||||||
|
this.contractPaymentRatioLimit =
|
||||||
|
ratioLimitValue === null ||
|
||||||
|
ratioLimitValue === undefined ||
|
||||||
|
String(ratioLimitValue).trim() === '' ||
|
||||||
|
!Number.isFinite(ratioLimit)
|
||||||
|
? null
|
||||||
|
: ratioLimit;
|
||||||
|
this.checkContractPaymentRatio(this.form.paymentRatio);
|
||||||
|
},
|
||||||
|
checkContractPaymentRatio(value) {
|
||||||
|
const paymentRatio = Number(value);
|
||||||
|
const hasPaymentRatio =
|
||||||
|
value !== null &&
|
||||||
|
value !== undefined &&
|
||||||
|
String(value).trim() !== '' &&
|
||||||
|
Number.isFinite(paymentRatio);
|
||||||
|
const exceeded =
|
||||||
|
this.form.paymentType === 'project_advance' &&
|
||||||
|
Boolean(this.form.contractId) &&
|
||||||
|
this.contractPaymentRatioLimit !== null &&
|
||||||
|
hasPaymentRatio &&
|
||||||
|
paymentRatio > this.contractPaymentRatioLimit;
|
||||||
|
if (exceeded && !this.contractPaymentRatioExceeded) {
|
||||||
|
this.$message.warning('已超合同配置比例');
|
||||||
|
}
|
||||||
|
this.contractPaymentRatioExceeded = exceeded;
|
||||||
|
},
|
||||||
validateRequiredAttachments() {
|
validateRequiredAttachments() {
|
||||||
if (!this.missingAttachmentMaterials.length) return true;
|
if (!this.missingAttachmentMaterials.length) return true;
|
||||||
this.$message.warning(
|
this.$message.warning(
|
||||||
@@ -1949,20 +2044,22 @@ export default {
|
|||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
},
|
},
|
||||||
normalizeAttachments(files) {
|
normalizeAttachments(files) {
|
||||||
this.form.attachments = (files || []).map(file => ({
|
const existingAttachments = this.form.attachments || [];
|
||||||
|
this.form.attachments = (files || []).map(file => {
|
||||||
|
const fileUrl = this.attachmentFileUrl(file);
|
||||||
|
const existingFile = existingAttachments.find(item => {
|
||||||
|
const sameUid = file.uid && item.uid && String(file.uid) === String(item.uid);
|
||||||
|
const sameUrl = fileUrl && this.attachmentFileUrl(item) === fileUrl;
|
||||||
|
return sameUid || sameUrl;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...existingFile,
|
||||||
...file,
|
...file,
|
||||||
attachmentType: (() => {
|
attachmentType:
|
||||||
const matchedType = this.resolveAttachmentTypeByFileName(file);
|
existingFile?.attachmentType || this.resolveAttachmentTypeByFileName(file),
|
||||||
const existingType = [
|
description: file.description || existingFile?.description || '',
|
||||||
...Object.values(MATERIAL_TYPE_MAP),
|
};
|
||||||
'other',
|
});
|
||||||
].includes(file.attachmentType)
|
|
||||||
? file.attachmentType
|
|
||||||
: 'other';
|
|
||||||
return matchedType !== 'other' ? matchedType : existingType;
|
|
||||||
})(),
|
|
||||||
description: file.description || '',
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
handleManualAttachmentUpload(uploadedFile) {
|
handleManualAttachmentUpload(uploadedFile) {
|
||||||
const uploadedUrl = this.attachmentFileUrl(uploadedFile);
|
const uploadedUrl = this.attachmentFileUrl(uploadedFile);
|
||||||
@@ -2206,6 +2303,12 @@ export default {
|
|||||||
.payment-form-page__attachment-download {
|
.payment-form-page__attachment-download {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
.payment-form-page__invoice-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.payment-form-page__attachment-file-name-cell {
|
.payment-form-page__attachment-file-name-cell {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -371,6 +371,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.receipt-claim-form-page__form {
|
||||||
|
padding-bottom: 72px;
|
||||||
|
}
|
||||||
.receipt-claim-form-page__form :deep(.el-select),
|
.receipt-claim-form-page__form :deep(.el-select),
|
||||||
.receipt-claim-form-page__form :deep(.el-input-number) {
|
.receipt-claim-form-page__form :deep(.el-input-number) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -71,10 +71,9 @@
|
|||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link v-if="column.link && canClaim(row)" type="primary" @click="openClaim(row)">
|
<el-link v-if="column.link" type="primary" @click="openDetail(row)">
|
||||||
{{ displayValue(row[column.prop]) }}
|
{{ displayValue(row[column.prop]) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<span v-else-if="column.link">{{ displayValue(row[column.prop]) }}</span>
|
|
||||||
<el-tag
|
<el-tag
|
||||||
v-else-if="column.status"
|
v-else-if="column.status"
|
||||||
:type="statusType(row.claimStatus)"
|
:type="statusType(row.claimStatus)"
|
||||||
@@ -105,6 +104,74 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="detailDialog.visible"
|
||||||
|
title="收款流水详情"
|
||||||
|
width="960px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div v-loading="detailDialog.loading">
|
||||||
|
<el-descriptions :column="3" border>
|
||||||
|
<el-descriptions-item label="认领通知单">
|
||||||
|
{{ displayValue(detailDialog.data.receiptNoticeNo) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="付款人">
|
||||||
|
{{ displayValue(detailDialog.data.payerName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="付款金额">
|
||||||
|
{{ formatMoney(detailDialog.data.receiptAmount) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="交易时间">
|
||||||
|
{{ displayValue(detailDialog.data.transactionTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="对方户名">
|
||||||
|
{{ displayValue(detailDialog.data.counterpartyName) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="对方开户行">
|
||||||
|
{{ displayValue(detailDialog.data.counterpartyBank) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="对方账号">
|
||||||
|
{{ displayValue(detailDialog.data.counterpartyAccount) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="明细流水号">
|
||||||
|
{{ displayValue(detailDialog.data.detailSerialNo) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="认领状态">
|
||||||
|
<el-tag :type="statusType(detailDialog.data.claimStatus)">
|
||||||
|
{{ displayValue(detailDialog.data.claimStatusName) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="已认领金额">
|
||||||
|
{{ formatMoney(detailDialog.data.claimedAmount) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="剩余可认领金额">
|
||||||
|
{{ formatMoney(detailDialog.data.remainingAmount) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="摘要">
|
||||||
|
{{ displayValue(detailDialog.data.summary) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
|
||||||
|
<div class="dialog-section-title receipt-flow-page__detail-title">认领记录</div>
|
||||||
|
<el-table :data="detailDialog.claimRecords" border empty-text="暂无认领记录">
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column prop="createTime" label="认领时间" min-width="170" align="center" />
|
||||||
|
<el-table-column label="认领金额" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.operationAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="operatorName" label="认领人" min-width="120" align="center" />
|
||||||
|
<el-table-column label="状态变化" min-width="170" align="center">
|
||||||
|
<template #default="{ row }">{{ claimStatusChange(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="content" label="认领说明" min-width="220" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="detailDialog.visible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -150,8 +217,10 @@ const formatMockDateTime = date => {
|
|||||||
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createReceiptFlowMockRows = () =>
|
const createReceiptFlowMockRows = () => {
|
||||||
receiptFlowMockTemplates.map((template, index) => {
|
const count = 20 + Math.floor(Math.random() * 31);
|
||||||
|
return Array.from({ length: count }, (_, index) => {
|
||||||
|
const template = receiptFlowMockTemplates[index % receiptFlowMockTemplates.length];
|
||||||
const transactionDate = new Date();
|
const transactionDate = new Date();
|
||||||
transactionDate.setDate(transactionDate.getDate() - Math.floor(Math.random() * 7));
|
transactionDate.setDate(transactionDate.getDate() - Math.floor(Math.random() * 7));
|
||||||
transactionDate.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60), 0);
|
transactionDate.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60), 0);
|
||||||
@@ -167,6 +236,7 @@ const createReceiptFlowMockRows = () =>
|
|||||||
sourceUpdatedTime: transactionTime,
|
sourceUpdatedTime: transactionTime,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const emptyQuery = () => ({
|
const emptyQuery = () => ({
|
||||||
receiptNoticeNo: '',
|
receiptNoticeNo: '',
|
||||||
@@ -190,6 +260,7 @@ export default {
|
|||||||
syncing: false,
|
syncing: false,
|
||||||
searchExpanded: false,
|
searchExpanded: false,
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
|
detailDialog: { visible: false, loading: false, data: {}, claimRecords: [] },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -250,6 +321,21 @@ export default {
|
|||||||
this.syncing = false;
|
this.syncing = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async openDetail(row) {
|
||||||
|
this.detailDialog = {
|
||||||
|
visible: true,
|
||||||
|
loading: true,
|
||||||
|
data: { ...row },
|
||||||
|
claimRecords: [],
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(await api.getDetail(row.id));
|
||||||
|
this.detailDialog.data = data || {};
|
||||||
|
this.detailDialog.claimRecords = data?.claimRecords || [];
|
||||||
|
} finally {
|
||||||
|
this.detailDialog.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
openClaim(row) {
|
openClaim(row) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/payment/receipt-flow/form',
|
path: '/payment/receipt-flow/form',
|
||||||
@@ -265,6 +351,11 @@ export default {
|
|||||||
statusType(status) {
|
statusType(status) {
|
||||||
return { claimed: 'success', partial: 'warning', unclaimed: 'info' }[status] || '';
|
return { claimed: 'success', partial: 'warning', unclaimed: 'info' }[status] || '';
|
||||||
},
|
},
|
||||||
|
claimStatusChange(row) {
|
||||||
|
const label = status =>
|
||||||
|
({ claimed: '认领完成', partial: '部分认领', unclaimed: '未认领' }[status] || '-');
|
||||||
|
return `${label(row.fromStatus)} → ${label(row.toStatus)}`;
|
||||||
|
},
|
||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
@@ -314,6 +405,9 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
.receipt-flow-page__detail-title {
|
||||||
|
margin: 20px 0 12px;
|
||||||
|
}
|
||||||
.receipt-flow-page :deep(.el-table) {
|
.receipt-flow-page :deep(.el-table) {
|
||||||
--el-table-border-color: #eff1f7;
|
--el-table-border-color: #eff1f7;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,9 @@
|
|||||||
<div class="settlement-detail-page__toolbar-left">
|
<div class="settlement-detail-page__toolbar-left">
|
||||||
<el-button type="primary" @click="openGenerateDialog">生成费用</el-button>
|
<el-button type="primary" @click="openGenerateDialog">生成费用</el-button>
|
||||||
<el-button type="primary" plain @click="openUpdateFeeDialog">更新费用</el-button>
|
<el-button type="primary" plain @click="openUpdateFeeDialog">更新费用</el-button>
|
||||||
<el-button type="primary" plain @click="openTransferDialog">批量转结算</el-button>
|
<el-button v-if="isPayable" type="primary" plain @click="openTransferDialog">
|
||||||
|
批量转结算
|
||||||
|
</el-button>
|
||||||
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settlement-detail-page__toolbar-right">
|
<div class="settlement-detail-page__toolbar-right">
|
||||||
@@ -849,11 +851,17 @@ export default {
|
|||||||
return this.cargoTypeOptions.filter(item => item.children?.length);
|
return this.cargoTypeOptions.filter(item => item.children?.length);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
watch: {
|
||||||
|
'$route.fullPath'() {
|
||||||
|
this.openRouteDetail();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
this.loadTransportTypeOptions();
|
this.loadTransportTypeOptions();
|
||||||
this.loadCargoTypeOptions();
|
this.loadCargoTypeOptions();
|
||||||
this.loadPriceUnitOptions();
|
this.loadPriceUnitOptions();
|
||||||
this.loadTable();
|
await this.loadTable();
|
||||||
|
await this.openRouteDetail();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.clearAdjustCalculations();
|
this.clearAdjustCalculations();
|
||||||
@@ -1191,6 +1199,29 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$router.push({ path, query: { detailNo } });
|
this.$router.push({ path, query: { detailNo } });
|
||||||
},
|
},
|
||||||
|
async openRouteDetail(detailId = this.$route.query.detailId) {
|
||||||
|
if (
|
||||||
|
!detailId ||
|
||||||
|
this.settlementType !== 'payable' ||
|
||||||
|
this.$route.path !== '/settlement/payable-detail'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let row = this.rows.find(item => String(item.id) === String(detailId));
|
||||||
|
if (!row) {
|
||||||
|
const res = await api.getList(1, 1, {
|
||||||
|
ids: detailId,
|
||||||
|
settlementType: this.settlementType,
|
||||||
|
});
|
||||||
|
const data = this.unwrapPage(res);
|
||||||
|
if (data.records?.[0]) row = this.decorateRow(data.records[0]);
|
||||||
|
}
|
||||||
|
if (!row) {
|
||||||
|
this.$message.info('当前记录未找到对应的应付明细');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.openDetailDialog(row);
|
||||||
|
},
|
||||||
openWaybillDetail(row) {
|
openWaybillDetail(row) {
|
||||||
if (!row.waybillId) {
|
if (!row.waybillId) {
|
||||||
this.$message.info('当前记录未关联运单详情');
|
this.$message.info('当前记录未关联运单详情');
|
||||||
|
|||||||
Reference in New Issue
Block a user