При оптимизации и доработке старых или нетиповых конфигураций на платформе 1С:Предприятие часто приходится сталкиваться с архитектурными ошибками прошлых разработчиков. Одна из классических ситуаций — использование регистра накопления с видом «Остатки» для сущностей, по которым физически не может быть остатков (например, для учета продаж, предоставленных услуг или выполненных работ). Естественно, остатки по такому регистру всегда равны нулю или уходят в бесконечный минус, а разработчики в отчетах обращаются исключительно к виртуальной таблице Обороты.
Рано или поздно встает задача исправить эту архитектурную оплошность и перевести регистр в правильный статус — «Обороты». Однако простое изменение свойства регистра в конфигураторе и последующая реструктуризация базы данных в СУБД могут преподнести неприятный сюрприз. После перепроведения документов или выборочной перезаписи наборов записей отчеты без детализации до регистратора начинают показывать странные, сильно уменьшенные или вовсе отрицательные цифры. При этом, если добавить в поля выборки детальную запись Регистратор, данные внезапно отображаются абсолютно корректно. В этой статье мы подробно разберем, почему возникает этот феномен и какими способами можно полностью восстановить целостность и корректность данных.
Для того чтобы понять природу этой ошибки, нам необходимо заглянуть на уровень физического хранения данных СУБД (MS SQL Server, PostgreSQL и др.) и проанализировать, как платформа 1С организует таблицы для разных видов регистров накопления.
Рассмотрим структуру физических таблиц для регистра накопления с видом «Остатки»:
_AccumReg<n>): В этой таблице хранятся все записи, которые делают документы. Примечательно, что числовые ресурсы (например, Количество или Сумма) всегда записываются со знаком «плюс» (абсолютные положительные величины). Направление движения определяется системным полем вида движения (_RecordKind): значение 0 соответствует Приходу, а 1 — Расходу._AccumRegTotals<n>): Хранит актуальные остатки в разрезе измерений на конец каждого месяца. При расчете итогов платформа «на лету» вычитает расходы из приходов на основании поля вида движения.Теперь посмотрим на структуру регистра накопления с видом «Обороты»:
_AccumReg<n>): Здесь физически отсутствует колонка _RecordKind. Направление изменения ресурса определяется исключительно его математическим знаком. Если это прямая продажа, ресурс пишется со знаком «плюс», а если сторно, корректировка или возврат — со знаком «минус»._AccumRegTurnovers<n>): Хранит агрегированные обороты (суммы и количества) за каждый календарный месяц.Когда мы меняем вид регистра с «Остатков» на «Обороты» в конфигураторе и запускаем реструктуризацию, платформа выполняет SQL-запрос на изменение структуры таблиц (DDL-операции). Системная колонка вида движения _RecordKind удаляется из таблицы движений. Однако исторические данные движений «Расхода», которые раньше хранились как положительные числа (а их знак «минус» определялся признаком расхода в удаленной колонке), теперь превращаются в обычные положительные обороты! Платформа не выполняет автоматическую инверсию знака для старых движений при такой реструктуризации. Это приводит к тотальному хаосу в математических расчетах.
Это классическое проявление механизма оптимизации запросов платформы 1С при работе с виртуальными таблицами:
_AccumRegTurnovers<n>. Так как данные итогов после изменения метаданных оказались непересчитанными и поврежденными, СУБД возвращает искаженные (часто мелкие отрицательные) значения._AccumReg<n>. По тем документам, которые мы уже успели перепровести вручную или программно, движения записались по новым правилам регистра оборотов (с верными знаками ресурсов). Именно поэтому детальный отчет выглядит правильно, создавая иллюзию успешного исправления проблемы.Многие разработчики совершают ошибку, полагая, что простое перепроведение документов решит проблему. На самом деле обновление таблиц итогов в 1С происходит инкрементально: платформа вычитает старые движения документа из итогов и прибавляет новые. Если базовая таблица итогов уже содержит искажения (после смены типа регистра), инкрементальный пересчет наложит верные изменения на сломанную основу, и итоговые цифры всё равно останутся неверными.
Рассмотрим по шагам несколько эффективных способов решения этой проблемы.
Самый простой, безопасный и не требующий стороннего софта метод — это принудительная очистка и последующий пересчет таблиц итогов силами встроенного языка платформы. При отключении использования итогов по конкретному регистру платформа 1С физически удаляет (дропает) соответствующую таблицу периодических итогов в СУБД. При повторном включении итогов платформа создает таблицу заново и осуществляет полный, честный расчет всех агрегатов на основе актуальных движений.
Для выполнения этой операции выполним несложный алгоритм:
// Шаг 1. Полностью отключаем расчет и использование итогов по регистру.
// Это приведет к очистке дефектной таблицы итогов оборотов в базе данных.
РегистрыНакопления.ПродажиТоваров.УстановитьИспользованиеИтогов(Ложь);
// Шаг 2. Включаем итоги обратно.
// Платформа выполнит полный пересчет итогов с нуля на основе правильных записей движений.
РегистрыНакопления.ПродажиТоваров.УстановитьИспользованиеИтогов(Истина);
После выполнения данного кода таблицы итогов гарантированно синхронизируются с таблицей движений, и любые запросы к виртуальной таблице Обороты (как с регистратором, так и без него) начнут возвращать абсолютно верные данные.
Если в процессе исправления конфигурации мы используем популярный пакет обработок «Инструменты разработчика» (автор — Tormozit), исправить проблему можно интерактивно без написания кода.
Проанализируем этот подход по шагам:
ПродажиТоваров. Обратите внимание, что стандартная обработка управления итогами в режиме Предприятия для некоторых старых версий платформы может скрывать оборотные регистры, тогда как специализированный инструмент ИР отображает абсолютно все регистры.Если база данных имеет огромный размер (сотни гигабайт или терабайты), прямое изменение метаданных регистра остатков на обороты и последующий пересчет итогов могут вызвать колоссальную нагрузку на дисковую подсистему СУБД, привести к длительной блокировке таблиц и даже к аварийному завершению работы из-за переполнения лога транзакций. В таких случаях мы рекомендуем использовать методику безопасной миграции данных через создание нового регистра.
Разберем данный процесс по шагам:
ПродажиТоваровНовый с правильным видом «Обороты» и идентичным составом измерений и ресурсов.
// Выбираем всех регистраторов, по которым есть движения в старом регистре
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Продажи.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.ПродажиТоваров КАК Продажи";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Получаем набор записей старого регистра
НаборСтарый = РегистрыНакопления.ПродажиТоваров.СоздатьНаборЗаписей();
НаборСтарый.Отбор.Регистратор.Установить(Выборка.Регистратор);
НаборСтарый.Прочитать();
Если НаборСтарый.Количество() = 0 Тогда
Продолжить;
КонецЕсли;
// Создаем набор записей в новом регистре оборотов
НаборНовый = РегистрыНакопления.ПродажиТоваровНовый.СоздатьНаборЗаписей();
НаборНовый.Отбор.Регистратор.Установить(Выборка.Регистратор);
Для Каждого СтрокаСтарая Из НаборСтарый Цикл
НоваяЗапись = НаборНовый.Добавить();
ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаСтарая);
// Корректируем знак ресурса в зависимости от вида движения
// Если в регистре остатков это был расход, то в оборотах это должно быть отрицательное число (сторно)
Если СтрокаСтарая.ВидДвижения = ВидДвиженияНакопления.Расход Тогда
НоваяЗапись.Количество = -СтрокаСтарая.Количество;
НоваяЗапись.Сумма = -СтрокаСтарая.Сумма;
КонецЕсли;
КонецЦикла;
// Записываем правильный набор движений в новый регистр
НаборНовый.Записать();
КонецЦикла;
ПродажиТоваров на новый ПродажиТоваровНовый.ПродажиТоваров в конфигураторе, а новый регистр ПродажиТоваровНовый переименовываем в старое имя ПродажиТоваров. Проводим финальное обновление конфигурации базы данных.Этот метод полностью исключает появление скрытых ошибок округления или искажения знаков при реструктуризации СУБД, так как математическое преобразование знаков ресурсов из «Рахода» в отрицательный «Оборот» жестко контролируется программным кодом переноса данных.