|
|
|
|
@ -116,7 +116,14 @@
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="8">
|
|
|
|
|
<el-form-item label="累计回款金额" prop="returnedMoney">
|
|
|
|
|
<el-input-number v-model="form.returnedMoney" :precision="2" placeholder="请输入累计回款金额" style="width: 210px"></el-input-number>
|
|
|
|
|
<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">
|
|
|
|
|
@ -321,15 +328,6 @@
|
|
|
|
|
<!-- 合同选择弹窗组件 -->
|
|
|
|
|
<ContractSelectDialog v-model:visible="contractSelectDialogVisible" @contract-selected="handleContractSelected" />
|
|
|
|
|
|
|
|
|
|
<!-- 部门选择弹窗 (可根据实际组件调整) -->
|
|
|
|
|
<el-dialog v-model="deptDialogVisible" title="选择部门" width="600px" append-to-body>
|
|
|
|
|
<div class="p-3 text-center text-gray-500">请根据实际项目集成部门选择组件</div>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="deptDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="handleDeptSelected">确定</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 人员选择弹窗 (可根据实际组件调整) -->
|
|
|
|
|
<el-dialog v-model="userDialogVisible" title="选择人员" width="600px" append-to-body>
|
|
|
|
|
<div class="p-3 text-center text-gray-500">请根据实际项目集成人员选择组件</div>
|
|
|
|
|
@ -358,14 +356,14 @@ import {
|
|
|
|
|
addFinInvoiceInfo,
|
|
|
|
|
updateFinInvoiceInfo,
|
|
|
|
|
getContractPaymentMethodList,
|
|
|
|
|
updateInvoiceAttach
|
|
|
|
|
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 { DeptTreeVO } from '@/api/system/dept/types';
|
|
|
|
|
import { ContractPaymentMethodVO } from '@/api/oa/erp/contractPaymentMethod/types';
|
|
|
|
|
|
|
|
|
|
import { UserVO } from '@/api/system/user/types';
|
|
|
|
|
@ -376,9 +374,9 @@ import { getContractInfo } from '@/api/oa/erp/contractInfo';
|
|
|
|
|
import { getProjectInfo, listProjectInfoByContractId } from '@/api/oa/erp/projectInfo';
|
|
|
|
|
|
|
|
|
|
const userOptions = ref<UserVO[]>([]);
|
|
|
|
|
const deptOptions = ref<DeptTreeVO[]>([]);
|
|
|
|
|
const enabledDeptOptions = ref<DeptTreeVO[]>([]);
|
|
|
|
|
const contractPaymentMethodVoList = ref<ContractPaymentMethodVO[]>([]);
|
|
|
|
|
/** 同合同下历史开票单(累计回款比例最大)的累计回款金额 */
|
|
|
|
|
const contractBaseReturnedMoney = ref(0);
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
@ -437,7 +435,6 @@ const isFormDisabled = computed(() => {
|
|
|
|
|
|
|
|
|
|
// 弹窗可见性
|
|
|
|
|
const projectSelectDialogVisible = ref(false);
|
|
|
|
|
const deptDialogVisible = ref(false);
|
|
|
|
|
const userDialogVisible = ref(false);
|
|
|
|
|
|
|
|
|
|
// 表格选中项
|
|
|
|
|
@ -563,10 +560,10 @@ const handleUserChange = async (newUserId) => {
|
|
|
|
|
form.value.requestByName = userOption.nickName;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getContractPaymentMethods = async (contractId) => {
|
|
|
|
|
const getContractPaymentMethods = async (contractId?: string | number) => {
|
|
|
|
|
if (!contractId) {
|
|
|
|
|
contractPaymentMethodVoList.value = [];
|
|
|
|
|
return;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const [pmRes, contractRes] = await Promise.all([
|
|
|
|
|
getContractPaymentMethodList(contractId),
|
|
|
|
|
@ -577,32 +574,13 @@ const getContractPaymentMethods = async (contractId) => {
|
|
|
|
|
if (c?.contractCode) {
|
|
|
|
|
form.value.contractCode = c.contractCode;
|
|
|
|
|
}
|
|
|
|
|
return c;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const paymentDescription = computed(() => {
|
|
|
|
|
return contractPaymentMethodVoList.value.map((item) => `${item.paymentDescription}`).join('');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 查询部门下拉树结构 */
|
|
|
|
|
const getDeptTree = async () => {
|
|
|
|
|
const res = await api.deptTreeSelect();
|
|
|
|
|
deptOptions.value = res.data;
|
|
|
|
|
enabledDeptOptions.value = filterDisabledDept(res.data);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 过滤禁用的部门 */
|
|
|
|
|
const filterDisabledDept = (deptList: DeptTreeVO[]) => {
|
|
|
|
|
return deptList.filter((dept) => {
|
|
|
|
|
if (dept.disabled) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (dept.children && dept.children.length) {
|
|
|
|
|
dept.children = filterDisabledDept(dept.children);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 计算合计金额 - 自动更新 issueAmount 和 issuancePercentage
|
|
|
|
|
const totalInvoiceAmount = computed(() => {
|
|
|
|
|
const detailList = form.value.erpFinInvoiceDetailList || [];
|
|
|
|
|
@ -636,24 +614,61 @@ watch(
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 监听累计回款金额变化
|
|
|
|
|
// 监听本次开票合计金额变化,自动重算累计回款金额
|
|
|
|
|
watch(
|
|
|
|
|
() => form.value.returnedMoney,
|
|
|
|
|
(newReturnedMoney) => {
|
|
|
|
|
// 实时计算回款比例
|
|
|
|
|
calculateReturnedRate();
|
|
|
|
|
() => 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) {
|
|
|
|
|
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;
|
|
|
|
|
@ -690,6 +705,7 @@ const initByContractId = async () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await getContractPaymentMethods(contractId);
|
|
|
|
|
initInvoiceDetailsFromContract((contract as any)?.contractMaterialList || []);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -729,13 +745,21 @@ onMounted(async () => {
|
|
|
|
|
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();
|
|
|
|
|
handleAddItem();
|
|
|
|
|
if (!form.value.erpFinInvoiceDetailList?.length) {
|
|
|
|
|
handleAddItem();
|
|
|
|
|
}
|
|
|
|
|
if (form.value.contractId) {
|
|
|
|
|
await fetchContractBaseReturnedMoney();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 数据加载完成后,设置初始化标志为 true
|
|
|
|
|
await nextTick();
|
|
|
|
|
@ -762,10 +786,11 @@ const handleSelectProject = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 处理项目选择
|
|
|
|
|
const handleProjectSelected = (result: any) => {
|
|
|
|
|
const handleProjectSelected = async (result: any) => {
|
|
|
|
|
const project = result.project;
|
|
|
|
|
const contractId = result.contract?.contractId;
|
|
|
|
|
Object.assign(form.value, {
|
|
|
|
|
contractId: result.contract?.contractId,
|
|
|
|
|
contractId,
|
|
|
|
|
contractFlag: project.contractFlag,
|
|
|
|
|
projectId: project.projectId,
|
|
|
|
|
projectName: project.projectName,
|
|
|
|
|
@ -776,8 +801,14 @@ const handleProjectSelected = (result: any) => {
|
|
|
|
|
customerName: result.contract?.oneCustomerName,
|
|
|
|
|
totalPrice: result.contract?.totalPrice
|
|
|
|
|
});
|
|
|
|
|
if (project.contractFlag === CONTRACT_FLAG.YES && result.contract?.contractId) {
|
|
|
|
|
getContractPaymentMethods(result.contract?.contractId);
|
|
|
|
|
if (contractId) {
|
|
|
|
|
const contractDetail = await getContractPaymentMethods(contractId);
|
|
|
|
|
initInvoiceDetailsFromContract((contractDetail as any)?.contractMaterialList || []);
|
|
|
|
|
} else if (isAddMode()) {
|
|
|
|
|
form.value.erpFinInvoiceDetailList = [];
|
|
|
|
|
contractBaseReturnedMoney.value = 0;
|
|
|
|
|
handleAddItem();
|
|
|
|
|
syncReturnedMoney();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -790,7 +821,7 @@ const showContractSelectDialog = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 处理合同选择
|
|
|
|
|
const handleContractSelected = (contract: any) => {
|
|
|
|
|
const handleContractSelected = async (contract: any) => {
|
|
|
|
|
Object.assign(form.value, {
|
|
|
|
|
contractId: contract.contractId,
|
|
|
|
|
contractCode: contract.contractCode,
|
|
|
|
|
@ -799,18 +830,8 @@ const handleContractSelected = (contract: any) => {
|
|
|
|
|
totalPrice: contract.totalPrice
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
getContractPaymentMethods(contract.contractId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 部门选择
|
|
|
|
|
const handleSelectDept = () => {
|
|
|
|
|
deptDialogVisible.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDeptSelected = () => {
|
|
|
|
|
// form.deptName = '财务部'; // 模拟选择
|
|
|
|
|
// form.deptId = 1;
|
|
|
|
|
deptDialogVisible.value = false;
|
|
|
|
|
const contractDetail = await getContractPaymentMethods(contract.contractId);
|
|
|
|
|
initInvoiceDetailsFromContract((contractDetail as any)?.contractMaterialList || []);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 人员选择
|
|
|
|
|
@ -904,6 +925,55 @@ const calculateTaxAmounts = (item: FinInvoiceDetailVO) => {
|
|
|
|
|
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];
|
|
|
|
|
@ -1039,6 +1109,11 @@ const handleSave = async (status: string, mode: boolean) => {
|
|
|
|
|
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;
|
|
|
|
|
|