Продажи по оплате или в поисках истины…




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

24 Comments

  1. krava_vlad

    недавно делал что то подобное.

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

    Если заказ на 10 000

    отгрузка на 6 000

    Оплата на 4 000

    Валовая прибыль по отгрузке 1 500

    то есть премия начисляется от 1000 грн.

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

    А как ваш отчет посчитает прибыль если будут такие условия

    Если заказ на 10 000

    отгрузка на 6 000

    Оплата на 8 000

    Валовая прибыль по отгрузке 1 500

    Reply
  2. script

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

    Сумма заказа не учитывается, но сам заказ участвует в распределении оплаты по отгрузкам.

    Если было продано на 10 000, а возвращено на 2 000, тогда программа посчитает что продано на 8 000.

    Если отчет обнаружит оплату по данному контрагенту + договору + заказу, на 8000 и более, эти данные будут отображены в отчете, иначе — нет.

    Кроме того отчет не расчитывает прибыль по какому то своему алгоритму. Принцып полностью идентичен отчету Валовая прибыль.

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

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

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

    Reply
  3. gull22

    Читал как поэму, плюс.

    Reply
  4. ildarovich

    Задача интересная, комплексная. И распределение оплаты по FIFO и интерполяция курсов валюты. И решение, на мой взгляд, хорошее, практичное. Без вкусовщины. Что-то циклом, что-то запросом, что-то в СКД. Так на самом деле и нужно на практике. Ну а в теории комбинаций вариантов много и для предельного ускорения еще не все перепробованы.

    Немного оказалось в кучу все перемешано, но тут, чтобы все по полочкам разложить еще столько же времени, наверное, понадобится.

    Если обсуждать отдельно переоценку, то хочу спросить: а не файловая ли база была, когда решения не дождались? В SQL такого, кажется, быть не должно. По своей сути эта задача вроде бы не затратная по времени. Кроме того, этой проблемы бы не было вообще, если бы сначала построить и проиндексировать таблицу курсов валют на все возможные даты заданного диапазона. В худшем случае (без использования индекса по периоду для поиска даты актуального курса) должна быть примерно 730х730/2 трудоемкость. Это существенно меньше, чем 100000х730/2.

    Метод распределения оплаченности по номенклатуре нравится. Более того, я предлагаю такой же метод применять для распределения оплаты между документами и если их время совпадает (ссылка).

    Reply
  5. DAnry

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

    Reply
  6. script

    (4) ildarovich,

    Решил попробовать переписать отчет для того что бы загнать переоценку в запрос с индексированием ВТ.

    В первом случае переоценка выполняеться в запросе с применением временных таблиц и индексирования.

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

    База файловая = 4 ГБ. Перед формированием отчета выполнялся перезапуск приложения.

    Итоги при формировании отчета за один квартал:

    — переоценка в запросе с индексированием = 59 сек.

    — переоценка в цикле при обходе результата запроса = 60 сек.

    Итоги при формировании отчета за один год:

    — переоценка в запросе с индексированием = 6 мин. 35 сек.

    — переоценка в цикле при обходе результата запроса = 4 мин 25 сек.

    Получили замедление при использовании переоценки в запросе с индексированием ВТ.

    Можно добавить в статью как третий облом

    Reply
  7. ildarovich

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

    Reply
  8. script
    Запрос.Текст = «ВЫБРАТЬ
    | КурсыВалют.Период КАК Период,
    | КурсыВалют.Валюта КАК Валюта,
    | КурсыВалют.Курс,
    | КурсыВалют.Кратность
    |ПОМЕСТИТЬ КурсыВалют
    |ИЗ
    | РегистрСведений.КурсыВалют КАК КурсыВалют
    |
    |ИНДЕКСИРОВАТЬ ПО
    | Период,
    | Валюта
    |;
    |
    |////////////////////////////////////////////////////////////­////////////////////
    |ВЫБРАТЬ
    | КурсыВалют.Период КАК НачалоПериода,
    | МИНИМУМ(ЕСТЬNULL(КурсыВалютКопия.Период, ДАТАВРЕМЯ(3999, 1, 1))) КАК КонецПериода,
    | КурсыВалют.Валюта КАК Валюта,
    | КурсыВалют.Курс,
    | КурсыВалют.Кратность
    |ПОМЕСТИТЬ ТаблицаКурсов
    |ИЗ
    | КурсыВалют КАК КурсыВалют
    |  ЛЕВОЕ СОЕДИНЕНИЕ КурсыВалют КАК КурсыВалютКопия
    |  ПО (КурсыВалютКопия.Период > КурсыВалют.Период)
    |   И (КурсыВалютКопия.Валюта = КурсыВалют.Валюта)
    |
    |СГРУППИРОВАТЬ ПО
    | КурсыВалют.Период,
    | КурсыВалют.Валюта,
    | КурсыВалют.Курс,
    | КурсыВалют.Кратность
    |
    |ИНДЕКСИРОВАТЬ ПО
    | НачалоПериода,
    | КонецПериода,
    | Валюта
    |;
    |
    |////////////////////////////////////////////////////////////­////////////////////
    

    Показать

    Reply
  9. script

    Вот так использую. Пробовал и через ВНУТРЕННЕЕ СОЕДИНЕНИЕ, особенной разницы не заметил.

     |   РегистрНакопления.ПродажиСебестоимость КАК ПродажиСебестоимость
    |    ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаКурсов КАК КурсыВалютУпр
    |    ПО (КурсыВалютУпр.Валюта = &ВалютаУпр)
    |     И (КурсыВалютУпр.НачалоПериода <= ПродажиСебестоимость.ДокументОприходования.Дата)
    |     И (КурсыВалютУпр.КонецПериода > ПродажиСебестоимость.ДокументОприходования.Дата)
    |    ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаКурсов КАК КурсыВалютОтчета
    |    ПО (КурсыВалютОтчета.Валюта = &ВалютаОтчета)
    |     И (КурсыВалютОтчета.НачалоПериода <= ПродажиСебестоимость.ДокументОприходования.Дата)
    |     И (КурсыВалютОтчета.КонецПериода > ПродажиСебестоимость.ДокументОприходования.Дата)
    

    Показать

    Reply
  10. script

    Разницу, которуя описал это разница выполнения всего отчета, просто остальной алгортм не изменяется — изменяется только способ получения продаж. Запросом с переоценкой или двумя разными СКД, выгрузкой в ТЗ и переоценкой в цикле.

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

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

    Вариант 1: все сделано в одном запросе с индексированной таблицей. Пример постом выше.

    Результат (Левое соединение) 2 мин. 08 сек.

    Результат (Внутр соединение) 2 мин. 60 сек.


    Вариант 2: Отдельно работает СКД с таблицей регистра «ПродажиОбороты». Отдельно — с таблицей регистра «Продажи себестоимость». Далее результаты обеих СКД выгружаются в таблицы значений. Далее обычный цикл и переоценка. Далее, таблицы помещаются в МВТ и объединяются запросом и опаять выгружаются в ТЗ.

    Результат 1 мин. 15 сек.

    Удивительное рядом…

    Reply
  11. script
    Reply
  12. ildarovich
    Reply
  13. script

    (12) ildarovich,

    Правильно ли я понял?

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

    Получиться так, что таблица курсов будет иметь записи только за период продаж, а не за весь период до даты окончания отчета. Это хорошая идея — нужно пробовать. Спасибо.

    Reply
  14. itar59

    Хотелось бы уточнить — валютный пересчет идет по какому курсу: на день оплаты? на день выписки счета? на момент отгрузки?

    у нвс эти даты могут очень сильно (до полугода) отличаться

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

    Reply
  15. itar59

    да, и как насчет оплаты безналом от юр лица? в гривнах

    Reply
  16. itar59

    Попадут ли в отчет товары отгруженные РАНЕЕ отчетного периода, но оплаченные в текущем?

    Reply
  17. script

    В этот не попадут.

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

    Reply
  18. script

    (14) itar59,

    Пример:

    01.01.2014 — оплата = 1000

    01.02.2014 — отгрузка = 1500

    10.03.2014 — оплата = 500.00

    Действия отчета:

    Если период в отчете будет установлен такой, в который попадает дата 10.03.2014. данные попадут в отчет

    потому что именно в эту дату закрылась сделка.

    Сумма отгрузки будет переоценена в валюту отчета следующим образом:

    1500 будут разделены на две суммы 1000 и 500 соответственно.

    1000 грн переоценится на дату 01.01.2014 г.

    500 грн.переоценится на дату 10.03.2014 г.

    Здесь нужно пояснить что в отчет выводится и номенклатура, по этому разделить отгрузку нужно до номенклатуры.

    Но в оплатах нет данных какая номенклатура оплачена. Для этого применяется спец. прием.

    Считается что каждый платеж оплачивает всю номенклатуру из накладной, но не на все количество.

    Т.е. если в накладной будет одна строка с количеством = 1 шт. на сумму 1500.00, тогда программа составит соотношения по количеству платежей

    500 :1500 = 0,33333333 = 500 * курс на (01.01.2014)

    1000:1500 = 0,66666666 = 1000 * курс на (10.03.2014)

    В дальнейшем количество (1 шт.) и сумма (1500.00) будет умножена на эти коэффициенты по порядку (по дате платежа), Полученный результат и будет означать какая часть накладной оплачена. Именно эта часть будет переоценена на дату платежа.

    Себестоимость переоценится на дату партии. Если партий было несколько, тогда на дату каждой партии отдельно.

    Остальные показатели — как обычно.

    Reply
  19. script

    Как программ определяет когда сделка полностью закрылась.

    1. В отчете указывается некоторый период (01.01.2014 — 10.03.2014)

    2. На дату начала отчета (01.01.2014), порграмма получает сальдо по контрагенту, договору, сделке(если взаиморасчеы ведутся по заказам/счетам — иначе по пустой сделке). Данные выгружаются в таблицу.

    3. Далее, для комбинации КонтрагентДоговорСделка, программа получает все «Оплаты», по контрагентам, договорам и сделкам из таблицы в п.1. (за все периоды работы в программе по дату окончания отчета 10.03.2014) — это движения в рег. взаиморасчеты со знком «минус».

    4. Для комбинации КонтрагентДоговорЗаказ покупателяНоменклатура …. программа получает все «Продажи» (за все периоды работы в программе по дату окончания отчета 10.03.2014) — это движения в регистре «Продажи», по тому же принципу.

    Пункты 1-4 выполняются одним пакетны запросом и индексированием.

    5. После получения данных об отгрузках и оплтах, порграмма выполняет погашение отгрузок оплатами по ФИФО.

    6. Те даты, когда очередная отгрузка была полностью погашена очередной оплатой, программа запоминает как «ДатуЗакрытия»

    7. Кроме погашения по ФИФО алгоритм выполняет переоценку сумм и заполняет итоговую таблицу, которая содержит полностью оплаченные продажи, а также дату когда эта продажа была полностью оплачена (колонка «Дата закрытия»).

    8. Итоговая таблица подается процессору СКД, в котором наложен фильтр на колонку «Дата закрытия», данные которого (фильтра) беруться из даты начала и окончания отчета.

    Reply
  20. itar59

    Спасибо за ясный подробный ответ.

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

    как насчет оплаты безналом от юр лица? в гривнах

    Reply
  21. itar59

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

    Reply
  22. script

    (21) itar59,

    КАК РАБОТАЕТ УТП, УТ, УПП.

    Пример:

    — Приход — 01.01.2014 — Товар1 — Прих. накл1 — 1шт. — 2000грн.

    — Приход — 01.02.2014 — Товар1 — Прих. накл2 — 1шт — 200 EUR.

    — Продажа 10.02.2014 — Товар1 — Расх. накл1 — 2шт. — 5000грн.

    Валюта упр. учета установлена. USD.

    // ПОКУПКА

    При проведении прих. накл. в регистры Продажи и продажи себестоимость ничего не записывается.

    но записывается в регистр Партиит товаров упр. учет.

    — Приход — Прих. накл1 — товар1 — 1шт. = (2000грн / курс USD * кратность) на 01.01.2014 (дата прих1)

    — Приход — Прих. накл2 — товар1 — 1шт. = (200EUR пересчитается по крос курсу в USD) на 01.02.2014 (дата прих2)

    В итоге в регистре партии товаров в упр. учете будет содержаться информация о двух партиях товара1 в USD.

    //ПРОДАЖА

    При проведении расх. накл1, программа записывает в регистры:

    1.Партии товаров(упр.): списание товаров будет произмедено по готовой, рассчитаной ранее USD стоимости.

    — Расход — Прих. накл1 — товар1 — 1шт. = (2000грн / курс USD * кратность) на 01.01.2014

    — Расход — Прих. накл2 — товар1 — 1шт. = (200EUR пересчитается по крос курсу в USD) на 01.02.2014

    2.Продажи: Товар1 — 2шт. (5000грн. * курс USD / кратность) на дату продажи (10.02.2014)

    3.Продажи себестоимость: здесь интереснее…

    Программа просто скопирует, те движения, которые сделает расх.накл1 в регистре Партии товаров упр. учет. в п.1.

    // КАК СРАБОТАЕТ МОЙ ОТЧЕТ ПРИ ОПРЕДЕЛЕНИИ СЕБЕСТОИМОСТИ, ЕСЛИ:

    — валюта отчета = валюте упр. учета (USD).

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

    — валюта отчета отличается от валюты упр. учета (напр. EUR)

    В этом случает отчет пересчитает сумму каждой партии из валюты упр. учета USD в валюту отчета EUR по крос-курсу на дату партии. После этого полученные себестоимости так же суммируются.

    Все это выполняется одним пакетным запросом с индексированием.

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

    После этого похожим образов формируется таблица «Продажи»

    Далее две таблицы загружаются в МенеджерВременныхТаблиц и передаются в запрос, в которым таблицы себестоимости и продаж объединяются, аналогично тому, как это сделано в отчете «Валовая прибиль».

    После выполнения такого запроса, данные из него выгружаются в таблицу значений «Сводная таблица», по которой и происходит погашение оплатами по методу ФИФО. По сути сводная таблица содержит все продажи и дальнейшая обработка, удаляет из нее записи о продажах, которые не оплачены. В итоге в «сводной таблице» остаются только оплаченные продажи, по тем контрагентам, по которым было сальдо на начало отчета, и по тем, по котовым были движения в периоде формирования отчета.

    Reply
  23. itar59

    (17(17)

    каковы перспективы доработаной версии?

    Reply
  24. snorchik

    Здравствуйте ! А может кто-нибудь кинуть erf файл этого отчета?

    Reply

Leave a Comment

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