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