2286 lines
82 KiB
Vue
2286 lines
82 KiB
Vue
<template>
|
||
<basic-container class="payment-form-page">
|
||
<div class="archive-page-form__title">
|
||
{{ readonly ? '查看付款申请' : recordId ? '编辑付款申请' : '新增付款申请' }}
|
||
</div>
|
||
<el-form
|
||
ref="formRef"
|
||
:model="form"
|
||
:rules="rules"
|
||
label-position="right"
|
||
label-width="auto"
|
||
class="payment-form-page__form"
|
||
>
|
||
<section-card title="付款基本信息">
|
||
<el-row :gutter="24">
|
||
<el-col :span="6"
|
||
><el-form-item label="单据号"
|
||
><el-input
|
||
v-model="form.paymentNo"
|
||
disabled
|
||
placeholder="系统自动生成" /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="付款类型" prop="paymentType"
|
||
><el-select v-model="form.paymentType" :disabled="readonly" @change="handleTypeChange"
|
||
><el-option
|
||
v-for="item in paymentTypeOptions"
|
||
:key="item.value"
|
||
v-bind="item" /></el-select></el-form-item
|
||
></el-col>
|
||
<el-col v-if="form.paymentType !== 'project_advance'" :span="6"
|
||
><el-form-item label="结算单号" prop="referenceId"
|
||
><el-input
|
||
v-model="referenceLabel"
|
||
readonly
|
||
:disabled="readonly"
|
||
placeholder="请选择预结算/正式结算单"
|
||
><template #append
|
||
><el-button :disabled="readonly" @click="openReference">选择</el-button></template
|
||
></el-input
|
||
></el-form-item
|
||
></el-col
|
||
>
|
||
<el-col :span="6">
|
||
<el-form-item label="所属项目">
|
||
<el-select
|
||
v-if="form.paymentType === 'project_advance'"
|
||
v-model="form.projectId"
|
||
:disabled="readonly"
|
||
filterable
|
||
clearable
|
||
:loading="projectLoading"
|
||
placeholder="请选择项目"
|
||
@change="handleProjectChange"
|
||
>
|
||
<el-option
|
||
v-for="item in projectOptions"
|
||
:key="item.id"
|
||
:label="item.projectName || item.projectShortName || item.projectCode"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
<el-input v-else v-model="form.projectName" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="合同名称">
|
||
<el-select
|
||
v-if="form.paymentType === 'project_advance'"
|
||
v-model="form.contractId"
|
||
:disabled="readonly || !form.projectId"
|
||
filterable
|
||
clearable
|
||
:loading="contractLoading"
|
||
placeholder="请先选择项目"
|
||
@change="handleAdvanceContractChange"
|
||
>
|
||
<el-option
|
||
v-for="item in contractOptions"
|
||
:key="item.id"
|
||
:label="item.contractName || item.contractNo"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
<el-input v-else v-model="form.contractName" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col v-if="form.paymentType !== 'project_advance'" :span="6"
|
||
><el-form-item label="结算金额" prop="settlementAmount"
|
||
><el-input-number
|
||
v-model="form.settlementAmount"
|
||
:disabled="readonly"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false"
|
||
style="width: 100%" /></el-form-item
|
||
></el-col>
|
||
<el-col v-if="form.paymentType !== 'project_advance'" :span="6"
|
||
><el-form-item label="可付款金额"
|
||
><el-input v-model="form.payableAmount" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="票款类型"
|
||
><el-input v-model="form.billType" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="付款比例(%)" prop="paymentRatio"
|
||
><div class="payment-form-page__percentage-input">
|
||
<el-input-number
|
||
v-model="form.paymentRatio"
|
||
:disabled="readonly"
|
||
:min="0"
|
||
:max="100"
|
||
:precision="2"
|
||
:controls="false"
|
||
/><span class="payment-form-page__percentage-append">%</span>
|
||
</div></el-form-item
|
||
></el-col
|
||
>
|
||
<el-col :span="6"
|
||
><el-form-item label="申请付款金额" prop="appliedAmount"
|
||
><el-input-number
|
||
v-model="form.appliedAmount"
|
||
:disabled="readonly"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false"
|
||
style="width: 100%" /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="付款方式" prop="paymentMethod"
|
||
><el-select
|
||
v-model="form.paymentMethod"
|
||
:disabled="readonly"
|
||
@change="handlePaymentMethodChange"
|
||
><el-option
|
||
v-for="item in paymentMethodOptions"
|
||
:key="item.value"
|
||
v-bind="item" /></el-select></el-form-item
|
||
></el-col>
|
||
<el-col v-if="billPayment" :span="6"
|
||
><el-form-item label="汇票单号" prop="billLedgerId"
|
||
><el-select
|
||
v-model="form.billLedgerId"
|
||
: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
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="收款方"
|
||
><el-input v-model="form.payeeName" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="收款账号" prop="receiptAccountId"
|
||
><el-select
|
||
v-model="form.receiptAccountId"
|
||
:disabled="readonly || !form.payeeName"
|
||
:loading="receiptAccountLoading"
|
||
filterable
|
||
clearable
|
||
placeholder="请选择收款方的收款账号"
|
||
no-data-text="该收款方未维护可用收款信息"
|
||
@change="handleReceiptAccountChange"
|
||
><el-option
|
||
v-for="item in receiptAccountOptions"
|
||
:key="item.id"
|
||
:label="receiptAccountLabel(item)"
|
||
:value="item.id" /></el-select></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="开户银行"
|
||
><el-input v-model="form.bankName" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="申请人"
|
||
><el-input v-model="form.applicantName" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="6"
|
||
><el-form-item label="申请日期"
|
||
><el-input v-model="form.applyDate" disabled /></el-form-item
|
||
></el-col>
|
||
<el-col :span="24"
|
||
><el-form-item label="备注"
|
||
><el-input
|
||
v-model="form.remark"
|
||
:disabled="readonly"
|
||
type="textarea"
|
||
maxlength="200"
|
||
show-word-limit /></el-form-item
|
||
></el-col>
|
||
</el-row>
|
||
</section-card>
|
||
<section-card v-if="form.paymentType !== 'project_advance'" title="结算信息">
|
||
<el-table :data="settlementRows" border
|
||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||
prop="settlementNo"
|
||
label="结算单号"
|
||
min-width="150" /><el-table-column
|
||
prop="settlementAmount"
|
||
label="结算总金额"
|
||
min-width="130" /><el-table-column
|
||
prop="payableAmount"
|
||
label="可付款金额"
|
||
min-width="130" /><el-table-column
|
||
prop="paymentRatio"
|
||
label="付款比例(%)"
|
||
min-width="110" /><el-table-column
|
||
prop="appliedAmount"
|
||
label="付款金额"
|
||
min-width="130"
|
||
/></el-table>
|
||
</section-card>
|
||
<section-card v-if="form.paymentType !== 'project_advance'" title="发票信息">
|
||
<el-table :data="form.invoices" border>
|
||
<el-table-column type="index" label="序号" width="70" />
|
||
<el-table-column prop="settlementNo" label="结算单号" min-width="120" />
|
||
<el-table-column prop="invoiceNo" label="发票号" min-width="140" />
|
||
<el-table-column prop="invoiceDate" label="开票日期" min-width="150" />
|
||
<el-table-column prop="invoiceType" label="发票类型" min-width="130" />
|
||
<el-table-column label="税率" min-width="100">
|
||
<template #default="{ row }">{{ Number(row.taxRate || 0) }}%</template>
|
||
</el-table-column>
|
||
<el-table-column label="发票金额(含税)" min-width="150">
|
||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="匹配金额(含税)" min-width="150">
|
||
<template #default="{ row }">{{ formatMoney(row.matchedAmount) }}</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="!readonly" label="操作" width="100">
|
||
<template #default="{ $index }">
|
||
<el-link type="danger" @click="form.invoices.splice($index, 1)">删除</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
<section-card title="付款记录">
|
||
<el-table :data="paymentRecords" border
|
||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||
label="付款金额"
|
||
min-width="160"
|
||
><template #default="{ row }">{{
|
||
formatMoney(row.paidAmount)
|
||
}}</template></el-table-column
|
||
><el-table-column prop="paidDate" label="付款日期" min-width="150" /><el-table-column
|
||
prop="paymentNo"
|
||
label="付款单号"
|
||
min-width="160" /><el-table-column
|
||
prop="kingdeeBillNo"
|
||
label="付款凭证"
|
||
min-width="160"
|
||
/></el-table>
|
||
</section-card>
|
||
<section-card>
|
||
<template #title>
|
||
<span>附件</span>
|
||
<span v-if="missingAttachmentMaterials.length" class="payment-form-page__missing-text">
|
||
未上传:{{ missingAttachmentMaterials.map(item => item.label).join('、') }}
|
||
</span>
|
||
</template>
|
||
<div v-loading="attachmentRuleLoading" class="payment-form-page__attachment-checklist">
|
||
<el-button
|
||
class="payment-form-page__attachment-download"
|
||
type="primary"
|
||
plain
|
||
:disabled="!downloadableAttachments.length"
|
||
@click="handleAttachmentBatchDownload"
|
||
>
|
||
批量下载
|
||
</el-button>
|
||
</div>
|
||
<el-table :data="form.attachments" border
|
||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||
label="附件类型"
|
||
width="160"
|
||
><template #default="{ row }"
|
||
><el-select v-model="row.attachmentType" :disabled="readonly"
|
||
><el-option label="磅单" value="weighing_slip" /><el-option
|
||
label="委托单"
|
||
value="entrust_order" /><el-option label="结算单" value="settlement" /><el-option
|
||
label="合同签章文件"
|
||
value="contract" /><el-option
|
||
label="特批附件"
|
||
value="special_approval" /><el-option
|
||
label="其他"
|
||
value="other" /></el-select></template></el-table-column
|
||
><el-table-column label="文件名" min-width="220"
|
||
><template #default="{ row }"
|
||
><div
|
||
class="payment-form-page__attachment-file-name-cell"
|
||
:title="attachmentFileName(row) || '-'"
|
||
>
|
||
<span
|
||
class="payment-form-page__attachment-file-name"
|
||
:class="{ 'is-disabled': !attachmentFileUrl(row) }"
|
||
@click="attachmentFileUrl(row) && previewAttachment(row)"
|
||
>{{ attachmentFileName(row) || '-' }}</span
|
||
>
|
||
</div></template
|
||
></el-table-column
|
||
><el-table-column label="附件描述" min-width="220"
|
||
><template #default="{ row }"
|
||
><el-input
|
||
v-model="row.description"
|
||
:disabled="readonly"
|
||
maxlength="200" /></template></el-table-column
|
||
><el-table-column label="文件大小" width="120"
|
||
><template #default="{ row }">{{
|
||
formatAttachmentSize(row)
|
||
}}</template></el-table-column
|
||
><el-table-column prop="uploadUserName" label="上传人" width="130" /><el-table-column
|
||
prop="uploadTime"
|
||
label="上传时间"
|
||
width="170"
|
||
/><el-table-column v-if="!readonly" label="操作" width="100"
|
||
><template #default="{ $index }"
|
||
><el-link type="danger" @click="form.attachments.splice($index, 1)"
|
||
>删除</el-link
|
||
></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<div class="payment-form-page__attachment-upload">
|
||
<vehicle-attachment-upload
|
||
v-model="form.attachments"
|
||
:readonly="readonly"
|
||
:multiple="true"
|
||
:limit="20"
|
||
:max-size="500"
|
||
:file-types="attachmentFileTypes"
|
||
:show-file-list="false"
|
||
button-text="上传附件"
|
||
@change="normalizeAttachments"
|
||
@success="handleManualAttachmentUpload"
|
||
/>
|
||
</div>
|
||
</section-card>
|
||
<div class="payment-form-page__actions">
|
||
<el-button @click="goBack">取消</el-button>
|
||
<el-button v-if="!readonly && canSave" type="primary" plain @click="saveDraft(false)"
|
||
>保存</el-button
|
||
>
|
||
<el-button
|
||
v-if="!readonly && canSave && hasPermission('payment_application_submit')"
|
||
type="primary"
|
||
@click="submitForm"
|
||
>提交</el-button
|
||
>
|
||
</div>
|
||
</el-form>
|
||
<el-dialog
|
||
v-model="referenceVisible"
|
||
:title="form.paymentType === 'project_advance' ? '选择项目合同' : '选择结算单'"
|
||
width="80%"
|
||
><el-table
|
||
v-if="form.paymentType === 'project_advance'"
|
||
:data="contractRows"
|
||
border
|
||
@row-click="selectContract"
|
||
><el-table-column prop="projectName" label="所属项目" /><el-table-column
|
||
prop="contractNo"
|
||
label="合同编号"
|
||
/><el-table-column prop="contractName" label="合同名称" /><el-table-column
|
||
prop="deptName"
|
||
label="所属组织"
|
||
/><el-table-column label="操作" width="100"
|
||
><template #default="{ row }"
|
||
><el-link type="primary" @click.stop="selectContract(row)">选择</el-link></template
|
||
></el-table-column
|
||
></el-table
|
||
>
|
||
<div v-else>
|
||
<div class="payment-form-page__reference-search">
|
||
<el-form
|
||
:model="referenceQuery"
|
||
label-position="right"
|
||
label-width="160px"
|
||
@submit.prevent
|
||
>
|
||
<div class="payment-form-page__reference-search-fields">
|
||
<el-form-item label="结算单号">
|
||
<el-input
|
||
v-model="referenceQuery.settlementNo"
|
||
clearable
|
||
placeholder="请输入结算单号"
|
||
@keyup.enter="handleReferenceSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="项目">
|
||
<el-input
|
||
v-model="referenceQuery.projectName"
|
||
clearable
|
||
placeholder="请输入项目"
|
||
@keyup.enter="handleReferenceSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="合同名称">
|
||
<el-input
|
||
v-model="referenceQuery.contractName"
|
||
clearable
|
||
placeholder="请输入合同名称"
|
||
@keyup.enter="handleReferenceSearch"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div class="payment-form-page__reference-search-actions">
|
||
<el-button type="primary" @click="handleReferenceSearch">搜索</el-button>
|
||
<el-button @click="resetReferenceSearch">清空</el-button>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
<el-tabs v-model="referenceTab" @tab-change="handleReferenceTabChange">
|
||
<el-tab-pane label="正式结算单" name="formal">
|
||
<el-table
|
||
v-loading="formalPage.loading"
|
||
:data="formalRows"
|
||
border
|
||
@selection-change="formalSelection = $event"
|
||
>
|
||
<el-table-column type="selection" width="50" />
|
||
<el-table-column prop="formalSettlementNo" label="结算单号" />
|
||
<el-table-column prop="projectName" label="所属项目" />
|
||
<el-table-column prop="contractName" label="合同名称" />
|
||
<el-table-column prop="settlementAmount" label="结算金额" />
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectFormal(row)">选择</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="payment-form-page__reference-confirm">
|
||
<el-button type="primary" :disabled="!formalSelection.length" @click="confirmFormalSelection">确定</el-button>
|
||
</div>
|
||
<div class="payment-form-page__reference-pagination">
|
||
<el-pagination
|
||
v-model:current-page="formalPage.current"
|
||
v-model:page-size="formalPage.size"
|
||
:total="formalPage.total"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@current-change="loadFormalReferences"
|
||
@size-change="handleFormalSizeChange"
|
||
/>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="预结算单" name="pre">
|
||
<el-table v-loading="prePage.loading" :data="preRows" border @selection-change="preSelection = $event">
|
||
<el-table-column type="selection" width="50" />
|
||
<el-table-column prop="preSettlementNo" label="结算单号" />
|
||
<el-table-column prop="projectName" label="所属项目" />
|
||
<el-table-column prop="contractName" label="合同名称" />
|
||
<el-table-column prop="settlementAmount" label="结算金额" />
|
||
<el-table-column label="操作" width="100">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click.stop="selectPre(row)">选择</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="payment-form-page__reference-pagination">
|
||
<el-pagination
|
||
v-model:current-page="prePage.current"
|
||
v-model:page-size="prePage.size"
|
||
:total="prePage.total"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@current-change="loadPreReferences"
|
||
@size-change="handlePreSizeChange"
|
||
/>
|
||
</div>
|
||
<div class="payment-form-page__reference-confirm">
|
||
<el-button type="primary" :disabled="!preSelection.length" @click="confirmPreSelection">确定</el-button>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs></div
|
||
></el-dialog>
|
||
<el-dialog
|
||
v-model="attachmentDocumentPreviewVisible"
|
||
:title="attachmentPreviewFile.name || '附件预览'"
|
||
append-to-body
|
||
destroy-on-close
|
||
width="90%"
|
||
top="4vh"
|
||
>
|
||
<pdf-preview
|
||
v-if="
|
||
attachmentDocumentPreviewVisible &&
|
||
attachmentPreviewFile.url &&
|
||
attachmentPreviewFile.isPdf
|
||
"
|
||
:source="attachmentPreviewFile.url"
|
||
@error="handleAttachmentPreviewError"
|
||
/>
|
||
<open-file-viewer
|
||
v-else-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||
:file="attachmentPreviewFile.url"
|
||
:file-name="attachmentPreviewFile.name"
|
||
:mime-type="attachmentPreviewFile.mimeType"
|
||
width="100%"
|
||
height="72vh"
|
||
fit="contain"
|
||
theme="auto"
|
||
locale="zh-CN"
|
||
:toolbar="attachmentViewerToolbar"
|
||
:plugins="attachmentViewerPlugins"
|
||
@unsupported="handleAttachmentPreviewUnsupported"
|
||
@error="handleAttachmentPreviewError"
|
||
/>
|
||
</el-dialog>
|
||
<el-image-viewer
|
||
v-if="attachmentImagePreviewVisible"
|
||
:url-list="attachmentImagePreviewUrls"
|
||
:initial-index="attachmentImagePreviewIndex"
|
||
@close="attachmentImagePreviewVisible = false"
|
||
/>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { ElImageViewer } from 'element-plus';
|
||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||
import { fallbackPlugin, imagePlugin, officePlugin, textPlugin } from '@open-file-viewer/core';
|
||
import '@open-file-viewer/core/style.css';
|
||
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 { getList as getProjectList } from '@/api/business/project-apply';
|
||
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||
import * as formalApi from '@/api/settlement/formalSettlement';
|
||
import * as preApi from '@/api/settlement/preSettlement';
|
||
import {
|
||
getDetail as getCustomerArchiveDetail,
|
||
getList as getCustomerArchiveList,
|
||
} from '@/api/vehicle/customer-archive';
|
||
import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer';
|
||
import { downloadFileByUrl } from '@/utils/util';
|
||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||
|
||
const attachmentViewerPlugins = [
|
||
imagePlugin(),
|
||
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||
textPlugin(),
|
||
fallbackPlugin(),
|
||
];
|
||
|
||
const ATTACHMENT_MATERIALS = {
|
||
weighingSlip: { key: 'weighingSlip', label: '磅单', keywords: ['磅单'] },
|
||
entrustOrder: { key: 'entrustOrder', label: '委托单', keywords: ['委托单'] },
|
||
settlement: { key: 'settlement', label: '结算单', keywords: ['结算单'] },
|
||
contract: {
|
||
key: 'contract',
|
||
label: '合同签章文件',
|
||
keywords: ['合同签章文件', '合同签章', '签章', '合同'],
|
||
},
|
||
specialApproval: {
|
||
key: 'specialApproval',
|
||
label: '特批附件',
|
||
keywords: ['特批附件', '特批'],
|
||
},
|
||
};
|
||
|
||
const MATERIAL_TYPE_MAP = {
|
||
weighingSlip: 'weighing_slip',
|
||
entrustOrder: 'entrust_order',
|
||
settlement: 'settlement',
|
||
contract: 'contract',
|
||
specialApproval: 'special_approval',
|
||
};
|
||
|
||
const emptyForm = () => ({
|
||
id: null,
|
||
paymentNo: '',
|
||
paymentType: 'project_advance',
|
||
settlementId: null,
|
||
settlementIds: [],
|
||
preSettlementId: null,
|
||
preSettlementIds: [],
|
||
projectId: null,
|
||
projectName: '',
|
||
deptId: null,
|
||
deptName: '',
|
||
contractId: null,
|
||
contractNo: '',
|
||
contractName: '',
|
||
payerName: '',
|
||
payeeName: '',
|
||
settlementAmount: 0,
|
||
payableAmount: 0,
|
||
billType: '',
|
||
paymentRatio: 50,
|
||
appliedAmount: 0,
|
||
paymentMethod: 'bank_transfer',
|
||
billLedgerId: null,
|
||
billNo: '',
|
||
receiptAccountId: null,
|
||
receiptAccountName: '',
|
||
bankName: '',
|
||
bankAccount: '',
|
||
applicantName: '',
|
||
applyDate: '',
|
||
remark: '',
|
||
attachments: [],
|
||
invoices: [],
|
||
});
|
||
export default {
|
||
name: 'PaymentApplicationForm',
|
||
components: {
|
||
ElImageViewer,
|
||
OpenFileViewer,
|
||
PdfPreview,
|
||
},
|
||
data() {
|
||
return {
|
||
form: emptyForm(),
|
||
amountSyncing: false,
|
||
transferPayload: null,
|
||
referenceRows: [],
|
||
paymentRecords: [],
|
||
referenceVisible: false,
|
||
referenceTab: 'formal',
|
||
referenceQuery: {
|
||
settlementNo: '',
|
||
projectName: '',
|
||
contractName: '',
|
||
},
|
||
formalRows: [],
|
||
formalSelection: [],
|
||
preRows: [],
|
||
preSelection: [],
|
||
formalPage: {
|
||
current: 1,
|
||
size: 10,
|
||
total: 0,
|
||
loading: false,
|
||
},
|
||
prePage: {
|
||
current: 1,
|
||
size: 10,
|
||
total: 0,
|
||
loading: false,
|
||
},
|
||
projectOptions: [],
|
||
projectLoading: false,
|
||
contractOptions: [],
|
||
contractLoading: false,
|
||
contractRows: [],
|
||
billOptions: [],
|
||
billLoading: false,
|
||
receiptAccountOptions: [],
|
||
receiptAccountLoading: false,
|
||
firstContractPayment: false,
|
||
contractSignatureFileAvailable: false,
|
||
payeeCustomerLevel: '',
|
||
attachmentRuleLoading: false,
|
||
attachmentImagePreviewVisible: false,
|
||
attachmentImagePreviewUrls: [],
|
||
attachmentImagePreviewIndex: 0,
|
||
attachmentDocumentPreviewVisible: false,
|
||
attachmentPreviewFile: {},
|
||
attachmentViewerPlugins,
|
||
attachmentViewerToolbar: {
|
||
download: true,
|
||
fullscreen: true,
|
||
print: true,
|
||
rotate: true,
|
||
zoom: true,
|
||
},
|
||
paymentTypeOptions: api.paymentTypeOptions,
|
||
paymentMethodOptions: api.paymentMethodOptions,
|
||
attachmentFileTypes: [
|
||
'pdf',
|
||
'bmp',
|
||
'jpeg',
|
||
'png',
|
||
'jpg',
|
||
'gif',
|
||
'webp',
|
||
'doc',
|
||
'docx',
|
||
'ppt',
|
||
'pptx',
|
||
'xlsx',
|
||
'xls',
|
||
'eml',
|
||
'msg',
|
||
'zip',
|
||
'rar',
|
||
],
|
||
rules: {
|
||
paymentType: [{ required: true, message: '请选择付款类型' }],
|
||
referenceId: [{ validator: this.validateReference, trigger: 'change' }],
|
||
paymentMethod: [{ required: true, message: '请选择付款方式' }],
|
||
receiptAccountId: [{ required: true, message: '请选择收款账号', trigger: 'change' }],
|
||
billLedgerId: [{ validator: this.validateBillLedger, trigger: 'change' }],
|
||
settlementAmount: [{ validator: this.validateSettlementAmount, trigger: 'change' }],
|
||
appliedAmount: [{ validator: this.validateAppliedAmount, trigger: 'change' }],
|
||
},
|
||
};
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission']),
|
||
recordId() {
|
||
return this.$route.query.id || '';
|
||
},
|
||
readonly() {
|
||
return this.$route.query.mode === 'view';
|
||
},
|
||
canSave() {
|
||
const code = this.recordId ? 'payment_application_edit' : 'payment_application_add';
|
||
return this.hasPermission(code);
|
||
},
|
||
billPayment() {
|
||
return this.form.paymentMethod !== 'bank_transfer';
|
||
},
|
||
paymentAmountBase() {
|
||
if (this.form.paymentType === 'project_advance') return 0;
|
||
const amount = this.form.settlementAmount;
|
||
return Number(amount || 0);
|
||
},
|
||
referenceLabel() {
|
||
if (this.referenceRows.length) {
|
||
return this.referenceRows
|
||
.map(item => item.settlementNo)
|
||
.filter(Boolean)
|
||
.join('、');
|
||
}
|
||
return this.form.settlementNo || this.form.preSettlementNo || '';
|
||
},
|
||
settlementRows() {
|
||
if (this.referenceRows.length) {
|
||
return this.referenceRows.map(item => ({
|
||
...item,
|
||
paymentRatio: this.form.paymentRatio,
|
||
appliedAmount:
|
||
item.fixedAppliedAmount === true
|
||
? Number(item.appliedAmount || 0)
|
||
: Number(
|
||
(
|
||
(Number(item.settlementAmount || 0) * Number(this.form.paymentRatio || 0)) /
|
||
100
|
||
).toFixed(2)
|
||
),
|
||
}));
|
||
}
|
||
if (!this.referenceLabel) return [];
|
||
return [
|
||
{
|
||
settlementNo: this.referenceLabel,
|
||
settlementAmount: this.form.settlementAmount,
|
||
payableAmount: this.form.payableAmount,
|
||
paymentRatio: this.form.paymentRatio,
|
||
appliedAmount: this.form.appliedAmount,
|
||
},
|
||
];
|
||
},
|
||
highRiskPayee() {
|
||
const level = String(this.payeeCustomerLevel || '')
|
||
.trim()
|
||
.toUpperCase();
|
||
return ['D', 'D级', 'E', 'E级', 'F', 'F级'].includes(level) || level.includes('高风险');
|
||
},
|
||
requiredAttachmentMaterials() {
|
||
if (this.form.paymentType === 'project_advance') {
|
||
return [ATTACHMENT_MATERIALS.contract];
|
||
}
|
||
const materials = [ATTACHMENT_MATERIALS.weighingSlip, ATTACHMENT_MATERIALS.settlement];
|
||
if (this.firstContractPayment) materials.push(ATTACHMENT_MATERIALS.contract);
|
||
if (this.highRiskPayee) materials.push(ATTACHMENT_MATERIALS.specialApproval);
|
||
return materials;
|
||
},
|
||
missingAttachmentMaterials() {
|
||
return this.requiredAttachmentMaterials.filter(item => !this.hasAttachmentMaterial(item));
|
||
},
|
||
downloadableAttachments() {
|
||
return (this.form.attachments || []).filter(item => this.attachmentFileUrl(item));
|
||
},
|
||
},
|
||
watch: {
|
||
'$route.query.transferToken': async function (token, oldToken) {
|
||
if (!this.recordId && token && token !== oldToken) {
|
||
this.transferPayload = readSettlementTransfer(token);
|
||
await this.initialize();
|
||
}
|
||
},
|
||
'form.paymentRatio'(value) {
|
||
if (this.amountSyncing || !this.paymentAmountBase) return;
|
||
this.amountSyncing = true;
|
||
this.form.appliedAmount = Number(
|
||
((this.paymentAmountBase * Number(value || 0)) / 100).toFixed(2)
|
||
);
|
||
this.$nextTick(() => {
|
||
this.amountSyncing = false;
|
||
});
|
||
},
|
||
'form.appliedAmount'(value) {
|
||
if (this.amountSyncing || !this.paymentAmountBase) return;
|
||
this.amountSyncing = true;
|
||
this.form.paymentRatio = Number(
|
||
((Number(value || 0) / this.paymentAmountBase) * 100).toFixed(2)
|
||
);
|
||
this.$nextTick(() => {
|
||
this.amountSyncing = false;
|
||
});
|
||
},
|
||
'form.settlementAmount'() {
|
||
if (
|
||
this.amountSyncing ||
|
||
this.form.paymentType === 'project_advance' ||
|
||
!this.paymentAmountBase
|
||
) {
|
||
return;
|
||
}
|
||
this.amountSyncing = true;
|
||
this.form.appliedAmount = Number(
|
||
((this.paymentAmountBase * Number(this.form.paymentRatio || 0)) / 100).toFixed(2)
|
||
);
|
||
this.$nextTick(() => {
|
||
this.amountSyncing = false;
|
||
});
|
||
},
|
||
},
|
||
created() {
|
||
this.transferPayload = readSettlementTransfer(this.$route.query.transferToken);
|
||
this.initialize();
|
||
},
|
||
methods: {
|
||
hasPermission(code) {
|
||
return this.permission?.[code] !== false;
|
||
},
|
||
validateReference(rule, value, callback) {
|
||
if (
|
||
this.form.paymentType === 'progress_advance' &&
|
||
!this.form.preSettlementId &&
|
||
!this.form.preSettlementIds?.length
|
||
) {
|
||
callback(new Error('请选择预结算单'));
|
||
return;
|
||
}
|
||
if (this.form.paymentType === 'settlement_payment' && !this.form.settlementId) {
|
||
callback(new Error('请选择正式结算单'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
refreshReferenceValidation() {
|
||
this.$nextTick(() => this.$refs.formRef?.validateField('referenceId').catch(() => {}));
|
||
},
|
||
validateSettlementAmount(rule, value, callback) {
|
||
if (this.form.paymentType === 'project_advance') {
|
||
callback();
|
||
return;
|
||
}
|
||
const amount = Number(value);
|
||
if (!Number.isFinite(amount) || amount < 0) {
|
||
callback(new Error('结算金额不能小于0'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateAppliedAmount(rule, value, callback) {
|
||
const amount = Number(value);
|
||
const payableAmount = Number(this.form.payableAmount || 0);
|
||
if (!Number.isFinite(amount) || amount < 0) {
|
||
callback(new Error('申请付款金额不能小于0'));
|
||
return;
|
||
}
|
||
if (
|
||
this.form.paymentType !== 'project_advance' &&
|
||
payableAmount > 0 &&
|
||
amount > payableAmount
|
||
) {
|
||
callback(new Error('申请付款金额不能超过可付款金额'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateBillLedger(rule, value, callback) {
|
||
if (!this.billPayment) {
|
||
callback();
|
||
return;
|
||
}
|
||
if (!value) {
|
||
callback(new Error('请选择汇票单号'));
|
||
return;
|
||
}
|
||
const selected = this.billOptions.find(item => String(item.id) === String(value));
|
||
if (
|
||
selected &&
|
||
Number(this.form.appliedAmount || 0) > Number(selected.availableBalance || 0)
|
||
) {
|
||
callback(new Error('申请付款金额不能超过汇票可用余额'));
|
||
return;
|
||
}
|
||
callback();
|
||
},
|
||
validateInvoices() {
|
||
if (this.form.paymentType === 'project_advance') return;
|
||
for (const invoice of this.form.invoices || []) {
|
||
if (String(invoice.settlementNo || '').length > 100) {
|
||
throw new Error('结算单号不能超过100个字符');
|
||
}
|
||
if (String(invoice.invoiceNo || '').length > 100) {
|
||
throw new Error('发票号不能超过100个字符');
|
||
}
|
||
if (String(invoice.invoiceType || '').length > 30) {
|
||
throw new Error('发票类型不能超过30个字符');
|
||
}
|
||
const invoiceAmount = Number(invoice.invoiceAmount || 0);
|
||
const matchedAmount = Number(invoice.matchedAmount || 0);
|
||
if (invoiceAmount < 0 || matchedAmount < 0) {
|
||
throw new Error('发票金额和匹配金额不能小于0');
|
||
}
|
||
if (matchedAmount > invoiceAmount) {
|
||
throw new Error('单张发票匹配金额不能超过发票金额');
|
||
}
|
||
}
|
||
},
|
||
validatePaymentRecords() {
|
||
if (this.form.paymentType === 'project_advance') return;
|
||
const appliedAmount = Number(this.form.appliedAmount || 0);
|
||
const paidAmount = (this.paymentRecords || []).reduce((total, record) => {
|
||
const amount = Number(record.paidAmount || 0);
|
||
if (!Number.isFinite(amount) || amount < 0) {
|
||
throw new Error('付款记录金额不能小于0');
|
||
}
|
||
if (String(record.paymentNo || '').length > 100) {
|
||
throw new Error('付款单号不能超过100个字符');
|
||
}
|
||
if (String(record.kingdeeBillNo || '').length > 100) {
|
||
throw new Error('付款凭证不能超过100个字符');
|
||
}
|
||
return total + amount;
|
||
}, 0);
|
||
if (paidAmount > appliedAmount) {
|
||
throw new Error('付款记录金额合计不能超过申请付款金额');
|
||
}
|
||
},
|
||
formatLocalDate(value) {
|
||
if (!value) return null;
|
||
const date = this.$dayjs(value);
|
||
return date.isValid() ? date.format('YYYY-MM-DD') : null;
|
||
},
|
||
unwrapData(response) {
|
||
const body = response?.data || response || {};
|
||
return body?.data || body;
|
||
},
|
||
async initialize() {
|
||
if (this.recordId) {
|
||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||
this.form = {
|
||
...emptyForm(),
|
||
...data,
|
||
preSettlementIds: data.preSettlementIds?.length
|
||
? data.preSettlementIds
|
||
: data.preSettlementId
|
||
? [data.preSettlementId]
|
||
: [],
|
||
attachments: this.parse(data.attachmentsJson),
|
||
invoices: data.invoices || [],
|
||
};
|
||
const referenceRow = this.createReferenceRow(data);
|
||
this.referenceRows = referenceRow.settlementNo ? [referenceRow] : [];
|
||
this.paymentRecords = data.paymentRecords || [];
|
||
if (this.form.paymentType === 'project_advance') {
|
||
await this.loadProjectOptions();
|
||
await this.loadContractOptions(this.form.projectId);
|
||
}
|
||
await this.loadReceiptAccountOptions(false);
|
||
await this.loadAttachmentRuleData(await this.loadCurrentSettlementDetails());
|
||
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
||
} else {
|
||
this.form = emptyForm();
|
||
this.form.paymentType = this.initialAddPaymentType();
|
||
this.referenceRows = [];
|
||
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
||
this.form.applicantName =
|
||
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
||
if (this.form.paymentType === 'project_advance') await this.loadProjectOptions();
|
||
if (this.transferPayload?.sourceFormalSettlements?.length) {
|
||
await this.loadTransferredFormalSettlements();
|
||
} else {
|
||
await this.loadTransferredPreSettlements();
|
||
}
|
||
}
|
||
},
|
||
initialAddPaymentType() {
|
||
const paymentType = String(this.$route.query.paymentType || '');
|
||
return api.paymentTypeOptions.some(item => item.value === paymentType)
|
||
? paymentType
|
||
: 'project_advance';
|
||
},
|
||
getTransferredPreSettlementIds() {
|
||
const payloadIds = (this.transferPayload?.sourcePreSettlements || []).map(
|
||
item => item.preSettlementId || item.id
|
||
);
|
||
const queryIds = String(this.$route.query.preSettlementIds || '')
|
||
.split(',')
|
||
.map(item => item.trim())
|
||
.filter(Boolean);
|
||
return [...new Set([...payloadIds, ...queryIds].map(String))].filter(id =>
|
||
this.isValidReferenceId(id)
|
||
);
|
||
},
|
||
isValidReferenceId(id) {
|
||
return id !== null && id !== undefined && String(id).trim() !== '-1';
|
||
},
|
||
async loadTransferredPreSettlements() {
|
||
const fallbackRows = this.transferPayload?.sourcePreSettlements || [];
|
||
const ids = this.getTransferredPreSettlementIds();
|
||
if (!ids.length) {
|
||
this.applyTransferredPreSettlements(fallbackRows);
|
||
await Promise.all([
|
||
this.loadReceiptAccountOptions(),
|
||
this.loadAttachmentRuleData(fallbackRows),
|
||
]);
|
||
return;
|
||
}
|
||
try {
|
||
const responses = await Promise.all(ids.map(id => preApi.getDetail(id)));
|
||
const rows = responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
||
const sourceRows = rows.length ? rows : fallbackRows;
|
||
this.applyTransferredPreSettlements(sourceRows);
|
||
await Promise.all([
|
||
this.loadReceiptAccountOptions(),
|
||
this.loadAttachmentRuleData(sourceRows),
|
||
]);
|
||
} catch (error) {
|
||
if (fallbackRows.length) {
|
||
this.applyTransferredPreSettlements(fallbackRows);
|
||
await Promise.all([
|
||
this.loadReceiptAccountOptions(),
|
||
this.loadAttachmentRuleData(fallbackRows),
|
||
]);
|
||
return;
|
||
}
|
||
this.$message.error('预结算信息加载失败,请返回后重新发起预付申请');
|
||
throw error;
|
||
}
|
||
},
|
||
async loadTransferredFormalSettlements() {
|
||
const fallbackRows = this.transferPayload?.sourceFormalSettlements || [];
|
||
const rows = await Promise.all(
|
||
fallbackRows.map(async row => {
|
||
if (!this.isValidReferenceId(row.id)) return row;
|
||
try {
|
||
return { ...row, ...this.unwrapData(await formalApi.getDetail(row.id)) };
|
||
} catch (error) {
|
||
return row;
|
||
}
|
||
})
|
||
);
|
||
this.applyTransferredFormalSettlements(rows.filter(item => this.isValidReferenceId(item?.id)));
|
||
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData(rows)]);
|
||
},
|
||
applyTransferredFormalSettlements(rows = []) {
|
||
if (!rows.length) return;
|
||
const contractIds = new Set(rows.map(item => String(item.contractId || '')));
|
||
if (contractIds.size !== 1 || contractIds.has('')) {
|
||
this.$message.warning('所选正式结算单合同信息不一致,请返回后重新选择');
|
||
return;
|
||
}
|
||
const first = rows[0];
|
||
const referenceRows = rows.map(item => ({
|
||
...this.createReferenceRow(item),
|
||
settlementNo: item.formalSettlementNo || item.settlementNo || '',
|
||
appliedAmount: Number(item.appliedAmount || 0),
|
||
fixedAppliedAmount: true,
|
||
}));
|
||
const settlementAmount = referenceRows.reduce(
|
||
(total, item) => total + Number(item.settlementAmount || 0),
|
||
0
|
||
);
|
||
const payableAmount = referenceRows.reduce(
|
||
(total, item) => total + Number(item.payableAmount || 0),
|
||
0
|
||
);
|
||
const appliedAmount = referenceRows.reduce(
|
||
(total, item) => total + Number(item.appliedAmount || 0),
|
||
0
|
||
);
|
||
const paymentRatio = settlementAmount
|
||
? Number(((appliedAmount / settlementAmount) * 100).toFixed(2))
|
||
: 0;
|
||
const settlementIds = rows.map(item => item.settlementId || item.id).filter(Boolean);
|
||
this.amountSyncing = true;
|
||
this.referenceRows = referenceRows;
|
||
Object.assign(this.form, {
|
||
paymentType: 'settlement_payment',
|
||
settlementId: settlementIds[0] || null,
|
||
settlementIds,
|
||
preSettlementId: null,
|
||
preSettlementIds: [],
|
||
settlementNo: referenceRows
|
||
.map(item => item.settlementNo)
|
||
.filter(Boolean)
|
||
.join('、'),
|
||
preSettlementNo: '',
|
||
projectId: first.projectId,
|
||
projectName: first.projectName,
|
||
deptId: first.deptId,
|
||
deptName: first.deptName,
|
||
contractId: first.contractId,
|
||
contractNo: first.contractNo,
|
||
contractName: first.contractName,
|
||
payerName: first.payerName,
|
||
payeeName: first.payeeName,
|
||
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||
payableAmount: Number(payableAmount.toFixed(2)),
|
||
billType: '正式结算单',
|
||
paymentRatio,
|
||
appliedAmount: Number(appliedAmount.toFixed(2)),
|
||
invoices: rows.flatMap(item =>
|
||
this.createInvoiceRows(item.invoices || [], item.formalSettlementNo || item.settlementNo)
|
||
),
|
||
});
|
||
this.paymentRecords = rows.flatMap(item =>
|
||
(item.paymentRecords || item.paymentApplications || []).map(record => ({
|
||
...record,
|
||
paidAmount: Number(record.paidAmount || 0),
|
||
paidDate: record.paidDate || record.paymentDate || record.createTime || '',
|
||
paymentNo: record.paymentNo || '',
|
||
voucherJson: record.voucherJson || '',
|
||
kingdeeBillNo: record.kingdeeBillNo || '',
|
||
}))
|
||
);
|
||
this.$nextTick(() => {
|
||
this.amountSyncing = false;
|
||
this.$refs.formRef?.clearValidate();
|
||
});
|
||
},
|
||
createReferenceRow(row) {
|
||
const settlementAmount = Number(row.settlementAmount || 0);
|
||
const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0);
|
||
return {
|
||
id: row.preSettlementId || row.settlementId || row.id,
|
||
settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '',
|
||
settlementAmount,
|
||
payableAmount:
|
||
row.payableAmount === undefined || row.payableAmount === null
|
||
? Math.max(0, settlementAmount - appliedAmount)
|
||
: Number(row.payableAmount || 0),
|
||
};
|
||
},
|
||
createInvoiceRows(invoices = [], settlementNo = '') {
|
||
return (invoices || []).map(invoice => ({
|
||
settlementNo,
|
||
invoiceNo: invoice.invoiceNo || '',
|
||
invoiceDate: invoice.invoiceDate || '',
|
||
invoiceType: invoice.invoiceType || '',
|
||
taxRate: Number(invoice.taxRate || 0),
|
||
invoiceAmount: Number(invoice.invoiceAmount || 0),
|
||
availableInvoiceAmount: Number(invoice.availableInvoiceAmount || 0),
|
||
matchedAmount: Number(invoice.matchedAmount || 0),
|
||
attachmentJson: invoice.attachmentJson || '',
|
||
}));
|
||
},
|
||
createMockPaymentRecords(settlementNo, amount = this.form.appliedAmount) {
|
||
const totalCents = Math.max(0, Math.round(Number(amount || 0) * 100));
|
||
const paidCents = [Math.round(totalCents * 0.2), Math.round(totalCents * 0.3)];
|
||
paidCents.push(totalCents - paidCents[0] - paidCents[1]);
|
||
const normalizedSettlementNo = String(settlementNo || 'SETTLEMENT').slice(0, 60);
|
||
this.paymentRecords = paidCents.map((value, index) => {
|
||
const paidDate = this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD');
|
||
const sequence = String(index + 1).padStart(2, '0');
|
||
return {
|
||
paidAmount: Number((value / 100).toFixed(2)),
|
||
paidDate,
|
||
paymentNo: `MOCK-${normalizedSettlementNo}-${sequence}`,
|
||
voucherJson: '',
|
||
kingdeeBillNo: `MOCK-KD${paidDate.replaceAll('-', '')}${sequence}`,
|
||
};
|
||
});
|
||
},
|
||
referencePaymentAmount(settlementAmount) {
|
||
return Number(
|
||
((Number(settlementAmount || 0) * Number(this.form.paymentRatio || 0)) / 100).toFixed(2)
|
||
);
|
||
},
|
||
applyTransferredPreSettlements(rows = []) {
|
||
if (!rows.length) return;
|
||
const contractIds = new Set(rows.map(item => String(item.contractId || '')));
|
||
if (contractIds.size > 1 || contractIds.has('')) {
|
||
this.$message.warning('所选预结算单合同信息不一致,请返回后重新选择');
|
||
return;
|
||
}
|
||
const first = rows[0];
|
||
this.form.invoices = [];
|
||
this.referenceRows = rows.map(item => this.createReferenceRow(item));
|
||
const settlementAmount = this.referenceRows.reduce(
|
||
(total, item) => total + Number(item.settlementAmount || 0),
|
||
0
|
||
);
|
||
const payableAmount = this.referenceRows.reduce(
|
||
(total, item) => total + Number(item.payableAmount || 0),
|
||
0
|
||
);
|
||
const paymentRatio = Number(this.form.paymentRatio || 0);
|
||
this.amountSyncing = true;
|
||
Object.assign(this.form, {
|
||
paymentType: 'progress_advance',
|
||
settlementId: null,
|
||
settlementNo: '',
|
||
preSettlementId: first.preSettlementId || first.id,
|
||
preSettlementIds: rows.map(item => item.preSettlementId || item.id),
|
||
preSettlementNo: this.referenceRows
|
||
.map(item => item.settlementNo)
|
||
.filter(Boolean)
|
||
.join('、'),
|
||
projectId: first.projectId,
|
||
projectName: first.projectName,
|
||
deptId: first.deptId,
|
||
deptName: first.deptName,
|
||
contractId: first.contractId,
|
||
contractNo: first.contractNo,
|
||
contractName: first.contractName,
|
||
payerName: first.payerName,
|
||
payeeName: first.payeeName,
|
||
settlementAmount: Number(settlementAmount.toFixed(2)),
|
||
payableAmount: Number(payableAmount.toFixed(2)),
|
||
billType: '预结算单',
|
||
appliedAmount: Number(((settlementAmount * paymentRatio) / 100).toFixed(2)),
|
||
});
|
||
this.createMockPaymentRecords(this.form.preSettlementNo, this.form.appliedAmount);
|
||
this.$nextTick(() => {
|
||
this.amountSyncing = false;
|
||
this.$refs.formRef?.clearValidate();
|
||
});
|
||
},
|
||
parse(value) {
|
||
if (!value) return [];
|
||
if (Array.isArray(value)) return value;
|
||
try {
|
||
return JSON.parse(value) || [];
|
||
} catch {
|
||
return [];
|
||
}
|
||
},
|
||
attachmentFileName(file) {
|
||
return String(file?.originalName || file?.name || file?.fileName || '').trim();
|
||
},
|
||
attachmentFileUrl(file) {
|
||
return (
|
||
file?.url ||
|
||
file?.link ||
|
||
file?.fileUrl ||
|
||
file?.downloadUrl ||
|
||
file?.src ||
|
||
file?.domain ||
|
||
''
|
||
);
|
||
},
|
||
attachmentFileExtension(file = {}) {
|
||
const name = String(this.attachmentFileName(file) || this.attachmentFileUrl(file)).split(
|
||
'?'
|
||
)[0];
|
||
const index = name.lastIndexOf('.');
|
||
return index > -1 ? name.slice(index + 1).toLowerCase() : '';
|
||
},
|
||
attachmentMimeType(file = {}) {
|
||
return String(file.mimeType || file.contentType || file.type || '').toLowerCase();
|
||
},
|
||
isAttachmentImage(file) {
|
||
return (
|
||
this.attachmentMimeType(file).startsWith('image/') ||
|
||
['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(this.attachmentFileExtension(file))
|
||
);
|
||
},
|
||
isAttachmentPdf(file) {
|
||
return (
|
||
this.attachmentMimeType(file).includes('application/pdf') ||
|
||
this.attachmentFileExtension(file) === 'pdf'
|
||
);
|
||
},
|
||
previewAttachment(file) {
|
||
const url = this.attachmentFileUrl(file);
|
||
if (!url) {
|
||
this.$message.warning('附件地址为空,无法预览');
|
||
return;
|
||
}
|
||
if (this.isAttachmentImage(file)) {
|
||
this.attachmentImagePreviewUrls = (this.form.attachments || [])
|
||
.filter(item => this.isAttachmentImage(item) && this.attachmentFileUrl(item))
|
||
.map(item => this.attachmentFileUrl(item));
|
||
this.attachmentImagePreviewIndex = Math.max(
|
||
this.attachmentImagePreviewUrls.indexOf(url),
|
||
0
|
||
);
|
||
this.attachmentImagePreviewVisible = true;
|
||
return;
|
||
}
|
||
const isPdf = this.isAttachmentPdf(file);
|
||
this.attachmentPreviewFile = {
|
||
name: this.attachmentFileName(file) || '附件',
|
||
url,
|
||
mimeType: isPdf ? 'application/pdf' : this.attachmentMimeType(file),
|
||
isPdf,
|
||
};
|
||
this.attachmentDocumentPreviewVisible = true;
|
||
},
|
||
handleAttachmentPreviewUnsupported() {
|
||
this.$message.warning('当前文件暂不支持在线预览');
|
||
},
|
||
handleAttachmentPreviewError() {
|
||
this.$message.error('附件预览失败');
|
||
},
|
||
attachmentMatchesMaterial(file, material) {
|
||
const fileName = this.attachmentFileName(file).toLocaleLowerCase();
|
||
return (
|
||
Boolean(this.attachmentFileUrl(file)) &&
|
||
(file.attachmentType === MATERIAL_TYPE_MAP[material.key] ||
|
||
material.keywords.some(keyword => fileName.includes(keyword.toLocaleLowerCase())))
|
||
);
|
||
},
|
||
resolveAttachmentTypeByFileName(file) {
|
||
let fileName = this.attachmentFileName(file) || this.attachmentFileUrl(file);
|
||
fileName = String(fileName || '').split(/[?#]/)[0].toLocaleLowerCase();
|
||
try {
|
||
fileName = decodeURIComponent(fileName);
|
||
} catch {}
|
||
const material = [...Object.values(ATTACHMENT_MATERIALS)]
|
||
.sort(
|
||
(a, b) =>
|
||
Math.max(...b.keywords.map(String.length)) - Math.max(...a.keywords.map(String.length))
|
||
)
|
||
.find(item =>
|
||
item.keywords.some(keyword => fileName.includes(String(keyword).toLocaleLowerCase()))
|
||
);
|
||
return material ? MATERIAL_TYPE_MAP[material.key] : 'other';
|
||
},
|
||
hasAttachmentMaterial(material) {
|
||
const hasUploadedAttachment = (this.form.attachments || []).some(file =>
|
||
this.attachmentMatchesMaterial(file, material)
|
||
);
|
||
if (
|
||
this.form.paymentType === 'project_advance' &&
|
||
material.key === ATTACHMENT_MATERIALS.contract.key
|
||
) {
|
||
return this.contractSignatureFileAvailable || hasUploadedAttachment;
|
||
}
|
||
return hasUploadedAttachment;
|
||
},
|
||
resolveAttachmentMaterial(file, materials = Object.values(ATTACHMENT_MATERIALS)) {
|
||
return [...materials]
|
||
.sort(
|
||
(a, b) =>
|
||
Math.max(...b.keywords.map(String.length)) - Math.max(...a.keywords.map(String.length))
|
||
)
|
||
.find(material => this.attachmentMatchesMaterial(file, material));
|
||
},
|
||
mergeAutomaticAttachments(files, materials, sourceName, defaultMaterial = null) {
|
||
const existingUrls = new Set(
|
||
(this.form.attachments || []).map(file => this.attachmentFileUrl(file)).filter(Boolean)
|
||
);
|
||
const imported = (files || [])
|
||
.map(file => ({
|
||
file,
|
||
material: this.resolveAttachmentMaterial(file, materials) || defaultMaterial,
|
||
}))
|
||
.filter(item => item.material && !existingUrls.has(this.attachmentFileUrl(item.file)))
|
||
.map(({ file, material }) => {
|
||
existingUrls.add(this.attachmentFileUrl(file));
|
||
return {
|
||
...file,
|
||
originalName: this.attachmentFileName(file),
|
||
name: this.attachmentFileName(file),
|
||
url: this.attachmentFileUrl(file),
|
||
link: file.link || this.attachmentFileUrl(file),
|
||
attachmentType: MATERIAL_TYPE_MAP[material.key],
|
||
description: file.description || `自动取自${sourceName}`,
|
||
sourceImported: true,
|
||
};
|
||
});
|
||
if (imported.length) this.form.attachments = [...this.form.attachments, ...imported];
|
||
},
|
||
async loadCurrentSettlementDetails() {
|
||
if (this.form.paymentType === 'project_advance') return [];
|
||
const requests = [];
|
||
if (this.isValidReferenceId(this.form.preSettlementId)) {
|
||
requests.push(preApi.getDetail(this.form.preSettlementId));
|
||
}
|
||
if (this.isValidReferenceId(this.form.settlementId)) {
|
||
requests.push(formalApi.getDetail(this.form.settlementId));
|
||
}
|
||
if (!requests.length) return [];
|
||
const responses = await Promise.all(requests);
|
||
return responses.map(response => this.unwrapData(response)).filter(item => item?.id);
|
||
},
|
||
async loadAttachmentRuleData(settlementRows = []) {
|
||
this.attachmentRuleLoading = true;
|
||
this.firstContractPayment = false;
|
||
this.contractSignatureFileAvailable = false;
|
||
try {
|
||
const settlementMaterials = [
|
||
ATTACHMENT_MATERIALS.weighingSlip,
|
||
ATTACHMENT_MATERIALS.entrustOrder,
|
||
ATTACHMENT_MATERIALS.settlement,
|
||
];
|
||
const settlementFiles = (settlementRows || []).flatMap(row =>
|
||
this.parse(row.attachmentsJson)
|
||
);
|
||
this.mergeAutomaticAttachments(settlementFiles, settlementMaterials, '结算单');
|
||
if (!this.form.contractId) {
|
||
this.firstContractPayment = false;
|
||
return;
|
||
}
|
||
const contractResponse = await getContractDetail(this.form.contractId);
|
||
const contract = this.unwrapData(contractResponse) || {};
|
||
if (this.form.paymentType === 'project_advance') {
|
||
const contractFiles = this.parse(contract.contractFileJson);
|
||
this.contractSignatureFileAvailable = contractFiles.some(file =>
|
||
Boolean(this.attachmentFileUrl(file))
|
||
);
|
||
this.mergeAutomaticAttachments(
|
||
contractFiles,
|
||
[ATTACHMENT_MATERIALS.contract],
|
||
'合同',
|
||
ATTACHMENT_MATERIALS.contract
|
||
);
|
||
return;
|
||
}
|
||
const paymentResponse = await api.getList(1, 9999, {
|
||
contractId: this.form.contractId,
|
||
});
|
||
const payments = this.unwrapData(paymentResponse)?.records || [];
|
||
this.firstContractPayment = !payments.some(
|
||
item =>
|
||
String(item.contractId || '') === String(this.form.contractId) &&
|
||
String(item.id || '') !== String(this.form.id || '') &&
|
||
item.approvalStatus !== 'voided'
|
||
);
|
||
if (this.firstContractPayment) {
|
||
this.mergeAutomaticAttachments(
|
||
this.parse(contract.contractFileJson),
|
||
[ATTACHMENT_MATERIALS.contract],
|
||
'合同',
|
||
ATTACHMENT_MATERIALS.contract
|
||
);
|
||
}
|
||
} catch (error) {
|
||
this.$message.warning('附件清单来源材料加载失败,请核对后手工补充');
|
||
} finally {
|
||
this.attachmentRuleLoading = false;
|
||
}
|
||
},
|
||
validateRequiredAttachments() {
|
||
if (!this.missingAttachmentMaterials.length) return true;
|
||
this.$message.warning(
|
||
`请先上传付款申请所需材料:${this.missingAttachmentMaterials
|
||
.map(item => item.label)
|
||
.join('、')}`
|
||
);
|
||
return false;
|
||
},
|
||
handleTypeChange() {
|
||
this.form.invoices = [];
|
||
this.paymentRecords = [];
|
||
Object.assign(this.form, {
|
||
settlementId: null,
|
||
settlementIds: [],
|
||
preSettlementId: null,
|
||
preSettlementIds: [],
|
||
settlementNo: '',
|
||
preSettlementNo: '',
|
||
projectId: null,
|
||
projectName: '',
|
||
deptId: null,
|
||
deptName: '',
|
||
contractId: null,
|
||
contractNo: '',
|
||
contractName: '',
|
||
payerName: '',
|
||
payeeName: '',
|
||
settlementAmount: 0,
|
||
payableAmount: 0,
|
||
appliedAmount: 0,
|
||
billType: '',
|
||
});
|
||
this.referenceRows = [];
|
||
this.contractOptions = [];
|
||
this.clearReceiptAccount();
|
||
this.loadAttachmentRuleData([]);
|
||
if (this.form.paymentType === 'project_advance') this.loadProjectOptions();
|
||
},
|
||
clearReceiptAccount() {
|
||
Object.assign(this.form, {
|
||
receiptAccountId: null,
|
||
receiptAccountName: '',
|
||
bankName: '',
|
||
bankAccount: '',
|
||
});
|
||
},
|
||
customerRecords(response) {
|
||
return this.unwrapData(response)?.records || [];
|
||
},
|
||
async loadProjectOptions() {
|
||
if (this.projectLoading) return;
|
||
this.projectLoading = true;
|
||
try {
|
||
const response = await getProjectList(1, 9999, {});
|
||
this.projectOptions = this.customerRecords(response);
|
||
if (
|
||
this.form.projectId &&
|
||
this.form.projectName &&
|
||
!this.projectOptions.some(item => String(item.id) === String(this.form.projectId))
|
||
) {
|
||
this.projectOptions.unshift({
|
||
id: this.form.projectId,
|
||
projectName: this.form.projectName,
|
||
});
|
||
}
|
||
} catch (error) {
|
||
this.projectOptions = [];
|
||
this.$message.warning('项目列表加载失败,请稍后重试');
|
||
} finally {
|
||
this.projectLoading = false;
|
||
}
|
||
},
|
||
async loadContractOptions(projectId) {
|
||
if (!projectId) {
|
||
this.contractOptions = [];
|
||
return;
|
||
}
|
||
this.contractLoading = true;
|
||
try {
|
||
const response = await formalApi.getContractOptions('', projectId);
|
||
this.contractOptions = (this.unwrapData(response) || []).filter(
|
||
item => String(item.projectId || '') === String(projectId)
|
||
);
|
||
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('项目合同加载失败,请稍后重试');
|
||
} finally {
|
||
this.contractLoading = false;
|
||
}
|
||
},
|
||
async handleProjectChange(projectId) {
|
||
const project = this.projectOptions.find(item => String(item.id) === String(projectId));
|
||
this.form.projectName = project?.projectName || project?.projectShortName || '';
|
||
Object.assign(this.form, {
|
||
deptId: project?.undertakeDeptId || project?.businessDeptId || null,
|
||
deptName: project?.undertakeDeptName || project?.businessDeptName || '',
|
||
contractId: null,
|
||
contractNo: '',
|
||
contractName: '',
|
||
payerName: '',
|
||
payeeName: '',
|
||
settlementAmount: 0,
|
||
payableAmount: 0,
|
||
appliedAmount: 0,
|
||
billType: '',
|
||
});
|
||
this.contractOptions = [];
|
||
this.clearReceiptAccount();
|
||
this.form.invoices = [];
|
||
this.paymentRecords = [];
|
||
await this.loadContractOptions(projectId);
|
||
await this.loadAttachmentRuleData([]);
|
||
},
|
||
async handleAdvanceContractChange(contractId) {
|
||
const contract = this.contractOptions.find(
|
||
item => String(item.id) === String(contractId)
|
||
);
|
||
if (!contract) {
|
||
Object.assign(this.form, {
|
||
contractNo: '',
|
||
contractName: '',
|
||
payerName: '',
|
||
payeeName: '',
|
||
});
|
||
this.clearReceiptAccount();
|
||
return;
|
||
}
|
||
Object.assign(this.form, {
|
||
contractNo: contract.contractNo || '',
|
||
contractName: contract.contractName || '',
|
||
payerName: contract.payerName || contract.partyA || '',
|
||
payeeName: contract.payeeName || contract.partyB || '',
|
||
settlementAmount: 0,
|
||
payableAmount: 0,
|
||
billType: '',
|
||
});
|
||
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
||
if (this.billPayment) this.loadBillOptions();
|
||
},
|
||
async loadReceiptAccountOptions(autoSelectDefault = true) {
|
||
const payeeName = String(this.form.payeeName || '').trim();
|
||
const preserveCurrentValue = autoSelectDefault === false;
|
||
this.receiptAccountOptions = [];
|
||
this.payeeCustomerLevel = '';
|
||
if (!payeeName) {
|
||
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||
return;
|
||
}
|
||
this.receiptAccountLoading = true;
|
||
try {
|
||
const [fullNameResponse, shortNameResponse] = await Promise.all([
|
||
getCustomerArchiveList(1, 20, {
|
||
fullName: payeeName,
|
||
approvalStatus: 'approved',
|
||
status: 1,
|
||
}),
|
||
getCustomerArchiveList(1, 20, {
|
||
shortName: payeeName,
|
||
approvalStatus: 'approved',
|
||
status: 1,
|
||
}),
|
||
]);
|
||
const customers = [
|
||
...this.customerRecords(fullNameResponse),
|
||
...this.customerRecords(shortNameResponse),
|
||
];
|
||
const customer = customers.find(
|
||
item => item.fullName === payeeName || item.shortName === payeeName
|
||
);
|
||
if (!customer?.id) {
|
||
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||
return;
|
||
}
|
||
const detail = this.unwrapData(await getCustomerArchiveDetail(customer.id));
|
||
this.payeeCustomerLevel = detail.customerLevel || '';
|
||
this.receiptAccountOptions = (detail.receiptAccounts || []).map(item => ({
|
||
...item,
|
||
id: String(item.id),
|
||
}));
|
||
const selected = this.receiptAccountOptions.find(
|
||
item => String(item.id) === String(this.form.receiptAccountId || '')
|
||
);
|
||
if (selected) {
|
||
this.applyReceiptAccount(selected);
|
||
return;
|
||
}
|
||
if (preserveCurrentValue) return;
|
||
this.clearReceiptAccount();
|
||
if (autoSelectDefault && !this.readonly) {
|
||
const defaultAccount =
|
||
this.receiptAccountOptions.find(item => Number(item.isDefault) === 1) ||
|
||
(this.receiptAccountOptions.length === 1 ? this.receiptAccountOptions[0] : null);
|
||
if (defaultAccount) this.applyReceiptAccount(defaultAccount);
|
||
}
|
||
} catch (error) {
|
||
if (!preserveCurrentValue) this.clearReceiptAccount();
|
||
this.$message.warning('收款方的客商收款信息加载失败,请稍后重试');
|
||
} finally {
|
||
this.receiptAccountLoading = false;
|
||
}
|
||
},
|
||
receiptAccountLabel(item) {
|
||
return [item.accountName || item.accountHolderName, item.bankName, item.bankAccount]
|
||
.filter(Boolean)
|
||
.join('|');
|
||
},
|
||
applyReceiptAccount(account) {
|
||
Object.assign(this.form, {
|
||
receiptAccountId: account.id,
|
||
receiptAccountName: account.accountName || account.accountHolderName || '',
|
||
bankName: account.bankName || '',
|
||
bankAccount: account.bankAccount || '',
|
||
});
|
||
this.$nextTick(() => this.$refs.formRef?.validateField('receiptAccountId').catch(() => {}));
|
||
},
|
||
handleReceiptAccountChange(id) {
|
||
const account = this.receiptAccountOptions.find(item => String(item.id) === String(id || ''));
|
||
if (account) {
|
||
this.applyReceiptAccount(account);
|
||
return;
|
||
}
|
||
this.clearReceiptAccount();
|
||
},
|
||
async handlePaymentMethodChange() {
|
||
if (!this.billPayment) {
|
||
this.form.billLedgerId = null;
|
||
this.form.billNo = '';
|
||
this.billOptions = [];
|
||
return;
|
||
}
|
||
await this.loadBillOptions();
|
||
},
|
||
async loadBillOptions(keyword = '', selectedId = this.form.billLedgerId) {
|
||
if (!this.billPayment) return;
|
||
this.billLoading = true;
|
||
try {
|
||
this.billOptions =
|
||
this.unwrapData(
|
||
await billLedgerApi.getAvailableOptions(keyword, this.form.deptId, selectedId)
|
||
) || [];
|
||
} finally {
|
||
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(() => {});
|
||
},
|
||
referenceParams(type) {
|
||
const { settlementNo, projectName, contractName } = this.referenceQuery;
|
||
return {
|
||
settlementType: 'payable',
|
||
approvalStatus: 'approved',
|
||
[type === 'formal' ? 'formalSettlementNo' : 'preSettlementNo']:
|
||
String(settlementNo || '').trim() || undefined,
|
||
projectName: String(projectName || '').trim() || undefined,
|
||
contractName: String(contractName || '').trim() || undefined,
|
||
};
|
||
},
|
||
async loadFormalReferences() {
|
||
this.formalPage.loading = true;
|
||
try {
|
||
const response = await formalApi.getList(
|
||
this.formalPage.current,
|
||
this.formalPage.size,
|
||
this.referenceParams('formal')
|
||
);
|
||
const data = this.unwrapData(response) || {};
|
||
this.formalRows = data.records || [];
|
||
this.formalPage.total = Number(data.total || 0);
|
||
} finally {
|
||
this.formalPage.loading = false;
|
||
}
|
||
},
|
||
async loadPreReferences() {
|
||
this.prePage.loading = true;
|
||
try {
|
||
const response = await preApi.getList(
|
||
this.prePage.current,
|
||
this.prePage.size,
|
||
this.referenceParams('pre')
|
||
);
|
||
const data = this.unwrapData(response) || {};
|
||
this.preRows = data.records || [];
|
||
this.prePage.total = Number(data.total || 0);
|
||
} finally {
|
||
this.prePage.loading = false;
|
||
}
|
||
},
|
||
loadSettlementReferences() {
|
||
return Promise.all([this.loadFormalReferences(), this.loadPreReferences()]);
|
||
},
|
||
handleReferenceSearch() {
|
||
this.formalPage.current = 1;
|
||
this.prePage.current = 1;
|
||
this.loadSettlementReferences();
|
||
},
|
||
resetReferenceSearch() {
|
||
Object.assign(this.referenceQuery, {
|
||
settlementNo: '',
|
||
projectName: '',
|
||
contractName: '',
|
||
});
|
||
this.handleReferenceSearch();
|
||
},
|
||
handleReferenceTabChange(name) {
|
||
if (name === 'formal') {
|
||
this.loadFormalReferences();
|
||
return;
|
||
}
|
||
this.loadPreReferences();
|
||
},
|
||
handleFormalSizeChange() {
|
||
this.formalPage.current = 1;
|
||
this.loadFormalReferences();
|
||
},
|
||
handlePreSizeChange() {
|
||
this.prePage.current = 1;
|
||
this.loadPreReferences();
|
||
},
|
||
async openReference() {
|
||
if (this.form.paymentType === 'project_advance') return;
|
||
this.referenceVisible = true;
|
||
this.formalSelection = [];
|
||
this.preSelection = [];
|
||
this.formalPage.current = 1;
|
||
this.prePage.current = 1;
|
||
await this.loadSettlementReferences();
|
||
},
|
||
async selectContract(row) {
|
||
this.referenceRows = [];
|
||
this.form.invoices = [];
|
||
this.paymentRecords = [];
|
||
this.clearReceiptAccount();
|
||
Object.assign(this.form, {
|
||
projectId: row.projectId,
|
||
projectName: row.projectName,
|
||
deptId: row.deptId,
|
||
deptName: row.deptName,
|
||
contractId: row.id,
|
||
contractNo: row.contractNo,
|
||
contractName: row.contractName,
|
||
payerName: row.payerName || row.partyA || '',
|
||
payeeName: row.payeeName || row.partyB || '',
|
||
settlementAmount: 0,
|
||
payableAmount: 0,
|
||
appliedAmount: 0,
|
||
billType: '',
|
||
});
|
||
this.referenceVisible = false;
|
||
await Promise.all([this.loadReceiptAccountOptions(), this.loadAttachmentRuleData([])]);
|
||
if (this.billPayment) this.loadBillOptions();
|
||
},
|
||
async selectFormal(row) {
|
||
const validReferenceId = this.isValidReferenceId(row.id);
|
||
const [amountResponse, detailResponse] = await Promise.all([
|
||
validReferenceId
|
||
? api.getReferenceAmount('settlement_payment', row.id, this.form.id)
|
||
: Promise.resolve(null),
|
||
validReferenceId ? formalApi.getDetail(row.id) : Promise.resolve(null),
|
||
]);
|
||
const amount = this.unwrapData(amountResponse) || {};
|
||
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
||
const payableAmount = Number(amount.payableAmount ?? 0);
|
||
this.referenceRows = [this.createReferenceRow({ ...row, settlementAmount, payableAmount })];
|
||
this.clearReceiptAccount();
|
||
Object.assign(this.form, {
|
||
paymentType: 'settlement_payment',
|
||
settlementId: row.id,
|
||
settlementIds: [row.id],
|
||
preSettlementId: null,
|
||
preSettlementIds: [],
|
||
settlementNo: row.formalSettlementNo,
|
||
preSettlementNo: '',
|
||
projectId: row.projectId,
|
||
projectName: row.projectName,
|
||
deptId: row.deptId,
|
||
deptName: row.deptName,
|
||
contractId: row.contractId,
|
||
contractNo: row.contractNo,
|
||
contractName: row.contractName,
|
||
settlementAmount,
|
||
payableAmount,
|
||
billType: '正式结算单',
|
||
payerName: row.payerName,
|
||
payeeName: row.payeeName,
|
||
});
|
||
this.referenceVisible = false;
|
||
this.refreshReferenceValidation();
|
||
const detail = this.unwrapData(detailResponse);
|
||
this.form.invoices = this.createInvoiceRows(
|
||
detail?.invoices,
|
||
detail?.formalSettlementNo || row.formalSettlementNo
|
||
);
|
||
this.createMockPaymentRecords(
|
||
row.formalSettlementNo,
|
||
this.referencePaymentAmount(settlementAmount)
|
||
);
|
||
await Promise.all([
|
||
this.loadReceiptAccountOptions(),
|
||
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
||
]);
|
||
if (this.billPayment) this.loadBillOptions();
|
||
},
|
||
async confirmFormalSelection() {
|
||
const rows = this.formalSelection || [];
|
||
if (!rows.length) return;
|
||
if (new Set(rows.map(item => String(item.contractId || ''))).size !== 1) {
|
||
this.$message.warning('所选正式结算单必须属于同一合同');
|
||
return;
|
||
}
|
||
const validRows = rows.filter(row => this.isValidReferenceId(row.id));
|
||
const details = await Promise.all(validRows.map(row => formalApi.getDetail(row.id)));
|
||
this.applyTransferredFormalSettlements(
|
||
details.map((response, index) => ({ ...validRows[index], ...this.unwrapData(response) }))
|
||
);
|
||
this.referenceVisible = false;
|
||
},
|
||
async selectPre(row) {
|
||
const validReferenceId = this.isValidReferenceId(row.id);
|
||
const [amountResponse, detailResponse] = await Promise.all([
|
||
validReferenceId
|
||
? api.getReferenceAmount('progress_advance', row.id, this.form.id)
|
||
: Promise.resolve(null),
|
||
validReferenceId ? preApi.getDetail(row.id) : Promise.resolve(null),
|
||
]);
|
||
const amount = this.unwrapData(amountResponse) || {};
|
||
const settlementAmount = Number(amount.settlementAmount ?? row.settlementAmount ?? 0);
|
||
const payableAmount = Number(amount.payableAmount ?? 0);
|
||
this.referenceRows = [this.createReferenceRow({ ...row, settlementAmount, payableAmount })];
|
||
this.form.invoices = [];
|
||
this.paymentRecords = [];
|
||
this.clearReceiptAccount();
|
||
Object.assign(this.form, {
|
||
paymentType: 'progress_advance',
|
||
preSettlementId: row.id,
|
||
preSettlementIds: [row.id],
|
||
settlementId: null,
|
||
settlementIds: [],
|
||
preSettlementNo: row.preSettlementNo,
|
||
settlementNo: '',
|
||
projectId: row.projectId,
|
||
projectName: row.projectName,
|
||
deptId: row.deptId,
|
||
deptName: row.deptName,
|
||
contractId: row.contractId,
|
||
contractNo: row.contractNo,
|
||
contractName: row.contractName,
|
||
settlementAmount,
|
||
payableAmount,
|
||
billType: '预结算单',
|
||
payerName: row.payerName,
|
||
payeeName: row.payeeName,
|
||
});
|
||
this.referenceVisible = false;
|
||
this.refreshReferenceValidation();
|
||
this.createMockPaymentRecords(
|
||
row.preSettlementNo,
|
||
this.referencePaymentAmount(settlementAmount)
|
||
);
|
||
const detail = this.unwrapData(detailResponse);
|
||
await Promise.all([
|
||
this.loadReceiptAccountOptions(),
|
||
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
||
]);
|
||
if (this.billPayment) this.loadBillOptions();
|
||
},
|
||
async confirmPreSelection() {
|
||
const rows = this.preSelection || [];
|
||
if (!rows.length) return;
|
||
if (new Set(rows.map(item => String(item.contractId || ''))).size !== 1) {
|
||
this.$message.warning('所选预结算单必须属于同一合同');
|
||
return;
|
||
}
|
||
const validRows = rows.filter(row => this.isValidReferenceId(row.id));
|
||
const details = await Promise.all(validRows.map(row => preApi.getDetail(row.id)));
|
||
this.applyTransferredPreSettlements(
|
||
details.map((response, index) => ({ ...validRows[index], ...this.unwrapData(response) }))
|
||
);
|
||
this.referenceVisible = false;
|
||
},
|
||
normalizeAttachments(files) {
|
||
this.form.attachments = (files || []).map(file => ({
|
||
...file,
|
||
attachmentType: (() => {
|
||
const matchedType = this.resolveAttachmentTypeByFileName(file);
|
||
const existingType = [
|
||
...Object.values(MATERIAL_TYPE_MAP),
|
||
'other',
|
||
].includes(file.attachmentType)
|
||
? file.attachmentType
|
||
: 'other';
|
||
return matchedType !== 'other' ? matchedType : existingType;
|
||
})(),
|
||
description: file.description || '',
|
||
}));
|
||
},
|
||
handleManualAttachmentUpload(uploadedFile) {
|
||
const uploadedUrl = this.attachmentFileUrl(uploadedFile);
|
||
const uploadedUid = uploadedFile?.uid;
|
||
const userInfo = this.$store.getters.userInfo || {};
|
||
const uploadUserName = userInfo.realName || userInfo.userName || '';
|
||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||
this.form.attachments = (this.form.attachments || []).map(file => {
|
||
const isUploadedFile =
|
||
(uploadedUid && file.uid === uploadedUid) ||
|
||
(uploadedUrl && this.attachmentFileUrl(file) === uploadedUrl);
|
||
return isUploadedFile
|
||
? {
|
||
...file,
|
||
uploadUserName,
|
||
uploadTime,
|
||
}
|
||
: file;
|
||
});
|
||
},
|
||
formatAttachmentSize(file = {}) {
|
||
const rawSize = file.size ?? file.fileSize ?? file.attachSize;
|
||
if (rawSize === undefined || rawSize === null || rawSize === '') return '-';
|
||
const matched = String(rawSize)
|
||
.trim()
|
||
.match(/^([\d.]+)\s*(B|K|KB|M|MB|G|GB)?$/i);
|
||
if (!matched) return '-';
|
||
const value = Number(matched[1]);
|
||
if (!Number.isFinite(value)) return '-';
|
||
const unit = (matched[2] || 'B').toUpperCase();
|
||
const unitToMb = {
|
||
B: 1 / 1024 / 1024,
|
||
K: 1 / 1024,
|
||
KB: 1 / 1024,
|
||
M: 1,
|
||
MB: 1,
|
||
G: 1024,
|
||
GB: 1024,
|
||
};
|
||
return `${(value * unitToMb[unit]).toFixed(2)} MB`;
|
||
},
|
||
handleAttachmentBatchDownload() {
|
||
this.downloadableAttachments.forEach((file, index) => {
|
||
window.setTimeout(
|
||
() => downloadFileByUrl(this.attachmentFileUrl(file), this.attachmentFileName(file)),
|
||
index * 500
|
||
);
|
||
});
|
||
},
|
||
payload() {
|
||
const {
|
||
id,
|
||
paymentType,
|
||
settlementId,
|
||
settlementIds,
|
||
preSettlementId,
|
||
preSettlementIds,
|
||
projectId,
|
||
projectName,
|
||
deptId,
|
||
deptName,
|
||
contractId,
|
||
contractNo,
|
||
contractName,
|
||
payerName,
|
||
payeeName,
|
||
settlementAmount,
|
||
payableAmount,
|
||
billType,
|
||
paymentRatio,
|
||
appliedAmount,
|
||
paymentMethod,
|
||
billLedgerId,
|
||
billNo,
|
||
receiptAccountId,
|
||
receiptAccountName,
|
||
bankName,
|
||
bankAccount,
|
||
attachments,
|
||
remark,
|
||
invoices,
|
||
} = this.form;
|
||
return {
|
||
id,
|
||
paymentType,
|
||
settlementId,
|
||
settlementIds,
|
||
preSettlementId,
|
||
preSettlementIds,
|
||
projectId,
|
||
projectName,
|
||
deptId,
|
||
deptName,
|
||
contractId,
|
||
contractNo,
|
||
contractName,
|
||
payerName,
|
||
payeeName,
|
||
settlementAmount,
|
||
payableAmount,
|
||
billType,
|
||
paymentRatio,
|
||
appliedAmount,
|
||
paymentMethod,
|
||
billLedgerId,
|
||
billNo,
|
||
receiptAccountId,
|
||
receiptAccountName,
|
||
bankName,
|
||
bankAccount,
|
||
attachmentsJson: JSON.stringify(attachments || []),
|
||
remark,
|
||
invoices:
|
||
paymentType === 'project_advance'
|
||
? []
|
||
: invoices.map(invoice => ({
|
||
settlementNo: invoice.settlementNo || '',
|
||
invoiceNo: invoice.invoiceNo || '',
|
||
invoiceDate: invoice.invoiceDate || null,
|
||
invoiceType: invoice.invoiceType || '',
|
||
taxRate: Number(invoice.taxRate || 0),
|
||
invoiceAmount: Number(invoice.invoiceAmount || 0),
|
||
matchedAmount: Number(invoice.matchedAmount || 0),
|
||
attachmentJson: invoice.attachmentJson || '',
|
||
})),
|
||
paymentRecords:
|
||
paymentType === 'project_advance'
|
||
? []
|
||
: this.paymentRecords.map(record => ({
|
||
paidAmount: Number(record.paidAmount || 0),
|
||
paidDate: this.formatLocalDate(record.paidDate),
|
||
paymentNo: record.paymentNo || '',
|
||
voucherJson: record.voucherJson || '',
|
||
kingdeeBillNo: record.kingdeeBillNo || '',
|
||
})),
|
||
};
|
||
},
|
||
async saveDraft(validateMaterials = false) {
|
||
await this.$refs.formRef.validate();
|
||
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
||
this.$message.warning('请选择所属项目');
|
||
return false;
|
||
}
|
||
if (validateMaterials && !this.validateRequiredAttachments()) return false;
|
||
try {
|
||
this.validateInvoices();
|
||
this.validatePaymentRecords();
|
||
} catch (error) {
|
||
this.$message.warning(error.message);
|
||
return false;
|
||
}
|
||
const data = this.unwrapData(await api.save(this.payload()));
|
||
this.form.id = data;
|
||
this.$message.success('保存成功');
|
||
return true;
|
||
},
|
||
async submitForm() {
|
||
const saved = await this.saveDraft(true);
|
||
if (!saved) return;
|
||
await api.submit({ id: this.form.id });
|
||
this.$message.success('提交成功');
|
||
this.goBack();
|
||
},
|
||
goBack() {
|
||
removeSettlementTransfer(this.$route.query.transferToken);
|
||
this.$router.push('/payment/payment-application');
|
||
},
|
||
formatMoney(value) {
|
||
return Number(value || 0).toFixed(2);
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.payment-form-page__form {
|
||
padding-bottom: 72px;
|
||
}
|
||
.payment-form-page__reference-search {
|
||
padding: 12px 12px 4px;
|
||
margin-bottom: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||
}
|
||
.payment-form-page__reference-search-fields {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 8px 16px;
|
||
}
|
||
.payment-form-page__reference-search :deep(.el-form-item) {
|
||
margin-bottom: 8px;
|
||
}
|
||
.payment-form-page__reference-search :deep(.el-form-item__label) {
|
||
white-space: nowrap;
|
||
}
|
||
.payment-form-page__reference-search-actions,
|
||
.payment-form-page__reference-pagination {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
.payment-form-page__reference-pagination {
|
||
margin-top: 12px;
|
||
}
|
||
.payment-form-page__actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding: 12px 24px;
|
||
position: fixed;
|
||
right: 0;
|
||
left: 230px;
|
||
bottom: 0;
|
||
margin: 0;
|
||
z-index: 10;
|
||
background: #fff;
|
||
border-top: 1px solid #eff1f7;
|
||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
:global(.avue--collapse .payment-form-page__actions) {
|
||
left: 60px;
|
||
}
|
||
:global(.avue-layout--horizontal .payment-form-page__actions) {
|
||
left: 0;
|
||
}
|
||
.payment-form-page__attachment-upload {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
margin-top: 12px;
|
||
}
|
||
.payment-form-page__missing-text {
|
||
margin-left: 12px;
|
||
color: var(--el-color-danger);
|
||
font-size: 13px;
|
||
font-weight: 400;
|
||
}
|
||
.payment-form-page__attachment-checklist {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.payment-form-page__attachment-download {
|
||
margin-left: auto;
|
||
}
|
||
.payment-form-page__attachment-file-name-cell {
|
||
display: block;
|
||
width: 100%;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-align: left;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.payment-form-page__attachment-file-name {
|
||
display: block;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
color: var(--el-text-color-regular);
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
transition: color 0.2s ease;
|
||
|
||
&:hover {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.is-disabled {
|
||
color: var(--el-text-color-regular);
|
||
cursor: default;
|
||
|
||
&:hover {
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
}
|
||
}
|
||
.payment-form-page__attachment-upload .vehicle-attachment-upload {
|
||
width: auto;
|
||
}
|
||
.payment-form-page__attachment-upload .el-upload {
|
||
display: inline-flex;
|
||
}
|
||
.payment-form-page :deep(.el-form-item) {
|
||
margin-bottom: 14px;
|
||
}
|
||
.payment-form-page :deep(.el-input-number .el-input__inner) {
|
||
text-align: left;
|
||
}
|
||
.payment-form-page__percentage-input {
|
||
display: flex;
|
||
width: 100%;
|
||
}
|
||
.payment-form-page__percentage-input :deep(.el-input-number) {
|
||
flex: 1;
|
||
width: 0;
|
||
}
|
||
.payment-form-page__percentage-input :deep(.el-input__wrapper) {
|
||
border-radius: var(--el-border-radius-base) 0 0 var(--el-border-radius-base);
|
||
}
|
||
.payment-form-page__percentage-append {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 40px;
|
||
padding: 0 16px;
|
||
color: var(--el-text-color-regular);
|
||
background-color: var(--el-fill-color-light);
|
||
border: 1px solid var(--el-border-color);
|
||
border-left: 0;
|
||
border-radius: 0 var(--el-border-radius-base) var(--el-border-radius-base) 0;
|
||
}
|
||
.payment-form-page :deep(.el-table) {
|
||
--el-table-border-color: #eff1f7;
|
||
}
|
||
.payment-form-page :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
|
||
background: #fafafa;
|
||
}
|
||
:deep(.payment-form-page.basic-container .basic-container__card > .el-card__body) {
|
||
padding: 0;
|
||
}
|
||
</style>
|