Распределение суммы по базе




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

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

    Также стоит обратить внимание на статью: Пропорциональное распределение в запросе . Решение в запросе любопытно тем, что при попытке уйти от метода «последней строки» мы попадаем на задачу вычисления «нарастающего итога». При большом количестве строк в базе распределения время запроса многократно возрастает.

    А вообще вопрос интересный: копеечный, но допускающий множество разнообразных решений.

    Reply
  2. Di_Denis

    Я вот так делал при распределении

    СтрокаСМаксимальнойОшибкой=Неопределено;
    
    МаксимальнаяОшибка=0;
    
    К=СуммаОплаты/СуммаБазы;
    
    Для каждого Данные Из Выборка Цикл
    
    СуммаБезОкругления=Данные.База*К;
    
    СуммаСокруглением=Окр(СуммаБезОкругления, 2);
    
    Ошибка=СуммаСокруглением-СуммаБезОкругления;
    
    Ошибка=?(Ошибка<0, -Ошибка, Ошибка);
    
    Если Ошибка>МаксимальнаяОшибка Тогда
    
    СтрокаСМаксимальнойОшибкой=Данные;
    
    МаксимальнаяОшибка=Ошибка;
    
    КонецЕсли;
    
    Данные.Сумма=СуммаСокруглением;
    
    КонецЦикла;
    
    Разница=СуммаОплаты-Выборка.Итог(«Сумма»);
    
    Если Разница<>0 Тогда
    
    СтрокаСМаксимальнойОшибкой.Сумма=СтрокаСМаксимальнойОшибкой.Сумма+Разница;
    
    КонецЕсли;
    

    Показать

    Reply
  3. starik-2005

    (1) ildarovich, а кто ж спорит-то?

    Reply
  4. starik-2005

    (2) Di_Denis, тоже вариант, просто вычислений больше.

    Reply
  5. starik-2005

    (1) ildarovich, кстати, я зашел в указанную статью и описанный метод предлагает дополнительное упорядочивание по базе. В принципе разумный подход.

    Reply
  6. Chrizt
    рекомендую курс для ИТ-менеджеров в открытом университете, который там сейчас находится в открытом доступе.

    По запросу «Открытый университет», гугл мне выдал кучу открытых университетов, но я так и не нашёл ни на одном из них вышеозначенного курса в открытом доступе. Можно чуть подробнее, пожалуйста.

    Процедура РаспределитьСуммуПропорциональноБазе(Таблица, ИмяКолонкиБазы, ИмяКолонкиДляРаспределения, Сумма)

    Я несколько язапутался:

    • «ИмяКолонкиБазы» — я так понял, в Вашем случае, это количество, верно?
    • «ИмяКолонкиДляРаспределения» — а это что?
    • «Сумма» — а это сумма, которую необходимо распределить, я правильно понял?

    навеян мне целочисленным алгоритмом построения линии

    Можно, пожалуйста, ссылку на этот алгоритм? Интересуюсь.

    Reply
  7. starik-2005

    (6) Chrizt, открытый университет «ИНТУИТ». Забыл, что их сейчас как мышей на мельнице…

    По поводу колонки — в моем случае именно так, но эта функция универсальная и никто не вправе требовать от Вас распределения именно по количеству, а не по стоимости, например. Главное, чтобы колонка была числового типа.

    ИмяКолонкиДляРаспределения — это то, куда мы будем добавлять распределяемую сумму. Она не обязательно будет равна колонке базы.

    Сумма — да, именно то, что нужно распределить по базе.

    По поводу ссылки — не знаю, существует ли она в природе, но была замечательная книжка, которую я издалека видел классе в 7-м. За пару вечеров тогда, помнится, на основании мельком вычитанных представлений о том, как можно сделать все целочисленными методами, написал на maсro 11 (см в вики, что это) простенький алгоритм. Суть в том, что определяется бОльшее смещение, потом из его половины вычитается меньшее и курсор сдвигается в сторону бОльшено смещения. Если появился флаг знака (вычли так, что стало меньше нуля) — к текущему значению смещения добавляем первоначальное и сдвигаем курсор в сторону меньшего смещения. Ну и каждый раз не забываем ставить точку (включая точку начала линии).

    Reply
  8. gortol

    спасибо за алгоритм, я похожим занимался, и на практике приходилось чуть ли не 2м проходом ПРОПОРЦИОНАЛЬНО общему количеству распределять суммы, те не просто первые 3 строчки +1 копейка, а именно если 30 строчек, значит на 10,20,30 строчке +1 копейка, ну как то так…

    Reply
  9. starik-2005

    (8) gortol, да всегда пожалуйста.

    Reply
  10. insurgut

    Спасибо за алгоритм, особенно за универсальность процедуры и возможность указания столбцов базы и распределения отдельно 🙂

    Reply
  11. starik-2005

    (10) да всегда пожалуйста. А по поводу процедуры, то, если я правильно помню, в ней есть некоторая неточность, но пытливому уму оное не проблема;)

    Reply
  12. Jhormungand_1C

    Сергей, добрый день.

    Спасибо за статью, очень интересная. Но как быть, если СуммаКРаспределению = 0 после округления до двух символов после запятой, как в моем случае? Столкнулся на практике с такой ситуацией, что в итоге СуммаКРаспределению равна 0 из-за ситуации, как на скриншоте и распределить не получается.

    Reply
  13. starik-2005

    (12) ну а чем ноль плох? Сумма распределения не уменьшится, а сумма базы уменьшится на столько незначительно, что в этом нет ничего такого. В других строках все встанет на место.

    Reply
  14. batsy66

    (13)

    м ноль плох? Сумма распределения не уменьшится, а сумма базы уменьшится на столько незначительно, что в этом нет ничего такого. В других строках все встанет на место.

    При распределении незначительного числа, типа 0,01, оно пропадает совсем

    Reply
  15. starik-2005

    (14)

    При распределении незначительного числа, типа 0,01, оно пропадает совсем

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

    Reply
  16. batsy66

    (15)

    Сумма к распределению: 0,01.

    Вроде всё правильно посчитал. Уменьшаем с каждым обходом базу и остаток суммы и пересчитываем коэффициент. Либо подскажите, что не так, либо не бросайтесь обвинениями попусту.

    Reply
  17. starik-2005

    (16)

    подскажите, что не так

    Так просто же все. Когда вычитаете из базы распределившуюся сумму — округляйте ее до второго знака. Вот есть у Вас 0,01 как сумма распределения и база 100 000. Первая строка — 50 000, вторая строка 30 000, третья строка 20 000. В итоге распределили на первую строку 0,01 / 100 000 * 50 000 = 0,005. Добавили к ней — получили 50 000,005, но сумма до второго знака у нас, что приводит к тому, что у нас 50 000,01 после округления. Распределенная сумма 0,01, остаток — 0,00. Все. Если же первую сумму распределить 20 000, то 0,01 / 100 000 * 20 000 = 20 000,002, округлили — 20 000,00, вычли из базы, в итоге база 80 000, сумма распределения опять 0,01. Дальше на 30 000, что даст 0,01 / 80 000 * 30 000 = 0,00375, снова округлили — опять ничего не распределили. Дальше база опять уменьшилась и стала 50 000, сумма распределения опять 0,01, сумма в строку последней 50 000, в итоге 0,01 / 50 000 * 50 000 = 0,01, в итоге в последней сроке 50 000,01.

    Если же не округлять, то будет 0,01 — 0,005 сумма распределения, 80 000 — сумма базы. дальше 0,005 — 0,001875 сумма распределения, 50 000 — сумма базы.

    Вообще, Вы в своей эксельке только сумму полученную округляете. Или ничего не округляйте, или все — иначе ничего не получится.

    Reply
  18. batsy66

    (17) Перепроверил расчет, ошибся в таблице. По итогу копейка ушла на первую строку. Округление в итоговой колонке нужно было для имитации поля регистра бухгалтерии (число (15,2)), куда должна сумма упасть

    Reply
  19. starik-2005

    (18) прикрепил картинки, как у меня с учетом округления.

    Reply

Leave a Comment

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