В практике разработчика 1С часто возникает задача, когда в существующий план счетов задним числом добавляется новое субконто. Например, на счете 50.01 «Касса организации» добавили субконто Кассы (тип — СправочникСсылка.Кассы). Бухгалтерия требует заполнить эту аналитику за прошлые периоды, но категорически запрещает перепроводить документы, чтобы не поплыли последовательности, закрытие периодов и сданная отчетность.
В данной статье мы подробно разберем, как решить эту задачу максимально эффективным и безопасным способом, рассмотрим готовые примеры кода, проанализируем подводные камни и выясним, как нейтрализовать так называемую «бомбу замедленного действия».
Прежде чем перейти к написанию кода, проанализируем ситуацию, описанную в сообщении 6. Если мы просто программно откроем движения регистра бухгалтерии, запишем туда нужное субконто и сохраним набор, это сработает мгновенно. Но ровно до тех пор, пока какой-нибудь пользователь (или регламентное задание) не решит перепровести этот документ.
При стандартном перепроведении алгоритмы конфигурации полностью очищают старые движения и формируют новые на основе данных самого документа. Поскольку в самом документе реквизит кассы не был заполнен, новое субконто снова станет пустым. Программно записанные данные будут безвозвратно затерты.
Чтобы избежать этой проблемы, применим один из следующих подходов:
РучнаяКорректировка. Если установить его в значение Истина, система заблокирует автоматическое обновление движений при перепроведении. Данные сохранятся, но документ визуально пометится как отредактированный вручную.РежимЗаписиДокумента.Запись), а затем уже обновить проводки. В таком случае даже при случайном перепроведении алгоритмы проведения корректно возьмут кассу из реквизита документа и заполнят субконто.Чтобы не перебирать все документы подряд, мы выберем только те уникальные регистраторы за указанный период, в проводках которых задействован счет 50.01, но аналитика Кассы еще не заполнена. Это значительно сбережет ресурсы сервера и ускорит выполнение операции.
Рассмотрим пример запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ХозрасчетныйДвиженияССубконто.Регистратор КАК Регистратор
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто(&НачалоПериода, &КонецПериода, Счет = &Счет50, , ) КАК ХозрасчетныйДвиженияССубконто
|ГДЕ
| (ХозрасчетныйДвиженияССубконто.СубконтоДт1 = НЕОПРЕДЕЛЕНО
| ИЛИ ХозрасчетныйДвиженияССубконто.СубконтоДт1 КАК Справочник.Кассы = ЗНАЧЕНИЕ(Справочник.Кассы.ПустаяСсылка))
| И (ХозрасчетныйДвиженияССубконто.СубконтоКт1 = НЕОПРЕДЕЛЕНО
| ИЛИ ХозрасчетныйДвиженияССубконто.СубконтоКт1 КАК Справочник.Кассы = ЗНАЧЕНИЕ(Справочник.Кассы.ПустаяСсылка))";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("Счет50", ПланыСчетов.Хозрасчетный.КассаОрганизации); // 50.01
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Теперь разберем алгоритм обхода документов. Для каждого регистратора мы будем создавать объект РегистрБухгалтерииНаборЗаписей.Хозрасчетный, устанавливать отбор по регистратору, считывать движения, находить нужные строки и программно подставлять значение нашей кассы. Запись набора будем производить с флагом ОбменДанными.Загрузка = Истина.
Обратите внимание на важный нюанс: при программном заполнении субконто нельзя обращаться к нему по числовому индексу (например, СубконтоДт[0]). Индексы субконто на счете могут измениться после обновления конфигурации или изменения порядка видов субконто пользователем. Обращайтесь к субконто только по ключу — объекту ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.
Посмотрим на пример реализации кода:
ВидСубконтоКассы = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Кассы;
Счет50 = ПланыСчетов.Хозрасчетный.КассаОрганизации;
Пока Выборка.Следующий() Цикл
// Получаем набор записей по текущему документу-регистратору
НаборЗаписей = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Выборка.Регистратор);
НаборЗаписей.Прочитать();
Модифицирован = Ложь;
Для Каждого Запись Из НаборЗаписей Цикл
// Проверяем Дебет счета 50.01
Если Запись.СчетДт = Счет50 Тогда
// Проверяем, доступен ли данный вид субконто на счете
Если Запись.СубконтоДт.Свойство(ВидСубконтоКассы) Тогда
ТекущееЗначение = Запись.СубконтоДт[ВидСубконтоКассы];
Если Не ЗначениеЗаполнено(ТекущееЗначение) Тогда
Запись.СубконтоДт[ВидСубконтоКассы] = ЗначениеКассы; // Наша касса из реквизита обработки
Модифицирован = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
// Проверяем Кредит счета 50.01
Если Запись.СчетКт = Счет50 Тогда
Если Запись.СубконтоКт.Свойство(ВидСубконтоКассы) Тогда
ТекущееЗначение = Запись.СубконтоКт[ВидСубконтоКассы];
Если Не ЗначениеЗаполнено(ТекущееЗначение) Тогда
Запись.СубконтоКт[ВидСубконтоКассы] = ЗначениеКассы;
Модифицирован = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если Модифицирован Тогда
// Записываем набор без проверок и запуска стандартных обработчиков проведения
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Записать();
// Защищаем документ от перезаписи при случайном перепроведении (установка ручной корректировки)
ДокументОбъект = Выборка.Регистратор.ПолучитьОбъект();
Если Метаданные.Документы[ДокументОбъект.Метаданные().Имя].Реквизиты.Найти("РучнаяКорректировка") <> Неопределено Тогда
Если Не ДокументОбъект.РучнаяКорректировка Тогда
ДокументОбъект.РучнаяКорректировка = Истина;
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
В современных конфигурациях на платформе «1С:Предприятие 8.3» (БП 3.0, ERP, КА) разработчикам доступен более элегантный способ установки аналитики с помощью встроенных функций БСП. Метод БухгалтерскийУчет.УстановитьСубконто() автоматически проверяет, принадлежит ли субконто данному счету, корректно инициализирует его структуру и подставляет значение.
Проанализируем пример использования этой функции:
// Вместо прямого обращения к свойству набора записей пишем:
БухгалтерскийУчет.УстановитьСубконто(Запись.СчетДт, Запись.СубконтоДт, ВидСубконтоКассы, ЗначениеКассы);
БухгалтерскийУчет.УстановитьСубконто(Запись.СчетКт, Запись.СубконтоКт, ВидСубконтоКассы, ЗначениеКассы);
Этот подход гарантирует стабильность кода при возможных будущих обновлениях платформы или конфигурации, так как логика проверки структуры субконто делегируется проверенным механизмам БСП.
Если документов за выбранный период очень много (десятки тысяч), выполнение обработки может заблокировать таблицы базы данных СУБД или привести к зависанию сеанса. Чтобы оптимизировать этот процесс, применим следующие приемы:
Пример кода с разделением на транзакции:
Счетчик = 0;
НачатьТранзакцию();
Пока Выборка.Следующий() Цикл
// [Здесь выполняется код чтения, изменения и записи набора данных...]
Счетчик = Счетчик + 1;
// Каждые 200 документов фиксируем транзакцию и открываем новую
Если Счетчик % 200 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Помните, что запись наборов в режиме ОбменДанными.Загрузка = Истина изменяет данные только в регистре бухгалтерии. Если в вашей конфигурации ведется параллельный оперативный учет денежных средств на регистрах накопления (например, в ERP или КА есть регистры накопления по денежным средствам), то после такой обработки возникнет расхождение между бухгалтерским балансом и оперативными отчетами. В таких сложных конфигурациях настоятельно рекомендуется перед началом работы проконсультироваться с ведущим архитектором системы и, при необходимости, программно скорректировать также записи соответствующих оперативных регистров накопления.