Fix document upload

This commit is contained in:
Vova 2025-12-12 23:56:50 +03:00
parent 5e591eaea9
commit c3259b7e6c
11 changed files with 399 additions and 322 deletions

View File

@ -2,7 +2,7 @@ NODE_ENV=development
VITE_API_URL=https://joyworkdev.ru VITE_API_URL=https://joyworkdev.ru
VITE_LARAVEL_URL=https://llar.joyworkdev.ru VITE_LARAVEL_URL=https://llar.joyworkdev.ru
VITE_LARAVEL_API_URL=https://joyworkdev.ru VITE_LARAVEL_API_URL=https://joyworkdev.ru
VITE_DEV_SESSION_ID=e140a9d56a24f47046b9ab0953e5a9ee VITE_DEV_SESSION_ID=87ae8a661f903a5958e2a36aeaffb970
VITE_DEV_REQUISITION_ID=17539 VITE_DEV_REQUISITION_ID=17539
#VITE_BASIC_AUTH_USER=adminlogin #VITE_BASIC_AUTH_USER=adminlogin
#VITE_BASIC_AUTH_PASS=adminpassword #VITE_BASIC_AUTH_PASS=adminpassword

View File

@ -1,7 +1,8 @@
<template> <template>
<div> <div>
<h1>Сделка</h1> <h1>Сделка</h1>
<a href="#" @click.prevent="createDeal(17539)">Открыть тестовую сделку</a> <div><a href="#" @click.prevent="createDeal(17539)">Создать новую сделку</a></div>
<div><a href="#" @click.prevent="editDeal(17539, 46)">Открыть тестовую сделку</a></div>
</div> </div>
<DynamicDialog /> <DynamicDialog />
</template> </template>
@ -36,12 +37,31 @@ const createDeal = (newDealReqId) => {
}) })
} }
const editDeal = (id) => { const editDeal = (newDealReqId, dealId) => {
isOpen.value = true console.log('[editDeal] Открываем DealModal через DynamicDialog', dealId)
dealId.value = id
newDealReqId.value = null dialog.open(DealModal, {
props: {
header: 'Редактирование сделки',
style: { width: '80vw' },
modal: true
},
data: {
newDealReqId: newDealReqId,
deal_id: dealId
},
onClose: (opt) => {
console.log('[DynamicDialog] Закрыто с данными:', opt?.data)
}
})
} }
// const editDeal = (id) => {
// isOpen.value = true
// dealId.value = id
// newDealReqId.value = null
// }
const backToStep = () => { const backToStep = () => {
isOpen.value = false isOpen.value = false
} }

View File

@ -294,10 +294,10 @@
</div> </div>
<div class="w-1/2"> <div class="w-1/2">
<div class="form__item flex flex-col gap-1 w-full mb-3"> <div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_sellers_count">Количество покупателей:</label> <label for="deal_buyers_count">Количество покупателей:</label>
<InputNumber <InputNumber
v-model="deal.buyers_count" v-model="deal.buyers_count"
id="deal_sellers_count" id="deal_buyers_count"
/> />
</div> </div>
<div class="form__item flex flex-col gap-1 w-full mb-3"> <div class="form__item flex flex-col gap-1 w-full mb-3">
@ -360,8 +360,14 @@
<h3>Документы</h3> <h3>Документы</h3>
<div class="flex w-full flex-wrap"> <div class="flex w-full flex-wrap">
<DocAddModal v-if="showModalDealDoc" :deal="deal" @close="closeModalDealDoc" @select="addDoc" /> <!-- <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" /> <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 p-2"> <div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2">
<button @click="openModalDealDoc" class="w-full h-full p-2 rounded flex flex-col items-center justify-center add-deal-doc-button"> <button @click="openModalDealDoc" class="w-full h-full p-2 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"> <img src="@/shared/assets/icons/plus-green.svg" alt="Добавить документ" class="w-6 h-6">
@ -373,7 +379,6 @@
<h3>Составить договор</h3> <h3>Составить договор</h3>
<div class="flex w-full flex-wrap"> <div class="flex w-full flex-wrap">
<InnerDocAddModal v-if="showModalDealContract" :deal="deal" @close="closeModalDealContract" @created="handleNewInnerDoc" />
<button @click="openModalDealContract" class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2 rounded items-center justify-center order-last add-deal-contract-button">Составить договор</button> <button @click="openModalDealContract" class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2 rounded items-center justify-center order-last add-deal-contract-button">Составить договор</button>
</div> </div>
<div class="flex w-full flex-wrap"> <div class="flex w-full flex-wrap">
@ -381,10 +386,6 @@
v-for="innerDoc in deal.inner_docs" v-for="innerDoc in deal.inner_docs"
:key="innerDoc.id" :key="innerDoc.id"
:innerDoc="innerDoc" :innerDoc="innerDoc"
:deal-clients="{
side_one_clients: deal.side_one_clients,
side_two_clients: deal.side_two_clients
}"
@remove="removeInnerDoc" @remove="removeInnerDoc"
class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2" class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2"
/> />
@ -409,12 +410,11 @@
<script setup> <script setup>
import MyButton from "@/shared/UI/MyButton.vue"; import MyButton from "@/shared/UI/MyButton.vue";
import {ref, onMounted, watch, inject} from "vue"; import {ref, onMounted, watch, inject, toRaw, markRaw} from "vue";
import { useLaravelApi } from '@/composables/useLaravelApi'; import { useLaravelApi } from '@/composables/useLaravelApi';
import { useRouter } from "vue-router";
//import useDeal from '@/composables/useDeal'; //import useDeal from '@/composables/useDeal';
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast"; import { useToast } from "primevue/usetoast";
import { useDialog } from 'primevue/usedialog';
import ConfirmDialog from 'primevue/confirmdialog'; import ConfirmDialog from 'primevue/confirmdialog';
import Toast from "primevue/toast"; import Toast from "primevue/toast";
import InputText from "primevue/inputtext"; import InputText from "primevue/inputtext";
@ -444,11 +444,11 @@ const showError = (message) => {
const props = defineProps({ const props = defineProps({
dealId: Number, dealId: Number,
isOpen: Boolean, isOpen: Boolean
onCloseModel: Function,
onDealAdded: Function
}); });
const dialog = useDialog();
const dialogRef = inject('dialogRef'); const dialogRef = inject('dialogRef');
const newDealReqId = dialogRef.value.data?.newDealReqId; const newDealReqId = dialogRef.value.data?.newDealReqId;
@ -649,7 +649,7 @@ async function loadDealData(dealId) {
} catch (error) { } catch (error) {
console.error('Ошибка загрузки сделки:', error); console.error('Ошибка загрузки сделки:', error);
showError(error.message); showError(String(error?.message ?? 'Неизвестная ошибка'));
} }
} }
@ -692,7 +692,6 @@ function resetForm() {
const url = import.meta.env.VITE_API_URL; const url = import.meta.env.VITE_API_URL;
const urlLaravel = import.meta.env.VITE_LARAVEL_URL const urlLaravel = import.meta.env.VITE_LARAVEL_URL
const titleForm = ref("Данные для сделки");
const deal = ref({ const deal = ref({
title: '', title: '',
side_one: '', side_one: '',
@ -805,6 +804,10 @@ const validateForm = () => {
}; };
onMounted(async () => { onMounted(async () => {
console.log('[DealModal] dialogRef.data:', dialogRef.value.data)
dealId = dialogRef.value.data?.deal_id
try { try {
// Загрузка справочников // Загрузка справочников
const [objects, clients, employees, requisitions] = await Promise.all([ const [objects, clients, employees, requisitions] = await Promise.all([
@ -824,16 +827,12 @@ onMounted(async () => {
})) || []; })) || [];
requisitionsList.value = reqList; requisitionsList.value = reqList;
originalRequisitionsList.value = reqList; // Сохраняем полный список originalRequisitionsList.value = reqList;
} catch (error) { } catch (error) {
console.error('Ошибка загрузки:', error); console.error('Ошибка загрузки:', error);
showError('Не удалось загрузить данные'); showError('Не удалось загрузить данные');
} }
// if (import.meta.env.MODE === 'development') {
// loadRequisition(import.meta.env.VITE_DEV_REQUISITION_ID, 'sideOne');
// }
}) })
@ -849,7 +848,7 @@ watch(selectedRequisitionSideOneId, async (newId) => {
// Добавляем who_work только при создании сделки // Добавляем who_work только при создании сделки
if (newDealReqId.value && requisitionSideOneDetails.value.who_work) { if (newDealReqId.value && requisitionSideOneDetails.value.who_work) {
addResponsibleEmployee(requisitionSideOneDetails.value.who_work); await addResponsibleEmployee(requisitionSideOneDetails.value.who_work);
} }
} }
@ -914,6 +913,7 @@ watch(selectedObjectSideOneId, async (newId) => {
selectedObjectSideOne.value = null; // Очищаем, если newId === null selectedObjectSideOne.value = null; // Очищаем, если newId === null
} }
}); });
// Получение деталей выбранного объекта 2 стороны // Получение деталей выбранного объекта 2 стороны
watch(selectedObjectSideTwoId, async (newId) => { watch(selectedObjectSideTwoId, async (newId) => {
if (newId) { if (newId) {
@ -929,6 +929,7 @@ watch(selectedObjectSideTwoId, async (newId) => {
// Логика для управления модальным окном и выбранными сотрудниками // Логика для управления модальным окном и выбранными сотрудниками
const showModalDealManager = ref(false); const showModalDealManager = ref(false);
const selectedEmployees = ref([]); const selectedEmployees = ref([]);
const openModalDealManager = () => { const openModalDealManager = () => {
@ -948,31 +949,53 @@ const removeEmployee = (employeeId) => {
selectedEmployees.value = selectedEmployees.value.filter(e => e.id !== employeeId); selectedEmployees.value = selectedEmployees.value.filter(e => e.id !== employeeId);
}; };
const showModalDealDoc = ref(false);
const selectedDocs = ref([]); const selectedDocs = ref([]);
// Открыть/закрыть модальное окно выбора документа
const openModalDealDoc = () => { const openModalDealDoc = () => {
showModalDealDoc.value = true; console.log('[openModalDealDoc] Открываем DocAddModal через DynamicDialog')
};
dialog.open(DocAddModal, {
props: {
header: 'Добавление документа',
style: { width: '50vw' },
modal: true
},
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 closeModalDealDoc = () => {
showModalDealDoc.value = false;
};
const addDoc = (doc) => { 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) { if (doc && doc.id && doc.name && doc.file) {
// Проверяем, что документ еще не добавлен // Проверяем, что документ еще не добавлен
const isDuplicate = selectedDocs.value.some((d) => d.id === doc.id); const isDuplicate = selectedDocs.value.some((d) => d.id === doc.id);
if (!isDuplicate) { if (!isDuplicate) {
selectedDocs.value.push(doc); // Добавляем документ в список selectedDocs.value.push(doc); // Добавляем документ в список
console.log('[DealModal] Документ добавлен в selectedDocs');
} }
} else { } else {
console.error('Документ не содержит обязательных полей:', doc); console.error('Документ не содержит обязательных полей:', doc);
} }
closeModalDealDoc(); // Закрываем модальное окно //closeModalDealDoc(); // Закрываем модальное окно
}; };
// Удалить документ // Удалить документ
@ -1015,6 +1038,98 @@ const handleCloseDealObject = (side) => {
}; };
const openModalDealContract = async () => {
if (!deal.value.id) {
console.log('[DealModal] Сделка не сохранена, сохраняем перед открытием модального окна договора.');
try {
let currentDeal = deal.value
const savedDeal = await saveDeal();
if (savedDeal?.id) {
if (!savedDeal?.id) return
deal.value = savedDeal
currentDeal = savedDeal
const plainDeal = JSON.parse(JSON.stringify(toRaw(currentDeal)))
console.log('[DealModal] Сделка успешно сохранена, ID:', deal.value);
console.log('InnerDocAddModal:', InnerDocAddModal);
dialog.open(InnerDocAddModal, {
props: {
header: 'Составить договор',
modal: true,
},
data: {
deal: plainDeal,
},
onClose: (options) => {
console.log('InnerDocAddModal закрыт, options:', options)
if (options?.data) {
handleNewInnerDoc(options.data);
}
},
});
} else {
console.error('[DealModal] Сделка не была сохранена (id отсутствует после saveDeal)');
}
} catch (error) {
console.error('[DealModal] Ошибка при сохранении сделки перед составлением договора:', error);
}
} else {
console.log('[DealModal] Сделка уже сохранена (ID:', deal.value.id, '), открываем модальное окно.');
dialog.open(InnerDocAddModal, {
props: {
header: 'Составить договор',
modal: true,
},
data: {
deal: deal.value,
},
onClose: (options) => {
console.log('InnerDocAddModal закрыт, options:', 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 saveDeal = async () => { const saveDeal = async () => {
@ -1026,7 +1141,7 @@ const saveDeal = async () => {
detail: 'Заполните все обязательные поля', detail: 'Заполните все обязательные поля',
life: 3000, life: 3000,
}); });
return; return null;
} }
const { fetchData } = useLaravelApi(); const { fetchData } = useLaravelApi();
@ -1060,14 +1175,16 @@ const saveDeal = async () => {
toast.add({ toast.add({
severity: 'success', severity: 'success',
summary: 'Успех', summary: 'Успех',
detail: isEditing.value ? 'Сделка успешно обновлена' : 'Сделка успешно создана', detail: String(response.data.message || (isEditing.value ? 'Сделка успешно обновлена' : 'Сделка успешно создана')),
life: 3000, life: 3000,
}); });
// Вызываем onDealAdded при успешном сохранении // Обновляем локальное состояние сделки
if (props.onDealAdded) { // if (response.data.deal) {
props.onDealAdded(); // deal.value = response.data.deal;
} // }
console.log('cat', response.data)
return response.data.deal;
} else { } else {
new Error(response.data.message || 'Ошибка при сохранении сделки'); new Error(response.data.message || 'Ошибка при сохранении сделки');
@ -1076,53 +1193,10 @@ const saveDeal = async () => {
toast.add({ toast.add({
severity: 'error', severity: 'error',
summary: 'Ошибка', summary: 'Ошибка',
detail: error.message, detail: String(error?.message ?? 'Неизвестная ошибка'),
life: 3000, life: 3000,
}); });
} return null;
};
const showModalDealContract = ref(false);
// Открыть/закрыть модальное окно составления договора
const openModalDealContract = () => {
showModalDealContract.value = true;
};
const closeModalDealContract = () => {
showModalDealContract.value = false;
};
// Обработка нового договора
const handleNewInnerDoc = (newInnerDoc) => {
console.log('Новый innerDoc получен:', newInnerDoc);
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) => {
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;
} }
}; };
@ -1135,14 +1209,6 @@ watch(() => props.isOpen, (newVal) => {
isOpen.value = newVal; isOpen.value = newVal;
}); });
// Обработчик закрытия
// function closeModel() {
// isOpen.value = false;
// if (props.onCloseModel) {
// props.onCloseModel(); // Это уже есть в вашем коде
// }
// }
const emit = defineEmits(['close', 'added']); const emit = defineEmits(['close', 'added']);
const closeModel = () => { const closeModel = () => {
emit('close'); emit('close');
@ -1179,17 +1245,6 @@ async function addResponsibleEmployee(userId) {
]; ];
} }
//Старые
//const fieldsStore = useFieldsStore();
const confirm = useConfirm();
const router = useRouter();
defineExpose({ closeModel }); defineExpose({ closeModel });
</script> </script>
@ -1248,13 +1303,14 @@ defineExpose({ closeModel });
/* Убираем стрелочки вверх/вниз для Firefox */ /* Убираем стрелочки вверх/вниз для Firefox */
input[type="number"] { input[type="number"] {
appearance: textfield;
-moz-appearance: textfield; -moz-appearance: textfield;
-webkit-appearance: textfield;
} }
input[type="radio"] { input[type="radio"] {
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none;
display: inline-block; display: inline-block;
width: 21px; width: 21px;
height: 21px; height: 21px;

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="modal dealAddDoc"> <div class="dealAddDoc">
<div class="modal-content"> <div class="modal-content">
<span class="close" @click="closeModal">&times;</span> <!-- <span class="close" @click="closeModal">&times;</span>-->
<h3 class="mb-4">Добавление документа</h3> <!-- <h3 class="mb-4">Добавление документа</h3>-->
<div class="mb-4"> <div class="mb-4">
<label for="document-name" class="block text-sm font-medium text-gray-700">Название документа</label> <label for="document-name" class="block text-sm font-medium text-gray-700">Название документа</label>
@ -35,19 +35,22 @@
</template> </template>
<script setup> <script setup>
import {ref} from 'vue'; import {inject, ref} from 'vue';
//import { useLaravelApi } from '@/composables/useLaravelApi'; //import { useLaravelApi } from '@/composables/useLaravelApi';
import { dealsApi } from '@/shared/api/deals'; import { dealsApi } from '@/shared/api/deals';
import InputText from "primevue/inputtext"; import InputText from "primevue/inputtext";
const props = defineProps({ // const props = defineProps({
deal: { // deal: {
type: Object, // type: Object,
required: true // required: true
}, // },
}); // });
const emit = defineEmits(['close', 'select']); const dialogRef = inject('dialogRef');
const props = dialogRef.value.data;
//const emit = defineEmits(['close', 'select']);
// Реактивные переменные для названия и файла // Реактивные переменные для названия и файла
const documentName = ref(''); const documentName = ref('');
@ -59,7 +62,7 @@ const handleFileChange = (event) => {
}; };
const closeModal = () => { const closeModal = () => {
emit('close'); //emit('close');
}; };
// Сохранение документа // Сохранение документа
@ -83,8 +86,9 @@ const saveDealDoc = async () => {
try { try {
const response = await dealsApi.uploadDocument(formData); const response = await dealsApi.uploadDocument(formData);
emit('select', response.data.data); //emit('select', response.data.data);
closeModal(); dialogRef.value.close(response.data.data)
//closeModal();
} catch (error) { } catch (error) {
console.error('Ошибка при загрузке документа:', error); console.error('Ошибка при загрузке документа:', error);
alert('Произошла ошибка при загрузке документа'); alert('Произошла ошибка при загрузке документа');

View File

@ -13,33 +13,32 @@
<!-- Название документа --> <!-- Название документа -->
<div class="ml-4 flex flex-col justify-between flex-grow"> <div class="ml-4 flex flex-col justify-between flex-grow">
<div class="font-bold text-gray-800">{{ doc.name }}</div> <div class="font-bold text-gray-800">{{ doc.name || 'N/A' }}</div>
<div class="flex"> <div class="flex">
<img src="@/shared/assets/icons/doc.svg" alt="Договор" class="w-5 h-5"> <img src="@/shared/assets/icons/doc.svg" alt="Договор" class="w-5 h-5">
<a :href="fileUrl" target="_blank" class="ml-2 underline document-file">{{ getFileName(doc.file) }}</a> <a :href="fileUrl" target="_blank" class="ml-2 underline document-file">{{ getFileName(doc.file) || 'N/A' }}</a>
</div> </div>
</div> </div>
<button @click="confirmDelete" class="ml-auto mt-2 px-4 py-2 rounded"> <!-- <button @click="confirmDelete" class="ml-auto mt-2 px-4 py-2 rounded">-->
<!-- <img src="@/shared/assets/icons/trash.svg" alt="Удалить" class="w-6 h-6">--> <!--&lt;!&ndash; <img src="@/shared/assets/icons/trash.svg" alt="Удалить" class="w-6 h-6">&ndash;&gt;-->
<span class="simple-button delete" title="Удалить"><i class="jw-action-icon-delete"></i></span> <!-- <span class="simple-button delete" title="Удалить"><i class="jw-action-icon-delete"></i></span>-->
</button> <!-- </button>-->
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { useConfirm } from 'primevue/useconfirm'; // import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast'; // import { useToast } from 'primevue/usetoast';
import { computed } from "vue"; import { computed } from "vue";
import defaultIconUrl from '@/shared/assets/images/deal/deal-doc-icon.png'; import defaultIconUrl from '@/shared/assets/images/deal/deal-doc-icon.png';
const props = defineProps({ const props = defineProps({
doc: { doc: {
type: Object, type: Object,
required: true, required: false,
validator: (doc) => doc.id && doc.name && doc.file validator: (doc) => doc.id && doc.name && doc.file
}, },
urlLaravel: { urlLaravel: {
@ -53,67 +52,64 @@ const fileUrl = computed(() => {
return `${props.urlLaravel}/storage/${props.doc.file}`; return `${props.urlLaravel}/storage/${props.doc.file}`;
}); });
// Проверка, является ли файл изображением // // Проверка, является ли файл изображением
const isImage = computed(() => { const isImage = computed(() => {
if (!props.doc.file) return false; if (!props.doc.file) return false;
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp']; const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
return imageExtensions.includes(fileExtension.value); return imageExtensions.includes(fileExtension.value);
}); });
// // Расширение файла
// Расширение файла
const fileExtension = computed(() => { const fileExtension = computed(() => {
if (!props.doc.file) return false; if (!props.doc.file) return false;
return props.doc.file.split('.').pop().toLowerCase(); return props.doc.file.split('.').pop().toLowerCase();
}); });
const getFileName = (fullPath) => { const getFileName = (fullPath) => {
return fullPath.split('/').pop(); return fullPath.split('/').pop();
} }
//
//
const emit = defineEmits(['remove']); // const emit = defineEmits(['remove']);
//
const confirm = useConfirm(); // const confirm = useConfirm();
const toast = useToast(); // const toast = useToast();
//
const confirmDelete = (event) => { // const confirmDelete = (event) => {
confirm.require({ // confirm.require({
target: event.currentTarget, // target: event.currentTarget,
message: `Удалить документ "${props.doc.name}"?`, // message: `Удалить документ "${props.doc.name}"?`,
header: 'Подтверждение удаления', // header: 'Подтверждение удаления',
acceptLabel: 'Удалить', // acceptLabel: 'Удалить',
rejectLabel: 'Отмена', // rejectLabel: 'Отмена',
acceptClass: 'p-button-danger', // acceptClass: 'p-button-danger',
accept: async () => { // accept: async () => {
try { // try {
emit('remove', props.doc.id); // emit('remove', props.doc.id);
toast.add({ // toast.add({
severity: 'success', // severity: 'success',
summary: 'Успешно', // summary: 'Успешно',
detail: 'Документ удален', // detail: 'Документ удален',
life: 3000 // life: 3000
}); // });
} catch (error) { // } catch (error) {
toast.add({ // toast.add({
severity: 'error', // severity: 'error',
summary: 'Ошибка', // summary: 'Ошибка',
detail: 'Не удалось удалить документ', // detail: 'Не удалось удалить документ',
life: 3000 // life: 3000
}); // });
} // }
}, // },
reject: () => { // reject: () => {
toast.add({ // toast.add({
severity: 'info', // severity: 'info',
summary: 'Отменено', // summary: 'Отменено',
detail: 'Удаление отменено', // detail: 'Удаление отменено',
life: 2000 // life: 2000
}); // });
} // }
}); // });
}; // };
</script> </script>
<style scoped> <style scoped>

View File

@ -1,59 +1,54 @@
<template> <template>
<div class="modal dealAddContract"> <div v-if="deal && deal.id" class="dealAddContract">
<div class="modal-content"> <div class="modal-content">
<span class="close" @click="closeModal">&times;</span> <h3>Составить договор</h3>
<h3>Составить договор</h3>
<div class="mb-4 mt-4 add-contract-title">
<label class="mb-2">Бланк документа:</label>
<Select
v-model="selectedInnerBlankId"
:options="innerBlanksList"
optionLabel="name"
optionValue="id"
class="w-full"
:key="innerBlanksList.length"
>
<!-- <template #dropdownicon>-->
<!-- <DropdownIcon />-->
<!-- </template>-->
</Select>
</div>
<div class="mb-4 add-contract-title">
<label class="mb-2">Комментарий:</label>
<Textarea
v-model="commentDealInnerBlank"
placeholder="Введите комментарий"
class="w-full"
rows="8"
cols="30"
style="height: auto"
/>
</div>
<button @click="saveDealInnerDoc" class="mt-4 p-2 rounded w-full add-deal-contract-button">
Составить договор
</button>
<div class="mb-4 mt-4 add-contract-title">
<label class="mb-2">Бланк документа:</label>
<Dropdown
v-model="selectedInnerBlankId"
:options="innerBlanksList"
optionLabel="name"
optionValue="id"
class="w-full"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Dropdown>
</div> </div>
<div class="mb-4 add-contract-title">
<label class="mb-2">Комментарий:</label>
<Textarea
v-model="commentDealInnerBlank"
placeholder="Введите комментарий"
class="w-full"
rows="8"
cols="30"
style="height: auto"
/>
</div>
<button @click="saveDealInnerDoc" class="mt-4 p-2 rounded w-full add-deal-contract-button">
Составить договор
</button>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import {defineEmits, onMounted, ref} from 'vue'; import {inject, onMounted, ref} from 'vue';
import { useLaravelApi } from '@/composables/useLaravelApi'; import { useLaravelApi } from '@/composables/useLaravelApi';
import DropdownIcon from "@/shared/assets/icons/dropdown-icon.vue"; //import DropdownIcon from "@/shared/assets/icons/dropdown-icon.vue";
import Textarea from "primevue/textarea"; import Textarea from "primevue/textarea";
import Select from 'primevue/select';
const props = defineProps({ const dialogRef = inject('dialogRef');
deal: { const deal = dialogRef.value?.data?.deal || {}
type: Object,
required: true
},
});
const emit = defineEmits(['close', 'select']);
// Состояния формы // Состояния формы
const selectedInnerBlankId = ref(null); const selectedInnerBlankId = ref(null);
@ -65,23 +60,30 @@ const validationErrors = ref({});
const { data: innerBlanksData, error: innerBlanksError, fetchData: fetchInnerBlanksData } = useLaravelApi(); const { data: innerBlanksData, error: innerBlanksError, fetchData: fetchInnerBlanksData } = useLaravelApi();
const innerBlanksList = ref([]); const innerBlanksList = ref([]);
console.log('[InnerDocAddModal] refs и useLaravelApi созданы');
onMounted(async () => { onMounted(async () => {
console.log(props.deal); // Загружаем список бланков
// Загружаем список бланков try {
await fetchInnerBlanksData('/inner-blanks'); await fetchInnerBlanksData('/inner-blanks');
if (innerBlanksData.value && innerBlanksData.value.success) { if (innerBlanksData.value && innerBlanksData.value.success) {
innerBlanksList.value = innerBlanksData.value.data; // Сохраняем данные innerBlanksList.value = innerBlanksData.value.data;
} else { console.log('[InnerDocAddModal] Inner blanks list установлен:', innerBlanksList.value);
console.error('Ошибка при загрузке списка бланков:', innerBlanksError.value); } else {
console.error('[InnerDocAddModal] Ошибка при загрузке списка бланков:', innerBlanksError.value);
}
} catch (err) {
console.error('[InnerDocAddModal] Исключение при загрузке бланков:', err);
} }
}); });
const closeModal = () => {
emit('close');
};
const getTemplateData = () => { const getTemplateData = () => {
if (!deal) {
console.warn('deal is undefined in getTemplateData');
return { sobstv: [], clients: [], object_id: null };
}
const sidesMap = { const sidesMap = {
'Продавец': 'sobstv', 'Продавец': 'sobstv',
'Арендодатель': 'sobstv', 'Арендодатель': 'sobstv',
@ -98,41 +100,41 @@ const getTemplateData = () => {
}; };
// Обработка стороны 1 // Обработка стороны 1
if (props.deal.side_one) { if (deal.side_one) {
const roleType = sidesMap[props.deal.side_one]; const roleType = sidesMap[deal.side_one];
if (roleType) { if (roleType) {
// Основной клиент // Основной клиент
if (props.deal.side_one_client_main) { if (deal.side_one_client_main) {
result[roleType].push(props.deal.side_one_client_main.id); result[roleType].push(deal.side_one_client_main.id);
} }
// Дополнительные клиенты // Дополнительные клиенты
if (props.deal.side_one_clients?.length) { if (deal.side_one_clients?.length) {
result[roleType].push(...props.deal.side_one_clients.map(c => c.id)); result[roleType].push(...deal.side_one_clients.map(c => c.id));
} }
} }
} }
// Обработка стороны 2 // Обработка стороны 2
if (props.deal.side_two) { if (deal.side_two) {
const roleType = sidesMap[props.deal.side_two]; const roleType = sidesMap[deal.side_two];
if (roleType) { if (roleType) {
// Основной клиент // Основной клиент
if (props.deal.side_two_client_main) { if (deal.side_two_client_main) {
result[roleType].push(props.deal.side_two_client_main.id); result[roleType].push(deal.side_two_client_main.id);
} }
// Дополнительные клиенты // Дополнительные клиенты
if (props.deal.side_two_clients?.length) { if (deal.side_two_clients?.length) {
result[roleType].push(...props.deal.side_two_clients.map(c => c.id)); result[roleType].push(...deal.side_two_clients.map(c => c.id));
} }
//result[roleType].push(...props.deal.side_two_clients.map(c => c.id)); //result[roleType].push(...deal.side_two_clients.map(c => c.id));
} }
} }
// Объект может быть только один либо из side_one, либо из side_two // Объект может быть только один либо из side_one, либо из side_two
if (props.deal.side_one_object_id) { if (deal.side_one_object_id) {
result.object_id = props.deal.side_one_object_id; result.object_id = deal.side_one_object_id;
} else if (props.deal.side_two_object_id) { } else if (deal.side_two_object_id) {
result.object_id = props.deal.side_two_object_id; result.object_id = deal.side_two_object_id;
} }
return result; return result;
@ -145,9 +147,7 @@ const saveDealInnerDoc = async () => {
try { try {
const { fetchData } = useLaravelApi(); const { fetchData } = useLaravelApi();
console.log('Props перед payload:', props); const dealId = deal.id ? Number(deal.id) : null;
const dealId = props.deal.id ? Number(props.deal.id) : null;
const templateData = getTemplateData(); const templateData = getTemplateData();
@ -155,32 +155,32 @@ const saveDealInnerDoc = async () => {
template_data: { template_data: {
...templateData, ...templateData,
blank_id: selectedInnerBlankId.value, blank_id: selectedInnerBlankId.value,
side_one_requisition_id: props.deal.side_one_requisition_id, side_one_requisition_id: deal.side_one_requisition_id,
}, },
deal_id: dealId, deal_id: dealId,
comment: commentDealInnerBlank.value || '', comment: commentDealInnerBlank.value || '',
side_one: [ side_one: [
// Основной клиент стороны 1 // Основной клиент стороны 1
(props.deal.side_one_client_main ? { (deal.side_one_client_main ? {
client_id: props.deal.side_one_client_main.id, client_id: deal.side_one_client_main.id,
role: props.deal.side_one role: deal.side_one
} : null), } : null),
// Дополнительные клиенты стороны 1 // Дополнительные клиенты стороны 1
...(props.deal.side_one_clients?.map(client => ({ ...(deal.side_one_clients?.map(client => ({
client_id: client.id, client_id: client.id,
role: props.deal.side_one role: deal.side_one
})) || []) })) || [])
].filter(Boolean), // Убираем null, если основного клиента нет ].filter(Boolean), // Убираем null, если основного клиента нет
side_two: [ side_two: [
// Основной клиент стороны 2 // Основной клиент стороны 2
(props.deal.side_two_client_main ? { (deal.side_two_client_main ? {
client_id: props.deal.side_two_client_main.id, client_id: deal.side_two_client_main.id,
role: props.deal.side_two role: deal.side_two
} : null), } : null),
// Дополнительные клиенты стороны 2 // Дополнительные клиенты стороны 2
...(props.deal.side_two_clients?.map(client => ({ ...(deal.side_two_clients?.map(client => ({
client_id: client.id, client_id: client.id,
role: props.deal.side_two role: deal.side_two
})) || []) })) || [])
].filter(Boolean), // Убираем null, если основного клиента нет ].filter(Boolean), // Убираем null, если основного клиента нет
}; };
@ -193,8 +193,12 @@ const saveDealInnerDoc = async () => {
}); });
if (response.data.success) { if (response.data.success) {
emit('created', response.data.data); console.log('[InnerDocAddModal] Doc сохранен:', response.data.data);
closeModal();
console.log('dialogRef.value:', dialogRef.value);
dialogRef.value.close(response.data.data);
//closeDialog(response.data.data);
} else { } else {
// Обработка ошибок валидации // Обработка ошибок валидации
if (response.data.errors) { if (response.data.errors) {
@ -215,31 +219,31 @@ const saveDealInnerDoc = async () => {
</script> </script>
<style> <!--<style>-->
.modal { <!--.modal {-->
display: flex; <!-- display: flex;-->
justify-content: center; <!-- justify-content: center;-->
align-items: center; <!-- align-items: center;-->
position: fixed; <!-- position: fixed;-->
z-index: 1; <!-- z-index: 1;-->
left: 0; <!-- left: 0;-->
top: 0; <!-- top: 0;-->
width: 100%; <!-- width: 100%;-->
height: 100%; <!-- height: 100%;-->
background-color: rgba(0, 0, 0, 0.5); <!-- background-color: rgba(0, 0, 0, 0.5);-->
} <!--}-->
.modal-content { <!--.modal-content {-->
background-color: #fff; <!-- background-color: #fff;-->
padding: 20px; <!-- padding: 20px;-->
border-radius: 10px; <!-- border-radius: 10px;-->
width: 80%; <!-- width: 80%;-->
max-width: 500px; <!-- max-width: 500px;-->
} <!--}-->
.close { <!--.close {-->
position: absolute; <!-- position: absolute;-->
top: 10px; <!-- top: 10px;-->
right: 20px; <!-- right: 20px;-->
font-size: 24px; <!-- font-size: 24px;-->
cursor: pointer; <!-- cursor: pointer;-->
} <!--}-->
</style> <!--</style>-->

View File

@ -10,7 +10,7 @@
<!-- Сторона 1 --> <!-- Сторона 1 -->
<div class="clients-inner-doc mt-4"> <div class="clients-inner-doc mt-4">
<h4 class="inner-doc-title">Сторона 1:</h4> <h4 class="inner-doc-title">Сторона 1:</h4>
<div v-for="side in innerDoc.deals[0]?.side_one || []" :key="side.client_id" class="mt-2 flex items-baseline"> <div v-for="side in currentDeal[0]?.side_one || []" :key="side.client_id" class="mt-2 flex items-baseline">
<span style="margin-right: 10px;" class="font-medium min-w-[80px]">{{ side.role }}:</span> <span style="margin-right: 10px;" class="font-medium min-w-[80px]">{{ side.role }}:</span>
<div class="ml-2 underline"> <div class="ml-2 underline">
<span>{{ getClientName(side) }} ({{ side.phone }})</span> <span>{{ getClientName(side) }} ({{ side.phone }})</span>
@ -21,7 +21,7 @@
<!-- Сторона 2 --> <!-- Сторона 2 -->
<div class="clients-inner-doc mt-4"> <div class="clients-inner-doc mt-4">
<h4 class="inner-doc-title">Сторона 2:</h4> <h4 class="inner-doc-title">Сторона 2:</h4>
<div v-for="side in innerDoc.deals[0]?.side_two || []" :key="side.client_id" class="mt-2 flex items-baseline"> <div v-for="side in currentDeal[0]?.side_two || []" :key="side.client_id" class="mt-2 flex items-baseline">
<span style="margin-right: 10px;" class="font-medium min-w-[80px]">{{ side.role }}:</span> <span style="margin-right: 10px;" class="font-medium min-w-[80px]">{{ side.role }}:</span>
<div class="ml-2 underline"> <div class="ml-2 underline">
<span>{{ getClientName(side) }} ({{ side.phone }})</span> <span>{{ getClientName(side) }} ({{ side.phone }})</span>
@ -74,10 +74,12 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed } from 'vue';
import { useConfirm } from 'primevue/useconfirm'; import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast'; import { useToast } from 'primevue/usetoast';
console.log('Запуск InnerDocCard');
const props = defineProps({ const props = defineProps({
innerDoc: { innerDoc: {
type: Object, type: Object,
@ -94,6 +96,10 @@ const emit = defineEmits(['remove']);
const confirm = useConfirm(); const confirm = useConfirm();
const toast = useToast(); const toast = useToast();
const currentDeal = computed(() => {
return props.innerDoc.deals?.[0];
});
// Получаем pivot данные для текущего документа // Получаем pivot данные для текущего документа
const currentPivot = computed(() => { const currentPivot = computed(() => {
return props.innerDoc.deals?.[0]?.pivot; return props.innerDoc.deals?.[0]?.pivot;
@ -105,7 +111,7 @@ const getClientName = (client) => {
}; };
const handleEditInnerDoc = () => { const handleEditInnerDoc = () => {
emit('edit', props.innerDoc); // Немедленно передаем документ на редактирование emit('edit', props.innerDoc);
}; };
const handleRemoveDealDoc = (event) => { const handleRemoveDealDoc = (event) => {

View File

@ -1,8 +1,7 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { import {
fetchDeal, fetchDeal,
createDeal, createDeal
uploadDealDocument
} from '@/shared/api/deals'; } from '@/shared/api/deals';
export default function useDeal() { export default function useDeal() {

View File

@ -42,14 +42,14 @@ const fetchData = (endpoint, options = {}) => {
// Извлекаем путь из endpoint // Извлекаем путь из endpoint
const path = endpoint.replace(/^\//, ''); const path = endpoint.replace(/^\//, '');
//
console.log('[laravelApiService] fetchData вызван с endpoint:', endpoint); // console.log('[laravelApiService] fetchData вызван с endpoint:', endpoint);
console.log('[laravelApiService] Тип data:', data && data.constructor.name); // console.log('[laravelApiService] Тип data:', data && data.constructor.name);
console.log('[laravelApiService] data instanceof FormData:', data instanceof FormData); // console.log('[laravelApiService] data instanceof FormData:', data instanceof FormData);
const isFormData = data instanceof FormData; const isFormData = data instanceof FormData;
if (isFormData) { if (isFormData) {
console.log('[laravelApiService] Используем directAxiosInstance для FormData'); //console.log('[laravelApiService] Используем directAxiosInstance для FormData');
const laravelApiUrl = 'http://llar.joyworkdev.ru/api'; const laravelApiUrl = 'http://llar.joyworkdev.ru/api';
@ -64,7 +64,7 @@ const fetchData = (endpoint, options = {}) => {
finalHeaders['X-Session-ID'] = sessionId; finalHeaders['X-Session-ID'] = sessionId;
} }
logMessage("Sending FormData directly to Laravel API: " + laravelApiUrl + endpoint); //logMessage("Sending FormData directly to Laravel API: " + laravelApiUrl + endpoint);
return directAxiosInstance({ return directAxiosInstance({
url: endpoint, url: endpoint,
method, method,
@ -74,8 +74,6 @@ const fetchData = (endpoint, options = {}) => {
}); });
} }
console.log('[laravelApiService] Используем laravel_api_instance через deal/index.php');
const finalHeaders = { ...headers }; const finalHeaders = { ...headers };
if (sessionId) { if (sessionId) {
finalHeaders['X-Session-ID'] = sessionId; finalHeaders['X-Session-ID'] = sessionId;
@ -94,10 +92,6 @@ const fetchData = (endpoint, options = {}) => {
}); });
}; };
function logMessage(message) {
console.log('[laravelApiService]', message);
}
export default { export default {
fetchData, fetchData,
}; };

View File

@ -3,7 +3,6 @@
fill="#4CAF50" fill="#4CAF50"
height="24px" height="24px"
width="24px" width="24px"
version="1.1"
id="Layer_1" id="Layer_1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink"

View File

@ -4,12 +4,11 @@
width="20px" width="20px"
height="20px" height="20px"
viewBox="0 0 20 20" viewBox="0 0 20 20"
version="1.1"
> >
<g id="layer1"> <g id="layer1">
<path <path
d="M 4 4.2910156 L 3.2910156 5 L 3.6464844 5.3535156 L 10 11.708984 L 16.353516 5.3535156 L 16.708984 5 L 16 4.2910156 L 15.646484 4.6464844 L 10 10.291016 L 4.3535156 4.6464844 L 4 4.2910156 z M 4 8.2910156 L 3.2910156 9 L 3.6464844 9.3535156 L 10 15.708984 L 16.353516 9.3535156 L 16.708984 9 L 16 8.2910156 L 15.646484 8.6464844 L 10 14.291016 L 4.3535156 8.6464844 L 4 8.2910156 z " d="M 4 4.2910156 L 3.2910156 5 L 3.6464844 5.3535156 L 10 11.708984 L 16.353516 5.3535156 L 16.708984 5 L 16 4.2910156 L 15.646484 4.6464844 L 10 10.291016 L 4.3535156 4.6464844 L 4 4.2910156 z M 4 8.2910156 L 3.2910156 9 L 3.6464844 9.3535156 L 10 15.708984 L 16.353516 9.3535156 L 16.708984 9 L 16 8.2910156 L 15.646484 8.6464844 L 10 14.291016 L 4.3535156 8.6464844 L 4 8.2910156 z "
style="fill: #757575; fill-opacity: 1; stroke: none; stroke-width: 0px" style="fill: #757575; fill-opacity: 1; stroke: none; stroke-width: 0"
/> />
</g> </g>
</svg> </svg>