Ошибка расчета остатков в СКД и ее программное исправление на примере Универсального отчета




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2025-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='\

46 Comments

  1. indigo_

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

    Reply
  2. vvr908

    (1) Да, я помню, что сильно удивился первый раз, когда мне пользователи на эту ошибку указали. Пришлось разбираться, т.к. уже было штук 30 различных сохраненных пользовательских настроек — не писать же на каждую свой отчет… 😉

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

    Reply
  3. Danil.Potapov

    «Мне удалось найти одно ограничение, связанное с этим решением»

    попробуй установить флаг у поля «регистратор» — обязательное.

    Reply
  4. vvr908

    (3) Я думал об этом, но не пробовал. Мне кажется, это не совсем верное решение проблемы, т.к. регистратор далеко не всегда вообще нужно выводить в отчет (как и детальные записи в принципе). Что будет выведено в отчет, если в настройках флажок «Детальные записи» снят, а поле Регистратор помечено в СКД как обязательное? Не знаю, надо будет попробовать.

    Reply
  5. vvr908

    (3) Попробовал сделать Регистратор обязательным полем. Сразу же полезли ошибки СКД из-за последовательности нумерации периодов. Никому этот вариант не рекомендую, не зря он казался мне подозрительным. 😉 Лучше уж оставить все в исходном варианте, как в статье.

    Reply
  6. Danil.Potapov

    (5) Да, про обязательное поле брякнул не то. Сейчас посмотрю….

    Reply
  7. Dirk Diggler

    На каких релизах проверяли?

    Reply
  8. vvr908

    (7) На Бух 1.6.22.4 и ранее.

    В УПП та же картина на нескольких релизах (точнее, на всех, виденных мной), точно номера не помню.

    Только что проверил на платформе 8.2.10.73 в той же Бух 1.6.22.4 — глючит все так же.

    Reply
  9. Glafira

    В стандартных отчетах 1С:Торговли при выводе детальных записей по регистратору необходимо добавлять также поле Период для правильного расчета группировок и остатков (Период+Документ движения (регистратор)). Видимо здесь такая же ситуация.

    Reply
  10. vvr908

    (9) Попробовал добавить к регистратору еще поле «Период секунда» — не помогает, как и добавление любого другого поля. Ошибка остается. Стандартные отчеты УТ, очевидно, реализованы несколько иначе (скорее всего, при их создании все же использовался конструктор СКД).

    Reply
  11. vvr908

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

    Я надеялся, что в новых версиях платформы что-нибудь изменится, но уже 8.2.10 вышла, а воз и ныне там.

    Reply
  12. madmpro

    У меня есть похожий отчет, только на основе универсального отчета, по регистрам партий (комплексная конфигурация, УПП). Проблема с выводом начальных остатков и расшифровок по документам. При выводе расшифровки строки в режиме период+регистратор и отключения детальных записей — все получается (проверено) кроме начальных остатков. Кто-нибудь поможет решать эту проблему?

    http://files.mail.ru/I4ZLS2

    Reply
  13. e.kogan
    Reply
  14. vvr908

    Недавно мне понадобилась эта статья, когда мы стали пытаться анализировать полученную из другой базы через COM-соединение таблицу остатков и оборотов с детальными записями. В исходной базе остатки считались корректно, но после выгрузки результата запроса в таблицу значений и обработки этой таблицы в другой БД настройки пришлось прописывать вручную — тут и вспомнилось про эту статью.

    Выяснилось, что если содержимое строки таблицы значений содержит движения накопления по ресурсам вида «нач.остаток/приход/расход/кон.остаток», то для корректного расчета остатков по группировкам средствами СКД необходимо выполнить настройки, аналогичные описываемым в статье.

    В исходной таблице значений обязательно должно быть поле периода (дата движения или период из р/н). Если его там нет — добавляем. Затем в настройках СКД нужно присвоить этому полю роль «Период, 1», и далее заполнить роли для полей остатков и измерений, как описано в статье.

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

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

    Reply
  15. Boroda

    Отличная статья, очень полезные комментарии, хорошо её дополняющие. Спасибо.

    Reply
  16. vvr908

    (15) Boroda, спасибо за столь положительный отзыв!

    Как раз после того, как написал последний комментарий, я еще раз все перечитал и решил полностью переработать текст статьи.

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

    Я не претендую на особое знание глубинных механизмов СКД, но делюсь тем, что удалось накопать мне самому и вместе с коллегами.

    Reply
  17. vvr908

    (13) e.kogan, в статье сразу предлагается прописать роли для полей остатков с помощью типовой процедуры общего модуля. 😉

    Т.е. вашу конструкцию вида

    полерес.Роль.Остаток=Истина;
    полерес.Роль.ТипОстатка=ТипОстаткаКомпоновкиДанных.НачальныйОстаток;
    полерес.Роль.ГруппаОстатка=Ресурс.Имя;
    

    можно заменить на:

    ТиповыеОтчеты.ЗаполнитьПолеНабораДанныхОстаток(полерес, Ресурс.Имя);
    

    По моему опыту, этого будет вполне достаточно.

    Хотя и ваш вариант, конечно, тоже будет работать.

    P.S. Все обсуждаемые изменения касаются процедуры ДобавитьПоляНабораДанных() модуля объекта УниверсальныйОтчетПоМетаданным.

    Все желающие «починить» универсальный отчет могут заменить код этой процедуры на код, приведенный e.kogan в (13).

    Reply
  18. romansun

    на мисте есть обсуждение этой темы… ссылку, к сожалению, не дам… гуглите

    на ИТС есть статья, полностью и на примерах раскрывающая тему ролей в СКД. В указанном обсуждении на мисте есть ссылки на скрины статьи ИТС 🙂

    что интересно, в обычных запросах с итогами остатки считаются верно самой 1С на уровне платформы. И вот эту «верность» срубить весьма непросто.. )) Даже при скидывании выборки во временную таблицу 1С всё равно «помнит» остаточные поля и верно считает остатки (т.е. арифметически — неверно). Отбить память об остатках получается только при втором или третьем перескидывании во временную таблицу…

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

    Reply
  19. vvr908

    (18) romansun, спасибо за наводку, надо будет поискать 🙂

    Только статья-то исходно была написана 2 года назад, когда еще никакой информации толком не было…

    UPD: вот, нашел на мисте кое-что с ИТС: статья «Типичные проблемы при расчете остатков»

    Reply
  20. e.kogan

    (17) это хорошо, когда конфигурация типовая 🙂

    Reply
  21. vvr908

    (20) e.kogan, ну у вас в коде тоже без вызова типовых функций все того же общего модуля не обошлось ))

    Reply
  22. e.kogan

    (21) Гм, оно у меня переопределяется 🙂 На самом деле это форма с экспортными процедурами 🙂 самый простой способ выдрать всё на свете из конфы.

    Reply
  23. e.kogan

    *упс, дважды отправилось

    Reply
  24. romansun

    (19)

    дадада.. 🙂 она самая

    и что меня убивает лично во всей этой ситуации — что такая, действительно, важная вещь, как «Роли» в библии СКД от Хрусталёвой (библии, ибо больше нигде консолидированно инфы по СКД толком нет) описана в виде одной странички с общим смыслом: «а еще в СКД есть роли, они нужны… нужны, т.к. помогают при расчете остатков и других клевых штук.. больше я вам ничего не скажу,… как-то так»

    Reply
  25. vvr908

    (23) e.kogan, хитро придумано )) я, честно говоря, тупо общий модуль перетащил в свое время в самописную конфу и поудалял оттуда все лишнее.

    Одно отличие тут может стать заметным — если общий модуль может быть серверным, то форма — нет.

    Reply
  26. vvr908

    Был приятно удивлен появлением ссылки на статью на главной странице в рубрике «Выбор экспертов».

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

    Reply
  27. Пацталоцци

    Самое удивительное и смешное во всей этой истории — это постоянные разговоры восьмёрочников о том, что восьмёрка якобы на порядок круче и эффективнее чем 7.7.

    Да это же стыд и срам.

    Это называется прогрессивная платформа!

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

    Reply
  28. Пацталоцци

    Вы хоть сами понимаете, что вместо нормальной и стабильной программерской работы, занимаетесь совершенно ненормальным «научным тыком» ???

    Reply
  29. Пацталоцци

    Для странного, алогичного поведения платформы вы ищете странные, алогичные методы багофиксинга.

    И я бы ещё понял, если бы выгода от использования СКД была настолько существенна, что перекрывала бы эти затраты времени и рассудка.

    Но ведь этого не происходит.

    Насколько я понимаю, основным плюсом СКД декларировалась очень высокая скорость разработки отчётов и, как следствие, экономия времени программиста?

    Ну и?

    Много времени сэкономили, бегая с бубном вокруг сабжа?

    Reply
  30. vvr908

    (29) Пацталоцци, я не вполне понимаю — какие есть альтернативы? Забить на СКД и перевести всех клиентов обратно на 7.7?

    Мы работаем с теми механизмами, какие есть, а претензии имеет смысл адресовать не сюда, а к 1С.

    Reply
  31. vvr908

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

    Reply
  32. nafa

    (31) Ничего себе специфические условия — поле остатка в запросе. В Бухгалтерии таких вобще-то процентов 90.

    Столкнулся с ситуацией, когда прописывания ролей полям оказалось недостаточным. Пришлось сделать обязательным 2 поля: период и регистратор. (на то как отчет выдается пользователю не влияет, только на процедуру расчета.)

    Дополнение

    Пришлось и все измерения обязательными сделать (т.к. иначе отсутствие/наличие одного из измерений в запросе влияло на остаток 🙁 ) зато теперь работает как часы.

    Reply
  33. vvr908

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

    Reply
  34. nafa

    (33)

    Поля остатка то распознаются, но почему без этих галок то в самом примитивном отчете по регистру (простой запрос типа выбрать измерение1, измерение2, начальный остаток, приход, расход, конечный остаток, период, регистратор из регистр.ххх.ОстаткиИОбороты(,, Регистратор,)) итог по полям остатка при наличии в выбранных пользователем группировках периода/регистратора и их отсутствии оказывается совершенно разный, а с галками одинаковый.

    (Отчет создан без единой ручной правки, только эти галки)

    Reply
  35. vvr908

    (34) nafa, да, насчет этих галок вообще вопрос интересный.

    А если пользователь, к примеру, будет группировать отчет по периоду, но с заранее неизвестной периодичностью (неделя или месяц)? Делать оба поля ПериодНеделя и ПериодМесяц обязательными? Как на это отреагирует СКД? Надо будет проэкспериментировать и дополнить статью результатами эксперимента…

    Reply
  36. nafa

    (35)

    Есть есть обязательные группировки Регистратор и Период, то пруппировки ПериодМесяц и ПериодНеделя обязательными делать не надо, и так работает. Кроме того в статье по ссылке выше написано что нельзя одновременно использовать неделю и более крупные периоды — можно, но просто надо понимать что в этом случае неделя которая находится на пересечении месяцев разделится на два кусочка.

    Reply
  37. Makushimo

    Полезная статья.

    Спасибо автор.

    Reply
  38. Al777

    Поставил обязательными поля Период и Регистратор. Остатки теперь выводит правильно, но по периодам перестал отчёт работать. В чём может быть причина?

    Reply
  39. olbu

    Потратил полдня на это, нигде не видел, что периодичность виртуальной таблицы должна быть «Авто». У меня было «Регистратор» — ничего не получалось, пока не поменял на «Авто»…

    Reply
  40. OpKc

    (32) nafa, спасибо тебе, мил человек! перерыл пол-интернета, но только в твоём комментарии нашёл истину: «Пришлось сделать обязательным 2 поля: период и регистратор.»

    Reply
  41. garaevilnur

    (32) nafa, спасибо! Присоединяюсь «обязательно»!

    Reply
  42. bashhhh

    Спасибо. Статья помогла разобраться в проблеме!

    Reply
  43. maxchaos

    Автор, исправьте ссылку на Мисту в конце публикации, а то открывается не то!

    Reply
  44. vis_tmp

    (43)Ссылка на статью

    https://its.1c.ru/db/metod8dev/content/3093/hdoc

    Reply
  45. vis_tmp

    (3)(6)Столкнулся с ситуацией когда остатки считались неверно до тех пор, пока не поставил галочки «Обязательное» и у «Регистратор» и у «ПериодСекунда».

    Reply
  46. AndroidRu

    Исправил все как в этой статье. Остатки считает правильно, но выводит как то странно.

    2-мя строчками. В 1 строчке в полях начального и конечного остатка суммы начального остатка, а во второй строчке суммы конечного остатка. Подскажите, пожалуйста, кто сталкивался с таким? Заранее благодарен.

    Reply

Leave a Comment

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