При разработке и кастомизации конфигураций на платформе 1С:Предприятие 8.3 разработчики часто сталкиваются с загадочным поведением регистров бухгалтерии. Типичный сценарий: на счете 41.01 («Товары на складах») в пользовательском интерфейсе и в карточке счета субконто расположены в привычном порядке:
Однако при чтении движений документа через объектную технику РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей() в свойствах проводки внезапно обнаруживается совершенно иная картина. Например, в СубконтоКт1 оказывается записана Партия, в СубконтоКт2 — Номенклатура, а в СубконтоКт3 — Склад. Почему платформа меняет их местами на физическом уровне и как написать отказоустойчивый код, который не зависит от этих изменений? Давайте детально разберем физические механизмы работы СУБД в 1С.
Чтобы понять первопричину проблемы, нам необходимо четко разграничить два уровня абстракции, которыми оперирует платформа 1С:
_AccRg...) под субконто выделено фиксированное количество колонок (по умолчанию до 3-х в типовых конфигурациях): СубконтоКт1, СубконтоКт2, СубконтоКт3 (и аналогично для дебета). Платформа связывает эти колонки с конкретными строками из табличной части «Виды субконто» конкретного счета в базе данных по их физическому индексу строки. Когда мы вызываем метод НаборЗаписей.Прочитать(), платформа считывает данные напрямую из этих физических колонок без какого-либо упорядочивания.Обороты, Остатки, ОстаткиИОбороты) — это динамически конструируемые платформой SQL-запросы. Если в параметрах этих таблиц мы передаем массив видов субконто, платформа берет на себя задачу «маппинга» (сопоставления) физических колонок с логическими. В результате, вне зависимости от того, как данные физически лежат в таблице СУБД, в выборке запроса поле Субконто1 будет строго соответствовать первому элементу переданного массива, Субконто2 — второму и так далее. Если же параметры виртуальной таблицы опустить, платформа применит сопоставление по умолчанию, что также может скрыть реальный физический порядок полей.Таким образом, когда мы пишем запрос к виртуальным таблицам, мы видим «красивый» логический порядок. Но как только мы переходим к чтению физических таблиц через НаборЗаписей, мы сталкиваемся с сырыми данными СУБД.
Выясним, почему физический порядок субконто на счете 41.01 («Товары на складах») в 1С:Бухгалтерии предприятия 3.0 может отличаться в разных базах данных.
Если мы откроем конфигуратор БП 3.0 и найдем в плане счетов предопределенный счет 41.01, мы обнаружим, что список видов субконто у него... пуст. Разработчики фирмы «1С» не задают виды субконто для счетов учета запасов жестко на этапе разработки. Состав аналитики определяется программно при первом запуске или при изменении настроек в режиме Предприятие (меню Администрирование — Параметры учета — Настройки плана счетов — Учет запасов).
Здесь пользователь может включить или отключить:
Проанализируем, что происходит в базе данных под капотом, когда мы включаем эти флаги в разной последовательности.
Каждый раз, когда включается очередная настройка учета запасов, специальный программный алгоритм конфигурации добавляет соответствующий вид субконто в табличную часть счета 41.01. Порядок добавления строк жестко определяет физический индекс субконто в таблице движений СУБД.
Рассмотрим два сценария развития событий в компании:
Сценарий А (Сначала включили склады, потом партии):
Номенклатура (занимает физическую позицию №1).Склады на первую свободную позицию (физическая позиция №2).Партии (физическая позиция №3).Сценарий Б (Сначала включили партии, потом склады):
Партии записалось на свободную физическую позицию №2.Склады записалось на свободную физическую позицию №3.Но почему тогда в режиме Предприятия (в плане счетов) мы всегда видим одинаковый порядок (Номенклатура -> Склады -> Партии)?
Все очень просто: форма списка плана счетов и карточка счета в БП 3.0 программно сортируют строки табличной части перед выводом их на экран пользователя. Это сделано для эстетики и удобства восприятия информации. Однако физический порядок строк в системных таблицах базы данных при этом умышленно не изменяется. Переиндексация и перезапись физических колонок в регистрах огромных баз данных приводили бы к колоссальным нагрузкам на СУБД, блокировкам таблиц и потенциальным сбоям в работе сотен пользователей.
Завязываться в программном коде на жесткие номера субконто типа Запись.СубконтоКт1 или Запись.СубконтоКт2 — это грубейшая архитектурная ошибка, которая обязательно приведет к багам при переносе кода на другие базы данных или при изменении настроек параметров учета.
Рассмотрим безопасные и современные подходы к написанию кода для работы с аналитикой регистра бухгалтерии.
Платформа «1С:Предприятие 8» позволяет обращаться к коллекции субконто в проводке не по номерам полей, а по ключу. В качестве ключа передается ссылка на элемент плана видов характеристик (ПВХ) ВидыСубконтоХозрасчетные. Платформа самостоятельно определяет, в какой физической колонке СУБД лежит требуемая аналитика, и возвращает её значение.
Разберем готовый шаблон безопасного чтения и записи субконто в проводках:
// 1. Сначала кэшируем необходимые виды субконто в локальные переменные
ВидНоменклатура = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура;
ВидСклады = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Склады;
ВидПартии = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Партии;
// 2. Создаем и считываем набор записей по регистратору
Набор = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(ДокументСсылка);
Набор.Прочитать();
Для Каждого Запись Из Набор Цикл
// Проверяем, что счет проводки по Кредиту — это счет 41.01
Если Запись.СчетКт = ПланыСчетов.Хозрасчетный.ТоварыНаСкладах Тогда
// БЕЗОПАСНОЕ ЧТЕНИЕ: Обращаемся по ключу ПВХ
Товар = Запись.СубконтоКт[ВидНоменклатура];
Склад = Запись.СубконтоКт[ВидСклады];
Партия = Запись.СубконтоКт[ВидПартии];
// Выполняем логику анализа
Если ЗначениеЗаполнено(Товар) Тогда
// Например, выводим информацию в журнал регистрации
ЗаписьЖурналаРегистрации("АнализПроводки",,,, "Товар: " + Товар + ", Склад: " + Склад);
КонецЕсли;
// БЕЗОПАСНАЯ ЗАПИСЬ: Установка значений также производится по ключу ПВХ
// Платформа сама запишет данные в нужную физическую колонку СУБД (будь то СубконтоКт1, 2 или 3)
Запись.СубконтоКт[ВидНоменклатура] = НоваяСсылкаНаТовар;
КонецЕсли;
КонецЦикла;
// Если вносили изменения, записываем набор
// Набор.Записать();
Используя этот подход, вы полностью абстрагируетесь от того, под каким индексом хранятся данные в физической таблице базы данных. Код будет стабильно работать и на пустой демо-базе, и на рабочей базе клиента с десятилетней историей.
Если вам необходимо прочитать данные из регистра с помощью запроса и гарантировать, что в результатах запроса Субконто1 — это всегда Номенклатура, а Субконто2 — это всегда Склад, обязательно используйте параметр &ВидыСубконто в виртуальных таблицах.
Проанализируем пример правильного построения запроса:
// 1. Формируем строго упорядоченный массив видов субконто
МассивВидов = Новый Массив;
МассивВидов.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура); // Индекс 0 -> Будет в Субконто1
МассивВидов.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Склады); // Индекс 1 -> Будет в Субконто2
МассивВидов.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Партии); // Индекс 2 -> Будет в Субконто3
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОбороты.Субконто1 КАК Товар,
| ХозрасчетныйОбороты.Субконто2 КАК Склад,
| ХозрасчетныйОбороты.Субконто3 КАК Партия,
| ХозрасчетныйОбороты.СуммаОборотКт КАК СуммаОборотКт
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(&НачДата, &КонДата, , Счет = &ВыбСчет, &ВидыСубконто, , , ) КАК ХозрасчетныйОбороты";
Запрос.УстановитьПараметр("ВыбСчет", ПланыСчетов.Хозрасчетный.ТоварыНаСкладах);
Запрос.УстановитьПараметр("НачДата", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("КонДата", КонецДня(ТекущаяДата()));
// Передаем наш массив в пятый параметр виртуальной таблицы Обороты
Запрос.УстановитьПараметр("ВидыСубконто", МассивВидов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Мы можем быть на 100% уверены, что в поле "Товар" попала именно Номенклатура,
// а в поле "Склад" — именно Склад, независимо от структуры физической БД.
Сообщить("Склад: " + Выборка.Склад + " -> " + Выборка.Товар);
КонецЦикла;
Если бы мы не передали массив МассивВидов в качестве параметра &ВидыСубконто, платформа сопоставила бы поля по умолчанию на основе физического размещения, и в поле Субконто1 в разных базах данных могли бы оказаться совершенно разные сущности.
Для того чтобы избежать трудноуловимых ошибок при работе с аналитикой бухгалтерского учета, всегда следуйте золотым правилам разработки под платформу 1С:
СубконтоКт1, СубконтоДт2 в объектной технике, если вы обращаетесь к ним по числовым индексам или именам свойств.ВидыСубконтоХозрасчетные.