При разработке обработок по автоматическому формированию бухгалтерских операций программисты часто сталкиваются с ситуацией, когда в файловом варианте базы данных код отрабатывает безупречно, а в клиент-серверном варианте (SQL) проводки внутри документа не создаются или бесследно исчезают. Проанализируем типичную ситуацию на примере документа ОперацияБух в конфигурации Бухгалтерия предприятия 3.0 КОРП.
Рассмотрим ситуацию: у нас есть внешняя обработка, которая читает данные из файла Excel и создает документы «Операция (бухгалтерский и налоговый учет)». В коде создания документа используется раздельный подход: сначала создается и записывается сам объект документа, а затем формируется НаборЗаписей регистра бухгалтерии с фильтром по этому документу. Давайте посмотрим на фрагмент кода, который приводит к ошибке:
ОперацияБух = Документы.ОперацияБух.СоздатьДокумент();
// ... заполнение реквизитов (Организация, Дата и т.д.)
ОперацияБух.Записать();
// Создание набора записей напрямую через менеджер регистра
НаборЗаписей = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ОперацияБух.Ссылка);
НаборЗаписей.Записывать = Истина;
// Цикл добавления проводок
Для Каждого СтрокаДанных Из МассивПлатежей Цикл
Движение = НаборЗаписей.Добавить();
Движение.Регистратор = ОперацияБух.Ссылка;
Движение.Период = ОперацияБух.Дата;
// ... заполнение счетов и субконто
КонецЦикла;
НаборЗаписей.Записать(); // Запись проводок в базу
// Финальная запись документа для обновления суммы
ОперацияБух.СуммаОперации = ИтоговаяСумма;
ОперацияБух.Записать(РежимЗаписиДокумента.Запись);
На первый взгляд код выглядит логичным, но именно в последней строке кроется причина того, почему на сервере проводки «пропадают». Вызов ОперацияБух.Записать() после НаборЗаписей.Записать() инициирует механизм стандартной записи документа, который анализирует коллекцию движений, находящуюся в памяти объекта ОперацияБух. Поскольку эта коллекция пуста (мы записывали проводки через отдельный менеджер записей, а не через свойство документа Движения), система считает, что у документа нет проводок, и очищает соответствующие записи в таблицах SQL-сервера.
Выясним причину разного поведения систем. В файловом варианте 1С механизмы кэширования объектов и управления транзакциями более «мягкие». Иногда объект документа в памяти может не успеть инициировать полную перезапись таблиц движений, если транзакции выполняются последовательно в одном потоке. Однако в клиент-серверном варианте (SQL) соблюдается строгая логика: при вызове метода Записать() объекта документа система выполняет команду DELETE для всех записей в регистрах по данному регистратору и следом команду INSERT для данных из коллекции Движения объекта. Если коллекция в памяти пуста — проводки удаляются окончательно.
Чтобы избежать конфликтов записи, правильнее всего использовать встроенную коллекцию документа. В этом случае документ «знает» о своих проводках, и при итоговой записи они будут корректно сохранены одной транзакцией. Разберем этот алгоритм по шагам:
СоздатьДокумент().Записать(), чтобы получить ссылку (необходимо для регистратора).Движения.Хозрасчетный напрямую через объект.Записывать = Истина для этой коллекции.Посмотрим на пример правильного кода:
ОперацияОбъект = Документы.ОперацияБух.СоздатьДокумент();
ОперацияОбъект.Дата = ДатаПериода;
ОперацияОбъект.Организация = ВыбраннаяОрганизация;
ОперацияОбъект.Записать(); // Получаем ссылку
// Работаем с движениями внутри объекта
Проводки = ОперацияОбъект.Движения.Хозрасчетный;
Проводки.Записывать = Истина;
СуммаДокумента = 0;
Для Каждого Стр Из ДанныеФайла Цикл
НоваяПроводка = Проводки.Добавить();
НоваяПроводка.Регистратор = ОперацияОбъект.Ссылка;
НоваяПроводка.Период = ОперацияОбъект.Дата;
НоваяПроводка.Организация = ОперацияОбъект.Организация;
НоваяПроводка.СчетДт = ПланыСчетов.Хозрасчетный.НайтиПоКоду("57.03");
// Используем типовой механизм для заполнения субконто
БухгалтерскийУчет.УстановитьСубконто(НоваяПроводка.СчетДт, НоваяПроводка.SubkontoДт, 1, Стр.Контрагент);
НоваяПроводка.СчетКт = ПланыСчетов.Хозрасчетный.НайтиПоКоду("62.01");
НоваяПроводка.Сумма = Стр.Сумма;
СуммаДокумента = СуммаДокумента + Стр.Сумма;
КонецЦикла;
ОперацияОбъект.СуммаОперации = СуммаДокумента;
// Теперь эта запись сохранит и реквизиты, и проводки
ОперацияОбъект.Записать(РежимЗаписиДокумента.Запись);
Если по каким-то причинам (например, из-за специфических расширений или сложной логики в модуле объекта) вам необходимо использовать РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей(), то критически важно не вызывать метод Записать() у объекта документа после того, как вы записали набор записей.
Проанализируем ситуацию: если вы запишете набор записей, проводки появятся в базе. Но если после этого вы захотите обновить реквизит документа (например, СуммаОперации), вызов Записать() уничтожит ваши проводки. Чтобы этого избежать, используйте метод ОбновитьОбъект или записывайте объект до формирования набора записей и больше не трогайте его.
Важный момент: При таком подходе сумма документа в списке операций может отображаться как 0. Это происходит потому, что реквизит СуммаОперации хранится в самой таблице документов. Чтобы она обновилась без перезаписи документа, в типовых конфигурациях достаточно открыть документ и нажать «Записать», либо программно рассчитать сумму заранее и сохранить её в объекте до записи проводок.
Рассмотрим еще несколько причин, по которым проводки могут не появляться:
Организация и Подразделение. Если эти поля пустые, RLS может заблокировать запись движений в таблицу базы данных, при этом сам документ (если у него есть права на чтение/запись по организации) будет создан.НайтиПоКоду() крайне опасно. Если в базе на сервере счет изменен, помечен на удаление или имеет другую иерархию (стал группой), метод вернет пустую ссылку. Запись проводки с пустой ссылкой в счете может пройти без ошибки (в зависимости от настроек), но проводка не будет отображаться в отчетах. Всегда проверяйте результат поиска: Если СчетДт.Пустая() Тогда...ПередЗаписью. Проанализируйте, нет ли там кода, который принудительно очищает движения при определенных условиях (например, если не заполнено обязательное субконто).Если вам запрещают использовать отладчик на сервере, проанализируем альтернативные способы проверки:
Записать() в блок обработки исключений и выводите ОписаниеОшибки() в текстовый файл на сервере или в сообщение пользователю.Сообщить(). Если количество больше нуля, значит, запись прошла, но данные затираются позже.Подводя итог, помните: для документа ОперацияБух самым стабильным решением является работа через свойство Движения объекта. Это гарантирует целостность данных и правильное отражение сумм в учете вне зависимости от варианта работы базы данных.