actual prod
This commit is contained in:
parent
11cdbeb6fd
commit
b67a39532a
@ -179,6 +179,12 @@ if($request == 'add_edit') {
|
||||
$params['complex_room_id'] = $data->complex_room_id;
|
||||
}
|
||||
|
||||
// Заявку нельзя привязать к найденному листингу парсера — листинг read-only, сначала копирование объекта в свои.
|
||||
// Offset-free: признак листинга — явный флаг is_external из JSON-тела (el.id коллизит с objects.id).
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/listing_guard.php';
|
||||
$objIsExternal = (isset($data->is_external) && $data->is_external) || (isset($data->src) && $data->src === 'ext');
|
||||
reject_if_external_listing($params['object_id'], $objIsExternal);
|
||||
|
||||
$req = new Requisitions($pdo);
|
||||
$result = $req->add_edit($params);
|
||||
if(isset($result['error'])){
|
||||
@ -226,6 +232,11 @@ if($request == 'update_req'){
|
||||
if(!empty($objects)){
|
||||
$object = $objects[0];
|
||||
}
|
||||
// Привязать к заявке найденный листинг парсера нельзя — листинг read-only.
|
||||
// Offset-free: признак листинга — явный флаг is_external из JSON-тела (el.id коллизит с objects.id).
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/listing_guard.php';
|
||||
$objIsExternal = (isset($data->is_external) && $data->is_external) || (isset($data->src) && $data->src === 'ext');
|
||||
reject_if_external_listing($object, $objIsExternal);
|
||||
$req_id_arr = explode(',',$req_id);
|
||||
foreach($req_id_arr as $req){
|
||||
$object_id_req = 0;
|
||||
@ -2297,6 +2308,231 @@ if($request == 'get_view_page'){
|
||||
|
||||
}
|
||||
|
||||
// copy_preview — дефолты для модалки копирования (funnel/who_work/base_type_id/warnings).
|
||||
if($request == 'copy_preview') {
|
||||
$srcId = isset($data->req_id) ? (int)$data->req_id : 0;
|
||||
if ($srcId <= 0) {
|
||||
echo json_encode(array('success' => false, 'error' => 'req_id обязателен'));
|
||||
exit();
|
||||
}
|
||||
|
||||
// ВАЖНО: $pdo здесь — MySQL, не Sphinx.
|
||||
$qSrc = $pdo->query("SELECT * FROM requisitions WHERE id = {$srcId} LIMIT 1");
|
||||
$src = $pdo->fetch_assoc($qSrc);
|
||||
if (!$src) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Заявка не найдена'));
|
||||
exit();
|
||||
}
|
||||
if ((int)$src['deleted'] === 1) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Заявка удалена'));
|
||||
exit();
|
||||
}
|
||||
|
||||
// set_user — для checkMenuPermissions внутри user_can_edit_req.
|
||||
$reqCheck = new Requisitions($pdo);
|
||||
$userForCheck = new User();
|
||||
$userForCheck->checkPermissions();
|
||||
$reqCheck->set_user($userForCheck);
|
||||
|
||||
if (!$reqCheck->user_can_edit_req($src, $_SESSION['id'])) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Нет прав на копирование этой заявки'));
|
||||
exit();
|
||||
}
|
||||
|
||||
$srcTypeId = (int)$src['type_id'];
|
||||
$qType = $pdo->query("SELECT id, heir FROM requisitions_type WHERE id = {$srcTypeId} LIMIT 1");
|
||||
$typeRow = $pdo->fetch_assoc($qType);
|
||||
$baseTypeId = $typeRow ? requisition_base_type_id($typeRow) : 0;
|
||||
|
||||
// warning contract_without_object — object_id не копируется, но у исходника есть договор продажи.
|
||||
$warnings = array();
|
||||
if (!requisition_should_copy_object($baseTypeId) && (int)$src['object_id'] > 0) {
|
||||
$qContract = $pdo->query(
|
||||
"SELECT id FROM contracts_req WHERE req_id = {$srcId} AND type = 2 LIMIT 1"
|
||||
);
|
||||
$hasSaleContract = $pdo->fetch_assoc($qContract);
|
||||
if ($hasSaleContract) {
|
||||
$warnings[] = 'contract_without_object';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(array(
|
||||
'success' => true,
|
||||
'default_funnel_id' => (int)$src['funnel_id'],
|
||||
'default_who_work_id' => (int)$src['who_work'],
|
||||
'base_type_id' => $baseTypeId,
|
||||
'warnings' => $warnings,
|
||||
));
|
||||
exit();
|
||||
}
|
||||
|
||||
// copy — валидация payload/прав/воронки/ответственного → Requisitions::copy().
|
||||
if($request == 'copy') {
|
||||
$srcId = isset($data->req_id) ? (int)$data->req_id : 0;
|
||||
$funnelId = isset($data->funnel_id) ? (int)$data->funnel_id : 0;
|
||||
$whoWorkId = isset($data->who_work_id) ? (int)$data->who_work_id : 0;
|
||||
$currentId = (int)$_SESSION['id'];
|
||||
$userAgency = (int)$_SESSION['agency_id'];
|
||||
|
||||
// funnel_id = 0 — дефолтная воронка (funnel_default).
|
||||
if ($srcId <= 0 || $funnelId < 0 || $whoWorkId <= 0) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'req_id, funnel_id, who_work_id обязательны',
|
||||
'code' => 'bad_payload',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
|
||||
$qSrc = $pdo->query("SELECT * FROM requisitions WHERE id = {$srcId} LIMIT 1");
|
||||
$src = $pdo->fetch_assoc($qSrc);
|
||||
if (!$src) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Заявка не найдена',
|
||||
'code' => 'src_not_found',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
if ((int)$src['deleted'] === 1) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Заявка удалена',
|
||||
'code' => 'src_not_found',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
|
||||
$reqCheck = new Requisitions($pdo);
|
||||
$userForCheck = new User();
|
||||
$userForCheck->checkPermissions();
|
||||
$reqCheck->set_user($userForCheck);
|
||||
|
||||
// Право на копирование == право на редактирование исходника.
|
||||
if (!$reqCheck->user_can_edit_req($src, $currentId)) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Нет прав на копирование этой заявки',
|
||||
'code' => 'no_edit_right',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
|
||||
// Доступ к воронке. funnel_id=0 — проверяем funnel_default.is_req.
|
||||
if ($funnelId === 0) {
|
||||
$qFunnel = $pdo->query(
|
||||
"SELECT `is_req` FROM `funnel_default`
|
||||
WHERE `agency_id` = {$userAgency}
|
||||
LIMIT 1"
|
||||
);
|
||||
$funnelDef = $pdo->fetch_assoc($qFunnel);
|
||||
if ($funnelDef && (int)$funnelDef['is_req'] === 0) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Воронка недоступна',
|
||||
'code' => 'funnel_forbidden',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
// Проверка доступности воронки — через канонический Funnel::get_funnels(): там корректно учитывается
|
||||
// is_no_see + раскрытие 'all_X' (подчинённые), users_no_see — это whitelist при is_no_see=1, имя обманчиво.
|
||||
$funnelCheckClass = new Funnel($pdo);
|
||||
$funnelCheckClass->set_agency_id($userAgency);
|
||||
$funnelCheckClass->set_user_id($currentId);
|
||||
$availableFunnels = $funnelCheckClass->get_funnels('requisitions');
|
||||
$isFunnelAvailable = false;
|
||||
if (is_array($availableFunnels)) {
|
||||
foreach ($availableFunnels as $af) {
|
||||
if ((int)$af['id'] === $funnelId) { $isFunnelAvailable = true; break; }
|
||||
}
|
||||
}
|
||||
if (!$isFunnelAvailable) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Воронка недоступна',
|
||||
'code' => 'funnel_forbidden',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Ответственный. Себя всегда можно назначить.
|
||||
if ($whoWorkId !== $currentId) {
|
||||
if ($funnelId === 0) {
|
||||
// user_can_be_responsible требует funnel_id > 0 — для дефолтной проверяем вручную (agency + blocked).
|
||||
$candidateAgency = (int)User::getUserAgencyID($whoWorkId);
|
||||
if ($candidateAgency !== $userAgency) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Выбранный ответственный недоступен',
|
||||
'code' => 'who_work_forbidden',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
$blockedQ = mysql_query("SELECT `blocked` FROM `users` WHERE `id` = {$whoWorkId} LIMIT 1");
|
||||
$blockedRow = mysql_fetch_assoc($blockedQ);
|
||||
if (!$blockedRow || (int)$blockedRow['blocked'] === 1) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Выбранный ответственный недоступен',
|
||||
'code' => 'who_work_forbidden',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
} else if (!$reqCheck->user_can_be_responsible($whoWorkId, $userAgency, $funnelId)) {
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => 'Выбранный ответственный недоступен',
|
||||
'code' => 'who_work_forbidden',
|
||||
));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$result = $reqCheck->copy($srcId, $funnelId, $whoWorkId, $currentId);
|
||||
echo json_encode($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($request == 'get_requisitions_by_stage') {
|
||||
$stageId = (int)(isset($data->stage_id) ? $data->stage_id : 0);
|
||||
$funnelId = (int)(isset($data->funnel_id) ? $data->funnel_id : 0);
|
||||
$page = max(1, (int)(isset($data->page) ? $data->page : 1));
|
||||
$search = trim(isset($data->search) ? $data->search : '');
|
||||
$period = isset($data->period) ? $data->period : '';
|
||||
$responsible = is_array($data->responsible) ? $data->responsible : [];
|
||||
$limit = 20;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$userId = (int)$_SESSION['id'];
|
||||
$user = new User($pdo);
|
||||
$user->getUserById($userId);
|
||||
|
||||
$req = new Requisitions($pdo);
|
||||
$req->set_userId($userId);
|
||||
$req->set_agencyId($user->agencyId);
|
||||
|
||||
$result = $req->getRequisitionsByStage($stageId, $funnelId, $search, $period, $responsible, $offset, $limit);
|
||||
|
||||
$html = '';
|
||||
foreach ($result['items'] as $item) {
|
||||
$html .= '<li id="kanban_item_li_' . $item['id'] . '" class="kanban__item p' . (isset($item['priority']) ? $item['priority'] : 0) . '">';
|
||||
$html .= '<div id="client_kanban_' . $item['id'] . '" class="kanban__item-inner">';
|
||||
$html .= '<div title="' . htmlspecialchars(isset($item['fio']) ? $item['fio'] : 'Без имени') . '" class="title-kanban">';
|
||||
$html .= '<span>' . htmlspecialchars(isset($item['fio']) ? $item['fio'] : 'Без имени') . '</span>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div></li>';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'html' => $html,
|
||||
'has_more' => count($result['items']) == $limit,
|
||||
'page' => $page
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
|
||||
if($request == 'get_deal_by_req'){
|
||||
$req_id = intval($data->req_id);
|
||||
$sql = "SELECT id FROM deals WHERE status != 2 AND (side_one_requisition_id = $req_id OR side_two_requisition_id = $req_id) LIMIT 1";
|
||||
|
||||
@ -2,6 +2,31 @@ Vue.config.devtools = true;
|
||||
|
||||
Vue.component('v-select', VueSelect.VueSelect);
|
||||
|
||||
// JW-style toast (стили — crm.css). Дубликат из requisitions_vue.js с idempotent-гардом.
|
||||
if (typeof window.jwShowCopyToast !== 'function') {
|
||||
window.jwShowCopyToast = function (text) {
|
||||
var container = document.getElementById('jw-toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'jw-toast-container';
|
||||
container.className = 'jw-toast-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'jw-toast';
|
||||
toast.textContent = String(text || '');
|
||||
container.appendChild(toast);
|
||||
void toast.offsetWidth; // reflow для transition
|
||||
toast.classList.add('jw-toast-show');
|
||||
setTimeout(function () {
|
||||
toast.classList.remove('jw-toast-show');
|
||||
setTimeout(function () {
|
||||
if (toast.parentNode) toast.parentNode.removeChild(toast);
|
||||
}, 250);
|
||||
}, 3500);
|
||||
};
|
||||
}
|
||||
|
||||
var requisitions_client = new Vue({
|
||||
el: '#requisitions_list_client',
|
||||
name: 'requisitions_list_client',
|
||||
@ -564,7 +589,34 @@ var room_requisitions = new Vue({
|
||||
ot: 0,
|
||||
do: 0,
|
||||
pagePagin: [],
|
||||
complexBus: null
|
||||
complexBus: null,
|
||||
// Состояние модалки копирования заявки. employees — серверный пререндер window.__copyEmployeesList; фильтр в Vue (без Select2).
|
||||
copyModal: {
|
||||
open: false,
|
||||
srcId: null,
|
||||
funnels: [],
|
||||
funnelId: null,
|
||||
employees: [],
|
||||
whoWorkId: null,
|
||||
whoWorkSearch: '',
|
||||
whoWorkOpen: false,
|
||||
whoWorkLabel: '',
|
||||
defaultWhoWorkId: null,
|
||||
warnings: [],
|
||||
submitting: false,
|
||||
error: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// Фильтр сотрудников по whoWorkSearch (case-insensitive).
|
||||
copyFilteredEmployees: function() {
|
||||
var q = (this.copyModal.whoWorkSearch || '').toLowerCase().trim();
|
||||
var emps = this.copyModal.employees || [];
|
||||
if (!q) return emps;
|
||||
return emps.filter(function(e) {
|
||||
return (e.name || '').toLowerCase().indexOf(q) !== -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods:
|
||||
@ -830,6 +882,131 @@ var room_requisitions = new Vue({
|
||||
this.complexBus = window.complexBus
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// Открыть модалку копирования. Funnels/employees — серверный пререндер из room_requisitions.php / footer.php.
|
||||
openCopyRequisitionModal: function(reqId) {
|
||||
var self = this;
|
||||
var preFunnels = Array.isArray(window.__copyFunnelsList) ? window.__copyFunnelsList : [];
|
||||
var preEmployees = Array.isArray(window.__copyEmployeesList) ? window.__copyEmployeesList : [];
|
||||
var employeesNormalized = preEmployees.map(function (e) {
|
||||
return {
|
||||
id: parseInt(e.id, 10),
|
||||
name: e.name,
|
||||
group: e.group || null,
|
||||
};
|
||||
});
|
||||
self.copyModal = {
|
||||
open: true,
|
||||
srcId: reqId,
|
||||
funnels: preFunnels.map(function (f) {
|
||||
return { id: parseInt(f.id, 10), name: f.name };
|
||||
}),
|
||||
funnelId: null,
|
||||
employees: employeesNormalized,
|
||||
whoWorkId: null,
|
||||
whoWorkSearch: '',
|
||||
whoWorkOpen: false,
|
||||
whoWorkLabel: '',
|
||||
defaultWhoWorkId: null,
|
||||
warnings: [],
|
||||
submitting: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
axios.post('/ajax/ajax_vue_requisitions.php', {request: 'copy_preview', req_id: reqId})
|
||||
.then(function (res) {
|
||||
if (!res.data.success) {
|
||||
self.copyModal.error = res.data.error || 'Не удалось загрузить данные';
|
||||
return;
|
||||
}
|
||||
self.copyModal.warnings = res.data.warnings || [];
|
||||
// Дефолтная воронка — из исходника, иначе первая доступная.
|
||||
var defaultFunnelId = res.data.default_funnel_id;
|
||||
var funnelMatch = self.copyModal.funnels.find(function (f) {
|
||||
return parseInt(f.id, 10) === parseInt(defaultFunnelId, 10);
|
||||
});
|
||||
if (funnelMatch) {
|
||||
self.copyModal.funnelId = parseInt(defaultFunnelId, 10);
|
||||
} else if (self.copyModal.funnels.length > 0) {
|
||||
self.copyModal.funnelId = self.copyModal.funnels[0].id;
|
||||
} else {
|
||||
self.copyModal.funnelId = null;
|
||||
}
|
||||
self.copyModal.defaultWhoWorkId = res.data.default_who_work_id;
|
||||
var defaultId = res.data.default_who_work_id;
|
||||
var hasInList = employeesNormalized.some(function (e) { return e.id === parseInt(defaultId, 10); });
|
||||
var chosenId = hasInList
|
||||
? parseInt(defaultId, 10)
|
||||
: (employeesNormalized.length > 0 ? employeesNormalized[0].id : null);
|
||||
self.copyModal.whoWorkId = chosenId;
|
||||
// whoWorkLabel — имя в свернутом поле; whoWorkSearch держим пустым (полный список при открытии).
|
||||
var chosenEmp = employeesNormalized.find(function (e) { return e.id === chosenId; });
|
||||
self.copyModal.whoWorkLabel = chosenEmp ? chosenEmp.name : '';
|
||||
self.copyModal.whoWorkSearch = '';
|
||||
self.copyModal.whoWorkOpen = false;
|
||||
})
|
||||
.catch(function () {
|
||||
self.copyModal.error = 'Сетевая ошибка';
|
||||
});
|
||||
},
|
||||
|
||||
// Выбор сотрудника. Сбрасываем whoWorkSearch, чтобы следующий раз показать полный список.
|
||||
selectCopyWhoWork: function(emp) {
|
||||
this.copyModal.whoWorkId = emp.id;
|
||||
this.copyModal.whoWorkLabel = emp.name;
|
||||
this.copyModal.whoWorkSearch = '';
|
||||
this.copyModal.whoWorkOpen = false;
|
||||
},
|
||||
|
||||
// Отправка копии: создаёт заявку и обновляет список комнаты.
|
||||
submitCopy: function() {
|
||||
var self = this;
|
||||
if (self.copyModal.submitting) return;
|
||||
|
||||
var whoWorkId = parseInt(self.copyModal.whoWorkId, 10);
|
||||
|
||||
if (isNaN(whoWorkId) || self.copyModal.funnelId === null || self.copyModal.funnelId === undefined) {
|
||||
self.copyModal.error = 'Заполните воронку и ответственного';
|
||||
return;
|
||||
}
|
||||
|
||||
self.copyModal.submitting = true;
|
||||
self.copyModal.error = null;
|
||||
|
||||
axios.post('/ajax/ajax_vue_requisitions.php', {
|
||||
request: 'copy',
|
||||
req_id: self.copyModal.srcId,
|
||||
funnel_id: self.copyModal.funnelId,
|
||||
who_work_id: whoWorkId,
|
||||
}).then(function (res) {
|
||||
self.copyModal.submitting = false;
|
||||
if (!res.data.success) {
|
||||
self.copyModal.error = res.data.error || 'Не удалось создать копию';
|
||||
return;
|
||||
}
|
||||
self.copyModal.open = false;
|
||||
if (typeof window.jwShowCopyToast === 'function') {
|
||||
window.jwShowCopyToast('Копия заявки создана');
|
||||
}
|
||||
if (typeof self.getList === 'function') self.getList();
|
||||
}).catch(function () {
|
||||
self.copyModal.submitting = false;
|
||||
self.copyModal.error = 'Сетевая ошибка';
|
||||
});
|
||||
},
|
||||
|
||||
// Закрыть модалку копирования.
|
||||
closeCopyModal: function() {
|
||||
this.copyModal.open = false;
|
||||
},
|
||||
|
||||
// Закрытие picker сотрудников по клику вне него (mousedown, чтобы опередить click внутри).
|
||||
onCopyModalDocumentClick: function(e) {
|
||||
if (!this.copyModal.whoWorkOpen) return;
|
||||
var picker = this.$refs && this.$refs.copyWhoWorkPicker;
|
||||
if (picker && (picker === e.target || picker.contains(e.target))) return;
|
||||
this.copyModal.whoWorkOpen = false;
|
||||
}
|
||||
},
|
||||
mounted()
|
||||
@ -837,6 +1014,11 @@ var room_requisitions = new Vue({
|
||||
this.$nextTick(() => {
|
||||
this.observeRefs();
|
||||
});
|
||||
document.addEventListener('mousedown', this.onCopyModalDocumentClick);
|
||||
},
|
||||
beforeDestroy: function()
|
||||
{
|
||||
document.removeEventListener('mousedown', this.onCopyModalDocumentClick);
|
||||
},
|
||||
watch:
|
||||
{
|
||||
@ -853,7 +1035,8 @@ var room_requisitions = new Vue({
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
// Watch 'copyModal.funnelId' удалён: список сотрудников не зависит от воронки, заполняется один раз из window.__copyEmployeesList.
|
||||
},
|
||||
created: function()
|
||||
{
|
||||
|
||||
@ -3,6 +3,31 @@ Vue.config.devtools = true;
|
||||
Vue.component('v-select', VueSelect.VueSelect)
|
||||
Vue.directive('mask', VueMask.VueMaskDirective);
|
||||
|
||||
// JW-style toast (стили — crm.css). Idempotent guard на случай повторной загрузки файла.
|
||||
if (typeof window.jwShowCopyToast !== 'function') {
|
||||
window.jwShowCopyToast = function (text) {
|
||||
var container = document.getElementById('jw-toast-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'jw-toast-container';
|
||||
container.className = 'jw-toast-container';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'jw-toast';
|
||||
toast.textContent = String(text || '');
|
||||
container.appendChild(toast);
|
||||
void toast.offsetWidth; // reflow для transition
|
||||
toast.classList.add('jw-toast-show');
|
||||
setTimeout(function () {
|
||||
toast.classList.remove('jw-toast-show');
|
||||
setTimeout(function () {
|
||||
if (toast.parentNode) toast.parentNode.removeChild(toast);
|
||||
}, 250);
|
||||
}, 3500);
|
||||
};
|
||||
}
|
||||
|
||||
if ($('#requisitions_list').length > 0) {
|
||||
var requisitions = new Vue({
|
||||
el: '#requisitions_list',
|
||||
@ -47,6 +72,33 @@ if ($('#requisitions_list').length > 0) {
|
||||
add_tag_contract: null,
|
||||
req_id_contract_tag: 0,
|
||||
prev_selectors: { 'li': null, 'ul': null },
|
||||
// Состояние модалки копирования заявки. employees — серверный пререндер window.__copyEmployeesList (см. requisitions.php); фильтр в Vue (без Select2).
|
||||
copyModal: {
|
||||
open: false,
|
||||
srcId: null,
|
||||
funnels: [],
|
||||
funnelId: null,
|
||||
employees: [],
|
||||
whoWorkId: null,
|
||||
whoWorkSearch: '',
|
||||
whoWorkOpen: false,
|
||||
whoWorkLabel: '',
|
||||
defaultWhoWorkId: null,
|
||||
warnings: [],
|
||||
submitting: false,
|
||||
error: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// Фильтр сотрудников по whoWorkSearch (case-insensitive).
|
||||
copyFilteredEmployees: function() {
|
||||
var q = (this.copyModal.whoWorkSearch || '').toLowerCase().trim();
|
||||
var emps = this.copyModal.employees || [];
|
||||
if (!q) return emps;
|
||||
return emps.filter(function(e) {
|
||||
return (e.name || '').toLowerCase().indexOf(q) !== -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -83,7 +135,8 @@ if ($('#requisitions_list').length > 0) {
|
||||
this.totalReqs = totalFromSt
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
// Watch 'copyModal.funnelId' удалён: список сотрудников не зависит от воронки, заполняется один раз из window.__copyEmployeesList.
|
||||
},
|
||||
methods: {
|
||||
setAutosearch(reqs) {
|
||||
@ -1172,11 +1225,146 @@ if ($('#requisitions_list').length > 0) {
|
||||
// }
|
||||
},
|
||||
|
||||
// Открыть модалку копирования: preview-запрос → defaults + warnings.
|
||||
// Списки воронок/сотрудников приходят из window.__copyFunnelsList / window.__copyEmployeesList (server-embed в requisitions.php).
|
||||
openCopyRequisitionModal: function(reqId) {
|
||||
var self = this;
|
||||
var preFunnels = Array.isArray(window.__copyFunnelsList) ? window.__copyFunnelsList : [];
|
||||
var preEmployees = Array.isArray(window.__copyEmployeesList) ? window.__copyEmployeesList : [];
|
||||
var employeesNormalized = preEmployees.map(function (e) {
|
||||
return {
|
||||
id: parseInt(e.id, 10),
|
||||
name: e.name,
|
||||
group: e.group || null,
|
||||
};
|
||||
});
|
||||
// Полное переприсваивание copyModal — чтобы Vue точно зарегистрировал изменения всех полей.
|
||||
self.copyModal = {
|
||||
open: true,
|
||||
srcId: reqId,
|
||||
funnels: preFunnels.map(function (f) {
|
||||
return { id: parseInt(f.id, 10), name: f.name };
|
||||
}),
|
||||
funnelId: null,
|
||||
employees: employeesNormalized,
|
||||
whoWorkId: null,
|
||||
whoWorkSearch: '',
|
||||
whoWorkOpen: false,
|
||||
whoWorkLabel: '',
|
||||
defaultWhoWorkId: null,
|
||||
warnings: [],
|
||||
submitting: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
axios.post('/ajax/ajax_vue_requisitions.php', {request: 'copy_preview', req_id: reqId})
|
||||
.then(function (res) {
|
||||
if (!res.data.success) {
|
||||
self.copyModal.error = res.data.error || 'Не удалось загрузить данные';
|
||||
return;
|
||||
}
|
||||
self.copyModal.warnings = res.data.warnings || [];
|
||||
// Дефолтная воронка — из исходной заявки; если недоступна юзеру — fallback на первую.
|
||||
var defaultFunnelId = res.data.default_funnel_id;
|
||||
var funnelMatch = self.copyModal.funnels.find(function (f) {
|
||||
return parseInt(f.id, 10) === parseInt(defaultFunnelId, 10);
|
||||
});
|
||||
if (funnelMatch) {
|
||||
self.copyModal.funnelId = parseInt(defaultFunnelId, 10);
|
||||
} else if (self.copyModal.funnels.length > 0) {
|
||||
self.copyModal.funnelId = self.copyModal.funnels[0].id;
|
||||
} else {
|
||||
self.copyModal.funnelId = null;
|
||||
}
|
||||
self.copyModal.defaultWhoWorkId = res.data.default_who_work_id;
|
||||
// Дефолтный ответственный — из preview; иначе первая опция списка.
|
||||
var defaultId = res.data.default_who_work_id;
|
||||
var hasInList = employeesNormalized.some(function (e) { return e.id === parseInt(defaultId, 10); });
|
||||
var chosenId = hasInList
|
||||
? parseInt(defaultId, 10)
|
||||
: (employeesNormalized.length > 0 ? employeesNormalized[0].id : null);
|
||||
self.copyModal.whoWorkId = chosenId;
|
||||
// whoWorkLabel — отображаемое имя в свернутом поле; whoWorkSearch держим пустым.
|
||||
var chosenEmp = employeesNormalized.find(function (e) { return e.id === chosenId; });
|
||||
self.copyModal.whoWorkLabel = chosenEmp ? chosenEmp.name : '';
|
||||
self.copyModal.whoWorkSearch = '';
|
||||
self.copyModal.whoWorkOpen = false;
|
||||
})
|
||||
.catch(function () {
|
||||
self.copyModal.error = 'Сетевая ошибка';
|
||||
});
|
||||
},
|
||||
|
||||
// Выбор сотрудника. Сбрасываем whoWorkSearch — следующий раз показать полный список.
|
||||
selectCopyWhoWork: function(emp) {
|
||||
this.copyModal.whoWorkId = emp.id;
|
||||
this.copyModal.whoWorkLabel = emp.name;
|
||||
this.copyModal.whoWorkSearch = '';
|
||||
this.copyModal.whoWorkOpen = false;
|
||||
},
|
||||
|
||||
// Отправка копии: создаёт новую заявку и обновляет список.
|
||||
submitCopy: function() {
|
||||
var self = this;
|
||||
if (self.copyModal.submitting) return;
|
||||
|
||||
var whoWorkId = parseInt(self.copyModal.whoWorkId, 10);
|
||||
|
||||
// funnel_id может быть 0 («Обычные») — проверяем именно на null/undefined.
|
||||
// who_work_id может быть 0 («Всё агентство») — проверяем только NaN.
|
||||
if (isNaN(whoWorkId) || self.copyModal.funnelId === null || self.copyModal.funnelId === undefined) {
|
||||
self.copyModal.error = 'Заполните воронку и ответственного';
|
||||
return;
|
||||
}
|
||||
|
||||
self.copyModal.submitting = true;
|
||||
self.copyModal.error = null;
|
||||
|
||||
axios.post('/ajax/ajax_vue_requisitions.php', {
|
||||
request: 'copy',
|
||||
req_id: self.copyModal.srcId,
|
||||
funnel_id: self.copyModal.funnelId,
|
||||
who_work_id: whoWorkId,
|
||||
}).then(function (res) {
|
||||
self.copyModal.submitting = false;
|
||||
if (!res.data.success) {
|
||||
self.copyModal.error = res.data.error || 'Не удалось создать копию';
|
||||
return;
|
||||
}
|
||||
self.copyModal.open = false;
|
||||
if (typeof window.jwShowCopyToast === 'function') {
|
||||
window.jwShowCopyToast('Копия заявки создана');
|
||||
}
|
||||
if (typeof self.getList === 'function') self.getList();
|
||||
}).catch(function () {
|
||||
self.copyModal.submitting = false;
|
||||
self.copyModal.error = 'Сетевая ошибка';
|
||||
});
|
||||
},
|
||||
|
||||
// Закрыть модалку копирования — никаких Select2 destroy, просто скрываем.
|
||||
closeCopyModal: function() {
|
||||
this.copyModal.open = false;
|
||||
},
|
||||
|
||||
// Закрытие picker сотрудников по клику вне него (mousedown, чтобы опередить click внутри).
|
||||
onCopyModalDocumentClick: function(e) {
|
||||
if (!this.copyModal.whoWorkOpen) return;
|
||||
var picker = this.$refs && this.$refs.copyWhoWorkPicker;
|
||||
if (picker && (picker === e.target || picker.contains(e.target))) return;
|
||||
this.copyModal.whoWorkOpen = false;
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if (document.location.pathname == '/requisitions.php') {
|
||||
|
||||
}
|
||||
document.addEventListener('mousedown', this.onCopyModalDocumentClick);
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
// Снимаем document-level слушатель, чтобы не утекал при пересборке инстанса.
|
||||
document.removeEventListener('mousedown', this.onCopyModalDocumentClick);
|
||||
},
|
||||
created: function() {
|
||||
//this.getList();
|
||||
@ -2763,6 +2951,7 @@ var req_form = new Vue({
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user