Каждый разработчик на платформе «1С:Предприятие 8» рано или поздно сталкивается с ситуацией, когда программно измененные реквизиты документа не соответствуют его движениям в регистрах. Типичный сценарий: мы берем проведенный документ, меняем в нем номенклатуру, сумму или счета учета, вызываем метод Записать(РежимЗаписиДокумента.Запись), и получаем опасный рассинхрон. В самом документе сохранены новые данные, а в проводках (регистрах) красуются старые значения.
Давайте подробно разберем, почему платформа ведет себя именно так, в чем кроется коварство официальной справки «1С» и какими красивыми архитектурными приемами можно решить эту задачу без ущерба для стабильности информационной базы.
Путаница часто возникает из-за формулировки в синтакс-помощнике. Открыв справку по значению РежимЗаписиДокумента.Запись, мы видим следующее описание: «В этом случае будут сохранены изменения, внесенные в документ, и движения». На первый взгляд кажется, что система должна обновить и проводки. Но это опасное заблуждение.
Проанализируем детально, как этот механизм реализован на уровне платформы:
Записать() в режиме обычной записи платформа не запускает обработчик события ОбработкаПроведения. Это значит, что никакой автоматический перерасчет наборов записей по алгоритмам документа производиться не будет.Записать(). Если вы предварительно очистили коллекцию или добавили туда строки вручную — эти изменения сохранятся.Движения в коде, платформа запишет новые реквизиты документа, а старые движения (проводки) просто оставит «как есть» без каких-либо изменений. При этом признак проведения документа (свойство Проведен) сохранит значение Истина.Почему же у пользователей и начинающих программистов возникает иллюзия, что простая запись всегда меняет проводки? Дело в разнице между интерактивным поведением системы и программным выполнением кода.
Интерактивно (в пользовательском интерфейсе): У расширения формы документа существует специальное свойство конфигуратора — «При записи перепроводить». В типовых конфигурациях (таких как «Бухгалтерия предприятия», ERP или «Управление торговлей») для большинства документов это свойство по умолчанию включено. Когда пользователь открывает проведенный документ, меняет значение в форме и нажимает кнопку «Записать», форма перехватывает это действие и неявно выполняет полноценное перепроведение документа. В результате проводки обновляются.
Программно (в коде): Метод объекта ДокументОбъект.Записать(РежимЗаписиДокумента.Запись) работает строго в соответствии со своими параметрами. Платформа полностью игнорирует настройки экранных форм и выполняет «сухую» запись данных объекта в таблицы СУБД, не трогая механизмы проведения, даже если документ уже был проведен ранее.
Рассогласование данных между документом и его движениями — одна из самых коварных проблем в «1С». Внешне документ выглядит проведенным (в списках отображается зеленая галочка), но отчеты (оборотно-сальдовая ведомость, ведомости по складам и взаиморасчетам) строятся исключительно на основании записей регистров. В итоге бухгалтер видит в отчете одну информацию, открывает документ — а там совершенно другие цифры или счета. Автоматический поиск таких расхождений может стать нетривиальной задачей.
Тем не менее, такое поведение платформы спроектировано намеренно. Оно критически важно для решения двух задач:
Запись позволяет мгновенно обновить текстовые реквизиты без изменения итогов.Запись с установленным флагом ОбменДанными.Загрузка = Истина, а затем следом загружает готовый и уже рассчитанный в другой базе набор проводок. Если бы система пыталась перепроводить документы при загрузке, это сделало бы невозможным корректный перенос истории при отсутствии остатков в принимающей базе.Вернемся к практической задаче, из-за которой возникла дискуссия. У нас есть импортируемый документ, который при определенных условиях (например, неверный забалансовый счет) не может провестись в системе. Как правильно спроектировать код, чтобы избежать нарушения логики учета?
Рассмотрим два надежных сценария реализации.
Самый безопасный подход заключается в том, чтобы попытаться провести измененный документ. Если проведение завершилось неудачей из-за ошибок в данных, мы должны отменить проведение документа (чтобы удалить старые некорректные проводки) и сохранить его как обычный незаписанный/непроведенный черновик. Пользователь сразу увидит, что документ не проведен, и оперативно исправит ошибки.
Реализуем данный алгоритм с помощью конструкции Попытка...Исключение:
НовыйДокумент = Документы.РеализацияТоваровУслуг.НайтиПоРеквизиту("ИдентификаторИсточника", ИдИзВнешнейСистемы).ПолучитьОбъект();
Если НовыйДокумент <> Неопределено Тогда
// Заполняем новые данные из внешней системы
НовыйДокумент.СчетУчета = СчетаУчетаНоменклатуры.НайтиПоКоду("003");
// ... заполнение остальных реквизитов ...
// Пытаемся провести документ штатным образом
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ успешно проведен с новыми данными!");
Исключение
// Если проведение не удалось, перехватываем ошибку
Сообщить("Не удалось провести документ: " + ОписаниеОшибки());
// Обязательно снимаем проведение, чтобы очистить старые неактуальные проводки!
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.ОтменаПроведения);
Сообщить("Старые проводки успешно удалены. Документ записан как непроведенный.");
Исключение
// На случай, если даже отмена проведения заблокирована бизнес-логикой
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Внимание! Документ записан без проводок.");
КонецПопытки;
КонецПопытки;
КонецЕсли;
Если специфика вашей бизнес-логики требует, чтобы документ оставался записанным, но старые проводки гарантированно исчезли при программном изменении данных (без вызова тяжелого метода проведения), вы можете очистить коллекции движений вручную. Посмотрим на пример реализации такого кода:
ДокументОбъект = ВыбраннаяСсылка.ПолучитьОбъект();
// Проверяем, был ли документ проведен ранее
Если ДокументОбъект.Проведен Тогда
// Отключаем автоматическое удаление движений, если необходимо управлять процессом вручную
ДокументОбъект.ЗаписьДвиженийПриЗаписи = ЗаписьДвиженийПриЗаписи.ЗаписыватьВыбранные;
// Перебираем все коллекции регистров, по которым документ делает движения
Для Каждого НаборДвижений Из ДокументОбъект.Движения Цикл
НаборДвижений.Очистить();
НаборДвижений.Записывать = Истина;
КонецЦикла;
// Сбрасываем флаг проведения документа
ДокументОбъект.Проведен = Ложь;
КонецЕсли;
// Записываем измененный документ в режиме обычной записи
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Используя приведенные методики, вы навсегда обезопасите свои информационные базы от расхождений между документами и регистрами учета, а ваши отчеты всегда будут показывать актуальную и достоверную информацию.