|
|
5<template>
|
|
|
<div class="p-3">
|
|
|
<el-card shadow="never" style="margin-top: 0; margin-bottom: 10px">
|
|
|
<!-- <template #header>-->
|
|
|
<!-- <div style="text-align: left; font-weight: bold; font-size: 24px">合同{{ form.contractId ? ' - 修改' : ' - 新增' }}</div>-->
|
|
|
<!-- </template>-->
|
|
|
<!-- 审批按钮组件 -->
|
|
|
<approvalButton
|
|
|
@submitForm="handleSave"
|
|
|
@approvalVerifyOpen="approvalVerifyOpen"
|
|
|
@handleApprovalRecord="handleApprovalRecord"
|
|
|
:buttonLoading="buttonLoading"
|
|
|
:id="form.invoiceId"
|
|
|
:status="form.flowStatus"
|
|
|
:pageType="routeParams.type"
|
|
|
:mode="false"
|
|
|
/>
|
|
|
</el-card>
|
|
|
|
|
|
<el-card shadow="never" class="mb-[15px]" header="开票信息">
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px" status-icon :disabled="isFormDisabled">
|
|
|
<el-row :gutter="24">
|
|
|
<!-- 第一行 -->
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="发出人员" prop="requestBy">
|
|
|
<el-select v-model="form.requestBy" placeholder="请选择发出人员" clearable filterable class="w-full" @change="handleUserChange">
|
|
|
<el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="发出部门" prop="requestDeptName">
|
|
|
<el-input v-model="form.requestDeptName" readonly />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="发出日期" prop="issueDate">
|
|
|
<el-date-picker v-model="form.issueDate" type="date" placeholder="请选择发出日期" value-format="YYYY-MM-DD" class="w-full" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<!-- 第二行:项目信息 -->
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="项目编号" prop="projectCode">
|
|
|
<el-input v-model="form.projectCode" placeholder="请选择项目编号" @click="handleSelectProject" readonly suffix-icon="Search">
|
|
|
</el-input>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="项目名称" prop="projectName">
|
|
|
<el-input v-model="form.projectName" placeholder="项目名称" readonly />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="合同编号" prop="contractCode">
|
|
|
<el-input
|
|
|
v-if="form.projectId && form.projectId !== '' && form.contractFlag === CONTRACT_FLAG.NO"
|
|
|
v-model="form.contractCode"
|
|
|
placeholder="请选择合同"
|
|
|
readonly
|
|
|
@click="showContractSelectDialog"
|
|
|
suffix-icon="Search"
|
|
|
/>
|
|
|
<el-input v-else v-model="form.contractCode" readonly />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="客户名称" prop="customerName">
|
|
|
<el-input v-model="form.customerName" placeholder="客户名称" readonly />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="合同金额" prop="totalPrice">
|
|
|
<el-input v-model="form.totalPrice" placeholder="合同金额" readonly></el-input>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="本次开具发票比例" prop="issuancePercentage">
|
|
|
<el-input-number
|
|
|
v-model="form.issuancePercentage"
|
|
|
:precision="2"
|
|
|
:min="1"
|
|
|
:max="100"
|
|
|
placeholder="请输入开具发票比例"
|
|
|
style="width: 210px"
|
|
|
>
|
|
|
</el-input-number>
|
|
|
<span style="margin-left: 5px">%</span>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="合同付款条款" prop="paymentMethod">
|
|
|
<span class="el-input__wrapper" style="padding: 0 11px; min-height: 32px; display: flex; align-items: center">
|
|
|
{{ paymentDescription }}
|
|
|
</span>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="项目类型" prop="invoiceCategory">
|
|
|
<el-radio-group v-model="form.invoiceCategory">
|
|
|
<el-radio v-for="dict in invoice_category" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="累计回款金额" prop="returnedMoney">
|
|
|
<el-input-number
|
|
|
v-model="form.returnedMoney"
|
|
|
:precision="2"
|
|
|
:controls="false"
|
|
|
disabled
|
|
|
placeholder="累计回款金额"
|
|
|
style="width: 210px"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="累计回款比例" prop="returnedRate">
|
|
|
<el-input v-model="form.returnedRate" placeholder="累计回款比例" readonly>
|
|
|
<template #append>%</template>
|
|
|
</el-input>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="提前开票" prop="earlyFlag">
|
|
|
<el-radio-group v-model="form.earlyFlag">
|
|
|
<el-radio
|
|
|
v-for="dict in early_flag"
|
|
|
:key="dict.value"
|
|
|
:value="dict.value"
|
|
|
:disabled="isSpareInvoiceCategory && dict.value === EARLY_FLAG.YES"
|
|
|
>
|
|
|
{{ dict.label }}
|
|
|
</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="验收日期" prop="acceptanceDate" v-if="form.earlyFlag !== EARLY_FLAG.YES">
|
|
|
<el-date-picker v-model="form.acceptanceDate" type="date" placeholder="请选择验收日期" value-format="YYYY-MM-DD" class="w-full" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="发货日期" prop="deliveryDate" v-if="form.earlyFlag !== EARLY_FLAG.YES">
|
|
|
<el-date-picker v-model="form.deliveryDate" type="date" placeholder="请选择发货日期" value-format="YYYY-MM-DD" class="w-full" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="提前开票原因" prop="earlyReason" v-if="form.earlyFlag === EARLY_FLAG.YES">
|
|
|
<el-input v-model="form.earlyReason" placeholder="请输入提前开票原因" type="textarea" :rows="1" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="发票备注信息" prop="remark">
|
|
|
<el-input v-model="form.remark" placeholder="请输入发票需备注的信息内容" type="textarea" :rows="1" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-form>
|
|
|
<!-- 发票附件单独表单:避免主表单在审批/待办时整体 disabled 导致无法上传;与权限角色 invoice 审批场景配合 -->
|
|
|
<el-form
|
|
|
v-if="routeParams.type === 'update' || routeParams.type === 'view' || routeParams.type === 'approval'"
|
|
|
:model="form"
|
|
|
label-width="130px"
|
|
|
status-icon
|
|
|
:disabled="invoiceAttachReadonly"
|
|
|
>
|
|
|
<el-row :gutter="24">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="发票附件" prop="ossId">
|
|
|
<FileUpload
|
|
|
v-model="ossIdString"
|
|
|
:limit="5"
|
|
|
:fileSize="20"
|
|
|
:fileType="['png', 'jpg', 'pdf', 'ofd', 'xml']"
|
|
|
:isShowTip="true"
|
|
|
:disabled="invoiceAttachReadonly"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 开票明细 -->
|
|
|
<el-card shadow="never" class="mb-[15px]">
|
|
|
<template #header>
|
|
|
<div class="flex justify-between items-center">
|
|
|
<span class="font-medium">开票明细</span>
|
|
|
<div>
|
|
|
<el-button type="primary" plain size="small" icon="Plus" @click="handleAddItem" :disabled="isFormDisabled">新增明细</el-button>
|
|
|
<el-button
|
|
|
type="danger"
|
|
|
plain
|
|
|
size="small"
|
|
|
icon="Delete"
|
|
|
@click="handleDeleteItems"
|
|
|
:disabled="isFormDisabled || selectedItems.length === 0"
|
|
|
>删除
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<el-table
|
|
|
:data="form.erpFinInvoiceDetailList"
|
|
|
border
|
|
|
stripe
|
|
|
@selection-change="handleSelectionChange"
|
|
|
max-height="400"
|
|
|
:header-cell-style="{ textAlign: 'center' }"
|
|
|
:cell-style="{ textAlign: 'center' }"
|
|
|
>
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
<el-table-column label="序号" type="index" width="60" align="center" />
|
|
|
<el-table-column label="开票内容" prop="billingItems" min-width="160">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-input
|
|
|
v-model="row.billingItems"
|
|
|
placeholder="请输入开票内容"
|
|
|
:disabled="isFormDisabled"
|
|
|
@change="handleItemChange($index)"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="规格型号" prop="specificationModel" min-width="140">
|
|
|
<template #default="{ row }">
|
|
|
<el-input v-model="row.specificationModel" placeholder="请输入规格型号" :disabled="isFormDisabled" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="单位" prop="unitName" width="120">
|
|
|
<template #default="{ row }">
|
|
|
<el-input v-model="row.unitName" placeholder="请输入单位" :disabled="isFormDisabled" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="数量" prop="quantity" width="145">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-input-number
|
|
|
v-model="row.quantity"
|
|
|
:min="0"
|
|
|
:precision="2"
|
|
|
controls-position="right"
|
|
|
:disabled="isFormDisabled"
|
|
|
@change="calculateItemAmount($index)"
|
|
|
style="width: 120px"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="税率(%)" prop="taxRate" width="110">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-select
|
|
|
v-model="row.taxRate"
|
|
|
filterable
|
|
|
allow-create
|
|
|
default-first-option
|
|
|
clearable
|
|
|
placeholder="税率"
|
|
|
style="width: 80px"
|
|
|
:disabled="isFormDisabled"
|
|
|
@change="calculateItemAmount($index)"
|
|
|
>
|
|
|
<el-option v-for="rate in taxRateOptions" :key="rate" :label="`${rate}`" :value="rate" />
|
|
|
</el-select>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="单价(含税)" prop="unitPrice" width="100">
|
|
|
<template #default="{ row }">
|
|
|
<span class="text-red-500 font-medium">{{ formatAmount(row.unitPrice) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="金额(含税)" prop="totalPrice" width="145">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-input-number
|
|
|
v-model="row.totalPrice"
|
|
|
:min="0"
|
|
|
:precision="2"
|
|
|
controls-position="right"
|
|
|
:disabled="isFormDisabled"
|
|
|
@change="calculateItemAmountByTotal($index)"
|
|
|
style="width: 120px"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="金额(不含税)" prop="totalPriceNoTax" width="150">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-input-number
|
|
|
v-model="row.totalPriceNoTax"
|
|
|
:min="0"
|
|
|
:precision="2"
|
|
|
controls-position="right"
|
|
|
:disabled="isFormDisabled"
|
|
|
@change="calculateItemAmountByNoTax($index)"
|
|
|
style="width: 120px"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column v-if="!isFormDisabled" label="操作" fixed="right" width="80" align="center">
|
|
|
<template #default="{ row, $index }">
|
|
|
<el-button link type="danger" icon="Delete" @click="handleRemoveItem($index, row)" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
|
|
|
<div class="flex justify-end mt-3 text-sm">
|
|
|
<div class="mr-4">
|
|
|
合计金额(含税):<span class="text-red-500 font-bold text-base">{{ totalInvoiceAmount }}</span>
|
|
|
元
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 项目选择弹窗 -->
|
|
|
|
|
|
<!-- 项目选择弹窗组件 -->
|
|
|
<ProjectSelectDialog v-model:visible="projectSelectDialogVisible" @project-selected="handleProjectSelected" />
|
|
|
|
|
|
<!-- 合同选择弹窗组件 -->
|
|
|
<ContractSelectDialog v-model:visible="contractSelectDialogVisible" @contract-selected="handleContractSelected" />
|
|
|
|
|
|
<!-- 人员选择弹窗 (可根据实际组件调整) -->
|
|
|
<el-dialog v-model="userDialogVisible" title="选择人员" width="600px" append-to-body>
|
|
|
<div class="p-3 text-center text-gray-500">请根据实际项目集成人员选择组件</div>
|
|
|
<template #footer>
|
|
|
<el-button @click="userDialogVisible = false">取消</el-button>
|
|
|
<el-button type="primary" @click="handleUserSelected">确定</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 提交审批组件 -->
|
|
|
<submitVerify ref="submitVerifyRef" @submit-callback="submitCallback" />
|
|
|
<!-- 审批记录 -->
|
|
|
<approvalRecord ref="approvalRecordRef" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="InvoiceInfoEdit">
|
|
|
import api from '@/api/system/user';
|
|
|
import { ref, reactive, computed, onMounted } from 'vue';
|
|
|
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
import ProjectSelectDialog from '@/views/oa/components/ProjectSelectDialog.vue';
|
|
|
import {
|
|
|
getFinInvoiceInfo,
|
|
|
getBaseInfo,
|
|
|
addFinInvoiceInfo,
|
|
|
updateFinInvoiceInfo,
|
|
|
getContractPaymentMethodList,
|
|
|
updateInvoiceAttach,
|
|
|
getMaxReturnedMoneyByContractId
|
|
|
} from '@/api/oa/erp/finInvoiceInfo';
|
|
|
import { FinInvoiceInfoForm, FinInvoiceInfoQuery } from '@/api/oa/erp/finInvoiceInfo/types';
|
|
|
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
|
|
import { FinInvoiceDetailForm, FinInvoiceDetailVO } from '@/api/oa/erp/finInvoiceDetail/types';
|
|
|
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
|
|
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
|
|
import { ContractPaymentMethodVO } from '@/api/oa/erp/contractPaymentMethod/types';
|
|
|
|
|
|
import { UserVO } from '@/api/system/user/types';
|
|
|
import ContractSelectDialog from '@/views/oa/components/ContractSelectDialog.vue';
|
|
|
import FileUpload from '@/components/FileUpload/index.vue';
|
|
|
import { checkPermi, checkRole } from '@/utils/permission';
|
|
|
import { getContractInfo } from '@/api/oa/erp/contractInfo';
|
|
|
import { getProjectInfo, listProjectInfoByContractId } from '@/api/oa/erp/projectInfo';
|
|
|
|
|
|
const userOptions = ref<UserVO[]>([]);
|
|
|
const contractPaymentMethodVoList = ref<ContractPaymentMethodVO[]>([]);
|
|
|
/** 同合同下历史开票单(累计回款比例最大)的累计回款金额 */
|
|
|
const contractBaseReturnedMoney = ref(0);
|
|
|
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
const routeParams = ref<Record<string, any>>({});
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
const buttonLoading = ref(false);
|
|
|
const formRef = ref<FormInstance>();
|
|
|
|
|
|
const BUSINESS_STATUS = reactive({
|
|
|
DRAFT: '1', //暂存
|
|
|
WAITING: '2', //审批中
|
|
|
AVAILABLE: '3' //可用
|
|
|
});
|
|
|
|
|
|
const { early_flag, invoice_category } = toRefs<any>(proxy?.useDict('early_flag', 'invoice_category'));
|
|
|
|
|
|
const EARLY_FLAG = reactive({
|
|
|
YES: '1', //是
|
|
|
NO: '0' //否
|
|
|
});
|
|
|
|
|
|
/** 项目类型:1实施类 2备件类 */
|
|
|
const INVOICE_CATEGORY = {
|
|
|
IMPLEMENTATION: '1',
|
|
|
SPARE: '2'
|
|
|
};
|
|
|
|
|
|
const isSpareInvoiceCategory = computed(() => form.value.invoiceCategory === INVOICE_CATEGORY.SPARE);
|
|
|
|
|
|
const FLOW_STATUS = reactive({
|
|
|
DRAFT: 'draft',
|
|
|
WAITING: 'waiting'
|
|
|
});
|
|
|
|
|
|
const CONTRACT_FLAG = {
|
|
|
YES: '1',
|
|
|
NO: '2'
|
|
|
};
|
|
|
|
|
|
const hasInvoiceAttachPer = checkPermi(['oa/erp:finInvoiceInfo:invoiceAttach']);
|
|
|
|
|
|
/** 仅允许下载:查看页;或修改页但流程审批中(与主表单锁定一致)。其余按菜单权限 / invoice 审批角色 */
|
|
|
const invoiceAttachReadonly = computed(() => {
|
|
|
if (routeParams.value.type === 'view') {
|
|
|
return true;
|
|
|
}
|
|
|
if (routeParams.value.type === 'update') {
|
|
|
return true;
|
|
|
}
|
|
|
const canInvoiceRoleAttach =
|
|
|
routeParams.value.type === 'approval' &&
|
|
|
form.value.flowStatus === FLOW_STATUS.WAITING &&
|
|
|
checkRole(['invoice']);
|
|
|
if (hasInvoiceAttachPer || canInvoiceRoleAttach) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
});
|
|
|
|
|
|
const isFormDisabled = computed(() => {
|
|
|
return routeParams.value.type === 'view' || routeParams.value.type === 'approval' || form.value.flowStatus === FLOW_STATUS.WAITING;
|
|
|
});
|
|
|
|
|
|
// 弹窗可见性
|
|
|
const projectSelectDialogVisible = ref(false);
|
|
|
const userDialogVisible = ref(false);
|
|
|
|
|
|
// 表格选中项
|
|
|
const selectedItems = ref<FinInvoiceDetailVO[]>([]);
|
|
|
|
|
|
const toDeletedInvoiceDetailIdList = ref([]);
|
|
|
const taxRateOptions = [0, 3, 9, 13];
|
|
|
const getTodayDateString = () => {
|
|
|
const now = new Date();
|
|
|
const year = now.getFullYear();
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
|
return `${year}-${month}-${day}`;
|
|
|
};
|
|
|
|
|
|
const initFormData: FinInvoiceInfoForm = {
|
|
|
invoiceId: undefined,
|
|
|
invoiceCode: undefined,
|
|
|
earlyFlag: EARLY_FLAG.NO,
|
|
|
invoiceType: undefined,
|
|
|
issueAmount: undefined,
|
|
|
issuancePercentage: undefined,
|
|
|
redInkFlag: undefined,
|
|
|
projectId: undefined,
|
|
|
projectCode: undefined,
|
|
|
projectName: undefined,
|
|
|
managerId: undefined,
|
|
|
acceptanceDate: undefined,
|
|
|
deliveryDate: undefined,
|
|
|
arrivalDate: undefined,
|
|
|
invoiceVersion: undefined,
|
|
|
invoiceCategory: undefined,
|
|
|
contractId: undefined,
|
|
|
totalPrice: undefined,
|
|
|
customerId: undefined,
|
|
|
customerName: undefined,
|
|
|
paymentMethod: undefined,
|
|
|
returnedMoney: undefined,
|
|
|
returnedRate: undefined,
|
|
|
feedingFlag: undefined,
|
|
|
costCompleteFlag: undefined,
|
|
|
saleOrderCreateFlag: undefined,
|
|
|
flowStatus: undefined,
|
|
|
invoiceStatus: undefined,
|
|
|
remark: undefined,
|
|
|
earlyReason: undefined,
|
|
|
issueDate: getTodayDateString(),
|
|
|
erpFinInvoiceDetailList: []
|
|
|
};
|
|
|
|
|
|
const data = reactive<PageData<FinInvoiceInfoForm, FinInvoiceInfoQuery>>({
|
|
|
form: { ...initFormData },
|
|
|
queryParams: {
|
|
|
pageNum: undefined,
|
|
|
pageSize: undefined,
|
|
|
params: {}
|
|
|
},
|
|
|
rules: {
|
|
|
projectCode: [{ required: true, message: '项目不能为空', trigger: 'blur' }],
|
|
|
invoiceCategory: [{ required: true, message: '项目类型不能为空', trigger: 'blur' }],
|
|
|
earlyFlag: [{ required: true, message: '提前开票不能为空', trigger: 'blur' }]
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const { form, rules } = toRefs(data);
|
|
|
|
|
|
watch(
|
|
|
() => form.value.invoiceCategory,
|
|
|
(category) => {
|
|
|
if (category === INVOICE_CATEGORY.SPARE && form.value.earlyFlag === EARLY_FLAG.YES) {
|
|
|
form.value.earlyFlag = EARLY_FLAG.NO;
|
|
|
form.value.earlyReason = undefined;
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
|
|
|
// 附件ID字符串转换(用于FileUpload组件)
|
|
|
const ossIdString = computed({
|
|
|
get: () => {
|
|
|
if (form.value.ossId === undefined || form.value.ossId === null) {
|
|
|
return '';
|
|
|
}
|
|
|
return String(form.value.ossId);
|
|
|
},
|
|
|
set: (val: string) => {
|
|
|
form.value.ossId = val || undefined;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
watch(
|
|
|
() => form.value.ossId,
|
|
|
async (newVal, oldVal) => {
|
|
|
// 使用 ?? 将 null 和 undefined 都统一为 'empty' 字符串
|
|
|
|
|
|
// 如果 ossId 从无到有,或者从有到无,或者值改变 (只在初始化完成后,并且值确实发生变化时才执行)
|
|
|
if (isInitialized.value && form.value.invoiceId && !invoiceAttachReadonly.value) {
|
|
|
const normalizedOld = oldVal ?? 'empty';
|
|
|
const normalizedNew = newVal ?? 'empty';
|
|
|
if (normalizedOld !== normalizedNew) {
|
|
|
try {
|
|
|
buttonLoading.value = true;
|
|
|
// 更新数据库
|
|
|
const invoiceAttachForm = {
|
|
|
invoiceId: form.value.invoiceId,
|
|
|
ossId: form.value.ossId
|
|
|
};
|
|
|
await updateInvoiceAttach(invoiceAttachForm);
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{ immediate: false }
|
|
|
);
|
|
|
|
|
|
const getUserList = async () => {
|
|
|
const query = {} as any;
|
|
|
const res = await api.getUserList(query);
|
|
|
userOptions.value = res.data;
|
|
|
};
|
|
|
|
|
|
const handleUserChange = async (newUserId) => {
|
|
|
const userOption = userOptions.value.find((item) => item.userId == newUserId);
|
|
|
if (!userOption) {
|
|
|
form.value.requestDeptName = undefined;
|
|
|
form.value.requestDept = undefined;
|
|
|
form.value.requestByName = undefined;
|
|
|
return;
|
|
|
}
|
|
|
form.value.requestDeptName = userOption.deptName;
|
|
|
form.value.requestDept = userOption.deptId;
|
|
|
form.value.requestByName = userOption.nickName;
|
|
|
};
|
|
|
|
|
|
const getContractPaymentMethods = async (contractId?: string | number) => {
|
|
|
if (!contractId) {
|
|
|
contractPaymentMethodVoList.value = [];
|
|
|
return null;
|
|
|
}
|
|
|
const [pmRes, contractRes] = await Promise.all([
|
|
|
getContractPaymentMethodList(contractId),
|
|
|
getContractInfo(contractId)
|
|
|
]);
|
|
|
contractPaymentMethodVoList.value = pmRes.data ?? [];
|
|
|
const c = contractRes?.data;
|
|
|
if (c?.contractCode) {
|
|
|
form.value.contractCode = c.contractCode;
|
|
|
}
|
|
|
return c;
|
|
|
};
|
|
|
|
|
|
const paymentDescription = computed(() => {
|
|
|
return contractPaymentMethodVoList.value.map((item) => `${item.paymentDescription}`).join('');
|
|
|
});
|
|
|
|
|
|
// 计算合计金额 - 自动更新 issueAmount 和 issuancePercentage
|
|
|
const totalInvoiceAmount = computed(() => {
|
|
|
const detailList = form.value.erpFinInvoiceDetailList || [];
|
|
|
const total = Number(detailList.reduce((sum, item) => sum + (item.totalPrice || 0), 0).toFixed(2));
|
|
|
|
|
|
// 更新开票金额
|
|
|
form.value.issueAmount = total;
|
|
|
|
|
|
// 更新开票比例(如果合同金额存在且不为0)
|
|
|
if (form.value.totalPrice && form.value.totalPrice > 0) {
|
|
|
form.value.issuancePercentage = Number(((total / form.value.totalPrice) * 100).toFixed(2));
|
|
|
} else {
|
|
|
form.value.issuancePercentage = undefined;
|
|
|
}
|
|
|
|
|
|
return total;
|
|
|
});
|
|
|
|
|
|
// 监听合同金额变化
|
|
|
watch(
|
|
|
() => form.value.totalPrice,
|
|
|
(newTotalPrice) => {
|
|
|
// 当合同金额变化时,重新计算开票比例
|
|
|
if (newTotalPrice && newTotalPrice > 0 && form.value.issueAmount) {
|
|
|
form.value.issuancePercentage = Number(((form.value.issueAmount / newTotalPrice) * 100).toFixed(2));
|
|
|
} else {
|
|
|
form.value.issuancePercentage = undefined;
|
|
|
}
|
|
|
|
|
|
calculateReturnedRate();
|
|
|
}
|
|
|
);
|
|
|
|
|
|
// 监听本次开票合计金额变化,自动重算累计回款金额
|
|
|
watch(
|
|
|
() => form.value.issueAmount,
|
|
|
() => {
|
|
|
syncReturnedMoney();
|
|
|
}
|
|
|
);
|
|
|
|
|
|
watch(
|
|
|
() => form.value.contractId,
|
|
|
(newVal, oldVal) => {
|
|
|
if (newVal !== oldVal && !isFormDisabled.value) {
|
|
|
fetchContractBaseReturnedMoney();
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
|
|
|
// 计算回款比例
|
|
|
const calculateReturnedRate = () => {
|
|
|
if (form.value.totalPrice && form.value.totalPrice > 0 && form.value.returnedMoney != null) {
|
|
|
form.value.returnedRate = Number(((form.value.returnedMoney / form.value.totalPrice) * 100).toFixed(2));
|
|
|
} else {
|
|
|
form.value.returnedRate = undefined;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/** 查询同合同下累计回款比例最大的历史累计回款金额 */
|
|
|
const fetchContractBaseReturnedMoney = async () => {
|
|
|
if (isFormDisabled.value) {
|
|
|
return;
|
|
|
}
|
|
|
if (!form.value.contractId) {
|
|
|
contractBaseReturnedMoney.value = 0;
|
|
|
syncReturnedMoney();
|
|
|
return;
|
|
|
}
|
|
|
try {
|
|
|
const res = await getMaxReturnedMoneyByContractId(form.value.contractId, form.value.invoiceId);
|
|
|
contractBaseReturnedMoney.value = Number(res.data ?? 0);
|
|
|
} catch {
|
|
|
contractBaseReturnedMoney.value = 0;
|
|
|
}
|
|
|
syncReturnedMoney();
|
|
|
};
|
|
|
|
|
|
/** 累计回款金额 = 历史最大累计回款金额 + 本次开票合计金额(含税) */
|
|
|
const syncReturnedMoney = () => {
|
|
|
if (isFormDisabled.value) {
|
|
|
return;
|
|
|
}
|
|
|
const currentAmount = Number(form.value.issueAmount ?? 0);
|
|
|
form.value.returnedMoney = Number((contractBaseReturnedMoney.value + currentAmount).toFixed(2));
|
|
|
calculateReturnedRate();
|
|
|
};
|
|
|
|
|
|
const syncManagerIdFromProject = async (projectId?: string | number) => {
|
|
|
if (!projectId) {
|
|
|
form.value.managerId = undefined;
|
|
|
return;
|
|
|
}
|
|
|
const res = await getProjectInfo(projectId);
|
|
|
form.value.managerId = res.data?.managerId;
|
|
|
};
|
|
|
|
|
|
// 新增开票页:从台账带入 contractId 时自动回填项目与合同信息
|
|
|
const initByContractId = async () => {
|
|
|
const contractId = routeParams.value.contractId as string | number | undefined;
|
|
|
if (!contractId) return;
|
|
|
|
|
|
const [contractRes, projectRes] = await Promise.all([getContractInfo(contractId), listProjectInfoByContractId(contractId)]);
|
|
|
const contract = contractRes?.data;
|
|
|
const projectList = (projectRes?.data || []) as any[];
|
|
|
const routeProjectId = routeParams.value.projectId as string | number | undefined;
|
|
|
const matchedProject =
|
|
|
projectList.find((item) => routeProjectId && String(item.projectId) === String(routeProjectId)) || projectList[0];
|
|
|
|
|
|
Object.assign(form.value, {
|
|
|
contractId,
|
|
|
contractCode: contract?.contractCode,
|
|
|
contractName: contract?.contractName,
|
|
|
customerId: contract?.oneCustomerId,
|
|
|
customerName: contract?.oneCustomerName,
|
|
|
totalPrice: contract?.totalPrice,
|
|
|
projectId: matchedProject?.projectId,
|
|
|
projectCode: matchedProject?.projectCode,
|
|
|
projectName: matchedProject?.projectName,
|
|
|
managerId: matchedProject?.managerId,
|
|
|
contractFlag: matchedProject?.contractFlag
|
|
|
});
|
|
|
|
|
|
await getContractPaymentMethods(contractId);
|
|
|
initInvoiceDetailsFromContract((contract as any)?.contractMaterialList || []);
|
|
|
};
|
|
|
|
|
|
|
|
|
const isInitialized = ref(false);
|
|
|
onMounted(async () => {
|
|
|
nextTick(async () => {
|
|
|
await getUserList(); // 初始化用户数据
|
|
|
// 获取路由参数
|
|
|
routeParams.value = route.query;
|
|
|
const id = routeParams.value.id as string | number;
|
|
|
try {
|
|
|
proxy?.$modal.loading('正在加载数据,请稍后...');
|
|
|
if (id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
|
|
|
const res = await getFinInvoiceInfo(id);
|
|
|
Object.assign(form.value, res.data);
|
|
|
const invoiceVo = res.data;
|
|
|
const detailFromApi =
|
|
|
invoiceVo.erpFinInvoiceDetailVoList ?? (invoiceVo as FinInvoiceInfoForm).erpFinInvoiceDetailList;
|
|
|
form.value.erpFinInvoiceDetailList = normalizeInvoiceDetailListFromApi(
|
|
|
Array.isArray(detailFromApi) ? detailFromApi : []
|
|
|
);
|
|
|
// 仅补算后端未落库的不含税金额/税额,避免整表重算把「无数量但有单价」等数据清空(与台账展示一致)
|
|
|
form.value.erpFinInvoiceDetailList.forEach((item) => {
|
|
|
if (
|
|
|
item.totalPrice != null &&
|
|
|
item.totalPriceNoTax == null &&
|
|
|
item.taxRate != null
|
|
|
) {
|
|
|
calculateTaxAmounts(item);
|
|
|
}
|
|
|
});
|
|
|
if (form.value.contractId) {
|
|
|
await getContractPaymentMethods(form.value.contractId);
|
|
|
} else {
|
|
|
contractPaymentMethodVoList.value = [];
|
|
|
}
|
|
|
if (form.value.projectId) {
|
|
|
await syncManagerIdFromProject(form.value.projectId);
|
|
|
}
|
|
|
if (form.value.contractId && !isFormDisabled.value) {
|
|
|
await fetchContractBaseReturnedMoney();
|
|
|
}
|
|
|
} else {
|
|
|
const res = await getBaseInfo();
|
|
|
Object.assign(form.value, res.data);
|
|
|
form.value.earlyFlag = form.value.earlyFlag || EARLY_FLAG.NO;
|
|
|
form.value.issueDate = form.value.issueDate || getTodayDateString();
|
|
|
await initByContractId();
|
|
|
if (!form.value.erpFinInvoiceDetailList?.length) {
|
|
|
handleAddItem();
|
|
|
}
|
|
|
if (form.value.contractId) {
|
|
|
await fetchContractBaseReturnedMoney();
|
|
|
}
|
|
|
}
|
|
|
// 数据加载完成后,设置初始化标志为 true
|
|
|
await nextTick();
|
|
|
isInitialized.value = true;
|
|
|
} finally {
|
|
|
proxy?.$modal.closeLoading();
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// onMounted(() => {
|
|
|
// const id = route.params?.id
|
|
|
// if (id) {
|
|
|
// getDetail(Number(id))
|
|
|
// } else {
|
|
|
// // 新增时默认添加一行空明细
|
|
|
// handleAddItem()
|
|
|
// }
|
|
|
// })
|
|
|
|
|
|
// 项目选择
|
|
|
const handleSelectProject = () => {
|
|
|
projectSelectDialogVisible.value = true;
|
|
|
};
|
|
|
|
|
|
// 处理项目选择
|
|
|
const handleProjectSelected = async (result: any) => {
|
|
|
const project = result.project;
|
|
|
const contractId = result.contract?.contractId;
|
|
|
Object.assign(form.value, {
|
|
|
contractId,
|
|
|
contractFlag: project.contractFlag,
|
|
|
projectId: project.projectId,
|
|
|
projectName: project.projectName,
|
|
|
projectCode: project.projectCode,
|
|
|
managerId: project.managerId,
|
|
|
contractCode: result.contract?.contractCode,
|
|
|
contractName: result.contract?.contractName,
|
|
|
customerName: result.contract?.oneCustomerName,
|
|
|
totalPrice: result.contract?.totalPrice
|
|
|
});
|
|
|
if (contractId) {
|
|
|
const contractDetail = await getContractPaymentMethods(contractId);
|
|
|
initInvoiceDetailsFromContract((contractDetail as any)?.contractMaterialList || []);
|
|
|
} else if (isAddMode()) {
|
|
|
form.value.erpFinInvoiceDetailList = [];
|
|
|
contractBaseReturnedMoney.value = 0;
|
|
|
handleAddItem();
|
|
|
syncReturnedMoney();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 合同选择弹窗相关
|
|
|
const contractSelectDialogVisible = ref(false);
|
|
|
|
|
|
// 显示合同选择弹窗
|
|
|
const showContractSelectDialog = () => {
|
|
|
contractSelectDialogVisible.value = true;
|
|
|
};
|
|
|
|
|
|
// 处理合同选择
|
|
|
const handleContractSelected = async (contract: any) => {
|
|
|
Object.assign(form.value, {
|
|
|
contractId: contract.contractId,
|
|
|
contractCode: contract.contractCode,
|
|
|
contractName: contract.contractName,
|
|
|
customerName: contract.oneCustomerName,
|
|
|
totalPrice: contract.totalPrice
|
|
|
});
|
|
|
|
|
|
const contractDetail = await getContractPaymentMethods(contract.contractId);
|
|
|
initInvoiceDetailsFromContract((contractDetail as any)?.contractMaterialList || []);
|
|
|
};
|
|
|
|
|
|
// 人员选择
|
|
|
const handleSelectUser = () => {
|
|
|
userDialogVisible.value = true;
|
|
|
};
|
|
|
|
|
|
const handleUserSelected = () => {
|
|
|
// form.userName = '张三'; // 模拟选择
|
|
|
// form.userId = 1;
|
|
|
userDialogVisible.value = false;
|
|
|
};
|
|
|
|
|
|
// 表格操作
|
|
|
const handleAddItem = () => {
|
|
|
form.value.erpFinInvoiceDetailList.push({
|
|
|
invoiceDetailId: undefined,
|
|
|
invoiceId: undefined,
|
|
|
billingItems: '',
|
|
|
specificationModel: '',
|
|
|
unitName: '',
|
|
|
quantity: undefined,
|
|
|
taxRate: undefined,
|
|
|
unitPrice: undefined,
|
|
|
totalPrice: 0,
|
|
|
totalPriceNoTax: 0,
|
|
|
taxPrice: undefined
|
|
|
} as FinInvoiceDetailForm as FinInvoiceDetailVO);
|
|
|
};
|
|
|
|
|
|
const handleRemoveItem = (index: number, row: FinInvoiceDetailVO) => {
|
|
|
form.value.erpFinInvoiceDetailList.splice(index, 1);
|
|
|
if (row.invoiceDetailId) {
|
|
|
toDeletedInvoiceDetailIdList.value.push(row.invoiceDetailId);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleDeleteItems = () => {
|
|
|
if (selectedItems.value.length === 0) return;
|
|
|
|
|
|
const remainingItems = form.value.erpFinInvoiceDetailList.filter((item) => !selectedItems.value.includes(item));
|
|
|
form.value.erpFinInvoiceDetailList = remainingItems;
|
|
|
selectedItems.value.forEach((row) => {
|
|
|
if (row.invoiceDetailId) {
|
|
|
toDeletedInvoiceDetailIdList.value.push(row.invoiceDetailId);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
selectedItems.value = [];
|
|
|
};
|
|
|
|
|
|
const handleSelectionChange = (selection: FinInvoiceDetailVO[]) => {
|
|
|
selectedItems.value = selection;
|
|
|
};
|
|
|
|
|
|
/** 接口 BigDecimal 等可能为数字或字符串,统一为表单可用的 number,便于 el-select / el-input-number 正确回显 */
|
|
|
const toFormNumber = (val: unknown): number | undefined => {
|
|
|
if (val === undefined || val === null || val === '') return undefined;
|
|
|
const n = Number(val);
|
|
|
return Number.isFinite(n) ? n : undefined;
|
|
|
};
|
|
|
|
|
|
const normalizeInvoiceDetailListFromApi = (list: FinInvoiceDetailVO[]): FinInvoiceDetailVO[] => {
|
|
|
return list.map((row) => ({
|
|
|
...row,
|
|
|
quantity: toFormNumber(row.quantity),
|
|
|
unitPrice: toFormNumber(row.unitPrice),
|
|
|
totalPrice: toFormNumber(row.totalPrice),
|
|
|
totalPriceNoTax: toFormNumber(row.totalPriceNoTax),
|
|
|
taxRate: toFormNumber(row.taxRate),
|
|
|
taxPrice: toFormNumber(row.taxPrice)
|
|
|
})) as FinInvoiceDetailVO[];
|
|
|
};
|
|
|
|
|
|
const formatAmount = (value?: number) => {
|
|
|
return value !== undefined && value !== null ? Number(value).toFixed(2) : '0.00';
|
|
|
};
|
|
|
|
|
|
const roundAmount = (value?: number) => {
|
|
|
if (value === undefined || value === null || Number.isNaN(Number(value))) {
|
|
|
return undefined;
|
|
|
}
|
|
|
return Number(Number(value).toFixed(2));
|
|
|
};
|
|
|
|
|
|
const calculateTaxAmounts = (item: FinInvoiceDetailVO) => {
|
|
|
const totalPrice = roundAmount(item.totalPrice) || 0;
|
|
|
const taxRate = Number(item.taxRate || 0);
|
|
|
const divisor = 1 + taxRate / 100;
|
|
|
item.totalPriceNoTax = roundAmount(totalPrice / divisor) || 0;
|
|
|
item.taxPrice = roundAmount(totalPrice - (item.totalPriceNoTax || 0)) || 0;
|
|
|
};
|
|
|
|
|
|
const isAddMode = () => {
|
|
|
const type = routeParams.value.type;
|
|
|
return !routeParams.value.id && type !== 'update' && type !== 'view' && type !== 'approval';
|
|
|
};
|
|
|
|
|
|
/** 合同物料 → 开票明细字段映射 */
|
|
|
const mapContractMaterialToInvoiceDetail = (material: Record<string, any>): FinInvoiceDetailVO => {
|
|
|
const quantity = toFormNumber(material.amount) ?? 0;
|
|
|
const unitPrice = toFormNumber(material.includingPrice);
|
|
|
const taxRate = toFormNumber(material.taxRate);
|
|
|
let totalPrice = toFormNumber(material.subtotal);
|
|
|
if ((totalPrice === undefined || totalPrice <= 0) && unitPrice !== undefined && quantity > 0) {
|
|
|
totalPrice = roundAmount(unitPrice * quantity) ?? 0;
|
|
|
}
|
|
|
const detail = {
|
|
|
invoiceDetailId: undefined,
|
|
|
invoiceId: undefined,
|
|
|
billingItems: material.saleMaterialName || material.materialName || material.productName || '',
|
|
|
specificationModel: material.specificationDescription || '',
|
|
|
unitName: material.unitName || '',
|
|
|
quantity,
|
|
|
taxRate,
|
|
|
unitPrice,
|
|
|
totalPrice: totalPrice ?? 0,
|
|
|
totalPriceNoTax: 0,
|
|
|
taxPrice: undefined
|
|
|
} as FinInvoiceDetailVO;
|
|
|
if (detail.totalPrice != null && detail.taxRate != null) {
|
|
|
calculateTaxAmounts(detail);
|
|
|
} else if (detail.totalPrice != null && detail.unitPrice == null && quantity > 0) {
|
|
|
detail.unitPrice = roundAmount(detail.totalPrice / quantity);
|
|
|
}
|
|
|
return detail;
|
|
|
};
|
|
|
|
|
|
/** 新增开票:用合同物料初始化开票明细 */
|
|
|
const initInvoiceDetailsFromContract = (contractMaterialList: Record<string, any>[]) => {
|
|
|
if (!isAddMode()) {
|
|
|
return;
|
|
|
}
|
|
|
const details = (contractMaterialList || []).map(mapContractMaterialToInvoiceDetail);
|
|
|
if (details.length > 0) {
|
|
|
form.value.erpFinInvoiceDetailList = details;
|
|
|
} else {
|
|
|
form.value.erpFinInvoiceDetailList = [];
|
|
|
handleAddItem();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 通过数量和金额(含税)回算单价
|
|
|
const calculateItemAmount = (index: number) => {
|
|
|
const item = form.value.erpFinInvoiceDetailList[index];
|
|
|
const qty = toFormNumber(item.quantity);
|
|
|
const total = toFormNumber(item.totalPrice);
|
|
|
if (qty !== undefined && qty > 0 && total !== undefined) {
|
|
|
item.unitPrice = roundAmount(total / qty);
|
|
|
} else {
|
|
|
item.unitPrice = undefined;
|
|
|
}
|
|
|
calculateTaxAmounts(item);
|
|
|
};
|
|
|
|
|
|
const calculateItemAmountByTotal = (index: number) => {
|
|
|
calculateItemAmount(index);
|
|
|
};
|
|
|
|
|
|
const calculateItemAmountByNoTax = (index: number) => {
|
|
|
const item = form.value.erpFinInvoiceDetailList[index];
|
|
|
const noTaxAmount = roundAmount(item.totalPriceNoTax);
|
|
|
if (noTaxAmount === undefined) {
|
|
|
item.totalPrice = 0;
|
|
|
item.unitPrice = undefined;
|
|
|
item.taxPrice = 0;
|
|
|
return;
|
|
|
}
|
|
|
const taxRate = Number(item.taxRate || 0);
|
|
|
item.totalPrice = roundAmount(noTaxAmount * (1 + taxRate / 100)) || 0;
|
|
|
const qty = toFormNumber(item.quantity);
|
|
|
if (qty !== undefined && qty > 0) {
|
|
|
item.unitPrice = roundAmount((item.totalPrice || 0) / qty);
|
|
|
} else {
|
|
|
item.unitPrice = undefined;
|
|
|
}
|
|
|
item.taxPrice = roundAmount((item.totalPrice || 0) - noTaxAmount) || 0;
|
|
|
};
|
|
|
|
|
|
// 明细变更时触发计算
|
|
|
const handleItemChange = (index: number) => {
|
|
|
calculateItemAmount(index);
|
|
|
};
|
|
|
|
|
|
const buildInvoiceDetailError = (): string | null => {
|
|
|
if (!form.value.erpFinInvoiceDetailList.length) {
|
|
|
return '请至少添加一条开票明细';
|
|
|
}
|
|
|
for (let index = 0; index < form.value.erpFinInvoiceDetailList.length; index++) {
|
|
|
const item = form.value.erpFinInvoiceDetailList[index];
|
|
|
const rowNo = index + 1;
|
|
|
if (!item.billingItems) {
|
|
|
return `第${rowNo}行开票内容不能为空`;
|
|
|
}
|
|
|
if (!item.unitName) {
|
|
|
return `第${rowNo}行单位不能为空`;
|
|
|
}
|
|
|
if (item.taxRate === undefined || item.taxRate === null) {
|
|
|
return `第${rowNo}行税率不能为空`;
|
|
|
}
|
|
|
if (!item.quantity || item.quantity <= 0) {
|
|
|
return `第${rowNo}行数量必须大于0`;
|
|
|
}
|
|
|
if (!item.totalPrice || item.totalPrice <= 0) {
|
|
|
return `第${rowNo}行金额(含税)必须大于0`;
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
};
|
|
|
|
|
|
const normalizeInvoiceForm = (): FinInvoiceInfoForm => {
|
|
|
const invoiceForm: FinInvoiceInfoForm = {
|
|
|
...form.value,
|
|
|
invoiceStatus: getInvoiceStatus(buttonStatus.value),
|
|
|
flowStatus: getFlowStatus(buttonStatus.value),
|
|
|
toDeletedInvoiceDetailIdList: [...toDeletedInvoiceDetailIdList.value]
|
|
|
};
|
|
|
if (invoiceForm.earlyFlag === EARLY_FLAG.YES) {
|
|
|
invoiceForm.acceptanceDate = undefined;
|
|
|
invoiceForm.deliveryDate = undefined;
|
|
|
} else {
|
|
|
invoiceForm.earlyReason = undefined;
|
|
|
}
|
|
|
return invoiceForm;
|
|
|
};
|
|
|
|
|
|
const buttonStatus = ref('draft');
|
|
|
|
|
|
// 审批相关组件引用
|
|
|
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
|
|
|
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
|
|
|
|
|
|
// 审批记录
|
|
|
const handleApprovalRecord = () => {
|
|
|
approvalRecordRef.value.init(form.value.invoiceId);
|
|
|
};
|
|
|
|
|
|
// 提交回调
|
|
|
const submitCallback = async () => {
|
|
|
await proxy.$tab.closePage(route);
|
|
|
router.go(-1);
|
|
|
};
|
|
|
|
|
|
// 审批(本页回调:invoice 角色在审批中打开弹窗前校验发票附件必填)
|
|
|
const approvalVerifyOpen = async () => {
|
|
|
const needInvoiceAttach =
|
|
|
routeParams.value.type === 'approval' &&
|
|
|
form.value.flowStatus === FLOW_STATUS.WAITING &&
|
|
|
checkRole(['invoice']);
|
|
|
if (needInvoiceAttach) {
|
|
|
const raw = form.value.ossId;
|
|
|
const hasFile = raw !== undefined && raw !== null && String(raw).trim() !== '';
|
|
|
if (!hasFile) {
|
|
|
ElMessage.warning('请先上传发票附件后再审批');
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
await submitVerifyRef.value.openDialog(routeParams.value.taskId);
|
|
|
};
|
|
|
|
|
|
// 保存
|
|
|
const handleSave = async (status: string, mode: boolean) => {
|
|
|
if (!formRef.value) return;
|
|
|
buttonStatus.value = status;
|
|
|
try {
|
|
|
await formRef.value.validate();
|
|
|
} catch {
|
|
|
ElMessage.warning('请先完善必填信息后再提交');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const detailError = buildInvoiceDetailError();
|
|
|
if (detailError) {
|
|
|
ElMessage.warning(detailError);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (form.value.invoiceCategory === INVOICE_CATEGORY.SPARE && form.value.earlyFlag === EARLY_FLAG.YES) {
|
|
|
ElMessage.warning('备件类项目不能选择提前开票');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (form.value.returnedRate != null && Number(form.value.returnedRate) > 100) {
|
|
|
ElMessage.warning('累计回款比例不能超过100%');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (form.value.returnedMoney && form.value.totalPrice && form.value.returnedMoney > form.value.totalPrice) {
|
|
|
ElMessage.warning('累计回款金额不能大于合同金额');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (status !== 'draft' && !form.value.managerId) {
|
|
|
ElMessage.warning('项目项目经理不能为空,请重新选择项目');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const invoiceForm = normalizeInvoiceForm();
|
|
|
try {
|
|
|
buttonLoading.value = true;
|
|
|
if (form.value.invoiceId) {
|
|
|
await updateFinInvoiceInfo(invoiceForm);
|
|
|
} else {
|
|
|
await addFinInvoiceInfo(invoiceForm);
|
|
|
}
|
|
|
ElMessage.success(status === 'draft' ? '暂存成功' : '提交成功');
|
|
|
goBack();
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const getInvoiceStatus = (status: string): string => {
|
|
|
return status === 'draft' ? BUSINESS_STATUS.DRAFT : BUSINESS_STATUS.WAITING;
|
|
|
};
|
|
|
|
|
|
const getFlowStatus = (status: string): string => {
|
|
|
return status === 'draft' ? 'draft' : 'waiting';
|
|
|
};
|
|
|
|
|
|
const goBack = () => {
|
|
|
const obj = {
|
|
|
path: '/fin/finInvoiceInfo',
|
|
|
query: {
|
|
|
t: Date.now(),
|
|
|
pageNum: routeParams.value.pageNum
|
|
|
}
|
|
|
};
|
|
|
proxy?.$tab.closeOpenPage(obj);
|
|
|
};
|
|
|
|
|
|
// 取消返回
|
|
|
const handleCancel = () => {
|
|
|
router.push('/oa/erp/finInvoiceInfo');
|
|
|
};
|
|
|
</script>
|