Пропорциональное распределение в запросе




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

19 Comments

  1. kvikster

    а если нужно распределить сумму 10 на 3 одинаковых документа с одинаковым долгом 3 ?

    Reply
  2. PythonJ

    (1) kvikster,

    Как понять «Распределить на три одинаковых документа»? Если документы (или договоры) с одинаковой суммой, но разными кодами, то излишек распределится в порядке очередности кода на сколько хватит. А если это одинаковые документы, то в чем тогда смысл распределения? Исходная таблица по определению не должна содержать одинаковые значения документа. Если они одинаковые, то их нужно сгруппировать до распределения. В конечном итоге предполагается получить движения, где документ — измерение, а распределенная сумма — ресурс. Какой смысл в движениях с разными ресурсами, но одинаковыми измерениями?

    Поясните или приведите пример задачи, если я неправильно понял.

    Reply
  3. ildarovich

    Для полноты картины не хватает ссылки на основательную статью по этой теме : «Честное распределение суммы по таблице значений». Безотносительно техники решения в указанной статье сравниваются разные принципы отнесения ошибок округления на конкретные строки базы распределения. Из статьи, кажется, следует, что упорядочивание по величине долга — не лучший метод. Мне кажется, что лучше упорядочивать по величине ошибки округления. Тогда будет работать критерий минимакса ошибки представления.

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

    Но по сравнению с отнесением всей невязки на одну строку (что приводит к еще более простому запросу) этот запрос получше.

    Reply
  4. PythonJ

    (4) Dimel, Действително плохо искал. Такое простое и изящное решение, мог бы и сам додуматься. Можно ссылку, откуда это? Здесь на Инфостарте есть? Хотелось бы добавить в статью, но без ссылки на источник некрасиво будет.

    Reply
  5. PythonJ

    (3) ildarovich, Отличная статья. По хорошему, нужно не ссылку сюда добавлять, а вообще статью с публикации снять. Как считаете, имеет смысл реализовать пакетными запросами все описанные там варианты, а не только вариант 4, и сравнить производительность на тестовой базе? Вариант с распределением по ошибке округления точнее, но приведет к лишнему этажу в пакетном запросе, что скажется на скорости. Я так понимаю, что 3 вариант реализован в комментарии (4) Dimel

    Reply
  6. Brawler

    (6) иной раз изящность решения задачи (ну скажем выпендрился и написал распределение в запросе) нафиг не нужно из-за своей сложности восприятия через некоторое время.

    Я сторонник не пихать все и вся в запросы без необходимости. Да и на практиче возникают ситуации и у меня были, когда нужно распределить 4 числа (БУ НУ ПР ВР) параллельно по 4-м базам (БУ НУ ПР ВР) и потом еще вывести погрешности на «чистую воду» и все проровнять (решалась задача постатейного закрытия 20, 23 счетов в БП 2.0, где субконто СтатьяЗатрат не оборотное, а потом все переделывалось под БП 3.0).

    К слову в БП 2.0/3.0 стандартные алгоритмы некорректно распределяют амортизацию, когда она распределяется по способам отражения, где очень много прописано разных дробных коэффициентов. Ошибка выражается в том, что правило БУ = НУ + ПР + ВР не выполняется, а должно. Пришлось написать обработку выравнивающую положение дел уже после расчета и распределения амортизации.

    Увы, но переписка с 1С ни к чему не привела, они тупо отписались, что не смогли воспроизвести ошибку… хотя все предоставлял им…

    Reply
  7. AlX0id

    (6)

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

    Reply
  8. PythonJ

    (7) Brawler, Все зависит от задачи. Пихать в запросы все подряд смысла нет, когда объемы обрабатываемых данных относительно невелики. Или когда задача разовая. У меня речь идет об огромных массивах, обрабатываемых ежедневно. Речь идет о конфигурации для ЖКХ и распределении оплаты коммунальных услуг по статьям по каждому лицевому счету. «Не смогли воспроизвести ошибку» — обычное дело 🙂

    Reply
  9. Dimel

    (5) В 2012 году было обсуждение на Мисте http://www.forum.mista.ru/topic.php?id=616801.

    Reply
  10. orfos

    Спасибо! Аналогичную задачу решал на днях (распределение 20-х счетов). Запросом круче.

    Reply
  11. urrymca

    За вот — такие решения в рабочих конфигураций хочется бить по башке и крайне сильно.

    Желаю вам приятного геммороя при раскапывании проблем, возникающих при вкрячивании таких запросов например в проведение документов (реверанс в сторону 1С).

    Reply
  12. PythonJ

    (12) urrymca,

    За подобные запросы в проведении документов действительно надо бить по голове. Все суммы должны быть рассчитаны до проведения и сохранены в табличной части документа. Динамический расчет при проведении запросом или кодом породит серьезную проблему при необходимости перепровести документы. Особенно если у них последовательность проведения изменилась. Причем здесь само решение? Задача поставлена распределить. Задача решена. А если кто-то вставит это в проведение — сам виноват. Я больше вам скажу, в той нетиповой конфигурации (за авторством КАМИН), с которой я в данный момент работаю именно так и было реализовано. Не запросом, правда, а кодом. Крайне запутанным, кстати. Но это еще полбеды. Эти ребята предусмотрели два варианта проведения. С распределением сумм до проведения и сохранения в табличной части с возможностью коррекции. И с распределением во время проведения. Мало того, что перепроведение такого документа было черевато. Так еще и код распределения был в двух разных местах продублирован. Распределение при проведении — в модуле документа. А распределение на форме — в модуле формы. И код этот чуть-чуть отличался. Видимо в какой-то момент исправили небольшую ошибку в одном из вариантов, а во втором — забыли. Что при этом происходило, думаю, объяснять не надо.

    А сам запрос прекрасно будет жить в рабочей конфигурации. Замечу, что при решении типовых задач никоим образом не будет лишним воспользоваться заранее придуманными алгоритмами. И это касается не только 1С. Если задача трудоемка и существует алгоритм, позволяющий увеличить скорость решения в разы, то им можно и нужно воспользоваться. На то он и существует. Это, кстати, сравнительно простое решение. Бывают гораздо более сложные. Разбираться в его коде при этом можно только один раз — когда первый раз увидел. Или вообще не разбираться, если источнику доверяешь. Главное знать, где его границы, где входные данные и где результат.

    Reply
  13. rozer

    (10)(10) Dimel, а зачем так сложно с тета-соединением и вычитать потом его ? Не догоняю… так тоже работает

    ВЫБРАТЬ
    «А» КАК Объект,
    100 КАК База
    ПОМЕСТИТЬ Данные
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
    «Б»,
    200
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
    «В»,
    1000
    
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    СУММА(Данные.База) КАК СуммаБазы
    ПОМЕСТИТЬ Итог
    ИЗ
    Данные КАК Данные
    ;
    
    ////////////////////////////////////////////////////////////­////////////////////
    ВЫБРАТЬ
    Данные.Объект,
    Данные.База,
    (ВЫРАЗИТЬ(&Сумма * Данные.База / Итог.СуммаБазы КАК ЧИСЛО(15, 2))) КАК РаспределеноПоБазе
    ИЗ
    Итог КАК Итог,
    Данные КАК Данные

    Показать

    Reply
  14. Dimel

    (14) rozer, Попробуй своим запросом распределить 10 на 3 равные части (куда у тебя ошибки округления денутся?).

    Reply
  15. rozer

    (15) Dimel, попробовал получилось 0.77+1.54+7.69=10 Но наверно да, ты прав, про округление я не подумал но как понять про использование ВТ «ДанныеСНакоплением» я так и не понял ( Чот мудреный запрос выходит… сам придумал или подсказал кто? Смысл можешь пояснить?

    Reply
  16. PythonJ

    (16) rozer, Смысл заключается в том, что ошибка округления накапливается и учитывается по мере распределения. Попробуй расписать процесс в числах с распределением, например, 20 на 7 одинаковых строк. Обрати внимание на распределение последней строки. Туда попадает весь остаток, независимо от доли. По аналогии происходит и в предыдущих строках.

    Reply
  17. mifka186

    (4) Спасибо очень помогло!

    Reply
  18. Wefast

    А что делать, если есть еще одна группировка. И нужно распределять внутри группировки

    Т.е. если добавить к примеру в задаче контрагента. И распределять долг контрагента по его договорам?

    Reply
  19. PythonJ

    (19) Лучше вообще не использовать приведенный в публикации метод. В обсуждении приведен другой вариант, более надежный https://infostart.ru/ajax/comment_pub.php?t=135893&c=4

    Reply

Leave a Comment

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