Простой механизм версионирования для нетиповых конфигураций, или зачем нужны утки и медведи




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

22 Comments

  1. Идальго

    Мне кажется, что правильнее говорить не о версионировании, а о записи в ЖР информации об измененных значениях реквизитов.

    Reply
  2. cargobird

    (1) Идальго, да, согласен с вами, версионированием это можно назвать с очень большой натяжкой, нулевая точка отсчета для последующей возможной его реализации.

    Reply
  3. cargobird

    Еще один возможный минус такой записи изменений. Поскольку порядок выполнения однотипных подписок на событие не определен, вполне возможно, что после записи в журнал регистрации изменения объекта, следующая подписка на событие ПередЗаписью вызовет Отказ и объект не запишется. А в журнал регистрации уже попала запись об изменении объекта. То, что это «ложное срабатывание», можно будет определить только наличием после события об изменении реквизитов объекта события об отмене транзакции…

    Reply
  4. maljaev

    В моем варианте версионирования:

    Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если (Не Отказ) И (Не ОбменДанными.Загрузка) Тогда
    ВерсионныйКонтроль.ЗапомнитьМодифицированность(ЭтотОбъект);
    КонецЕсли;
    КонецПроцедуры
    
    Процедура ПриЗаписи(Отказ)
    Если (Не Отказ) И (Не ОбменДанными.Загрузка) Тогда
    ВерсионныйКонтроль.СоздатьВерсию(ЭтотОбъект);
    КонецЕсли;
    КонецПроцедуры
    

    Показать

    Запоминается состояние документа в событии «ПередЗаписью», потом в событии «ПриЗаписи» (если не было отказов) сравниваются реквизиты и ТЧ целиком. Создается запись в специальном регистре сведений, туда записывается сам факт действия и все измененные реквизиты и ТЧ, упакованные в один пакет с максимальным сжатием. Потом можно сформировать как глобальный отчет по изменениям, так и по конкретному документу посмотреть/сравнить/откатить.

    Reply
  5. kapustinag

    (0) Сомнительное решение, по-моему. Журнал регистрации распухает очень быстрыми темпами в любой активно использующейся базе. А тут еще в него будут записываться довольно объемные записи. Причем, если такой механизм прикручен ко многим объектам, и их активно изменяют — таких записей будет много. В результате штатными средствами с Ж.Р. будет еще хуже работать.

    Может быть, если Ж.Р переделан на хранение во внешней SQL-базе — есть публикация на Инфостарте об этом — будет нормально работать.

    Reply
  6. cargobird

    (4) maljaev, спасибо, интересно посмотреть другие варианты.

    Reply
  7. cargobird

    (5) kapustinag, регистрация изменений установлена только на критичные объекты — на несколько ключевых справочников и на все товаросодержащие документы, поэтому пока существенного увеличения ЖР не ожидаем. За наводку на SQL-ный журнал спасибо, мысль хорошая, подумаю.

    Reply
  8. karapuzzzz
    Как видно из кода, значения реквизитов Источника, это то, что было ДО изменения, значения реквизитов Источник.Ссылка — это то, что БУДЕТ ПОСЛЕ записи изменения в случае, если объект таки будет записан.

    Скорее наоборот. Значение реквизитов Источника это то, что будет после принятия изменений. По ссылке мы обращаемся к записям БД, которые как раз и есть значениями до изменения

    Reply
  9. karapuzzzz

    Вместо журнала используйте регистр сведений, который можно периодически чистить. Отчет по записям регистра будет строиться несоизмеримо быстрее, чем анализировать записи журнала регистрации. Тем более, что подчищать регистр сведений опять таки проще.

    Reply
  10. cargobird

    (8) karapuzzzz, спасибо, посмотрю, может и правда перемудрил.

    Reply
  11. cargobird

    (9) karapuzzzz, ставилась цель без особых механизмов сделать простой инструмент для того, чтобы пользователь мог просматривать изменения документа. Поэтому решили по максимуму использовать штатные средства. Как я уже написал в (5), изменения регистрируются у нескольких справочников и документов, поэтому распухание ЖР скорее всего не грозит.

    Reply
  12. cargobird
  13. vde69

    во первых решение действительно «сомнительное», по тому как в реквизитах «ссылки» а не строки и там «моментов» много

    во вторых банально туповато сделано в цикле….

    я например вот так сделал (тут то же есть спорные моменты, но куда лучше чем дергать каждый реквизит отдельно):

    МассивНеПроверяемыхРеквизитов.Добавить(«ИмяПредопределенныхДанных»);
    МассивНеПроверяемыхРеквизитов.Добавить(«Предопределенный»);
    МассивНеПроверяемыхРеквизитов.Добавить(«Ссылка»);
    МассивНеПроверяемыхРеквизитов.Добавить(«ЭтоГруппа»);
    
    // проверяем, что в объекте действительно поменяли что-то важное
    Мета = Объект.Метаданные();
    Запрос = Новый Запрос;
    Запрос.Текст =
    «ВЫБРАТЬ
    | ИСТИНА КАК П
    |ИЗ
    | «+Мета.ПолноеИмя()+» КАК Т
    |ГДЕ Т.Ссылка = &Ссылка
    |  И (ЛОЖЬ»;
    
    Запрос.УстановитьПараметр(«Ссылка», Объект.Ссылка);
    
    
    Для Каждого мРек из Мета.Реквизиты Цикл
    мИмя = мРек.Имя;
    Если МассивНеПроверяемыхРеквизитов.Найти(мИмя) = Неопределено Тогда
    Запрос.Текст = Запрос.Текст + »
    |   ИЛИ Т.»+мИмя+» <> &»+мИмя;
    Запрос.УстановитьПараметр(мИмя, Объект[мИмя]);
    КонецЕсли;
    КонецЦикла;
    
    Для Каждого мРек из Мета.СтандартныеРеквизиты Цикл
    мИмя = мРек.Имя;
    Если МассивНеПроверяемыхРеквизитов.Найти(мИмя) = Неопределено Тогда
    Запрос.Текст = Запрос.Текст + »
    |   ИЛИ Т.»+мИмя+» <> &»+мИмя;
    Запрос.УстановитьПараметр(мИмя, Объект[мИмя]);
    КонецЕсли;
    КонецЦикла;
    
    Запрос.Текст = Запрос.Текст + «)»;
    
    Если Запрос.Выполнить().Пустой() Тогда
    Возврат; // изменений важных реквизитов не найдено….
    КонецЕсли;
    

    Показать

    Reply
  14. cargobird

    (13) vde69, спасибо за уточнение, посмотрю. Пользователю, правда, на форме доступны именно те реквизиты, которые могут на что-то повлиять, остальные можно не трогать. А Полные права всегда что-то меняют под флагом ОбменДанными.Загрузка = Истина, поэтому их изменения не регистрируются.

    Reply
  15. karapuzzzz

    (11) Если отбросить проблему распухания журнала регистрации, то остается еще 3:

    1. Формирование отчета. Можно просмотреть информацию по документу из ЖР, но сформировать отчет можно быстрее. Даже не так. НАМНОГО БЫСТРЕЕ. Отчет предоставить больше данных. Это и отбор по периоду документа (ЖР такого не даст), и удобочитаемая форма, и представление ссылок как ссылок с возможностью открыть элемент справочника (например, если наименование у двух элементов одинаковое).

    2. Разделение доступа. Человек, анализирующий записи может не иметь доступа на чтение ЖР и наоборот — человек, имеющий доступ к ЖР не должен иметь доступа к протоколу изменений (там же конкретные данные документов)

    3. Очистка данных. Если использовать ЖР, то вместе с чисткой протокола изменений будет очищен и журнал. И наоборот.

    Reply
  16. cargobird

    (15) karapuzzzz, у нас была задача максимальной простоты реализации и максимального удобства для пользователя с минимумом выводимой информации.

    Поэтому

    1. Пользуемся вариантом обработки Кто сидел на моем стуле и сломал его? (анализ изменений документа по журналу регистрации). При выборе документа автоматически ставится период с даты документа по текущую дату, в общем случае этого достаточно, период при желании можно изменить.

    2. В журнал регистрации никто не смотрит, т.к. большинство не умеют пользоваться отборами, работы хватает, не до этого и т.д.

    3. Сейчас мы наоборот, на этапе накопления информации, вопрос пока не стоит.

    В нашу специфику описанный метод вписывается вполне, осталось только сделать нормальный анализ таблиц.

    Reply
  17. karapuzzzz

    (16) специфика журнала регистрации предполагает выборку всех данных, а потом ее анализ. Отбор можно установить только на конкретный документ, на пользователя, на период записей в журнале. Это и все, если не считать еще несколько отборов не нужных пользователям. Остальные отборы только на уже готовую выборку. А это время. Это очень большая нагрузка на систему.

    Вы сейчас идете по принципу «система маленькая, нам хватает, развиваться не будем». Придет время рвать волосы и думать — чего раньше не сделали по уму. Я пытаюсь дать Вам понять, что реализация версионирования (протоколирования и т.п.) с использованием журнала регистрации это очень плохая идея. Многие настраивают перечень действий, вносимых в журнал регистрации, чтобы данных было минимум. Время от времени выгружают журнал и архивируют, оставляя только некий последний период. Все механизмы в системе стараются для ведения своего лога использовать свои таблицы, а не общую песочницу.

    Если Вы уже добавили подписку на событие и процедуру в одном из общих модулей, что мешает добавить регистр сведений? Подумайте о своих коллегах, которым после Вас поддерживать продукт и анализировать, почему база пухнет. Простой анализ дает понять, что ссылка занимает меньше места, чем представление ссылки, а каждая запись журнала регистрации помимо осмысленных данных хранит еще кучу системной информации о клиенте, подключении и т.п. Строить правильную систему надо сначала, когда база это позволяет.

    Reply
  18. cargobird

    (17) karapuzzzz, я с вами ни в коей мере не спорю, коллега, а только обрисовываю ситуацию какая у нас сейчас.

    Да, согласен, что в скором времени надо реализовывать более быстрый и оптимальный механизм.

    Если резюмировать ваши комментарии, и комментарии коллеги в (4) и (13) надо реализовать примерно следующее:

    1. В обработчике ПередЗаписью запомнить разницу между объектом и ссылкой, причем просматривать только необходимые реквизиты.

    2. В обработчике ПриЗаписи, если не Отказ, окончательно записать изменения в свой созданный регистр сведений, желательно в реквизит типа ХранилищеЗначения чтобы иметь возможность упаковки с максимальной степенью сжатия:

    Хранилище = Новый ХранилищеЗначения(Знач, Новый СжатиеДанных(9));

    3. Затем обработкой/отчетом извлекать полученные данные, конвертируя их в удобочитаемый вид.

    Так что есть над чем поработать, и спасибо всем за ценную информацию, это пригодится не только мне.

    Reply
  19. V.Nikonov

    (18) Есть ещё несколько соображений по поводу протоколирования:

    1) Далеко не всегда известен объект анализа! Например, часто требуется найти все Документы в которых изменено количество некоторого Товара… Соответственно, изменяется Табличная Часть документа, реквизит Количество, а поиск удобнее организовать по ссылке Номенклатуры. При сворачивании Таблицы изменений — поиск по дополнительным признакам резко осложняется.

    2) Штатное сравнение Табличных частей будет реагировать на изменение порядка строк в ТЧ! Частным случаем для накладных окажется изменение Единицы измерения с соответствующим пересчетом реквизита Количество…

    Для борьбы с первой проблемой — указывал в ЖР ссылку Номенклатуры (вместо изменяемого документа), при сохранении ВидаМетаданных

    Для борьбы со второй проблемой — особая ветка анализа ТЧ «Товары». Сравнивались Свернутые по измерениям (Номенклатура, Характеристика, Серия, Качество и т.п.) и приведенное к Единицам хранения количество… одновременно отбрасывались неактуальные реквизиты строк.

    Reply
  20. cargobird

    (19) V.Nikonov, да, предложенная в статье методика, что называется «просто и быстро», нулевой вариант. Отчета с отборами не получишь, а только фактическую информацию об изменениях всего чего можно. Изменения порядка строк в табличных частях выдадут большое количество изменений, среди которых нужное не найдешь и т.д.

    Сейчас работаю над тем, чтобы этот вариант работал более-менее корректно с табличными частями. А там и до регистра сведений с настройками реквизитов вообще и табличных частей в частности доберемся.

    За дельные советы спасибо)

    Reply
  21. cargobird

    Успешное использование механизма: менеджер случайно изменила в существующей позиции номенклатуры часть реквизитов — восстановили оригинал обратно в короткие сроки.

    Reply
  22. cargobird

    (4) maljaev, внедрил доп.контроль при записи, как у вас.

    Обнаружил такую штуку.

    Если отказ происходит в обработчике проведения (например, не заполнено количество), документ не записывается, но запись об изменении реквизитов все равно попадает в журнал регистрации (у вас — в механизм версионирования).

    Делать дополнительную подписку на событие ПриПроведении тоже смысла нет, т.к. порядок подписок не определен.

    Так что стопроцентного доп.контроля для документов, видимо, не сделать.

    А для справочников годится.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *