В процессе разработки и автоматизации на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с ситуацией, когда при программном изменении реквизита документа код выполняется без ошибок, отладчик показывает успешное присвоение значения, но после вызова метода Записать() изменения в базе данных не сохраняются. Особенно часто эта проблема проявляется в типовых конфигурациях, таких как 1С:Бухгалтерия предприятия, при работе с банковскими и кассовыми документами.
В этой статье мы подробно разберем, почему возникает подобная ошибка на примере документа ПоступлениеНаРасчетныйСчет, проанализируем внутреннюю архитектуру хранения данных в типовых конфигурациях и рассмотрим правильные методы программного заполнения реквизитов.
Рассмотрим типовой сценарий, с которым столкнулся автор темы. Нам необходимо программно найти документ по входящему номеру и заполнить в нем сумму банковских услуг (комиссии). Исходный код выглядит следующим образом:
СсылкаНаДокумент = Документы.ПоступлениеНаРасчетныйСчет.НайтиПоРеквизиту("НомерВходящегоДокумента", Выборка.НомерВходящегоДокумента);
ТекущийДокумент = СсылкаНаДокумент.ПолучитьОбъект();
ТекущийДокумент.СуммаУслуг = Сумма_Услуг;
ТекущийДокумент.Записать(РежимЗаписи.Запись);
При пошаговой отладке данного фрагмента кода переменная Сумма_Услуг содержит корректное числовое значение, и свойство ТекущийДокумент.СуммаУслуг успешно принимает это значение. Однако после завершения транзакции записи и открытия документа в пользовательском интерфейсе поле «Сумма услуг» остается пустым или возвращается к своему прежнему значению. Выясним причину такого поведения платформы.
Первая и самая главная причина кроется в различиях между реквизитами формы и реквизитами объекта (метаданными). В типовой конфигурации «1С:Бухгалтерия предприятия» поле «Сумма услуг» отображается на форме документа для удобства пользователя. Визуально оно может находиться в шапке или подвале документа, создавая иллюзию того, что это простой реквизит шапки.
На самом деле, на уровне метаданных документа ПоступлениеНаРасчетныйСчет отдельного реквизита СуммаУслуг в шапке может не существовать вовсе, либо он является расчетным. Физически эти данные хранятся в строках табличной части РасшифровкаПлатежа.
При интерактивной работе пользователя на форме срабатывают обработчики событий, которые переносят данные из полей ввода в соответствующие строки табличной части. При программном же обращении к объекту события формы не вызываются, и прямое присвоение значения несуществующему или пересчитываемому реквизиту объекта просто игнорируется или сбрасывается.
Даже если в объекте существует реквизит шапки СуммаУслуг, в модуле объекта или в общих модулях конфигурации практически всегда реализован механизм автоматического пересчета итогов. Давайте проанализируем, что происходит в момент вызова метода Записать():
ПередЗаписью в модуле документа.РасшифровкаПлатежа пуста или содержит строки, в которых колонка СуммаУслуг равна нулю.СуммаУслуг нулевым значением, затирая то, что было присвоено программно перед вызовом метода Записать().Чтобы успешно записать сумму услуг, нам необходимо работать напрямую с источником данных — табличной частью РасшифровкаПлатежа. Рассмотрим два практических сценария реализации этой задачи.
Если документ уже создан, заполнен пользователем и нам требуется лишь скорректировать или дозаписать значение суммы услуг, мы должны обратиться к первой строке табличной части (или распределить сумму по всем строкам, если это необходимо). Разберем этот процесс по шагам:
СсылкаНаДокумент = Документы.ПоступлениеНаРасчетныйСчет.НайтиПоРеквизиту("НомерВходящегоДокумента", Выборка.НомерВходящегоДокумента);
Если ЗначениеЗаполнено(СсылкаНаДокумент) Тогда
ТекущийДокумент = СсылкаНаДокумент.ПолучитьОбъект();
// Проверяем, заполнена ли табличная часть расшифровки платежа
Если ТекущийДокумент.РасшифровкаПлатежа.Количество() > 0 Тогда
// Записываем значение в первую строку табличной части
СтрокаРасшифровки = ТекущийДокумент.РасшифровкаПлатежа[0];
СтрокаРасшифровки.СуммаУслуг = Сумма_Услуг;
// ВАЖНО: При заполнении суммы услуг необходимо также заполнить
// связанные реквизиты для корректного отражения расходов в бухгалтерском учете
СтрокаРасшифровки.СчетЗатрат = ПланыСчетов.Хозрасчетный.ПрочиеРасходы; // Счет 91.02
// Заполняем субконто для счета затрат (статью прочих расходов)
СтрокаРасшифровки.СубконтоСтрока = Справочники.ПрочиеДоходыИРасходы.РасходыНаУслугиБанков;
// Записываем документ с проведением, если он уже был проведен ранее
РежимЗаписиДокумента = ?(ТекущийДокумент.Проведен, РежимЗаписи.Проведение, РежимЗаписи.Запись);
Попытка
ТекущийДокумент.Записать(РежимЗаписиДокумента);
Сообщить("Документ " + СсылкаНаДокумент + " успешно обновлен.");
Исключение
Сообщить("Не удалось записать документ: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Иначе
Сообщить("В документе " + СсылкаНаДокумент + " отсутствует табличная часть Расшифровка платежа!");
КонецЕсли;
КонецЕсли;
Если мы создаем документ с нуля (например, при импорте данных из внешнего файла или интеграции по API), нам необходимо программно инициализировать табличную часть и добавить в нее новую строку. Посмотрим на пример реализации:
НовыйДокумент = Документы.ПоступлениеНаРасчетныйСчет.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = СсылкаНаОрганизацию;
НовыйДокумент.БанковскийСчет = СсылкаНаБанковскийСчет;
НовыйДокумент.Контрагент = СсылкаНаКонтрагента;
НовыйДокумент.ДоговорКонтрагента = СсылкаНаДоговор;
НовыйДокумент.СуммаДокумента = ОбщаяСуммаПлатежа;
// Добавляем строку в табличную часть РасшифровкаПлатежа
НоваяСтрока = НовыйДокумент.РасшифровкаПлатежа.Добавить();
НоваяСтрока.СуммаПлатежа = ОбщаяСуммаПлатежа;
НоваяСтрока.СуммаУслуг = Сумма_Услуг;
НоваяСтрока.СтатьяДвиженияДенежныхСредств = Справочники.СтатьиДвиженияДенежныхСредств.ОплатаПокупателей;
// Обязательные реквизиты для отражения комиссии банка
НоваяСтрока.СчетЗатрат = ПланыСчетов.Хозрасчетный.ПрочиеРасходы;
НоваяСтрока.ПрочиеДоходыИРасходы = Справочники.ПрочиеДоходыИРасходы.РасходыНаУслугиБанков;
// Записываем новый документ в режиме обычной записи
НовыйДокумент.Записать(РежимЗаписи.Запись);
При решении подобных задач мы рекомендуем всегда обращать внимание на следующие архитектурные особенности платформы 1С:
Записать(РежимЗаписи.Запись), данные в регистрах бухгалтерии и накопления останутся старыми. Чтобы изменения вступили в силу и отразились на счетах учета, документ необходимо перепровести, используя РежимЗаписи.Проведение.ПоступлениеНаРасчетныйСчет, нажмите на него правой кнопкой мыши и выберите пункт «Поиск ссылок на объект». Проанализируйте все найденные подписки на события с типом ПередЗаписью или ПриЗаписи. В них может содержаться логика, специфичная для вашей отраслевой или доработанной конфигурации.Таким образом, понимание принципов нормализации данных в 1С и механизмов работы обработчиков событий позволяет быстро находить причины подобных ошибок и писать надежный, поддерживаемый программный код.