1862 lines
62 KiB
Vue
1862 lines
62 KiB
Vue
<template>
|
||
<div class="deal-modal addDeal">
|
||
<p style="color: #757575">
|
||
В данном окне вы можете объединить стороны сделки, проставить условия, ответственных сотрудников и добавить документы.
|
||
</p>
|
||
<form class="form addDeal__form" @submit.prevent>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="title">Название сделки:</label>
|
||
<InputText
|
||
type="text"
|
||
id="title"
|
||
placeholder="Введите название сделки"
|
||
:class="{ 'p-invalid': errors.title }"
|
||
v-model="deal.title"
|
||
class="w-full"
|
||
/>
|
||
<small v-if="errors.title" class="p-error">{{ errorMessages.title }}</small>
|
||
</div>
|
||
<div class="parties_deal flex w-full gap-6">
|
||
<div class="side_deal w-1/2">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label>Укажите сторону 1:</label>
|
||
<Select
|
||
v-model="deal.side_one"
|
||
:options="selectedRequisitionSideOne ? sideOneOptions : fullSidesList"
|
||
:class="{ 'p-invalid': errors.side_one }"
|
||
:disabled="isEditing"
|
||
>
|
||
<template #dropdownicon>
|
||
<DropdownIcon />
|
||
</template>
|
||
</Select>
|
||
<small v-if="errors.side_one" class="p-error">{{ errorMessages.side_one }}</small>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label>Заявка стороны 1:</label>
|
||
<InputText
|
||
v-if="selectedRequisitionSideOne"
|
||
:value="selectedRequisitionSideOne.name"
|
||
disabled
|
||
class="w-full bg-gray-100"
|
||
/>
|
||
</div>
|
||
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<RequisitionDetails v-if="selectedRequisitionSideOne" :deal="deal" :requisition="selectedRequisitionSideOne" :clientsList="clientsList" side="sideOne" @closeDealRequisition="handleCloseDealRequisition" />
|
||
</div>
|
||
|
||
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideOne && (selectedRequisitionSideOne.type?.heir || selectedRequisitionSideOne.type_id) === 2">
|
||
<!-- Селект объекта скрыт, объект подтягивается из заявки
|
||
<label>Выберите объект:</label>
|
||
<Select
|
||
v-model="selectedObjectSideOneId"
|
||
:options="objectsListSideOne"
|
||
optionLabel="name"
|
||
optionValue="id"
|
||
placeholder="Выберите объект"
|
||
:disabled="isEditing"
|
||
>
|
||
<template #dropdownicon>
|
||
<DropdownIcon />
|
||
</template>
|
||
</Select>
|
||
-->
|
||
</div>
|
||
|
||
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideOne && (selectedRequisitionSideOne.type?.heir || selectedRequisitionSideOne.type_id) === 2">
|
||
<ObjectDetails v-if="selectedObjectSideOne" :deal="deal" :object="selectedObjectSideOne" side="sideOne" @closeDealObject="handleCloseDealObject" />
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="side_deal w-1/2">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="side2">Укажите сторону 2:</label>
|
||
<Select
|
||
v-model="deal.side_two"
|
||
:options="sideTwoOptions"
|
||
:class="{ 'p-invalid': errors.side_two }"
|
||
>
|
||
<template #dropdownicon>
|
||
<DropdownIcon />
|
||
</template>
|
||
</Select>
|
||
<small v-if="errors.side_two" class="p-error">{{ errorMessages.side_two }}</small>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label>Заявка стороны 2:</label>
|
||
<Select
|
||
v-model="selectedRequisitionSideTwoId"
|
||
:options="requisitionsList"
|
||
optionLabel="name"
|
||
optionValue="id"
|
||
placeholder="Выберите заявку"
|
||
:filter="true"
|
||
:class="{ 'p-invalid': errors.req_side_two }"
|
||
@filter="filterRequisitionsList"
|
||
:virtualScrollerOptions="{ itemSize: 40 }"
|
||
>
|
||
<template #dropdownicon>
|
||
<DropdownIcon />
|
||
</template>
|
||
</Select>
|
||
<small v-if="errors.req_side_two" class="p-error">{{ errorMessages.req_side_two }}</small>
|
||
</div>
|
||
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<RequisitionDetails v-if="selectedRequisitionSideTwo" :deal="deal" :requisition="selectedRequisitionSideTwo" :clientsList="clientsList" side="sideTwo" @closeDealRequisition="handleCloseDealRequisition" />
|
||
</div>
|
||
|
||
<!-- Селект объекта скрыт, объект подтягивается из заявки
|
||
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideTwo && (selectedRequisitionSideTwo.type?.heir || selectedRequisitionSideTwo.type_id) === 2">
|
||
<label for="nameHome">Выберите объект:</label>
|
||
<Select
|
||
v-model="selectedObjectSideTwoId"
|
||
:options="objectsListSideTwo"
|
||
optionLabel="name"
|
||
optionValue="id"
|
||
placeholder="Выберите объект"
|
||
>
|
||
<template #dropdownicon>
|
||
<DropdownIcon />
|
||
</template>
|
||
</Select>
|
||
</div>
|
||
-->
|
||
|
||
<div class="form__item" v-if="selectedRequisitionSideTwo && (selectedRequisitionSideTwo.type?.heir || selectedRequisitionSideTwo.type_id) === 2">
|
||
<ObjectDetails v-if="selectedObjectSideTwo" :deal="deal" :object="selectedObjectSideTwo" side="sideTwo" @closeDealObject="handleCloseDealObject" />
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Ответственные сотрудники за сделку</h3>
|
||
<div class="flex w-full flex-wrap">
|
||
<EmployeeCard v-for="employee in selectedEmployees" :key="employee.id" :employee="employee" :url="url" :locked="isWhoWork(employee.id)" @remove="removeEmployee" />
|
||
<div class="sm:w-1/2 md:w-1/3 lg:w-1/4 pr-4 mb-4">
|
||
<button @click="openModalDealManager" class="w-full h-full p-4 rounded flex flex-col items-center justify-center add-employee-button">
|
||
<img src="@/shared/assets/icons/plus-green.svg" alt="Добавить сотрудника">
|
||
<p>Добавить сотрудника</p>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="flex w-full gap-6">
|
||
<div class="deal_value w-1/2">
|
||
<h3 class="mb-3">Стоимость сделки и комиссионные</h3>
|
||
<div class="flex">
|
||
<div class="w-1/2 mr-4">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_contract_price">Стоимость в договоре:</label>
|
||
<InputNumber
|
||
v-model="deal.contract_price"
|
||
id="deal_contract_price"
|
||
placeholder="Введите стоимость"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_commission_buyer">Комиссионное вознаграждение с покупателя/арендатора:</label>
|
||
<InputNumber
|
||
v-model="deal.commission_buyer"
|
||
id="deal_commission_buyer"
|
||
placeholder="Введите комиссионное вознаграждение"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_agent_commission">Комиссия агента:</label>
|
||
<InputNumber
|
||
v-model="deal.agent_commission"
|
||
id="deal_agent_commission"
|
||
placeholder="Введите комиссию агента"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="w-1/2">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_object_price">Цена объекта:</label>
|
||
<InputNumber
|
||
v-model="deal.object_price"
|
||
id="deal_object_price"
|
||
placeholder="Введите цену объекта"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_commission_seller">Комиссионное вознаграждение с продавца/арендодателя:</label>
|
||
<InputNumber
|
||
v-model="deal.commission_seller"
|
||
id="deal_commission_seller"
|
||
placeholder="Введите комиссионное вознаграждение"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_total_commission">Комиссионное вознаграждение Итого:</label>
|
||
<InputNumber
|
||
v-model="deal.total_commission"
|
||
id="deal_total_commission"
|
||
placeholder="Введите комиссионное вознаграждение"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="deal_additional_details w-1/2">
|
||
<h3 class="mb-3">Дополнительные данные по сделке</h3>
|
||
<div class="flex w-full">
|
||
<div class="w-1/2 mr-4">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_sellers_count">Количество продавцов:</label>
|
||
<InputNumber
|
||
v-model="deal.sellers_count"
|
||
id="deal_sellers_count"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_proxy_sale">Продажа по доверенности:</label>
|
||
<div class="deal_proxy_sale">
|
||
<label class="mr-4">
|
||
<input type="radio" v-model="deal.proxy_sale" :value="true" name="deal_proxy_sale" />
|
||
Да
|
||
</label>
|
||
<label>
|
||
<input type="radio" v-model="deal.proxy_sale" :value="false" name="deal_proxy_sale" />
|
||
Нет
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_e_registration">Электронная регистрация:</label>
|
||
<div class="deal_e_registration">
|
||
<label class="mr-4">
|
||
<input type="radio" v-model="deal.e_registration" :value="true" name="deal_e_registration" />
|
||
Да
|
||
</label>
|
||
<label>
|
||
<input type="radio" v-model="deal.e_registration" :value="false" name="deal_e_registration" />
|
||
Нет
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_object_mortgaged">Объект в залоге:</label>
|
||
<div class="deal_object_mortgaged">
|
||
<label class="mr-4">
|
||
<input type="radio" v-model="deal.mortgaged" :value="true" name="deal_object_mortgaged" />
|
||
Да
|
||
</label>
|
||
<label>
|
||
<input type="radio" v-model="deal.mortgaged" :value="false" name="deal_object_mortgaged" />
|
||
Нет
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div v-if="deal.mortgaged">
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_mortgagee">Залогодержатель:</label>
|
||
<InputText
|
||
v-model="deal.mortgagee"
|
||
id="deal_mortgagee"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_debt_amount">Сумма долга:</label>
|
||
<InputNumber
|
||
v-model="deal.debt_amount"
|
||
id="deal_debt_amount"
|
||
placeholder="Введите сумму долга"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="w-1/2">
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="deal_buyers_count">Количество покупателей:</label>
|
||
<InputNumber
|
||
v-model="deal.buyers_count"
|
||
id="deal_buyers_count"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full mb-3">
|
||
<label for="has_counter_agent">Есть встречный агент:</label>
|
||
<div class="has_counter_agent">
|
||
<label class="mr-4">
|
||
<input type="radio" v-model="deal.has_counter_agent" :value="true" name="has_counter_agent" />
|
||
Да
|
||
</label>
|
||
<label>
|
||
<input type="radio" v-model="deal.has_counter_agent" :value="false" name="has_counter_agent" />
|
||
Нет
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div v-if="deal.has_counter_agent">
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_counter_agent_name">ФИО встречного агента:</label>
|
||
<InputText
|
||
v-model="deal.counter_agent_name"
|
||
id="deal_counter_agent_name"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_counter_agent_commission">Комиссия встречному агенту:</label>
|
||
<InputNumber
|
||
v-model="deal.counter_agent_commission"
|
||
id="deal_counter_agent_commission"
|
||
:mode="'decimal'"
|
||
:useGrouping="true"
|
||
:locale="'ru-RU'"
|
||
/>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_counter_agent_phone">Телефон встречного агента:</label>
|
||
<InputText
|
||
v-model="deal.counter_agent_phone"
|
||
id="deal_counter_agent_phone"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="form__item flex flex-col gap-1 w-full">
|
||
<label for="deal_date">Планируемая дата сделки:</label>
|
||
<DatePicker
|
||
v-model="deal.deal_date"
|
||
id="deal_date"
|
||
placeholder="Выберите дату сделки"
|
||
:showTime="true"
|
||
hourFormat="24"
|
||
dateFormat="dd.mm.yy"
|
||
:showIcon="true"
|
||
:showOtherMonths="true"
|
||
:selectOtherMonths="true"
|
||
class="w-full"
|
||
:class="{ 'p-invalid': errors.deal_date }"
|
||
/>
|
||
<small v-if="errors.deal_date" class="p-error">{{ errorMessages.deal_date }}</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Документы</h3>
|
||
<div class="flex w-full flex-wrap">
|
||
<!-- <DocAddModal v-if="showModalDealDoc" :deal="deal" @close="closeModalDealDoc" @select="addDoc" />-->
|
||
<DocsCard
|
||
v-for="doc in selectedDocs"
|
||
:key="doc.id"
|
||
:doc="doc"
|
||
:urlLaravel="urlLaravel"
|
||
@remove="removeDoc"
|
||
/>
|
||
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4">
|
||
<button @click="openModalDealDoc" class="w-full h-full p-6 rounded flex flex-col items-center justify-center add-deal-doc-button">
|
||
<img src="@/shared/assets/icons/plus-green.svg" alt="Добавить документ" class="w-6 h-6">
|
||
<p>Добавить документ</p>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<template v-if="isEditing">
|
||
<h3>Составить договор</h3>
|
||
<div class="flex w-full flex-wrap">
|
||
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2">
|
||
<button @click="openModalDealContract" class="w-full h-full p-5 rounded flex flex-col items-center justify-center add-contract-button">
|
||
<img src="@/shared/assets/icons/plus-green.svg" alt="Составить договор">
|
||
<p>Составить договор</p>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="flex w-full flex-wrap">
|
||
<InnerDocCard
|
||
v-for="innerDoc in deal.inner_docs"
|
||
:key="innerDoc.id"
|
||
:innerDoc="innerDoc"
|
||
@remove="removeInnerDoc"
|
||
class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
|
||
<div class="form-footer mt-6" style="display: flex; justify-content: center; width: 100%;">
|
||
<MyButton
|
||
:theme="'green'"
|
||
type="button"
|
||
class="submit mt-7.5"
|
||
style="background: linear-gradient(to bottom, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%); color: #fff; padding: 10px 130px; font-size: 18px; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;"
|
||
@click="saveDeal"
|
||
>
|
||
Сохранить
|
||
</MyButton>
|
||
</div>
|
||
</form>
|
||
|
||
<ConfirmDialog></ConfirmDialog>
|
||
<Toast />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import MyButton from "@/shared/UI/MyButton.vue";
|
||
import {computed, inject, onMounted, ref, watch, nextTick } from "vue";
|
||
import {useLaravelApi} from '@/composables/useLaravelApi';
|
||
//import useDeal from '@/composables/useDeal';
|
||
import {useToast} from "primevue/usetoast";
|
||
import {useDialog} from 'primevue/usedialog';
|
||
import ConfirmDialog from 'primevue/confirmdialog';
|
||
import Toast from "primevue/toast";
|
||
import InputText from "primevue/inputtext";
|
||
import InputNumber from "primevue/inputnumber";
|
||
import DropdownIcon from "@/shared/assets/icons/dropdown-icon.vue";
|
||
import dayjs from 'dayjs';
|
||
import ru from 'dayjs/locale/ru';
|
||
import RequisitionDetails from '@/components/deal/addEdit/RequisitionDetails.vue';
|
||
import ObjectDetails from "@/components/deal/addEdit/ObjectDetails.vue";
|
||
import EmployeeAddModal from '@/components/deal/addEdit/EmployeeAddModal.vue';
|
||
import EmployeeCard from '@/components/deal/addEdit/EmployeeCard.vue';
|
||
import DocAddModal from '@/components/deal/addEdit/DocAddModal.vue';
|
||
import DocsCard from '@/components/deal/addEdit/DocsCard.vue';
|
||
import InnerDocAddModal from "@/components/deal/addEdit/InnerDocAddModal.vue";
|
||
import InnerDocCard from "@/components/deal/addEdit/InnerDocCard.vue";
|
||
|
||
const toast = useToast();
|
||
|
||
const showError = (message) => {
|
||
toast.add({
|
||
severity: 'error',
|
||
summary: 'Ошибка',
|
||
detail: message,
|
||
life: 3000
|
||
});
|
||
};
|
||
|
||
const props = defineProps({
|
||
dealId: Number,
|
||
isOpen: Boolean
|
||
});
|
||
|
||
const dialog = useDialog();
|
||
|
||
const dialogRef = inject('dialogRef');
|
||
const newDealReqId = dialogRef.value.data?.newDealReqId;
|
||
const dialogRefDealId = dialogRef.value.data?.deal_id;
|
||
|
||
let dealId = dialogRefDealId || null;
|
||
const isEditing = ref(!!dialogRefDealId);
|
||
|
||
const isSideOneLoaded = ref(false);
|
||
|
||
const { data: dealData, fetchData: fetchDealData } = useLaravelApi();
|
||
const { data: employeesData, fetchData: fetchEmployeesData } = useLaravelApi();
|
||
const { data: requisitionsListData, fetchData: fetchRequisitionsListData } = useLaravelApi();
|
||
const { data: requisitionSideOneDetails, fetchData: fetchRequisitionSideOneDetails } = useLaravelApi();
|
||
const { data: requisitionSideTwoDetails, fetchData: fetchRequisitionSideTwoDetails } = useLaravelApi();
|
||
|
||
/*Объекты*/
|
||
const { data: objectsListData, fetchData: fetchObjectsListData } = useLaravelApi();
|
||
const objectsListSideOne = ref([]);
|
||
const objectsListSideTwo = ref([]);
|
||
|
||
const { data: objectSideOneDetails, fetchData: fetchObjectSideOneDetails } = useLaravelApi();
|
||
const { data: objectSideTwoDetails, fetchData: fetchObjectSideTwoDetails } = useLaravelApi();
|
||
|
||
// Клиенты
|
||
const { data: clientsListData, fetchData: fetchClientsListData } = useLaravelApi();
|
||
const clientsList = ref([]);
|
||
|
||
// Сотрудники
|
||
const { data: employeesListData, fetchData: fetchEmployeesListData } = useLaravelApi();
|
||
const employeesList = ref([]);
|
||
|
||
//Документы
|
||
const { data: documentsData, fetchData: fetchDocumentsData } = useLaravelApi();
|
||
const docsList = ref([]); // Список документов
|
||
|
||
|
||
async function loadRequisition(reqId, side) {
|
||
if (!reqId) return;
|
||
|
||
console.log('loadRequisition:' + reqId)
|
||
|
||
try {
|
||
let response;
|
||
if (side === 'sideOne') {
|
||
response = await fetchRequisitionSideOneDetails(`/requisitions/${reqId}`);
|
||
} else {
|
||
response = await fetchRequisitionSideTwoDetails(`/requisitions/${reqId}`);
|
||
}
|
||
if (response.data) {
|
||
if (side === 'sideOne') {
|
||
selectedRequisitionSideOneId.value = reqId;
|
||
selectedRequisitionSideOne.value = response.data;
|
||
|
||
if (response.data.object_id) {
|
||
selectedObjectSideOneId.value = response.data.object_id;
|
||
}
|
||
|
||
//Установка клиента в deal
|
||
if (response.data.client && typeof response.data.client === 'object') {
|
||
deal.value.side_one_client_main = response.data.client; // кладём как массив
|
||
} else {
|
||
deal.value.side_one_client_main = null;
|
||
}
|
||
|
||
console.log('Загружены основной клиент стороны 1:', deal.value.side_one_client_main);
|
||
|
||
} else {
|
||
selectedRequisitionSideTwoId.value = reqId;
|
||
selectedRequisitionSideTwo.value = response.data;
|
||
|
||
if (response.data.object_id) {
|
||
selectedObjectSideTwoId.value = response.data.object_id;
|
||
}
|
||
|
||
// Установка клиента в deal
|
||
if (response.data.client && typeof response.data.client === 'object') {
|
||
deal.value.side_two_client_main = response.data.client;
|
||
} else {
|
||
deal.value.side_two_client_main = null;
|
||
}
|
||
|
||
console.log('Загружены клиенты стороны 2:', deal.value.side_two_clients);
|
||
}
|
||
|
||
return response.data;
|
||
}
|
||
} catch (error) {
|
||
console.error(`Ошибка загрузки заявки ${reqId}:`, error);
|
||
showError(`Не удалось загрузить заявку ${reqId}`);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
watch(() => props.dealId, (newId) => {
|
||
if (newId) {
|
||
console.log('При редактировании сделки ID сделки:', newId);
|
||
dealId = newId;
|
||
isEditing.value = true;
|
||
if (newId) {
|
||
loadDealData(newId);
|
||
} else {
|
||
resetForm();
|
||
}
|
||
}
|
||
});
|
||
|
||
watch(() => newDealReqId, async (newVal) => {
|
||
console.log('При создании сделки из этапа ID заявки:', newVal);
|
||
if (!newVal) return;
|
||
try {
|
||
const reqData = await loadRequisition(newVal, 'sideOne');
|
||
|
||
if (reqData && reqData.name) {
|
||
deal.value.title = `Сделка по заявке: "${reqData.name}"`;
|
||
}
|
||
|
||
selectedRequisitionSideOneId.value = Number(newVal);
|
||
|
||
deal.value.side_one = getSideByRequisitionType(reqData.type_id, reqData.type_name);
|
||
|
||
isSideOneLoaded.value = true;
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки заявки:', error);
|
||
showError('Не удалось загрузить заявку');
|
||
}
|
||
}, { immediate: true });
|
||
|
||
|
||
async function loadRequisitionsForDeal(dealData) {
|
||
await Promise.all([
|
||
dealData.side_one_requisition_id && loadRequisition(dealData.side_one_requisition_id, 'sideOne'),
|
||
dealData.side_two_requisition_id && loadRequisition(dealData.side_two_requisition_id, 'sideTwo')
|
||
]);
|
||
}
|
||
|
||
async function loadObjectsForDeal(dealData) {
|
||
await Promise.all([
|
||
dealData.side_one_object_id && fetchObjectSideOneDetails(`/objects/${dealData.side_one_object_id}`),
|
||
dealData.side_two_object_id && fetchObjectSideTwoDetails(`/objects/${dealData.side_two_object_id}`)
|
||
]);
|
||
}
|
||
|
||
async function loadDealData(dealId) {
|
||
console.log('Loading deal data for ID:', dealId);
|
||
|
||
// Валидация входных данных
|
||
if (!dealId || isNaN(dealId)) {
|
||
showError('Некорректный ID сделки');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Параллельная загрузка основных данных
|
||
const [dealRes, employeesRes] = await Promise.all([
|
||
fetchDealData(`/deals/${dealId}`),
|
||
fetchEmployeesData(`/deals/${dealId}/employees`)
|
||
]);
|
||
|
||
// Проверка успешности ответа
|
||
if (!dealRes.data?.success) {
|
||
throw new Error(dealRes.data?.message || 'Не удалось загрузить данные сделки');
|
||
}
|
||
|
||
// Обновление состояния сделки
|
||
const dealData = dealRes.data.data;
|
||
Object.assign(deal.value, dealData);
|
||
|
||
// Форматирование даты
|
||
if (dealData.deal_date) {
|
||
dayjs.locale(ru);
|
||
deal.value.deal_date = dayjs(dealData.deal_date, 'YYYY-MM-DD HH:mm:ss').toDate();
|
||
}
|
||
|
||
const [requisitionRes, objectRes, documentsRes] = await Promise.all([
|
||
loadRequisitionsForDeal(dealData),
|
||
loadObjectsForDeal(dealData),
|
||
fetchDocumentsData('/documents', {
|
||
params: { target_id: dealId, target_type: 10 }
|
||
})
|
||
]);
|
||
|
||
console.log('Документы:',documentsRes.data);
|
||
|
||
// Загружаем документы в selectedDocs
|
||
if (documentsRes.data?.data?.length) {
|
||
selectedDocs.value = documentsRes.data.data.map(doc => ({
|
||
id: doc.id,
|
||
name: doc.name,
|
||
file: doc.file,
|
||
created_at: doc.created_at
|
||
}));
|
||
} else {
|
||
selectedDocs.value = [];
|
||
}
|
||
|
||
// Обработка сотрудников
|
||
if (employeesRes.data) {
|
||
manualEmployees.value = employeesRes.data.map(emp => ({
|
||
...emp,
|
||
fullName: `${emp.last_name} ${emp.first_name} ${emp.middle_name}` || emp.fio,
|
||
}));
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки сделки:', error);
|
||
showError(String(error?.message ?? 'Неизвестная ошибка'));
|
||
}
|
||
}
|
||
|
||
|
||
// Функция сброса формы
|
||
function resetForm() {
|
||
deal.value = {
|
||
title: '',
|
||
side_one: '',
|
||
side_two: '',
|
||
side_one_requisition_id: null,
|
||
side_two_requisition_id: null,
|
||
side_one_object_id: null,
|
||
side_two_object_id: null,
|
||
contract_price: null,
|
||
object_price: null,
|
||
commission_buyer: null,
|
||
commission_seller: null,
|
||
agent_commission: null,
|
||
total_commission: null,
|
||
sellers_count: null,
|
||
buyers_count: null,
|
||
proxy_sale: false,
|
||
e_registration: false,
|
||
mortgaged: false,
|
||
mortgagee: '',
|
||
debt_amount: null,
|
||
has_counter_agent: false,
|
||
counter_agent_name: '',
|
||
counter_agent_commission: null,
|
||
counter_agent_phone: '',
|
||
deal_date: null,
|
||
};
|
||
requisitionEmployees.value = [];
|
||
manualEmployees.value = [];
|
||
selectedRequisitionSideOneId.value = null;
|
||
selectedRequisitionSideTwoId.value = null;
|
||
selectedObjectSideOneId.value = null;
|
||
selectedObjectSideTwoId.value = null;
|
||
}
|
||
|
||
const url = import.meta.env.VITE_API_URL;
|
||
const urlLaravel = import.meta.env.VITE_LARAVEL_URL
|
||
const deal = ref({
|
||
title: '',
|
||
side_one: '',
|
||
side_two: '',
|
||
side_one_requisition_id: null,
|
||
side_two_requisition_id: null,
|
||
side_one_object_id: null,
|
||
side_two_object_id: null,
|
||
//side_one_clients: null,
|
||
//side_two_clients: null,
|
||
contract_price: null,
|
||
object_price: null,
|
||
commission_buyer: null,
|
||
commission_seller: null,
|
||
agent_commission: null,
|
||
total_commission: null,
|
||
sellers_count: null,
|
||
buyers_count: null,
|
||
proxy_sale: false,
|
||
e_registration: false,
|
||
mortgaged: false,
|
||
mortgagee: '',
|
||
debt_amount: null,
|
||
has_counter_agent: false,
|
||
counter_agent_name: '',
|
||
counter_agent_commission: null,
|
||
counter_agent_phone: '',
|
||
deal_date: null,
|
||
});
|
||
|
||
const deal_sides_list = ['Покупатель', 'Продавец', 'Арендодатель', 'Арендатор', 'Застройщик'];
|
||
|
||
const originalRequisitionsList = ref([]);
|
||
const requisitionsList = ref([]);
|
||
|
||
const selectedRequisitionSideOneId = ref(null);
|
||
const selectedRequisitionSideOne = ref(null);
|
||
|
||
const selectedRequisitionSideTwoId = ref(null);
|
||
const selectedRequisitionSideTwo = ref(null);
|
||
|
||
const selectedObjectSideOneId = ref(null);
|
||
const selectedObjectSideOne = ref(null);
|
||
|
||
const selectedObjectSideTwoId = ref(null);
|
||
const selectedObjectSideTwo = ref(null);
|
||
|
||
const errors = ref({
|
||
title: false,
|
||
side_one: false,
|
||
side_two: false,
|
||
req_side_two: false,
|
||
deal_date: false
|
||
});
|
||
|
||
const errorMessages = ref({
|
||
title: '',
|
||
side_one: '',
|
||
side_two: '',
|
||
req_side_two: '',
|
||
deal_date: ''
|
||
});
|
||
|
||
watch(() => deal.value.title, () => {
|
||
if (errors.value.title) {
|
||
errors.value.title = false;
|
||
errorMessages.value.title = '';
|
||
}
|
||
});
|
||
|
||
watch(() => deal.value.side_one, () => {
|
||
if (errors.value.side_one) {
|
||
errors.value.side_one = false;
|
||
errorMessages.value.side_one = '';
|
||
}
|
||
});
|
||
|
||
watch(() => deal.value.side_two, () => {
|
||
if (errors.value.side_two) {
|
||
errors.value.side_two = false;
|
||
errorMessages.value.side_two = '';
|
||
}
|
||
});
|
||
|
||
watch(() => deal.value.deal_date, () => {
|
||
if (errors.value.deal_date) {
|
||
errors.value.deal_date = false;
|
||
errorMessages.value.deal_date = '';
|
||
}
|
||
});
|
||
|
||
watch(selectedRequisitionSideTwoId, () => {
|
||
if (errors.value.req_side_two) {
|
||
errors.value.req_side_two = false;
|
||
errorMessages.value.req_side_two = '';
|
||
}
|
||
});
|
||
|
||
const validateForm = () => {
|
||
let isValid = true;
|
||
|
||
// Валидация названия сделки
|
||
if (!deal.value.title) {
|
||
errors.value.title = true;
|
||
errorMessages.value.title = 'Название сделки обязательно';
|
||
isValid = false;
|
||
} else if (deal.value.title.length < 3) {
|
||
errors.value.title = true;
|
||
errorMessages.value.title = 'Минимум 3 символа';
|
||
isValid = false;
|
||
} else {
|
||
errors.value.title = false;
|
||
errorMessages.value.title = '';
|
||
}
|
||
|
||
// Валидация стороны 1
|
||
if (!deal.value.side_one) {
|
||
errors.value.side_one = true;
|
||
errorMessages.value.side_one = 'Выберите сторону 1';
|
||
isValid = false;
|
||
} else {
|
||
errors.value.side_one = false;
|
||
errorMessages.value.side_one = '';
|
||
}
|
||
|
||
// Валидация стороны 2
|
||
if (!deal.value.side_two) {
|
||
errors.value.side_two = true;
|
||
errorMessages.value.side_two = 'Выберите сторону 2';
|
||
isValid = false;
|
||
} else {
|
||
errors.value.side_two = false;
|
||
errorMessages.value.side_two = '';
|
||
}
|
||
|
||
// Валидация заявки стороны 2
|
||
if (!selectedRequisitionSideTwoId.value) {
|
||
errors.value.req_side_two = true;
|
||
errorMessages.value.req_side_two = 'Выберите заявку стороны 2';
|
||
isValid = false;
|
||
} else {
|
||
errors.value.req_side_two = false;
|
||
errorMessages.value.req_side_two = '';
|
||
}
|
||
|
||
// Валидация даты сделки
|
||
if (!deal.value.deal_date) {
|
||
errors.value.deal_date = true;
|
||
errorMessages.value.deal_date = 'Укажите дату сделки';
|
||
isValid = false;
|
||
} else {
|
||
errors.value.deal_date = false;
|
||
errorMessages.value.deal_date = '';
|
||
}
|
||
|
||
return isValid;
|
||
};
|
||
|
||
onMounted(async () => {
|
||
|
||
console.log('[DealModal] dialogRef.data:', dialogRef.value.data)
|
||
dealId = dialogRef.value.data?.deal_id
|
||
|
||
try {
|
||
// Загрузка справочников
|
||
const [objects, clients, employees, requisitions] = await Promise.all([
|
||
fetchObjectsListData(`/objects?fields=short`),
|
||
fetchClientsListData('/clients?fields=short&no_pagination=1'),
|
||
fetchEmployeesListData('/users?fields=short&no_pagination=1'),
|
||
fetchRequisitionsListData('/requisitions?no_pagination=1&exclude_linked_for_deal=1' + (dealId ? '&exclude_linked_for_deal_id=' + dealId : ''))
|
||
]);
|
||
|
||
objectsListSideOne.value = objects.data?.data || [];
|
||
objectsListSideTwo.value = objects.data?.data || [];
|
||
clientsList.value = clients.data?.data || [];
|
||
employeesList.value = employees.data?.data || [];
|
||
|
||
const reqList = requisitions.data?.data?.map(item => ({
|
||
id: item.id,
|
||
name: item.name || `Заявка ${item.id}`,
|
||
type_id: item.type_id
|
||
})) || [];
|
||
|
||
originalRequisitionsList.value = reqList;
|
||
requisitionsList.value = reqList.filter(req => req.id !== newDealReqId.value);
|
||
|
||
if (isEditing.value && dealId) {
|
||
await loadDealData(dealId);
|
||
// Фильтруем список заявок для стороны 2 по типу стороны 1
|
||
if (selectedRequisitionSideOne.value) {
|
||
const sideOneTypeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
|
||
let filtered;
|
||
if (sideOneTypeId === 2) {
|
||
filtered = reqList.filter(req => req.type_id !== 2);
|
||
} else {
|
||
filtered = reqList.filter(req => req.type_id === 2);
|
||
}
|
||
requisitionsList.value = filtered.filter(req => req.id !== selectedRequisitionSideOneId.value);
|
||
}
|
||
} else if (isSideOneLoaded) {
|
||
const sideOneTypeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
|
||
let filtered;
|
||
if (sideOneTypeId === 2) {
|
||
filtered = reqList.filter(req => req.type_id !== 2);
|
||
} else {
|
||
filtered = reqList.filter(req => req.type_id === 2);
|
||
}
|
||
requisitionsList.value = filtered.filter(req => req.id !== selectedRequisitionSideOneId.value);
|
||
} else {
|
||
requisitionsList.value = reqList.filter(req => req.id !== selectedRequisitionSideOneId.value);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки:', error);
|
||
showError('Не удалось загрузить данные');
|
||
}
|
||
})
|
||
|
||
|
||
// Получение деталей выбранной заявки 1 стороны
|
||
watch(selectedRequisitionSideOneId, async (newId, oldId) => {
|
||
if (newId != null) {
|
||
// Убираем who_work старой заявки стороны 1 из requisitionEmployees
|
||
if (oldId && selectedRequisitionSideOne.value) {
|
||
const oldWhoWork = selectedRequisitionSideOne.value.who_work;
|
||
if (oldWhoWork) {
|
||
const otherSideWhoWork = selectedRequisitionSideTwo.value?.who_work;
|
||
if (oldWhoWork !== otherSideWhoWork) {
|
||
requisitionEmployees.value = requisitionEmployees.value.filter(e => e.id !== oldWhoWork);
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('watch selectedRequisitionSideOneId:' + newId)
|
||
try {
|
||
await fetchRequisitionSideOneDetails(`/requisitions/${newId}`);
|
||
if (requisitionSideOneDetails.value) {
|
||
selectedRequisitionSideOne.value = requisitionSideOneDetails.value;
|
||
deal.value.side_one_requisition_id = newId;
|
||
|
||
// Добавляем who_work заявки стороны 1
|
||
if (requisitionSideOneDetails.value.who_work) {
|
||
await addResponsibleEmployee(requisitionSideOneDetails.value.who_work);
|
||
}
|
||
|
||
// Автовыбор стороны 1 (Арендодатель/Продавец) по operation_type объекта
|
||
let operationType = null;
|
||
if (!isEditing.value && requisitionSideOneDetails.value.object_id) {
|
||
selectedObjectSideOneId.value = requisitionSideOneDetails.value.object_id;
|
||
await fetchObjectSideOneDetails(`/objects/${requisitionSideOneDetails.value.object_id}`);
|
||
const objData = objectSideOneDetails.value;
|
||
if (objData && objData.success && objData.data?.operation_type !== undefined) {
|
||
operationType = objData.data.operation_type;
|
||
deal.value.side_one = operationType === 0 ? 'Арендодатель' : 'Продавец';
|
||
}
|
||
}
|
||
|
||
// Автовыбор стороны 2 (Арендатор/Покупатель) по operation_type объекта
|
||
if (!isEditing.value && operationType !== null && !deal.value.side_two) {
|
||
deal.value.side_two = operationType === 0 ? 'Арендатор' : 'Покупатель';
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('Ошибка при загрузке данных:', err);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Получение деталей выбранной заявки 2 стороны
|
||
watch(selectedRequisitionSideTwoId, async (newId, oldId) => {
|
||
|
||
if (oldId && newId !== oldId) {
|
||
selectedObjectSideTwoId.value = null;
|
||
selectedObjectSideTwo.value = null;
|
||
deal.value.side_two_object_id = null;
|
||
|
||
// Убираем who_work старой заявки стороны 2 из requisitionEmployees
|
||
if (oldId && selectedRequisitionSideTwo.value) {
|
||
const oldWhoWork = selectedRequisitionSideTwo.value.who_work;
|
||
if (oldWhoWork) {
|
||
// Не удаляем если этот сотрудник — who_work другой стороны
|
||
const otherSideWhoWork = selectedRequisitionSideOne.value?.who_work;
|
||
if (oldWhoWork !== otherSideWhoWork) {
|
||
requisitionEmployees.value = requisitionEmployees.value.filter(e => e.id !== oldWhoWork);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (newId) {
|
||
await fetchRequisitionSideTwoDetails(`/requisitions/${newId}`);
|
||
if (requisitionSideTwoDetails.value) {
|
||
selectedRequisitionSideTwo.value = requisitionSideTwoDetails.value;
|
||
deal.value.side_two_requisition_id = newId;
|
||
|
||
if (requisitionSideTwoDetails.value.object_id) {
|
||
selectedObjectSideTwoId.value = requisitionSideTwoDetails.value.object_id;
|
||
}
|
||
|
||
// Добавляем who_work заявки стороны 2 в selectedEmployees
|
||
if (requisitionSideTwoDetails.value.who_work) {
|
||
await addResponsibleEmployee(requisitionSideTwoDetails.value.who_work);
|
||
}
|
||
}
|
||
|
||
// Установка клиента в deal для стороны 2
|
||
if (requisitionSideTwoDetails.value?.client && typeof requisitionSideTwoDetails.value.client === 'object') {
|
||
deal.value.side_two_client_main = requisitionSideTwoDetails.value.client;
|
||
} else {
|
||
deal.value.side_two_client_main = null;
|
||
}
|
||
console.log('Загружены основной клиент стороны 2:', deal.value.side_two_client_main);
|
||
} else {
|
||
selectedRequisitionSideTwo.value = null;
|
||
}
|
||
});
|
||
|
||
//Поиск в списке заявок
|
||
// async function filterRequisitionsList(event) {
|
||
// const query = event.filter || '';
|
||
// try {
|
||
// const response = await fetchRequisitionsListData(`/requisitions?search=${encodeURIComponent(query)}`);
|
||
// requisitionsList.value = response.data.data || [];
|
||
// } catch (error) {
|
||
// console.error('Ошибка поиска заявок:', error);
|
||
// }
|
||
// }
|
||
async function filterRequisitionsList(event) {
|
||
const query = event.value ?? event.filter ?? '';
|
||
|
||
if (query === '') {
|
||
requisitionsList.value = originalRequisitionsList.value.filter(req => req.id !== selectedRequisitionSideOneId.value);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetchRequisitionsListData(`/requisitions?search=${encodeURIComponent(query)}&exclude_linked_for_deal=1` + (dealId ? '&exclude_linked_for_deal_id=' + dealId : ''));
|
||
const items = (response.data.data || response.data.list || []).map(r => ({
|
||
...r,
|
||
name: r.name + ' (ID: ' + r.id + ')',
|
||
}));
|
||
requisitionsList.value = items;
|
||
} catch (error) {
|
||
console.error('Ошибка поиска заявок:', error);
|
||
requisitionsList.value = [...originalRequisitionsList.value];
|
||
}
|
||
}
|
||
|
||
|
||
// Получение деталей выбранного объекта 1 стороны
|
||
watch(selectedObjectSideOneId, async (newId) => {
|
||
console.log('[DealModal] watch selectedObjectSideOneId:', newId);
|
||
if (newId) {
|
||
await fetchObjectSideOneDetails(`/objects/${newId}`);
|
||
console.log('[DealModal] objectSideOneDetails:', objectSideOneDetails.value);
|
||
if (objectSideOneDetails.value && objectSideOneDetails.value.success) {
|
||
selectedObjectSideOne.value = objectSideOneDetails.value.data;
|
||
deal.value.side_one_object_id = newId;
|
||
|
||
const exists = objectsListSideOne.value.some(o => o.id === newId);
|
||
if (!exists && objectSideOneDetails.value.data) {
|
||
objectsListSideOne.value.push({ id: newId, name: objectSideOneDetails.value.data.nazv || `Объект ${newId}` });
|
||
}
|
||
}
|
||
} else {
|
||
selectedObjectSideOne.value = null;
|
||
}
|
||
});
|
||
|
||
// Получение деталей выбранного объекта 2 стороны
|
||
watch(selectedObjectSideTwoId, async (newId) => {
|
||
if (newId) {
|
||
await fetchObjectSideTwoDetails(`/objects/${newId}`);
|
||
if (objectSideTwoDetails.value && objectSideTwoDetails.value.success) {
|
||
selectedObjectSideTwo.value = objectSideTwoDetails.value.data;
|
||
deal.value.side_two_object_id = newId;
|
||
|
||
const exists = objectsListSideTwo.value.some(o => o.id === newId);
|
||
if (!exists && objectSideTwoDetails.value.data) {
|
||
objectsListSideTwo.value.push({ id: newId, name: objectSideTwoDetails.value.data.nazv || `Объект ${newId}` });
|
||
}
|
||
}
|
||
} else {
|
||
selectedObjectSideTwo.value = null;
|
||
}
|
||
});
|
||
|
||
// Логика для управления модальным окном и выбранными сотрудниками
|
||
const requisitionEmployees = ref([]);
|
||
const manualEmployees = ref([]);
|
||
|
||
const selectedEmployees = computed(() => {
|
||
const sideOneWhoWork = selectedRequisitionSideOne.value?.who_work;
|
||
const sideOneEmp = sideOneWhoWork ? requisitionEmployees.value.filter(e => e.id === sideOneWhoWork) : [];
|
||
const otherRequisitionEmps = requisitionEmployees.value.filter(e => !sideOneWhoWork || e.id !== sideOneWhoWork);
|
||
return [...sideOneEmp, ...otherRequisitionEmps, ...manualEmployees.value];
|
||
});
|
||
|
||
const isWhoWork = (employeeId) => {
|
||
return requisitionEmployees.value.some(e => e.id === employeeId);
|
||
};
|
||
|
||
// Исключаем who_work заявок из списка выбора сотрудников
|
||
const filteredEmployeesList = computed(() => {
|
||
const excludeIds = requisitionEmployees.value.map(e => e.id);
|
||
if (excludeIds.length === 0) return employeesList.value;
|
||
return employeesList.value.filter(emp => !excludeIds.includes(emp.id));
|
||
});
|
||
|
||
const openModalDealManager = () => {
|
||
console.log('[openModalDealManager] Открываем EmployeeAddModal через DynamicDialog')
|
||
|
||
dialog.open(EmployeeAddModal, {
|
||
props: {
|
||
header: 'Выберите сотрудников',
|
||
style: { width: '30vw' },
|
||
modal: true,
|
||
pt: {
|
||
header: {
|
||
style: {
|
||
padding: '0.75rem 1rem'
|
||
}
|
||
}
|
||
}
|
||
},
|
||
data: {
|
||
employees: filteredEmployeesList.value,
|
||
selected: manualEmployees.value
|
||
},
|
||
onClose: (opt) => {
|
||
console.log('[DynamicDialog] EmployeeAddModal закрыт:', opt?.data)
|
||
if (opt?.data) {
|
||
manualEmployees.value = opt.data;
|
||
}
|
||
}
|
||
})
|
||
};
|
||
|
||
const removeEmployee = (employeeId) => {
|
||
if (isWhoWork(employeeId)) return;
|
||
manualEmployees.value = manualEmployees.value.filter(e => e.id !== employeeId);
|
||
};
|
||
|
||
const selectedDocs = ref([]);
|
||
|
||
const openModalDealDoc = () => {
|
||
console.log('[openModalDealDoc] Открываем DocAddModal через DynamicDialog')
|
||
|
||
dialog.open(DocAddModal, {
|
||
props: {
|
||
header: 'Добавление документа',
|
||
style: { width: '50vw' },
|
||
modal: true,
|
||
pt: {
|
||
header: {
|
||
style: {
|
||
padding: '0.75rem 1rem'
|
||
}
|
||
}
|
||
}
|
||
},
|
||
data: {
|
||
deal: deal.value
|
||
},
|
||
onClose: (opt) => {
|
||
console.log('[DynamicDialog] Закрыто с данными:', opt?.data)
|
||
if (opt?.data) {
|
||
console.log('opt', opt?.data)
|
||
addDoc(opt.data);
|
||
//selectedDocs.value.push(opt.data)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
|
||
const addDoc = (doc) => {
|
||
console.log('[DealModal] addDoc вызван с:', doc);
|
||
|
||
// ✅ Проверка, что doc — объект и имеет id и name
|
||
if (!doc || typeof doc !== 'object' || !doc.id || !doc.name) {
|
||
console.error('[DealModal] addDoc: получен невалидный doc:', doc);
|
||
return;
|
||
}
|
||
|
||
// Проверяем, что объект документа содержит обязательные поля
|
||
if (doc && doc.id && doc.name && doc.file) {
|
||
// Проверяем, что документ еще не добавлен
|
||
const isDuplicate = selectedDocs.value.some((d) => d.id === doc.id);
|
||
if (!isDuplicate) {
|
||
selectedDocs.value.push(doc); // Добавляем документ в список
|
||
console.log('[DealModal] Документ добавлен в selectedDocs');
|
||
}
|
||
} else {
|
||
console.error('Документ не содержит обязательных полей:', doc);
|
||
}
|
||
//closeModalDealDoc(); // Закрываем модальное окно
|
||
};
|
||
|
||
// Удалить документ
|
||
const removeDoc = async (docId) => {
|
||
const doc = selectedDocs.value.find(d => d.id === docId);
|
||
selectedDocs.value = selectedDocs.value.filter(d => d.id !== docId);
|
||
|
||
try {
|
||
const { fetchData } = useLaravelApi();
|
||
await fetchData(`/documents/${docId}`, {
|
||
method: 'PUT',
|
||
data: { soft_delete: true },
|
||
});
|
||
toast.add({
|
||
severity: 'success',
|
||
summary: 'Документ удалён',
|
||
detail: doc ? `«${doc.name}» удалён` : 'Документ удалён',
|
||
life: 3000
|
||
});
|
||
} catch (error) {
|
||
toast.add({
|
||
severity: 'warn',
|
||
summary: 'Документ убран из списка',
|
||
detail: 'Не удалось удалить на сервере, будет удалён при сохранении',
|
||
life: 3000
|
||
});
|
||
}
|
||
};
|
||
|
||
// Обработка закрытия заявки
|
||
const handleCloseDealRequisition = (side) => {
|
||
if (side === 'sideOne') {
|
||
selectedRequisitionSideOneId.value = null
|
||
selectedRequisitionSideOne.value = null; // Очищаем заявку для стороны 1
|
||
deal.value.side_one_requisition_id = null;
|
||
} else if (side === 'sideTwo') {
|
||
selectedRequisitionSideTwoId.value = null
|
||
selectedRequisitionSideTwo.value = null; // Очищаем заявку для стороны 2
|
||
deal.value.side_two_requisition_id = null;
|
||
}
|
||
};
|
||
|
||
// Закрытие ObjectDetails
|
||
const handleCloseDealObject = (side) => {
|
||
if (side === 'sideOne') {
|
||
selectedObjectSideOneId.value = null; // Очищаем выбранный объект
|
||
selectedObjectSideOne.value = null; // Очищаем данные объекта
|
||
deal.value.side_one_object_id = null;
|
||
} else if (side === 'sideTwo') {
|
||
selectedObjectSideTwoId.value = null; // Очищаем выбранный объект
|
||
selectedObjectSideTwo.value = null; // Очищаем данные объекта
|
||
deal.value.side_two_object_id = null;
|
||
}
|
||
};
|
||
|
||
|
||
const openModalDealContract = async () => {
|
||
if (!deal.value.id) {
|
||
return;
|
||
}
|
||
dialog.open(InnerDocAddModal, {
|
||
props: {
|
||
header: 'Составить договор',
|
||
pt: {
|
||
header: {
|
||
style: {
|
||
padding: '0.75rem 1rem',
|
||
borderBottom: 'none'
|
||
}
|
||
},
|
||
},
|
||
modal: true,
|
||
},
|
||
data: {
|
||
deal: deal.value,
|
||
},
|
||
onClose: (options) => {
|
||
if (options?.data) {
|
||
handleNewInnerDoc(options.data);
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
// Обработка нового договора
|
||
const handleNewInnerDoc = (newInnerDoc) => {
|
||
console.log('Новый innerDoc получен:', newInnerDoc);
|
||
|
||
if (!newInnerDoc || typeof newInnerDoc !== 'object' || !newInnerDoc.id || !newInnerDoc.name) {
|
||
console.error('[DealModal] handleNewInnerDoc: получен невалидный newInnerDoc:', newInnerDoc);
|
||
return;
|
||
}
|
||
|
||
if (!deal.value.inner_docs) {
|
||
console.warn('Массив inner_docs не существует в deal. Создаём новый массив.');
|
||
deal.value.inner_docs = [];
|
||
}
|
||
|
||
console.log('Добавляем новый договор в deal.inner_docs');
|
||
deal.value.inner_docs.push(newInnerDoc);
|
||
|
||
console.log('Обновлённый список inner_docs:', deal.value.inner_docs);
|
||
};
|
||
|
||
// Удаление документа
|
||
const { fetchData: deleteData } = useLaravelApi();
|
||
|
||
const removeInnerDoc = async (docId) => {
|
||
console.log('[DealModal] deleteInnerDoc:', docId);
|
||
try {
|
||
await deleteData(`/inner-docs/${docId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
// Удаляем документ из локального состояния
|
||
deal.value.inner_docs = deal.value.inner_docs.filter(doc => doc.id !== docId);
|
||
} catch (error) {
|
||
console.error('Ошибка при удалении документа:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
const scrollToFirstError = () => {
|
||
const firstErrorField = document.querySelector('.p-invalid');
|
||
|
||
if (firstErrorField) {
|
||
firstErrorField.scrollIntoView({
|
||
behavior: 'smooth',
|
||
block: 'center'
|
||
});
|
||
|
||
firstErrorField.classList.add('error-highlight');
|
||
setTimeout(() => {
|
||
firstErrorField.classList.remove('error-highlight');
|
||
}, 2000);
|
||
}
|
||
};
|
||
|
||
// Сохранение сделки
|
||
const saveDeal = async () => {
|
||
|
||
// Проверяем валидацию перед сохранением
|
||
if (!validateForm()) {
|
||
|
||
await nextTick();
|
||
scrollToFirstError();
|
||
|
||
toast.add({
|
||
severity: 'error',
|
||
summary: 'Ошибка',
|
||
detail: 'Заполните все обязательные поля',
|
||
life: 3000,
|
||
});
|
||
return null;
|
||
}
|
||
|
||
const { fetchData } = useLaravelApi();
|
||
|
||
dayjs.locale(ru);
|
||
const formattedDate = dayjs(deal.value.deal_date).format('YYYY-MM-DD HH:mm:ss');
|
||
|
||
// Подготовка данных клиентов
|
||
// const sideOneClients = deal.value.side_one_clients || [];
|
||
// const sideTwoClients = deal.value.side_two_clients || [];
|
||
|
||
const payload = {
|
||
...deal.value,
|
||
deal_date: formattedDate,
|
||
employees: selectedEmployees.value.map(employee => employee.id),
|
||
documents: selectedDocs.value.map(doc => doc.id),
|
||
};
|
||
|
||
try {
|
||
const url = isEditing.value ? `/deals/${dealId}` : '/deals';
|
||
const method = isEditing.value ? 'PUT' : 'POST';
|
||
|
||
const response = await fetchData(url, {
|
||
method,
|
||
data: payload,
|
||
});
|
||
|
||
if (response.data.success) {
|
||
const docCount = selectedDocs.value.length;
|
||
const docText = docCount > 0 ? ` (${docCount} док.)` : '';
|
||
toast.add({
|
||
severity: 'success',
|
||
summary: 'Успех',
|
||
detail: String(response.data.message || (isEditing.value ? 'Сделка обновлена' : 'Сделка создана') + docText),
|
||
life: 3000,
|
||
});
|
||
|
||
// // Обновляем локальное состояние сделки
|
||
// if (response.data.deal) {
|
||
// //deal.value = response.data.deal;
|
||
// const rawDeal = response.data.deal;
|
||
//
|
||
// // Конвертируем строки в Date, чтобы избежать падений в computed
|
||
// if (rawDeal.deal_date) {
|
||
// rawDeal.deal_date = dayjs(rawDeal.deal_date, [
|
||
// 'YYYY-MM-DD HH:mm:ss',
|
||
// 'DD.MM.YYYY HH:mm'
|
||
// ], true).toDate();
|
||
// }
|
||
// if (rawDeal.created_at) {
|
||
// rawDeal.created_at = new Date(rawDeal.created_at);
|
||
// }
|
||
// if (rawDeal.updated_at) {
|
||
// rawDeal.updated_at = new Date(rawDeal.updated_at);
|
||
// }
|
||
//
|
||
// deal.value = rawDeal;
|
||
// }
|
||
|
||
if (response.data.deal) {
|
||
const rawDeal = { ...response.data.deal };
|
||
|
||
// Конвертируем строки в Date
|
||
if (rawDeal.deal_date) {
|
||
rawDeal.deal_date = dayjs(rawDeal.deal_date, [
|
||
'YYYY-MM-DD HH:mm:ss',
|
||
'DD.MM.YYYY HH:mm'
|
||
], true).toDate();
|
||
}
|
||
if (rawDeal.created_at) {
|
||
rawDeal.created_at = new Date(rawDeal.created_at);
|
||
}
|
||
if (rawDeal.updated_at) {
|
||
rawDeal.updated_at = new Date(rawDeal.updated_at);
|
||
}
|
||
|
||
// Вместо перезаписи — обновляем поля
|
||
Object.assign(deal.value, rawDeal);
|
||
}
|
||
|
||
if (!isEditing.value && deal.value.id) {
|
||
isEditing.value = true;
|
||
dealId = deal.value.id;
|
||
toast.add({
|
||
severity: 'info',
|
||
summary: 'Сделка сохранена',
|
||
detail: 'Теперь вы можете составить договор',
|
||
life: 5000,
|
||
});
|
||
}
|
||
|
||
console.log('cat deal', response.data)
|
||
return deal.value;
|
||
|
||
} else {
|
||
new Error(response.data.message || 'Ошибка при сохранении сделки');
|
||
}
|
||
} catch (error) {
|
||
toast.add({
|
||
severity: 'error',
|
||
summary: 'Ошибка',
|
||
detail: String(error?.message ?? 'Неизвестная ошибка'),
|
||
life: 3000,
|
||
});
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const getSideByRequisitionType = (typeId) => {
|
||
if (typeId === 2) {
|
||
return 'Продавец';
|
||
}
|
||
return 'Покупатель';
|
||
};
|
||
|
||
// Список сторон в зависимости от типа заявки
|
||
const sideOneOptions = computed(() => {
|
||
if (!selectedRequisitionSideOne.value) return [];
|
||
const typeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
|
||
if (typeId === 2) {
|
||
return ['Продавец', 'Арендодатель', 'Застройщик'];
|
||
}
|
||
return ['Покупатель', 'Арендатор'];
|
||
});
|
||
|
||
const sideTwoOptions = computed(() => {
|
||
const sideOne = deal.value.side_one;
|
||
|
||
if (!sideOne) return [];
|
||
|
||
// Если сторона 1 - продавец/арендодатель/застройщик
|
||
if (sideOne === 'Продавец' || sideOne === 'Арендодатель' || sideOne === 'Застройщик') {
|
||
return ['Покупатель', 'Арендатор'];
|
||
}
|
||
|
||
// Если сторона 1 - покупатель/арендатор
|
||
if (sideOne === 'Покупатель' || sideOne === 'Арендатор') {
|
||
return ['Продавец', 'Арендодатель', 'Застройщик'];
|
||
}
|
||
|
||
return [];
|
||
});
|
||
|
||
// Полный список для случаев, когда заявка не выбрана
|
||
const fullSidesList = ['Покупатель', 'Продавец', 'Арендодатель', 'Арендатор', 'Застройщик'];
|
||
|
||
|
||
// Локальное состояние
|
||
const isOpen = ref(props.isOpen);
|
||
|
||
// Синхронизация с props
|
||
watch(() => props.isOpen, (newVal) => {
|
||
isOpen.value = newVal;
|
||
});
|
||
|
||
const emit = defineEmits(['close', 'added']);
|
||
const closeModel = () => {
|
||
if (window.isDealOpen !== undefined) window.isDealOpen = false;
|
||
document.body.classList.remove('lock');
|
||
const backdrop = document.querySelector('.modal-backdrop');
|
||
if (backdrop) backdrop.remove();
|
||
emit('close');
|
||
};
|
||
|
||
|
||
async function addResponsibleEmployee(userId) {
|
||
if (!employeesList.value || employeesList.value.length === 0) {
|
||
console.warn('Список сотрудников не загружен!');
|
||
return;
|
||
}
|
||
|
||
const employee = employeesList.value.find(emp => emp.id === userId);
|
||
if (!employee) return;
|
||
|
||
if (requisitionEmployees.value.some(e => e.id === userId)) return;
|
||
|
||
const empData = {
|
||
...employee,
|
||
fullName: `${employee.last_name} ${employee.first_name}` || employee.fio,
|
||
};
|
||
|
||
// Если сотрудник уже в manualEmployees — переносим в requisitionEmployees
|
||
const manualIdx = manualEmployees.value.findIndex(e => e.id === userId);
|
||
if (manualIdx >= 0) {
|
||
manualEmployees.value.splice(manualIdx, 1);
|
||
}
|
||
|
||
requisitionEmployees.value = [...requisitionEmployees.value, empData];
|
||
}
|
||
|
||
defineExpose({ closeModel });
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.addDeal {
|
||
width: 100%;
|
||
height: auto;
|
||
|
||
.p-inputtext {
|
||
width: 100%;
|
||
font-size: 1rem;
|
||
color: #000;
|
||
background:#fff;
|
||
height: 36px;
|
||
line-height: 10px;
|
||
}
|
||
|
||
button {
|
||
background: transparent;
|
||
border:none;
|
||
}
|
||
|
||
.ml-2 {
|
||
margin-left: calc(var(--spacing) * 2);
|
||
}
|
||
|
||
|
||
.p-datepicker-trigger {
|
||
color: #64A853;
|
||
border: 1px solid #64A853;
|
||
height: 36px;
|
||
}
|
||
.p-datepicker-trigger:hover {
|
||
background: transparent;
|
||
color: #64A853;
|
||
}
|
||
|
||
|
||
|
||
&__form {
|
||
width: 100%;
|
||
height: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
align-items: flex-start;
|
||
margin-top: 30px;
|
||
row-gap: 20px;
|
||
|
||
/* Убираем стрелочки вверх/вниз для Chrome, Safari, Edge, Opera */
|
||
input[type="number"]::-webkit-outer-spin-button,
|
||
input[type="number"]::-webkit-inner-spin-button {
|
||
-webkit-appearance: none;
|
||
margin: 0;
|
||
}
|
||
|
||
/* Убираем стрелочки вверх/вниз для Firefox */
|
||
input[type="number"] {
|
||
appearance: textfield;
|
||
-moz-appearance: textfield;
|
||
-webkit-appearance: textfield;
|
||
}
|
||
|
||
input[type="radio"] {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
display: inline-block;
|
||
width: 21px;
|
||
height: 21px;
|
||
border: 1px solid #64A853;
|
||
border-radius: 50%;
|
||
position: relative;
|
||
cursor: pointer;
|
||
}
|
||
|
||
input[type="radio"]:checked::before {
|
||
content: "";
|
||
display: inline-block;
|
||
width: 12px;
|
||
height: 12px;
|
||
background-color: #64A853;
|
||
border-radius: 50%;
|
||
position: absolute;
|
||
top: 4px;
|
||
left: 4px;
|
||
}
|
||
|
||
h3 {
|
||
font-family: 'Lato', sans-serif;
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-size: 28px;
|
||
line-height: 34px;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #000000;
|
||
}
|
||
|
||
|
||
|
||
.add-employee-button {
|
||
border: 1px dashed #64A853;
|
||
border-radius: 5px;
|
||
height: 100%;
|
||
}
|
||
|
||
.add-contract-button {
|
||
border: 1px dashed #64A853;
|
||
border-radius: 5px;
|
||
height: 100%;
|
||
}
|
||
|
||
|
||
.add-deal-doc-button {
|
||
border: 1px dashed #64A853;
|
||
border-radius: 5px;
|
||
height: 100%;
|
||
}
|
||
|
||
|
||
.form__item {
|
||
.p-invalid {
|
||
border-color: #e24c4c !important;
|
||
box-shadow: 0 0 0 1px #e24c4c;
|
||
}
|
||
|
||
.p-error {
|
||
color: #e24c4c;
|
||
}
|
||
|
||
.error-highlight {
|
||
animation: shake 0.4s ease;
|
||
box-shadow: 0 0 0 2px #e24c4c;
|
||
}
|
||
|
||
label {
|
||
font-size: 16px;
|
||
font-weight: 400;
|
||
color: #757575;
|
||
}
|
||
|
||
.item__content {
|
||
width: 100%;
|
||
height: auto;
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: flex-start;
|
||
column-gap: 16px;
|
||
row-gap: 16px;
|
||
}
|
||
|
||
.item__text {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.item__btns {
|
||
width: 100%;
|
||
height: auto;
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
column-gap: 8px;
|
||
}
|
||
}
|
||
|
||
.small__item {
|
||
grid-template-columns: 1fr 276px;
|
||
}
|
||
}
|
||
|
||
.flex-grap-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
row-gap: 20px;
|
||
}
|
||
|
||
.p-dropdown {
|
||
width: 100% !important;
|
||
}
|
||
|
||
.add-deal-contract-button {
|
||
background: linear-gradient(180deg, #429F46 0%, #388E3C 100%), #FFFFFF;
|
||
border-radius: 3px;
|
||
color: #FFFFFF;
|
||
padding: 15px;
|
||
font-family: 'Lato', sans-serif;
|
||
font-style: normal;
|
||
font-weight: 500;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item {
|
||
max-width: 380px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
|
||
|
||
.dealAddDoc.modal {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
|
||
.modal-content {
|
||
position: absolute;
|
||
background-color: #fff;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
width: 80%;
|
||
max-width: 500px;
|
||
}
|
||
.close {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 20px;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
}
|
||
button {
|
||
color: #FFFFFF;
|
||
background: linear-gradient(180deg, #429F46 0%, #388E3C 100%), #FFFFFF;
|
||
}
|
||
}
|
||
|
||
|
||
.dealAddContract {
|
||
.add-contract-title {
|
||
label {
|
||
font-family: 'Lato', sans-serif;
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-size: 18px;
|
||
line-height: 22px;
|
||
color: #757575;
|
||
}
|
||
label::placeholder {
|
||
color: #000;
|
||
}
|
||
}
|
||
}
|
||
|
||
.simple-button.settings {
|
||
font-size: 22px;
|
||
margin-top: 0;
|
||
}
|
||
.simple-button {
|
||
font-size: 22px;
|
||
}
|
||
|
||
}
|
||
|
||
.p-dropdown:not(.p-disabled).p-focus {
|
||
box-shadow: none;
|
||
}
|
||
|
||
.p-inputtext:enabled:hover {
|
||
border-color: #64A853;
|
||
}
|
||
.p-inputtext:enabled:focus {
|
||
box-shadow: none;
|
||
border-color: #64A853;
|
||
}
|
||
.p-dropdown:not(.p-disabled):hover {
|
||
border-color: #64A853;
|
||
}
|
||
.p-dropdown:not(.p-disabled):focus {
|
||
border-color: #64A853;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight {
|
||
color: #64A853;
|
||
background: #F5F5F5;
|
||
}
|
||
|
||
.p-checkbox .p-checkbox-box.p-highlight {
|
||
border-color: #64A853;
|
||
background: #64A853;
|
||
}
|
||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight {
|
||
color: #000000;
|
||
background: #f2f2f2;
|
||
}
|
||
|
||
.p-multiselect:not(.p-disabled).p-focus {
|
||
box-shadow: none;
|
||
}
|
||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight.p-focus {
|
||
box-shadow: none;
|
||
background: #f2f2f2;
|
||
}
|
||
|
||
@keyframes shake {
|
||
0%, 100% { transform: translateX(0); }
|
||
20% { transform: translateX(-4px); }
|
||
40% { transform: translateX(4px); }
|
||
60% { transform: translateX(-4px); }
|
||
80% { transform: translateX(4px); }
|
||
}
|
||
|
||
</style>
|