diff --git a/api/v2/app/src/controllers/CommonController.php b/api/v2/app/src/controllers/CommonController.php index 694015a..7669984 100644 --- a/api/v2/app/src/controllers/CommonController.php +++ b/api/v2/app/src/controllers/CommonController.php @@ -5,6 +5,17 @@ namespace App\v2\Controller; class CommonController extends ApiController { + // Offset-free: листинг парсера (external_listings) различается по ЯВНОМУ флагу из запроса. + // Реальный el.id листинга коллизит с objects.id, поэтому различить его по id нельзя. + // Канон полей: is_external — объект-листинг; is_external_listing_event — событие листинга (echo'ит mJW обратно). + // Хелпер приводит присланный флаг (1 / '1' / true / 'true') к булеву признаку. + private static function _request_flag($src, $key) { + if (!is_array($src) || !isset($src[$key])) + return false; + $v = $src[$key]; + return ($v === true || $v === 1 || $v === '1' || $v === 'true'); + } + public function getEmployees($app, $get = null, $post = null) { $error = false; @@ -228,32 +239,46 @@ class CommonController extends ApiController $userIds[] = $userId; $who_work = 0; if($id > 0){ - - $sql = "SELECT * FROM clients where id = {$id}"; - if($section == 'requisitions'){ - $sql = "SELECT * FROM requisitions where id = {$id}"; - } - if($section == 'objects'){ - $sql = "SELECT id, id_add_user as who_work FROM objects WHERE id = {$id}"; - } - //echo $sql; - $q = mysql_query($sql); - $r = mysql_fetch_assoc($q); - - if (isset($r['doers']) && !empty($r['doers'])) { - $doers = json_decode(html_entity_decode($r['doers']), true); - - foreach ($doers as $user_id => $d){ - if ($d == 1){ - $userIds[] = (int)$user_id; + + // Найденный листинг парсера (offset-free): строки в objects нет — ни владельца, ни doers. + // Признак листинга — явный флаг is_external из запроса (id больше не диапазонный: el.id коллизит с objects.id). + // Ответственного выбираем по той же иерархии видимости, что и раздел «Задачи» (как на десктопе + // select_form_who_work_tasks.php): getUsersFilter вернёт подчинённых по правам (рядовой агент — только себя). + if($section == 'objects' && self::_request_flag($get, 'is_external')){ + $cl = new \Clients(); + $hierarchyIds = $cl->getUsersFilter(array(), $_SESSION['id'], $_SESSION['users_admin']); + if(is_array($hierarchyIds)){ + foreach($hierarchyIds as $hid){ + $userIds[] = (int)$hid; } } + } else { + $sql = "SELECT * FROM clients where id = {$id}"; + if($section == 'requisitions'){ + $sql = "SELECT * FROM requisitions where id = {$id}"; + } + if($section == 'objects'){ + $sql = "SELECT id, id_add_user as who_work FROM objects WHERE id = {$id}"; + } + //echo $sql; + $q = mysql_query($sql); + $r = mysql_fetch_assoc($q); + + if (isset($r['doers']) && !empty($r['doers'])) { + $doers = json_decode(html_entity_decode($r['doers']), true); + + foreach ($doers as $user_id => $d){ + if ($d == 1){ + $userIds[] = (int)$user_id; + } + } + } + if($r['who_work'] > 0){ + $userIds[] = (int)$r['who_work']; + $who_work = (int)$r['who_work']; + } } - if($r['who_work'] > 0){ - $userIds[] = (int)$r['who_work']; - $who_work = (int)$r['who_work']; - } - + } //Определение отделов @@ -2110,7 +2135,7 @@ class CommonController extends ApiController } $is_default = false; - if ($use_custom) { + /*if ($use_custom) { $is_default = true; $funnels[] = [ 'id' => 0, @@ -2154,6 +2179,29 @@ class CommonController extends ApiController } else { $error = true; $errors[] = mysql_error(); + }*/ + + $funnelClass = new \Funnel(); + $funnelClass->set_agency_id($agency_id); + $funnelClass->set_user_id($user_id); + $funnelsRaw = $funnelClass->get_funnels($section); + $funnels = array(); + if (is_array($funnelsRaw)) { + foreach ($funnelsRaw as $rF) { + $is_default_custom = false; + if (!$is_default && (int)$rF['$funnel'] == 0) { + $is_default_custom = true; + $is_default = true; + } + $funnels[] = array( + 'id' => (int)$rF['id'], + 'name' => (isset($rF['name']) && $rF['name'] !== null && $rF['name'] !== '') + ? (string)$rF['name'] + : 'Обычные', + 'count' => ($funnelCounts[intval($rF['id'])] > 0) ? intval($funnelCounts[intval($rF['id'])]) : null, + 'is_default' => $is_default_custom + ); + } } if (!$error) { @@ -3079,8 +3127,16 @@ class CommonController extends ApiController $protocol = (self::is_ssl()) ? 'https://' : 'http://'; $host = $protocol . $_SERVER['SERVER_NAME']; - $sql = "SELECT * FROM clients_files as c + // Найденный листинг парсера (offset-free): событие-родитель файла лежит в external_listing_events, + // event_id и object_id хранятся как РЕАЛЬНЫЕ el.id (без смещения) — валидируем владение по листинговой + // таблице. Признак листинга — явный флаг is_external из запроса (id больше не диапазонный: коллизит с objects.id). + if ($section == 'objects' && self::_request_flag($get, 'is_external')) { + $sql = "SELECT * FROM clients_files as c + WHERE object_id = {$source_id} AND event_id > 0 AND (SELECT id FROM external_listing_events WHERE id = c.event_id) > 0"; + } else { + $sql = "SELECT * FROM clients_files as c WHERE {$field} = {$source_id} AND event_id > 0 AND (SELECT id from {$table} WHERE id = c.event_id) > 0"; + } if ($q = $pdo->query($sql)) { if ($pdo->num_rows($q) > 0) { @@ -3849,6 +3905,20 @@ class CommonController extends ApiController if ($event_id && $type == 'transfer-object' && isset($data['object_id'])) { $object_id = (int)$data['object_id']; + + // Защитный отказ — приём передачи владения на найденном листинге парсера недоступен (offset-free). + // Признак листинга — явный флаг is_external из запроса. Передачи листинга быть не должно (инициация + // загейчена), гейтим от рассинхрона. Делать ДО UPDATE objects. + if (self::_request_flag($data, 'is_external')) { + $app->response->header('Content-Type', 'application/json'); + $app->response->setStatus(403); + $app->response->setBody(json_encode([ + 'success' => false, + 'errors' => ['Действие недоступно для найденного листинга. Скопируйте объект в свои, чтобы управлять им.'], + ], JSON_UNESCAPED_UNICODE)); + $app->stop(); + } + $sql = "SELECT * FROM objects_to_send WHERE id = $event_id AND object_id = $object_id"; if ($rez = mysql_query($sql)) { $row = mysql_fetch_assoc($rez); @@ -4076,6 +4146,16 @@ class CommonController extends ApiController $error = true; $errors[] = mysql_error(); } + + // синкаем нормализованную таблицу, которую читает десктоп (Clients → clients_doers WHERE confirm=1) + $sql = "UPDATE clients_doers SET confirm = 1 WHERE client_id = $client_id AND user_id = $user_id"; + if ($requisition_id > 0) + $sql = "UPDATE requisition_doers SET confirm = 1 WHERE requisition_id = $requisition_id AND user_id = $user_id"; + + if (!mysql_query($sql)) { + $error = true; + $errors[] = mysql_error(); + } } } else { $error = true; @@ -4634,23 +4714,50 @@ class CommonController extends ApiController if (!is_null($object_id)) { - $sql = "SELECT * FROM objects WHERE id=$object_id"; + // Найденный листинг парсера (offset-free): строки в objects нет, образ собираем из external_listings + // по РЕАЛЬНОМУ el.id. Признак листинга — явный флаг is_external из запроса (id коллизит с objects.id). + require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_legacy.php'; + $is_listing = self::_request_flag($data, 'is_external'); - if ($rez = mysql_query($sql)) { - $obj = mysql_fetch_assoc($rez); + $obj = false; + $obj_found = false; + if ($is_listing) { + $obj = \external_listing_object_row($object_id, false); + if (is_null($obj)) { + // Листинг удалён по TTL — отвечаем ошибкой, как для ненайденного объекта. + $error = true; + $errors[] = 'Объявление снято с публикации'; + } else { + // Поля внешнего источника экранируем для прямой подстановки в SQL. + $obj['nazv'] = mysql_real_escape_string($obj['nazv']); + $obj['adres'] = mysql_real_escape_string($obj['adres']); + $obj_found = true; + } + } else { + $sql = "SELECT * FROM objects WHERE id=$object_id"; + if ($rez = mysql_query($sql)) { + $obj = mysql_fetch_assoc($rez); + $obj_found = true; + } + } + + if ($obj_found) { + + // Листинг адресуется явным маркером src=ext (bare id уже не различает листинг от своего объекта). + $src_marker = $is_listing ? '&src=ext' : ''; if ($send_type == 2) { - $pdfs = $this->_getPDF([$object_id], $descriptions, $new_prices, $hide_contacts); + $pdfs = $this->_getPDF([$object_id], $descriptions, $new_prices, $hide_contacts, $is_listing ? [$object_id] : []); if (isset($pdfs[$object_id]['link'])) $linkUrl = $pdfs[$object_id]['link']; } else { if (self::is_ssl()) - $linkUrl = "https://joywork.ru/object-view.php?id=$object_id&token=" . md5($user_id); + $linkUrl = "https://joywork.ru/object-view.php?id=$object_id&token=" . md5($user_id) . $src_marker; else - $linkUrl = "http://joywork.ru/object-view.php?id=$object_id&token=" . md5($user_id); + $linkUrl = "http://joywork.ru/object-view.php?id=$object_id&token=" . md5($user_id) . $src_marker; } @@ -4667,14 +4774,32 @@ class CommonController extends ApiController if (in_array($object_id, $hide_contacts)) $close_contact = 1; - $sql_up = "INSERT INTO user_object_contact_close SET close={$close_contact}, user_id={$user_id}, object_id = {$object_id}, client_id = {$client_id}"; - if (!mysql_query($sql_up)) { - $error = true; - $errors[] = mysql_error(); + // Для найденного листинга флаг скрытия контакта не пишем — его контакт скрыт всегда. + if (!$is_listing) { + $sql_up = "INSERT INTO user_object_contact_close SET close={$close_contact}, user_id={$user_id}, object_id = {$object_id}, client_id = {$client_id}"; + if (!mysql_query($sql_up)) { + $error = true; + $errors[] = mysql_error(); + } } $token = md5(microtime()); - $sql = "INSERT INTO sended_pdf(id_agent, id_object, date_send, id_client, nazv, path, send_type, close_contact, token, new_price) VALUES('$user_id', '$object_id', NOW(), '$client_id', '$obj[nazv] $obj[adres]', '$linkUrl', $send_type, $close_contact, '$token', '$new_price')"; + + // Per-send описание, переданное mJW в POST-параметре `descriptions`. + $descriptionForInsert = "NULL"; + if (isset($descriptions[intval($object_id)]) && $descriptions[intval($object_id)] !== '') { + $cleanedDesc = cleanHtml($descriptions[intval($object_id)], '
') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, ' ') === false && strpos($cleanedDesc, '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
request->getBody();
$data = json_decode($body, true);
- if (($user_id = $_SESSION['id']) && (isset($data['objects_ids']) || isset($data['newbuildings_ids']))) {
+ if (($user_id = $_SESSION['id']) && (isset($data['objects_ids']) || isset($data['external_objects_ids']) || isset($data['newbuildings_ids']))) {
// Открываем соединение с БД
$mdb = $app->container->get('mysql');
@@ -4789,6 +4914,12 @@ class CommonController extends ApiController
if (isset($data['objects_ids']))
$objects_ids = array_unique(array_map('intval', (array)$data['objects_ids']));
+ // Offset-free: листинги парсера приходят ОТДЕЛЬНЫМ массивом реальных el.id (objects_ids — только свои объекты).
+ // Разделить по диапазону id больше нельзя — el.id коллизит с objects.id.
+ $external_objects_ids = [];
+ if (isset($data['external_objects_ids']))
+ $external_objects_ids = array_unique(array_map('intval', (array)$data['external_objects_ids']));
+
$newbuildings_ids = [];
if (isset($data['newbuildings_ids']))
$newbuildings_ids = array_unique(array_map('intval', (array)$data['newbuildings_ids']));
@@ -4833,7 +4964,7 @@ class CommonController extends ApiController
}
$link = null;
- if (!empty($objects_ids) || !empty($newbuildings_ids)) {
+ if (!empty($objects_ids) || !empty($external_objects_ids) || !empty($newbuildings_ids)) {
/*error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('display_errors', 1);*/
@@ -4862,9 +4993,17 @@ class CommonController extends ApiController
}
}
- if (!empty($objects_ids)) {
+ if (!empty($objects_ids) || !empty($external_objects_ids)) {
- $sql = "SELECT `id`, `nazv`, `adres`, `phone`, `sobstv` FROM `objects` WHERE `id` IN (".implode(',', $objects_ids).")";
+ // Offset-free: свои объекты и найденные листинги парсера приходят РАЗНЫМИ массивами реальных id.
+ // objects_ids — только свои (objects), external_objects_ids — листинги (external_listings, ключ = el.id).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_legacy.php';
+ $listing_ids = $external_objects_ids;
+ $own_object_ids = $objects_ids;
+
+ if (!empty($own_object_ids)) {
+
+ $sql = "SELECT `id`, `nazv`, `adres`, `phone`, `sobstv` FROM `objects` WHERE `id` IN (".implode(',', $own_object_ids).")";
if ($query = mysql_query($sql)) {
@@ -4890,7 +5029,21 @@ class CommonController extends ApiController
}
}
- $sql = "INSERT INTO `sended_pdf`(`id_agent`, `id_object`, `date_send`, `id_client`, `nazv`, `path`, `send_type`, `close_contact`, `hide_address`, `new_price`, `token`, `sent_catalog_id`) VALUES('$user_id', '$object_id', NOW(), '$client_id', '$object[nazv] $object[adres]', '', $send_type, $is_hide_contacts, $is_hide_address, '$new_price', '', '$catalog_id')";
+ // Per-send описание, переданное mJW в POST-параметре `descriptions`.
+ $descriptionForInsert = "NULL";
+ if (isset($descriptions[intval($object_id)]) && $descriptions[intval($object_id)] !== '') {
+ $cleanedDesc = cleanHtml($descriptions[intval($object_id)], '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
0) {
while ($photoInfo = mysql_fetch_assoc($rezPhotoInfo)) {
$p = $photoInfo['photo'];
@@ -5147,7 +5426,7 @@ class CommonController extends ApiController
}
$arrTop['replace_plan'] = false;
- $sql = "SELECT * FROM objects_plan WHERE object_id = $id";
+ $sql = "SELECT * FROM objects_plan WHERE object_id = $objSubId";
if ($rez_plan = mysql_query($sql)) {
if (mysql_num_rows($rez_plan) > 0) {
$obj_plan = mysql_fetch_assoc($rez_plan);
@@ -5179,7 +5458,7 @@ class CommonController extends ApiController
$errors[] = mysql_error();
}
- $sqlComment = "SELECT * FROM user_object_comments WHERE user_id = '$_SESSION[id]' AND object_id='$id'";
+ $sqlComment = "SELECT * FROM user_object_comments WHERE user_id = '$_SESSION[id]' AND object_id='$objSubId'";
if ($rezComment = mysql_query($sqlComment)) {
if (mysql_num_rows($rezComment) > 0) {
$commentRow = mysql_fetch_assoc($rezComment);
@@ -5209,7 +5488,8 @@ class CommonController extends ApiController
$imgDesc = '';
}
- $arrProps = getProps($id);
+ // Для листинга справочные характеристики objects недоступны — блок параметров пуст.
+ $arrProps = !empty($obj['is_external']) ? [] : getProps($id);
try {
@@ -5285,11 +5565,6 @@ class CommonController extends ApiController
$error = true;
$errors[] = $e;
}
- }
- } else {
- $error = true;
- $errors[] = $sql;
- $errors[] = mysql_error();
}
}
@@ -5307,7 +5582,7 @@ class CommonController extends ApiController
$body = $app->request->getBody();
$data = json_decode($body, true);
- if (($user_id = $_SESSION['id']) && (isset($data['objects_ids']) || isset($data['newbuildings_ids'])) && isset($data['clients_ids'])) {
+ if (($user_id = $_SESSION['id']) && (isset($data['objects_ids']) || isset($data['external_objects_ids']) || isset($data['newbuildings_ids'])) && isset($data['clients_ids'])) {
$result = \SelectelApi::getTokenAndUrl();
$token = $result[0];
@@ -5319,10 +5594,18 @@ class CommonController extends ApiController
// Открываем соединение с БД
$mdb = $app->container->get('pdo');
- $objects_ids = null;
+ // По умолчанию массив (не null): ниже array_merge с external_objects_ids; в PHP 5.6 array_merge(null,..) = warning+NULL.
+ $objects_ids = [];
if (isset($data['objects_ids']))
$objects_ids = array_unique(array_map('intval', (array)$data['objects_ids']));
+ // Offset-free: листинги парсера приходят ОТДЕЛЬНЫМ массивом реальных el.id (objects_ids — только свои объекты).
+ // Разделить по диапазону id больше нельзя (el.id коллизит с objects.id); набор-сет используется в циклах ниже.
+ $external_objects_ids = [];
+ if (isset($data['external_objects_ids']))
+ $external_objects_ids = array_unique(array_map('intval', (array)$data['external_objects_ids']));
+ $listing_id_set = array_flip($external_objects_ids);
+
$newbuildings_ids = null;
if (isset($data['newbuildings_ids']))
$newbuildings_ids = array_unique(array_map('intval', (array)$data['newbuildings_ids']));
@@ -5402,6 +5685,7 @@ class CommonController extends ApiController
$catalog_link = $this->getCatalogLink($app, null, [
'objects_ids' => $objects_ids,
+ 'external_objects_ids' => $external_objects_ids,
'newbuildings_ids' => $newbuildings_ids,
'clients_ids' => $clients_ids,
'catalog_id' => $catalog_id,
@@ -5461,6 +5745,10 @@ class CommonController extends ApiController
$app->stop();
} else { // Отправка вложением pdf, списком ссылок на презентации
+
+ // Хелперы найденных листингов парсера (offset-free): их нет в objects, образ строки берём из external_listings по реальному el.id.
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_legacy.php';
+
$pdf_ready = [];
$token = md5(microtime());
$theme = !empty($subject) ? $subject : "Презентации объектов недвижимости";
@@ -5471,22 +5759,38 @@ class CommonController extends ApiController
if (!empty(trim($message)) && $provide == 1)
$message = $message . '
';
- if (count($objects_ids) > 0 || count($newbuildings_ids) > 0) {
+ if (count($objects_ids) > 0 || count($external_objects_ids) > 0 || count($newbuildings_ids) > 0) {
/*error_reporting(E_ERROR | E_WARNING | E_PARSE);
ini_set('display_errors', 1);*/
- $sql = "SELECT objects.id, objects.operation_type, objects.nazv, objects.etazh, objects.etazh_iz, objects.stoim,
- objects.peshkom, objects.transport, objects.adres, objects.dom, objects.korpus, objects.litera,
- objects.id_rf_region, rayon.rayon, objects.ploshad, objects.ploshad_komn, objects.ploshad_k, objects.srok, objects.opis,
- objects.type, objects.type_category
- FROM objects
- LEFT JOIN rayon rayon on objects.rayon = rayon.id
- WHERE objects.id IN (".implode(',', $objects_ids).")";
+ // Запрос objects — только по своим id (offset-free: листинги собираем отдельно из external_listings).
+ // Гейт по непустому списку: IN() с пустым набором = синтаксическая ошибка SQL.
+ $pdf_rows = [];
+ if (count($objects_ids) > 0) {
+ $sql = "SELECT objects.id, objects.operation_type, objects.nazv, objects.etazh, objects.etazh_iz, objects.stoim,
+ objects.peshkom, objects.transport, objects.adres, objects.dom, objects.korpus, objects.litera,
+ objects.id_rf_region, rayon.rayon, objects.ploshad, objects.ploshad_komn, objects.ploshad_k, objects.srok, objects.opis,
+ objects.type, objects.type_category
+ FROM objects
+ LEFT JOIN rayon rayon on objects.rayon = rayon.id
+ WHERE objects.id IN (".implode(',', $objects_ids).")";
- if ($query = mysql_query($sql)) {
+ if ($query = mysql_query($sql)) {
+ while ($pdf_row = mysql_fetch_assoc($query))
+ $pdf_rows[] = $pdf_row;
+ } else {
+ $error = true;
+ $errors[] = $sql;
+ $errors[] = mysql_error();
+ }
+ }
- while($obj = mysql_fetch_assoc($query)) {
+ // Найденные листинги парсера (offset-free): их нет в objects — добавляем образы строк
+ // из external_listings по реальным el.id (умерший по TTL листинг в выборку не попадает и пропускается как ненайденный).
+ $pdf_rows = array_merge($pdf_rows, self::_listingRowsForPdf($external_objects_ids));
+
+ foreach ($pdf_rows as $obj) {
$id = $obj['id'];
$arrTop = [];
@@ -5534,9 +5838,12 @@ class CommonController extends ApiController
$arrTop['stoim'] = number_format($new_price, 0, ',', ' ');
}
- $mtr = [];
+ // Для листинга objects-only подзапросы (метро/локация/план/комментарий) по el.id коллизят с objects.id →
+ // гейтим через $objSubId=-1 (пусто): адрес берётся из образа листинга, план — из первого фото, метро/коммент отсутствуют.
+ $objSubId = empty($obj['is_external']) ? (int)$obj['id'] : -1;
+ $mtr = [];
$sql = "SELECT metro.metro FROM metro, sp_metro
- WHERE sp_metro.id_metro = metro.id AND sp_metro.id_obj = $obj[id]";
+ WHERE sp_metro.id_metro = metro.id AND sp_metro.id_obj = $objSubId";
if ($result = mysql_query($sql)) {
while($m = mysql_fetch_row($result)) $mtr[] = $m[0];
@@ -5556,7 +5863,7 @@ class CommonController extends ApiController
$errors[] = mysql_error();
}
- $sqlLocationInfo = "SELECT * FROM object_location WHERE object_id=".$obj['id'];
+ $sqlLocationInfo = "SELECT * FROM object_location WHERE object_id=".$objSubId;
if ($rezLocationInfo = mysql_query($sqlLocationInfo)) {
if (mysql_num_rows($rezLocationInfo) > 0) {
$objLocationInfo = mysql_fetch_assoc($rezLocationInfo);
@@ -5617,7 +5924,13 @@ class CommonController extends ApiController
if ($useLocalPhoto) {
$photos = [];
$sqlPhotoInfo = "SELECT oop.sort_order, p.photo FROM objects_object_photo oop, object_photo p WHERE p.id = oop.object_photo_id AND oop.object_id = $id ORDER BY oop.sort_order";
- if ($rezPhotoInfo = mysql_query($sqlPhotoInfo)) {
+ if (!empty($obj['is_external'])) {
+ // Фото листинга — готовые CDN-URL (photo1..N из образа строки), mpdf скачает их по сети.
+ for ($ph_i = 1; $ph_i <= 40; $ph_i++) {
+ if (!empty($obj['photo' . $ph_i]))
+ array_push($photos, $obj['photo' . $ph_i]);
+ }
+ } else if ($rezPhotoInfo = mysql_query($sqlPhotoInfo)) {
if (mysql_num_rows($rezPhotoInfo) > 0) {
while ($photoInfo = mysql_fetch_assoc($rezPhotoInfo)) {
$p = $photoInfo['photo'];
@@ -5635,7 +5948,7 @@ class CommonController extends ApiController
}
$arrTop['replace_plan'] = false;
- $sql = "SELECT * FROM objects_plan WHERE object_id = $id";
+ $sql = "SELECT * FROM objects_plan WHERE object_id = $objSubId";
if ($rez_plan = mysql_query($sql)) {
if (mysql_num_rows($rez_plan) > 0) {
$obj_plan = mysql_fetch_assoc($rez_plan);
@@ -5667,7 +5980,7 @@ class CommonController extends ApiController
$errors[] = mysql_error();
}
- $sqlComment = "SELECT * FROM user_object_comments WHERE user_id = '$_SESSION[id]' AND object_id='$id'";
+ $sqlComment = "SELECT * FROM user_object_comments WHERE user_id = '$_SESSION[id]' AND object_id='$objSubId'";
if ($rezComment = mysql_query($sqlComment)) {
if (mysql_num_rows($rezComment) > 0) {
$commentRow = mysql_fetch_assoc($rezComment);
@@ -5697,7 +6010,8 @@ class CommonController extends ApiController
$imgDesc = '';
}
- $arrProps = getProps($id);
+ // Для листинга справочные характеристики objects недоступны — блок параметров пуст.
+ $arrProps = !empty($obj['is_external']) ? [] : getProps($id);
try {
@@ -5756,11 +6070,6 @@ class CommonController extends ApiController
$error = true;
$errors[] = $e;
}
- }
- } else {
- $error = true;
- $errors[] = $sql;
- $errors[] = mysql_error();
}
}
@@ -5778,8 +6087,23 @@ class CommonController extends ApiController
$pdfs = $adresses = [];
- if (count($objects_ids)) {
- foreach ($objects_ids as $object_id) {
+ if (count($objects_ids) || count($external_objects_ids)) {
+ // Offset-free: общий проход по своим объектам и листингам (объединённый список реальных id),
+ // признак листинга — по сету external_objects_ids, а не по диапазону id (коллизия el.id == objects.id).
+ foreach (array_merge($objects_ids, $external_objects_ids) as $object_id) {
+
+ // Найденный листинг парсера: строки в objects нет, образ собираем из external_listings по реальному el.id.
+ $listing_row = null;
+ if (isset($listing_id_set[$object_id])) {
+ $listing_row = \external_listing_object_row($object_id, false);
+ // Листинг удалён по TTL — пропускаем до накопления вложений (pdfs/adresses идут парами).
+ if (is_null($listing_row))
+ continue;
+ // Поля внешнего источника экранируем для прямой подстановки в SQL.
+ $listing_row['nazv'] = mysql_real_escape_string($listing_row['nazv']);
+ $listing_row['adres'] = mysql_real_escape_string($listing_row['adres']);
+ }
+
$pdf_path = $_SERVER['DOCUMENT_ROOT'] . "/pdf/$object_id-$user_id.pdf";
$pdfs[] = $pdf_path;
@@ -5803,6 +6127,79 @@ class CommonController extends ApiController
$pdfs_links[$object_id] = null;
}
+ // Найденный листинг парсера: пишем историю отправки и снапшот по реальному el.id, контакта нет.
+ if (!is_null($listing_row)) {
+
+ $obj = $listing_row;
+ $adresses[] = $obj['nazv'];
+
+ // Снапшот листинга в archive: phone/sobstv пустые (контакт скрыт по 152-ФЗ).
+ $sql = "SELECT * FROM `archive` WHERE `id_obj` = '$object_id'";
+ if ($query = mysql_query($sql)) {
+ if (mysql_num_rows($query)) {
+ $sql = "UPDATE `archive` SET `nazv`='$obj[nazv]', `adres`='$obj[adres]', `phone`='', `sobstv`='' WHERE `id_obj` = '$object_id'";
+ if (!mysql_query($sql)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+ } else {
+ $sql = "INSERT INTO archive(nazv, adres, phone, sobstv, id_obj) VALUES('$obj[nazv]', '$obj[adres]', '', '', '$object_id')";
+ if (!mysql_query($sql)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+ }
+ } else {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+
+ $new_price = 0;
+ if (isset($new_prices[$object_id])) {
+ $new_price = $new_prices[$object_id];
+ }
+
+ foreach ($clients_ids as $client_id) {
+ $lnk = $pdfs_links[$object_id];
+
+ // Per-send описание, переданное mJW в POST-параметре `descriptions`.
+ $descriptionForInsert = "NULL";
+ if (isset($descriptions[intval($object_id)]) && $descriptions[intval($object_id)] !== '') {
+ $cleanedDesc = cleanHtml($descriptions[intval($object_id)], '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
0) {
+ $asfilter = new \AutoSearch();
+ foreach ($asfilter_objects as $filter_id => $object_id) {
+ if (!is_null($filter_id) && !is_null($object_id))
+ $asfilter->addObjectToSended($filter_id, $object_id);
+ }
+ }
+
+ continue;
+ }
+
$sql = "SELECT * FROM objects WHERE id=$object_id";
if ($rez = mysql_query($sql)) {
$obj = mysql_fetch_assoc($rez);
@@ -5849,7 +6246,22 @@ class CommonController extends ApiController
foreach ($clients_ids as $client_id) {
$lnk = $pdfs_links[$object_id];
- $sql = "INSERT INTO sended_pdf(id_agent, id_object, date_send, id_client, nazv, path, send_type, new_price) VALUES('$user_id', '$object_id', NOW(), '$client_id', '$obj[nazv] $obj[adres]', '$lnk', $send_type, '$new_price')";
+
+ // Per-send описание, переданное mJW в POST-параметре `descriptions`.
+ $descriptionForInsert = "NULL";
+ if (isset($descriptions[intval($object_id)]) && $descriptions[intval($object_id)] !== '') {
+ $cleanedDesc = cleanHtml($descriptions[intval($object_id)], '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
0) {
+ $asfilter = new \AutoSearch();
+ foreach ($toSendASFilter as $filter_id => $object_id) {
+ if (!is_null($filter_id) && !is_null($object_id))
+ $asfilter->addObjectToSended($filter_id, $object_id);
+ }
+ }
+
+ if ($send_type == 1) {
+ $txt .= "
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
,
+ // чтобы абзацы не склеились в одну строку при отображении через echo $obj['opis'] в object-view.php.
+ if ($cleanedDesc !== '' && strpos($cleanedDesc, '
response->header('Content-Type', 'application/json');
+ $app->response->setStatus(403);
+ $app->response->setBody(json_encode([
+ 'success' => false,
+ 'errors' => ['Действие недоступно для найденного листинга. Скопируйте объект в свои, чтобы управлять им.'],
+ ], JSON_UNESCAPED_UNICODE));
+ return;
+ }
+
$message = null;
if (isset($data['message']))
$message = trim($data['message']);
@@ -6792,6 +7360,20 @@ class CommonController extends ApiController
if (isset($post['object_id']))
$object_id = intval($post['object_id']);
+ // Найденный листинг парсера — read-only: привязка файлов/фото к нему запрещена.
+ // Признак листинга — явный флаг is_external из запроса (offset-free: id коллизит с objects.id).
+ // clients_files.object_id остаётся обычным INT — листинговый id сюда не попадает (привязка запрещена ниже).
+ if ($object_id && self::_request_flag($post, 'is_external')) {
+ $app->response->header('Content-Type', 'application/json');
+ $app->response->setStatus(200);
+ $app->response->setBody(json_encode([
+ 'success' => false,
+ 'user_id' => (int)$_SESSION['id'],
+ 'errors' => ['Прикрепление файлов к найденному листингу недоступно'],
+ ], JSON_UNESCAPED_UNICODE));
+ $app->stop();
+ }
+
$client_id = null;
if (isset($post['client_id']))
$client_id = intval($post['client_id']);
@@ -7417,11 +7999,90 @@ class CommonController extends ApiController
$user->checkPermissions($user_id, false);
$agency_id = $user->agencyId;
+ // Offset-free: признак листинга — явный флаг is_external из запроса (echo'ит mJW). object_id = реальный el.id.
+ // Для каждой эмитируемой задачи листинга помечаем is_external_listing_event=true, чтобы CRUD (правка/удаление/
+ // завершение/суб-коммент) маршрутизировался в листинговую таблицу без диапазонного признака id.
+ $is_listing_tasks = self::_request_flag($get, 'is_external');
+
if ($object_id && $section == 'objects') {
$sql = '';
- if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
+ // Хелперы событий найденных листингов парсера (offset-free, ключ — реальный el.id).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
+ if ($is_listing_tasks) {
+
+ // Найденный листинг парсера: лента событий из external_listing_events (ключ — реальный el.id, без смещения).
+ // Суб-комменты листинга — section_id = 4 (общий section_id = 1 коллидировал бы по event_id
+ // с суб-комментами задач из user_object_events). У листинга нет cancel/sum/document_id — отдаём 0/NULL,
+ // чтобы общий цикл обработки ниже не менялся.
+ $listing_real_id = (int)$object_id;
+
+ if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
+ $agent_ids = [$user_id];
+ $searchUserId = $user_id;
+ if ($_SESSION['users_admin']) {
+ $user = new \User;
+ $user->checkPermissions($user_id, false);
+ $searchUserId = $user->agencyId;
+ $agent_ids[] = $user->agencyId;
+ }
+ $agents = \User::getAllAgents($searchUserId);
+ foreach ($agents as $agent) {
+ if (!in_array($agent['id'], $agent_ids))
+ $agent_ids[] = $agent['id'];
+ }
+ if ($_SESSION['agency'] || $_SESSION['users_admin']) {
+ $agents = \User::getAllManagers($searchUserId);
+ foreach ($agents as $agent) {
+ if (!in_array($agent['id'], $agent_ids))
+ $agent_ids[] = $agent['id'];
+ }
+ }
+ // + удалённые сотрудники компании: их события на ЭТОМ листинге видны команде (история сохраняется,
+ // авторство не меняем). Это лента ОДНОГО листинга для агентства, не фильтруемый список задач.
+ $agent_ids = array_values(array_unique(array_merge($agent_ids, \external_listing_notes_scope_user_ids($user_id))));
+ $listing_user_filter = "(e.user_id IN (" . implode(',', $agent_ids) . ") OR e.from_id IN (" . implode(',', $agent_ids) . "))";
+ } else {
+ $listing_user_filter = "(e.user_id = '$user_id' OR e.from_id = '$user_id')";
+ }
+
+ // Offset-free: id события — реальный e.id (без смещения), различитель листинга = is_external_listing_event ниже.
+ $sql = "SELECT e.id AS id,
+ e.type,
+ e.user_id,
+ e.from_id,
+ e.comment,
+ e.address,
+ e.schedule_date,
+ NULL AS schedule_date_to,
+ e.name,
+ e.calendar_view,
+ e.create_date,
+ 0 AS cancel,
+ 0 AS `sum`,
+ NULL AS document_id,
+ IF(IFNULL(s.id, '') = '', 0, s.id) AS sub_id,
+ IF(IFNULL(s.sub_comment, '') = '', NULL, s.sub_comment) AS sub_comment,
+ IF(s.created_by <> 0, (
+ SELECT CONCAT(u.last_name, ' ', u.first_name, ' ', u.middle_name)
+ FROM users AS u WHERE u.id = s.created_by
+ ), NULL) AS sub_comment_author,
+ IF(IFNULL(s.created_by, '') = '', 0, s.created_by) AS sub_comment_author_id,
+ IF(s.user_for <> 0, (
+ SELECT CONCAT(u.last_name, ' ', u.first_name, ' ', u.middle_name)
+ FROM users AS u WHERE u.id = s.user_for
+ ), NULL) AS sub_comment_user_for,
+ IF(IFNULL(s.user_for, '') = '', 0, s.user_for) AS sub_comment_user_for_id
+ FROM external_listing_events AS e
+ LEFT JOIN user_sub_comments AS s ON s.event_id = e.id AND s.section_id = 4
+ WHERE {$listing_user_filter} AND
+ e.external_listing_id = {$listing_real_id}
+ GROUP BY e.id
+ ORDER BY e.create_date ASC";
+
+ } else if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
$agent_ids = [$user_id];
$searchUserId = $user_id;
@@ -7561,6 +8222,7 @@ class CommonController extends ApiController
$event['who_work'] = $from_who_work['user_fio'];
$data = [];
+ $data['moved_on_copy'] = !empty($event['moved_on_copy']) ? 1 : 0; //перенесено в объект при копировании из Поиска (бейдж)
//$data['_raw'] = $event;
$event_type = isset($event['type']) ? $event['type'] : null;
switch ($event['type']) {
@@ -7947,12 +8609,14 @@ class CommonController extends ApiController
$tasks[] = [
'id' => intval($event['id']),
+ // Offset-free: id события — реальный e.id; различитель листинга — явный флаг (echo'ит mJW в CRUD).
+ 'is_external_listing_event' => $is_listing_tasks,
'chat_date' => isset($event['chat_date']) ? $event['chat_date'] : null,
'chat_position' => isset($event['chat_position']) ? $event['chat_position'] : null,
'user_id' => intval($from_id),
'user_name' => isset($event['user_fio']) ? $event['user_fio'] : null,
'who_work' => isset($event['who_work']) ? $event['who_work'] : null,
-
+
'user_logo' => (!empty($event['user_logo'])) ? $host . '/photos/agency/' . $event['user_logo'] . '?=' . time() : null,
'type' => $event_type,
'event' => $data,
@@ -7976,6 +8640,7 @@ class CommonController extends ApiController
e.create_date,
e.checklist,
e.type,
+ e.type_call,
e.user_id,
e.record,
e.client_id,
@@ -8013,6 +8678,7 @@ class CommonController extends ApiController
ev.date as create_date,
0 as checklist,
'partner' as `type`,
+ NULL as type_call,
ev.user_id,
'' as record,
ev.client_id,
@@ -8052,6 +8718,7 @@ class CommonController extends ApiController
e.create_date,
e.checklist,
e.type,
+ e.type_call,
e.user_id,
e.record,
e.client_id,
@@ -8089,6 +8756,7 @@ class CommonController extends ApiController
ev.date as create_date,
0 as checklist,
'partner' as `type`,
+ NULL as type_call,
ev.user_id,
'' as record,
ev.client_id,
@@ -8588,6 +9256,8 @@ class CommonController extends ApiController
$tasks[] = [
'id' => intval($event['id']),
+ // Клиент/заявка — не листинг; флаг держим для единообразия формы ответа.
+ 'is_external_listing_event' => false,
'chat_date' => isset($event['chat_date']) ? $event['chat_date'] : null,
'chat_position' => isset($event['chat_position']) ? $event['chat_position'] : null,
'user_id' => intval($from_id),
@@ -8596,6 +9266,7 @@ class CommonController extends ApiController
'who_work_fio' => isset($event['who_work_fio']) ? $event['who_work_fio'] : null,
'user_logo' => (!empty($event['user_logo'])) ? $host . '/photos/agency/' . $event['user_logo'] . '?=' . time() : null,
'type' => isset($event['type']) ? $event['type'] : null,
+ 'type_call' => isset($event['type_call']) ? $event['type_call'] : null,
'event' => $data,
'created_at' => isset($event['create_date']) ? date('Y.m.d H:i:s', strtotime($time_zone, strtotime($event['create_date']))) : null,
'time' => isset($event['create_date']) ? date("H:i", strtotime($time_zone, strtotime($event['create_date']))) : null,
@@ -8670,11 +9341,67 @@ class CommonController extends ApiController
$user->checkPermissions($user_id, false);
$agency_id = $user->agencyId;
+ // Offset-free: признак события листинга — явный флаг is_external_listing_event из запроса (echo'ит mJW).
+ // event_id = реальный e.id (без смещения), коллизия с id события своего объекта снимается этим флагом.
+ $is_listing_task = self::_request_flag($get, 'is_external_listing_event');
+
$sql = '';
if ($section == 'objects') {
$sql = '';
- if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
+
+ // Хелперы событий найденных листингов парсера (offset-free, ключ — реальный el event id).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
+ if ($is_listing_task) {
+
+ // Найденный листинг парсера: одиночное событие из external_listing_events (реальный id = event_id, без смещения).
+ // cancel/sum/document_id у листинга нет — отдаём 0/NULL.
+ $listing_real_id = (int)$event_id;
+
+ if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
+ $agent_ids = [$user_id];
+ $searchUserId = $user_id;
+ if ($_SESSION['users_admin']) {
+ $user = new \User;
+ $user->checkPermissions($user_id, false);
+ $searchUserId = $user->agencyId;
+ $agent_ids[] = $user->agencyId;
+ }
+ $agents = \User::getAllAgents($searchUserId);
+ foreach ($agents as $agent) {
+ if (!in_array($agent['id'], $agent_ids))
+ $agent_ids[] = $agent['id'];
+ }
+ if ($_SESSION['agency'] || $_SESSION['users_admin']) {
+ $agents = \User::getAllManagers($searchUserId);
+ foreach ($agents as $agent) {
+ if (!in_array($agent['id'], $agent_ids))
+ $agent_ids[] = $agent['id'];
+ }
+ }
+ $listing_user_filter = "e.user_id IN (" . implode(',', $agent_ids) . ")";
+ } else {
+ $listing_user_filter = "e.user_id = '$user_id'";
+ }
+
+ // Offset-free: id события — реальный e.id (без смещения), различитель листинга = is_external_listing_event ниже.
+ $sql = "SELECT e.id AS id,
+ e.type, e.user_id, e.from_id, e.comment, e.address,
+ e.schedule_date, NULL AS schedule_date_to, e.name, e.calendar_view, e.create_date,
+ 0 AS cancel, 0 AS `sum`, NULL AS document_id,
+ IF(e.from_id <> 0, (SELECT u.user_logo FROM users AS u WHERE u.id = e.from_id), (SELECT u.user_logo FROM users AS u WHERE u.id = e.user_id)) AS user_logo,
+ IF(e.from_id <> 0, (SELECT u.fio FROM users AS u WHERE u.id = e.from_id), (SELECT u.fio FROM users AS u WHERE u.id = e.user_id)) AS user_fio
+ FROM external_listing_events AS e
+ WHERE {$listing_user_filter} AND
+ e.id = {$listing_real_id}";
+
+ if ($event_type)
+ $sql .= " AND e.type = '$event_type'";
+
+ $sql .= " LIMIT 1";
+
+ } else if ($_SESSION['agency'] || $_SESSION['manager'] || $_SESSION['users_admin']) {
$agent_ids = [$user_id];
$searchUserId = $user_id;
@@ -8702,19 +9429,19 @@ class CommonController extends ApiController
}
}
- $sql = "SELECT *,
+ $sql = "SELECT *,
if (
- e.from_id <> 0,
- (SELECT u.user_logo FROM users AS u WHERE u.id = e.from_id),
+ e.from_id <> 0,
+ (SELECT u.user_logo FROM users AS u WHERE u.id = e.from_id),
(SELECT u.user_logo FROM users AS u WHERE u.id = e.user_id)
- ) AS user_logo,
+ ) AS user_logo,
if (
- e.from_id <> 0,
- (SELECT u.fio FROM users AS u WHERE u.id = e.from_id),
+ e.from_id <> 0,
+ (SELECT u.fio FROM users AS u WHERE u.id = e.from_id),
(SELECT u.fio FROM users AS u WHERE u.id = e.user_id)
- ) AS user_fio
- FROM user_object_events AS e
- WHERE user_id IN (" . implode(',', $agent_ids) . ") AND
+ ) AS user_fio
+ FROM user_object_events AS e
+ WHERE user_id IN (" . implode(',', $agent_ids) . ") AND
e.id = '$event_id'";
if ($event_type)
@@ -8952,6 +9679,8 @@ class CommonController extends ApiController
$task = [
'id' => intval($event['id']),
+ // Offset-free: id события — реальный e.id; различитель листинга — явный флаг (echo'ит mJW в CRUD).
+ 'is_external_listing_event' => $is_listing_task,
'chat_date' => isset($event['chat_date']) ? $event['chat_date'] : null,
'chat_position' => isset($event['chat_position']) ? $event['chat_position'] : null,
'user_id' => intval($from_id),
@@ -9533,6 +10262,8 @@ class CommonController extends ApiController
$task = [
'id' => intval($event['id']),
+ // Клиент/заявка — не листинг; флаг держим для единообразия формы ответа.
+ 'is_external_listing_event' => false,
'chat_date' => isset($event['chat_date']) ? $event['chat_date'] : null,
'chat_position' => isset($event['chat_position']) ? $event['chat_position'] : null,
'user_id' => intval($from_id),
@@ -9622,6 +10353,30 @@ class CommonController extends ApiController
$section = $data['section'];
}
if (!is_null($event_id)) {
+
+ // Offset-free: событие листинга различаем по явному флагу is_external_listing_event из запроса (echo'ит mJW).
+ // event_id = реальный id в external_listing_events (без смещения; коллизию с id своего объекта снимает флаг).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+ if (self::_request_flag($data, 'is_external_listing_event')) {
+
+ $listing_done = false;
+ if ($action == 'state' && !is_null($state)) {
+ $state = $state ? 1 : 0;
+ $listing_done = (bool)mysql_query("UPDATE `external_listing_events` SET `calendar_view` = {$state} WHERE `id` = " . (int)$event_id . \external_listing_events_scope_clause()); // scope агентства — нельзя тогглить чужое событие
+ }
+ // Действие cancel к событиям листинга неприменимо — флага cancel в их таблице нет.
+
+ $app->response->header('Content-Type', 'application/json');
+ $app->response->setStatus(200);
+ $app->response->setBody(json_encode([
+ 'success' => $listing_done,
+ 'user_id' => $user_id,
+ 'section' => 'objects',
+ 'tasks' => null,
+ ], JSON_UNESCAPED_UNICODE));
+ $app->stop();
+ }
+
if ($action == 'state') {
if ($object_id) {
$section = 'objects';
@@ -9736,6 +10491,33 @@ class CommonController extends ApiController
$section = null;
if (!is_null($event_id)) {
+ // Offset-free: событие листинга различаем по явному флагу is_external_listing_event из запроса (echo'ит mJW) —
+ // удаляем из external_listing_events по реальному id (event_id, без смещения).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+ if (self::_request_flag($get, 'is_external_listing_event')) {
+
+ $listing_where = "`id` = " . (int)$event_id . \external_listing_events_scope_clause(); // scope агентства — нельзя удалить чужое событие листинга
+ // Если фронт прислал и сам листинг (object_id = реальный el.id, флаг is_external) — сверяем принадлежность события.
+ if ($object_id && self::_request_flag($get, 'is_external'))
+ $listing_where .= " AND `external_listing_id` = " . (int)$object_id;
+
+ if (!mysql_query("DELETE FROM `external_listing_events` WHERE " . $listing_where)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+
+ $app->response->header('Content-Type', 'application/json');
+ $app->response->setStatus(200);
+ $app->response->setBody(json_encode([
+ 'success' => !$error,
+ 'user_id' => $user_id,
+ 'section' => 'objects',
+ 'tasks' => null,
+ 'errors' => $errors,
+ ], JSON_UNESCAPED_UNICODE));
+ $app->stop();
+ }
+
if ($object_id) {
$section = 'objects';
$sql = "DELETE FROM `user_object_events` WHERE `id` = '{$event_id}' AND `object_id` = '{$object_id}'";
@@ -9829,6 +10611,11 @@ class CommonController extends ApiController
$object_id = intval($data['object_id']);
}
+ // Offset-free: листинг и его событие различаем по явным флагам из запроса (echo'ит mJW), а не по диапазону id.
+ // is_external — объект-листинг (object_id = реальный el.id); is_external_listing_event — событие листинга (event_id = реальный el event id).
+ $is_listing_object = self::_request_flag($data, 'is_external');
+ $is_listing_event = self::_request_flag($data, 'is_external_listing_event');
+
$client_id = null;
if (isset($data['client_id'])) {
$client_id = intval($data['client_id']);
@@ -9898,7 +10685,58 @@ class CommonController extends ApiController
$document_id = $document['doc_id'];
}
- if ($object_id && $section == 'objects') {
+ // Хелперы событий найденных листингов парсера (offset-free, ключ — реальный el.id).
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
+ // Найденный листинг парсера: события живут в external_listing_events (ключ — реальный el.id, без смещения).
+ // Признак листинга/его события — явные флаги из запроса (echo'ит mJW); object_id/event_id — реальные id.
+ // При создании наружу возвращаем реальный id + is_external_listing_event=true (см. ответ ниже).
+ if ($object_id && $section == 'objects' && $is_listing_object) {
+
+ if ($event_type == 'deal' || $event_type == 'file') {
+ // На найденном листинге недоступны: сделка (сначала копирование в свои объекты) и прикрепление файла
+ // (read-only, clients_files к листингу не привязываем). UI оба пункта прячет; это серверный гард.
+ $error = true;
+ $errors[] = ($event_type == 'file') ? 'Прикрепление файлов к найденному листингу недоступно' : 'Сделка на найденном листинге недоступна';
+ } else if ($event_id && $is_listing_event && $event_type) {
+ // Правка существующего события листинга (набор полей — как у обычного объекта).
+ $listing_fields = [
+ 'schedule_date' => ($schedule_date === 'NULL') ? 'NULL' : "'" . $schedule_date . "'",
+ 'comment' => "'" . mysql_real_escape_string($comment) . "'",
+ 'address' => ($address === 'NULL' || $address === '') ? 'NULL' : "'" . mysql_real_escape_string($address) . "'",
+ 'name' => ($title !== '') ? "'" . mysql_real_escape_string($title) . "'" : 'NULL',
+ ];
+ if (!\update_external_listing_event((int)$event_id, $listing_fields)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+ } else if (!$event_id && $event_type) {
+ // Новое событие на листинге: ответственный — выбранный исполнитель либо автор (владельца у листинга нет).
+ $who_work_id = ($who_work_event > 0) ? $who_work_event : $user_id;
+ $inserted_id = \insert_external_listing_event(
+ (int)$object_id,
+ $who_work_id,
+ $user_id,
+ $event_type,
+ ($comment !== '') ? $comment : null,
+ ($address !== 'NULL' && $address !== '') ? $address : null,
+ ($schedule_date !== 'NULL') ? "'" . $schedule_date . "'" : null,
+ ($title !== '') ? $title : null
+ );
+ if ($inserted_id === false) {
+ $error = true;
+ $errors[] = mysql_error();
+ } else {
+ // Наружу id события — реальный e.id (offset-free); признак листинга вернём флагом is_external_listing_event.
+ $event_id = (int)$inserted_id;
+ }
+ } else {
+ // Несогласованные идентификаторы: правка события листинга требует флага is_external_listing_event при заданном event_id.
+ $error = true;
+ $errors[] = 'Некорректный id события листинга';
+ }
+
+ } else if ($object_id && $section == 'objects') {
$sql = "SELECT `id_add_user` FROM `objects` WHERE `id` = '$object_id'";
@@ -10557,7 +11395,8 @@ class CommonController extends ApiController
$tasks = [];
$history = [];
if ($section == 'objects') {
- $tasks = self::getTasks($app, ['object_id' => $object_id], null, true);
+ // Offset-free: для листинга прокидываем флаг is_external, чтобы getTasks читал external_listing_events.
+ $tasks = self::getTasks($app, ['object_id' => $object_id, 'is_external' => $is_listing_object], null, true);
$history = ClientsController::getHistory($app, ['object_id' => $object_id], null, true);
} else if ($section == 'clients') {
$tasks = self::getTasks($app, ['client_id' => $client_id], null, true);
@@ -10568,7 +11407,9 @@ class CommonController extends ApiController
}
foreach ($tasks as $key => $task) {
- if ($task['id'] == $event_id)
+ // Сверяем по реальному id и одинаковой природе события (листинг vs свой объект) — id коллизит между пространствами.
+ $same_kind = ((bool)$is_listing_object === !empty($task['is_external_listing_event']));
+ if ($same_kind && $task['id'] == $event_id)
$tasks[$key]['is_new'] = true;
else
$tasks[$key]['is_new'] = false;
@@ -10580,6 +11421,8 @@ class CommonController extends ApiController
'success' => !$error,
'user_id' => $user_id,
'event_id' => $event_id,
+ // Признак события листинга — наружу явным флагом (mJW echo'ит его обратно в правке/удалении/завершении).
+ 'is_external_listing_event' => $is_listing_object,
'event_type' => $event_type,
'section' => $section,
//'sqls' => $sqls,
@@ -10646,9 +11489,48 @@ class CommonController extends ApiController
if (isset($data['sub_id']))
$sub_id = intval($data['sub_id']);
+ // Offset-free: суб-комменты листинга различаем по явным флагам из запроса (echo'ит mJW), не по диапазону id.
+ $is_listing_event = self::_request_flag($data, 'is_external_listing_event');
+ $is_listing_object = self::_request_flag($data, 'is_external');
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
if (!is_null($section) && !is_null($event_id) && !is_null($sub_comment)) {
if ($section == "objects") {
- if (!empty($employee_id)) {
+ if ($is_listing_event || $is_listing_object) {
+
+ // Найденный листинг парсера: суб-комменты под section_id = 4, ключ — реальный el event id (без смещения;
+ // section_id = 1 коллидировал бы по event_id с суб-комментами задач своих объектов из user_object_events).
+ $real_event_id = (int)$event_id;
+ $real_listing_id = (int)$object_id;
+
+ // Изоляция компаний: суб-коммент можно писать только к событию листинга своего агентства
+ // (листинг общий → иначе запись коммента к чужой задаче по угадываемому event_id). Зеркало desktop addEventSubComment.
+ if (!\external_listing_event_in_scope($real_event_id, $user_id)) {
+ $error = true;
+ $errors[] = 'Нет доступа к событию листинга';
+ } else if (!empty($employee_id)) {
+ if ($sub_id > 0) {
+ $sql = "UPDATE user_sub_comments SET sub_comment='{$sub_comment}', user_for='{$employee_id}' WHERE id={$sub_id}";
+ } else {
+ $sql = "INSERT INTO `user_sub_comments` (`event_id`, `req_id`, `section_id`, `sub_comment`, `created_by`, `user_for`) VALUES ('$real_event_id', '$real_listing_id', 4, '$sub_comment', '$user_id', '$employee_id')";
+ }
+
+ if (!mysql_query($sql)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+ } else {
+
+ $sql = "DELETE FROM `user_sub_comments` WHERE `event_id` = '$real_event_id' AND `section_id` = 4 AND `created_by` = '$user_id'";
+
+ $sql2 = "INSERT INTO `user_sub_comments` (`event_id`, `req_id`, `section_id`, `sub_comment`, `created_by`) VALUES ('$real_event_id', '$real_listing_id', 4, '$sub_comment', '$user_id')";
+
+ if (!mysql_query($sql) || !mysql_query($sql2)) {
+ $error = true;
+ $errors[] = mysql_error();
+ }
+ }
+ } else if (!empty($employee_id)) {
if ($sub_id > 0) {
$sql = "UPDATE user_sub_comments SET sub_comment='{$sub_comment}', user_for='{$employee_id}' WHERE id={$sub_id}";
} else {
@@ -10744,10 +11626,19 @@ class CommonController extends ApiController
if (isset($get['sub_id']))
$sub_id = intval($get['sub_id']);
+ // Offset-free: суб-коммент листинга различаем по явному флагу is_external_listing_event из запроса (echo'ит mJW).
+ $is_listing_event = self::_request_flag($get, 'is_external_listing_event');
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
if (!is_null($section) && !is_null($event_id)) {
if ($section == "objects") {
if ($sub_id > 0) {
+ // Удаление по id корректно для любой таблицы (id суб-коммента — без смещения).
$sql = "DELETE FROM `user_sub_comments` WHERE id='$sub_id'";
+ } else if ($is_listing_event) {
+ // Найденный листинг парсера: суб-комменты под section_id = 4, ключ — реальный el event id (без смещения).
+ $real_event_id = (int)$event_id;
+ $sql = "DELETE FROM `user_sub_comments` WHERE `event_id` = '$real_event_id' AND `section_id` = 4 AND `created_by` ='$user_id'";
} else {
$sql = "DELETE FROM `user_sub_comments` WHERE `event_id` = '$event_id' AND `section_id` = 1 AND `created_by` ='$user_id'";
}
@@ -11780,7 +12671,7 @@ class CommonController extends ApiController
'is_deleted' => boolval($step['deleted'] == 1),
'max_days' => intval($step['days_step']), // Дней на этап
'branch' => intval($step['branch']),
- 'create_deal' => intval($step['create_deal']),
+ 'create_deal' => intval($step['create_deal']),
'prev_steps' => $prev_steps, // Предыдущие этапы, доступные для выбора
'prev_step_id' => $prev_step_id, // Предыдущий этап
'next_steps_list' => $next_steps_list, // Следующий этап после закрытия
@@ -13620,6 +14511,8 @@ class CommonController extends ApiController
$list[] = [
'id' => $item_id,
'event_id' => (int)$event['id'],
+ // Свой объект — не листинг; флаг держим для единообразия формы события календаря.
+ 'is_external_listing_event' => false,
'section' => 'objects',
'start' => date('Y-m-d H:i:s', strtotime($event['schedule_date'])),//date(\DateTime::ATOM, strtotime($event['schedule_date'])),
'start_date' => date('Y-m-d', strtotime($event['schedule_date'])),
@@ -13638,6 +14531,83 @@ class CommonController extends ApiController
$errors[] = mysql_error();
}
+ // События по найденным листингам парсера: лежат в отдельной таблице external_listing_events (ключ — реальный el.id).
+ // Offset-free: наружу event_id = реальный e.id, object_id = реальный el.id; различитель листинга — явные флаги
+ // is_external (объект) + is_external_listing_event (событие), которые mJW echo'ит обратно в CRUD-методы.
+ require_once $_SERVER['DOCUMENT_ROOT'] . '/engine/helpers/external_listing_events.php';
+
+ $sql = "SELECT `e`.`id`,
+ `e`.`user_id`,
+ `e`.`from_id`,
+ `e`.`external_listing_id`,
+ `e`.`create_date`,
+ `e`.`type`,
+ `e`.`address`,
+ `e`.`schedule_date`,
+ `e`.`comment`,
+ `e`.`calendar_view`,
+ `e`.`name`,
+ `el`.`address_text`
+ FROM `external_listing_events` AS `e`
+ LEFT JOIN `external_listings` AS `el` ON `el`.`id` = `e`.`external_listing_id`
+ WHERE `e`.`user_id` = '$user_id' AND
+ `e`.`schedule_date` IS NOT NULL AND " . implode (' AND ', $where);
+
+ if ($query = mysql_query($sql)) {
+ while ($event = mysql_fetch_assoc($query)) {
+
+ $type = null;
+ $is_viewed = false;
+
+ // Заголовок — адрес листинга; после TTL-удаления листинга адреса уже нет.
+ $nazv = !is_null($event['address_text']) ? $event['address_text'] : 'Объявление снято с публикации';
+ $nazv = str_replace('"', "'", $nazv);
+ $nazv = str_replace('"', "'", $nazv);
+ $title = $nazv;
+
+ if ($event['calendar_view'] != 0)
+ $is_viewed = true;
+
+ if ($event['type'] == 'call') {
+ $type = "call";
+ $title = "Звонок " . $nazv;
+ } else if ($event['type'] == 'meet') {
+ $type = "meet";
+ $title = "Встреча " . $nazv;
+ } else if ($event['type'] == 'show') {
+ $type = "show";
+ $title = "Показ " . $nazv;
+ } else if ($event['type'] == 'even') {
+ $type = "even";
+ $title = $event['name'] . " " . $nazv;
+ }
+
+ if (!is_null($type)) {
+ $item_id++;
+ $list[] = [
+ 'id' => $item_id,
+ // Offset-free: event_id = реальный e.id, object_id = реальный el.id; листинг помечен флагами ниже.
+ 'event_id' => (int)$event['id'],
+ 'section' => 'objects',
+ 'start' => date('Y-m-d H:i:s', strtotime($event['schedule_date'])),
+ 'start_date' => date('Y-m-d', strtotime($event['schedule_date'])),
+ 'start_time' => date('H:i:s', strtotime($event['schedule_date'])),
+ 'client_id' => null,
+ 'object_id' => (int)$event['external_listing_id'],
+ 'requisition_id' => null,
+ 'employee_id' => null,
+ 'title' => $title,
+ 'event_type' => $type,
+ 'is_viewed' => $is_viewed,
+ 'is_external' => true,
+ 'is_external_listing_event' => true,
+ ];
+ }
+ }
+ } else {
+ $errors[] = mysql_error();
+ }
+
// События по заявкам и клиентам
$where2 = $where;
$agency_id = \User::getUserAgencyID($user_id);