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




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2026-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 не будет опубликован. Обязательные поля помечены *