[ПОЛЕЗНЯШКА, 7.7] Как посчитать итоги по документам черным запросом с изподвывертом?




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

    Для 1sqlite как то так это выглядит

    SELECT sum(S.Сумма) AS Сумма, count(*) AS Счетчик

    FROM

    (SELECT sum(ДР.Сумма) AS Сумма

    FROM Журнал AS Ж

    JOIN [ДокументСтроки.Реализация] AS ДР ON ДР.IDDOC=Ж.IDDOC

    WHERE Ж.IDDOCDEF=:ВидДокумента.Реализация AND Ж.DATE BETWEEN :НачДата AND :КонДата

    GROUP BY Ж.IDDOC) AS S

    Reply
  2. Djelf

    А 1с`запросом красиво получилось. Не видал пока такого! Спасибо.

    Reply
  3. CheBurator

    Спасибо

    Потестю на досуге если соображу как этот текст всунуть в 1sqlite

    Reply
  4. CheBurator

    (2) спасибо на добром слове

    Тут фишка над которой я малость поломал голову в условии когда — в результате получилось красиво потому что получается типа

    Документ1 номерстроки0

    Или

    Документ2 номерстроки1

    Документ2 номерстроки2

    Reply
  5. CheBurator

    (2) у меня вот просьба если можно

    Можешь ли ты посмотреть в какой скульный запрос транслируется этот черный запрос — интересно мне. А у меня в округе нет скульных баз семерочных да и со скулем чтобы посмотреть во что это превратилось — я не дружу

    ??

    Reply
  6. Djelf

    (5) MS SQL не использую ;(

    И я не верю что превратится в то же самое…

    На дбф 1с переваривает такой запрос перебором, т.е. сначала дергает документы, а потом по каждому дергает табличную часть.

    Это видно в строке состояния. Красиво, но по скорости — гадость!

    А тут четкое попадание в яблочко!

    первый этап WHERE Ж.IDDOCDEF=:ВидДокумента.Реализация AND Ж.DATE BETWEEN :НачДата AND :КонДата

    попадает в индекс DOCTYPE — IDDOCDEF,DATE,TIME,IDDOC, по трем первым позициям

    второй этап JOIN [ДокументСтроки.Реализация] AS ДР ON ДР.IDDOC=Ж.IDDOC

    попадает в индекс IDLINE — IDDOC,LINENO, по 1й позиции

    дальше 2 свертки, а 1sqlite делает это безумно быстро

    Ха… 1С делает то же самое, судя по виду выполнения, но почему то значительно медленнее 😉

    имхо, быстрее выдумать уже некуда!

    З.Ы. Этот запрос и для MS SQL должен сработать с минимальными изменениями.

    Reply
  7. CheBurator

    (6) а на чем у тебя клюшки крутятся псли не на скуле?

    Reply
  8. CheBurator

    (6) спсб за пояснения

    Reply
  9. Djelf

    (7) А у меня 1C на Ubuntu в основной конторе 😉 И терминальный доступ.

    Дисковые операции отдыхают по нагрузке, правда formex грузит не слабо, но ресурс проца еще есть т.е. не критично.

    MSSQL на отдельном сервере был, Selta@etersoft на PostgreSQL пробовался, но видимо dbf мое все!

    И 1sqlite не работает с таблицами mssql, починить то можно, но нет стимула 😉

    Reply
  10. Il

    (6) Djelf, можно использовать класс http://infostart.ru/public/20823/, так вообще будет универсально под скуль и 1sqlite, вот только под связку постгри и этерсофт не пробовал.

    Reply
  11. chel-new

    (5) если еще интересно:

    declare @p1 int
    set @p1=180150017
    declare @p3 int
    set @p3=2
    declare @p4 int
    set @p4=1
    declare @p5 int
    set @p5=-1
    exec sp_cursoropen @p1 output,N’sel ect convert(datetime, substring(_1SJOURN.DATE_TIME_IDDOC, 1, 8)), substring(_1SJOURN.DATE_TIME_IDDOC, 9, 6),
    _1SJOURN.IDDOCDEF, _1SJOURN.IDDOC,DT1611.SP1604 ,0,DT1611.SP1604,DH1611.SP1583,DT1611.LINENO_
    fr om  _1SJOURN(NOLOCK) left outer join DT1611(NOLOCK) on _1SJOURN.IDDOC=DT1611.IDDOC join DH1611(NOLOCK) on _1SJOURN.IDDOC=DH1611.IDDOC
    where _1SJOURN.IDDOCDEF = 1611 and _1SJOURN.DATE_TIME_IDDOC >= »20160201     0     0   » and _1SJOURN.DATE_TIME_IDDOC <= »20160229FHML6O     0   »
    and _1SJOURN.CLOSED&1 = 1 and (((DH1611.SP1583 =»   4NG   »)))’,@p3 output,@p4 output,@p5 output
    select @p1, @p3, @p4, @p5

    Показать

    если честно нифига не нашел где Счётчик и где проверка на номер строки, похоже это уже 1Ска сама делает…

    Reply
  12. premierex

    (0) а во такое условие Функция КоличествоДокументов = Счётчик() Когда(Рейтинг<2); зачем? Если в документе нет строк, то он тоже скорее всего, попадёт в результат и увеличит количество документов, хотя по сумме ни в какой результат не попадет.

    P.S. Так, мелкая придирка, давно на 7.7 не писал, но код, как ни странно, ошеломления не вызвал )))

    Reply
  13. CheBurator

    (12) Да, все верно. Запрос считает максимально «широко» — без ограничений на проведенность/непроведенность/удаление документа. В т.ч. и без ограничений по товароному составу (количеству строк документа). Если надо считать ТОЛЬКО ДОК С ЗАПОЛНЕННОЙ ТЧ (подробно обсасывали на форуме, по следам которого и сделал эту минипубликашку), то Функция КоличествоДокументов = Счётчик() Когда(Рейтинг=1);

    Reply
  14. CheBurator

    (12) Опять же — это как пример. Кто-то захочет посчитать количество документов, где нет ТЧ (например, ПКО) — поправит реквизиты выборки и все. А если НомерСтроки=1 — то ничего и не посчитает. А с исходным условием — посчитается норм.

    Reply
  15. leov-001
    SET NOCOUNT ON
    SELECT
    SUM($docsReal.Сумма) as Сумма
    , COUNT(j.IDDOC) КоличествоДокументов
    FROM
    _1SJOURN as j WITH (NOLOCK)
    LEFT JOIN $Документ.Реализация as docReal WITH (NOLOCK) on docReal.IDDOC = j.IDDOC AND $docReal.Контрагент = :ВыбКонтрагент
    INNER JOIN $ДокументСтроки.Реализация as docsReal WITH (NOLOCK) on docsReal.IDDOC = docReal.IDDOC
    WHERE
    j.IDDOCDEF=$ВидДокумента.Реализация
    AND j.DATE BETWEEN :ДатаН AND :ДатаК
    AND j.CLOSED&1 = 1
    GROUP BY j.IDDOC
    

    Показать

    Reply
  16. AlexO

    Немножко корявый запрос )

    И нужно уж тогда

    |ОбрабатыватьДокументы ВСЕ;

    иначе по умолчанию — учитываются только проведенные. Также, если понадобится сортировка — обязательно нужно получить поле

    |Док = Документ.Айфономания.ТекущийДокумент;

    И функция СЧЁТЧИК() — далеко не универсальная и кривая: работает не всегда так, как ожидается, и только при наличии поля выбора хотя бы по одному реквизиту.

    А их у документов общих может и не быть вовсе.

    Если нужен действительно универсальный запрос — то использовать СУММА():

     ТекстЗапроса=»
    |ОбрабатыватьДокументы ВСЕ;
    |Период С НачДата По КонДата;
    |Рейтинг = Документ.Реализация.НомерСтроки;
    |Функция КоличествоДокументов = Сумма(1) Когда(Рейтинг<2);
    |Группировка Документ;
    //|Условие (Контрагент = ВыбКонтрагент);
    |»;

    В остальном — идея с рейтингом хорошая, но не совсем верная: если у документа вообще нет ТЧ — происходит ошибка и аварийное завершение работы при попытке получения НомерСтроки.

    Reply
  17. AlexO

    (2) потому и не видал, что идея из 8-шного пришла )))

    Reply
  18. CheBurator

    (17) с учетом того, что я внутрь восьмерки заглядывал всего пару раз и вообще неа 8-ке не программлю — то какие там идеи внутрях — мне не особо ведомо.. 😉

    Reply

Leave a Comment

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