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