Почти динамическая группировка по периоду в СКД




Принцип обмена данными из 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='\

18 Comments

  1. Yashazz

    Я бы это делал вычисляемыми полями…

    Reply
  2. slazzy

    (1) Yashazz, можно и полями, собственно какая разница? 🙂

    Reply
  3. echo77

    Плюс за идею

    Reply
  4. Lyns_owner

    Я уже писал подобную статью, а почитав комментарии к ней, можно написать отличный отчет — http://infostart.ru/public/125216/

    Самым правильным вариантом считаю составление текста запроса с учетом переданного параметра (задавать периодичность виртуальной таблицы) с последующей передачей внешнего набора данных (таблицы) в макет СКД.

    Как-то так:

    «ВЫБРАТЬ
    | ВирутальнаяТаблица.Поле1 КАК Поле1,
    | ВирутальнаяТаблица.Поле2 КАК Поле2,
    | НАЧАЛОПЕРИОДА(ВирутальнаяТаблица.Период, » + Периодичность + «) КАК Период,
    |ИЗ
    | РегистрНакопления.ВирутальнаяТаблица.Обороты(&НачалоПериода, &КонецПериода, » + Периодичность + «, ) КАК ВирутальнаяТаблицаОбороты»
    Reply
  5. Поручик

    Очень интересно. Не забыть попробовать переделать один отчет. Пытался несколько раз, но ничего путного не выходило

    Reply
  6. zqzq
    В СКД пользователь тоже может это сделать сам, но для этого ему надо изменять вариант отчета

    В настройках СКД можно включить группировки (строк, колонок, подчинённых строк) в быстрые пользовательские настройки. Тогда пользователь хоть период, хоть 2 периода, хоть любую другую группировку сможет убрать/добавить в отчет без изменения варианта. Без всякого программирования и порчи текста запроса.

    Reply
  7. slazzy

    (4) Lyns_owner, спасибо за ссылку, Вашу статью я не нашел при поиске. По поводу

    Самым правильным вариантом считаю составление текста запроса с учетом переданного параметра (задавать периодичность виртуальной таблицы) с последующей передачей внешнего набора данных (таблицы) в макет СКД.

    У меня только один вопрос. На каком основании Вы считаете, что этот метод «самый правильный»? Я вот так не считаю. К тому же далеко не всегда у нас виртуальная таблица оборотов используется в отчете.

    (6) zqzq, да можно, но всё равно это не так удобно, однако как вариант почему нет. Я же написал — это один из вариантов. Мне удобнее делать так, кому-то удобнее иначе 🙂

    Reply
  8. Lyns_owner

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

    К тому же далеко не всегда у нас виртуальная таблица оборотов используется в отчете.

    Я стесняюсь спросить, ЧТО вы еще собираетесь в отчете с вертикальной группировкой по периоду? Регистр сведений? Может вообще дату документов? Опять же возвращаемся к разговору про быстродействие.

    Reply
  9. slazzy
    Reply
  10. Lyns_owner

    (9)

    Мы пишем не на С++ и 1С сама по себе довольно медлительна и много где теряет производительность

    А вам сказать, благодаря кому она такая медленная? или Сами догадаетесь? А тестировали вы на каких данных? Сколько строк в результирующем запросе? 10?

    Программист пишет не так, чтобы это было легко сделать (очень близко к определению понятия «говнокод»), а так, чтобы это работа правильно и максимально быстро.

    Да извольте анализировать процедуры, написанные автором. Все правильное реализуется непросто, а что реализуется просто — то поделка школьника. Возьмите к примеру запросы вычисления страховых взносов в ЗУПе — их тупо листать устанешь, не то что разбирать. А на первый взгляд, там сложного ничего нет — есть база и есть процент))

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

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

    Но дело ведь не в этом, правда? 🙂

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

    Reply
  11. slazzy

    (10),

    А еще ваши ответы характеризуют вас как некомпетентного специалиста.

    Пойду поплачу.

    А теперь по делу. Сделал регистр сведений, добавил туда пару полей и одно из полей Дата, добавил в него 510к строк. Судя по плану запроса вычисление периода тратит 1% от общего времени(ориентировочно)



    Такие дела.

    Да и, к слову, в 1С есть стандарты разработки, которые ставят качество восприятия кода на одну ступень с его быстродействием, за редчайшим исключением вроде процедур проведения, где каждая секунда важна.

    И если по Вашему стоит из-за любого чиха обработку переносить в модуль, вместо СКД и считаете это правильным, то мне надо Вас огорчить. Сказать почему? Ну например СКД динамически формирует запрос и отборы в СКД автоматически переносятся в запрос тоже… Может случиться такая ситуация, что пользователю будет нужна одна строка, а вы создадите свою таблицу из миллиона записей в модуле и СКД всё равно отберет одну строку….поспорим о производительности? Или будете все допустимые отборы выносить на форму и обрабатывать в своем запросе? СКД автоматически работает с характеристиками. Но главная причина спора и производительность это тема тоже сложная. Я например абсолютно убежден, что сделать запрос на миллион строк и выгрузить его в ТЗ, а потом передать как внешний источник данных в СКД — нереально медленная операция, тут потери производительности на создание ТЗ просто громадны и они даже близко не сопоставимы с мизерными потерями на расчет периода в строке.

    Да и просто анализировать единый запрос в СКД куда правильнее чем раскидывать код по модулям без причин.

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

    (10), я надеюсь на этом мы закончим нашу беседу, мне она не очень интересна, извините уж 🙂

    Reply
  12. alest
    Ну например СКД динамически формирует запрос и отборы в СКД автоматически переносятся в запрос тоже…

    Ну а зачем трюк с нуллом? Выбрал в запросе сразу поля, а скд откинет лишнее. Она даже соединения таблиц убирает, если поля не выбраны в настройках.

    Прошу прощения, не так понял, вы не хотели заморачиваться с выбором вариантов…

    (10) Lyns_owner, я тоже считаю, что ваш вариант с программным составлением явно не для СКД. Может вы вообще все за СКД сделаете и отдадите ей только те поля в запросе, которые в конкретном варианте настроек будут, да еще и отборы сразу наложите?

    Reply
  13. slazzy

    (12) alest,

    Ну а зачем трюк с нуллом? Выбрал в запросе сразу поля, а скд откинет лишнее. Она даже соединения таблиц убирает, если поля не выбраны в настройках.

    Хороший вопрос 🙂 нет тут дело немного в другом, в моем примере я именно «выбираю все поля», но те, где NULL, автоматически отсекаются. То есть если бы там не было NULL, они бы вывелись.

    Вы же говорите про другое, то есть я гипотетически могу создать 4 поля(для 4-х периодов) и выводить только одно из них, а остальные сами отсекутся. Да, это так, но для этого надо вручную менять структуру отчета, чтобы включить только нужные поля. Я же хотел именно чтобы к структуре отчета пользователь даже не подходил. Но, справедливости ради, это и правда довольно нетипичный метод и в данном случае он излишне сложный 🙂 я его показал просто как пример работы с NULL.

    Очень похожий способ используется в типовых конфигурациях, когда идет работа с таблицей ОстаткиИОбороты и надо вывести регистратор.

    Reply
  14. dabu-dabu

    Если используется виртуальная таблица Обороты или ОстаткиИОбороты, то можно просто поставить параметр компоновки, например так:

    ВЫБРАТЬ
    ПродажиОбороты.Период,
    ПродажиОбороты.Контрагент,
    ПродажиОбороты.СтоимостьОборот
    ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, ДЕНЬ{(&Периодичность)}, ) КАК ПродажиОбороты
    Reply
  15. slazzy

    (14) dabu-dabu, а Вы не могли бы поподробнее показать? Дело в том, что я попробовал так сделать. Передаю параметр, например, «Месяц», а всё равно период берется как день, что и логично в общем-то…у меня не получилось сделать корректный разворот по динамическому периоду

    Reply
  16. dabu-dabu

    (15) Тип формируемого параметра не надо менять, либо установить тип число. Если я не ошибаюсь, значения следующие: 0 — период, 1 — запись, 2 — регистратор, 3 — секунда, 4 — минута и т.д.

    Список доступных значений, при этом, устанавливать не обязательно. СКД как-то сам понимает, что это периодичность.

    Reply
  17. Ibrogim

    Интересен второй метод, автору +

    Однако к каждому измерению(квартал,месяц, … ) выводится Итог, а если в «других настройках» для измерений установить Расположение итогов = нет, то почему то не выводится ресурс…

    Возможно Я где то напутал конечно…

    Reply
  18. taasha25

    Спасибо!!!

    Reply

Leave a Comment

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