Мы, разработчики и пользователи 1С, постоянно стремимся к оптимизации рутинных операций. Одна из таких задач, которая часто возникает в работе бухгалтеров и операционистов, — это автоматическое заполнение поля "Договор" при выборе "Контрагента" в документах, например, в "Поступлении товаров и услуг" или "Реализации товаров и услуг". Эта автоматизация значительно ускоряет ввод данных и снижает вероятность ошибок. Давайте подробно рассмотрим, как реализовать такую функциональность в 1С:Бухгалтерии 3.0, учитывая все нюансы и лучшие практики, обсуждаемые в сообществе.
Прежде чем приступать к написанию собственного кода, мы всегда анализируем, какие возможности предоставляет типовая конфигурация. В 1С:Бухгалтерии 3.0 уже есть встроенный механизм для частичной автоматизации этой задачи – это концепция "Основного договора".
Настройка основного договора: Чтобы система могла автоматически подставлять договор, его необходимо пометить как основной. Мы открываем карточку контрагента, переходим на вкладку "Договоры", выбираем нужный договор и нажимаем кнопку "Использовать как основной". Зеленая галочка рядом с договором будет означать, что он помечен как основной.
Условия подстановки: Основной договор будет автоматически подставляться в документы при выборе контрагента, но только если он соответствует определенным условиям. Ключевые условия включают:
На практике мы часто сталкиваемся с тем, что стандартный механизм не всегда удовлетворяет всем требованиям. Например, если основной договор не подходит по одному из условий, или если у контрагента нет явно помеченного основного договора, но есть единственный или наиболее подходящий. В этих случаях стандартная функциональность не срабатывает, и мы видим, что поле "Договор" остается пустым, требуя ручного заполнения или выбора. Именно для таких сценариев нам и потребуется разработка собственного, более гибкого алгоритма.
Для того чтобы сделать подстановку договора более умной и учесть различные сценарии, мы создадим программный код, который будет выполняться при изменении поля "Контрагент". Давайте разберем основные шаги и компоненты нашего алгоритма.
Выбор места для кода: Оптимальным местом для запуска нашей логики является событие ПриИзменении для поля Контрагент на форме документа. Это событие срабатывает сразу после того, как пользователь выбрал или изменил контрагента.
Создание серверной функции: Для работы с данными справочников (Справочники.ДоговорыКонтрагентов) и регистров сведений (РегистрСведений.ОсновныеДоговорыКонтрагента) нам потребуется серверный контекст. Поэтому основная логика поиска договора будет реализована в серверной функции. Она будет принимать выбранного контрагента и другие параметры документа, а затем возвращать найденный договор.
Критерии для поиска договора: Чтобы наш алгоритм был максимально точным и полезным, мы должны учитывать следующие параметры при поиске:
Обработка различных сценариев:
Договор.Договор пустым, чтобы он мог создать новый договор или выбрать его вручную.Давайте перейдем к практической реализации нашего алгоритма. Мы будем работать с модулем формы документа, где расположены поля Контрагент и Договор. Предположим, что путь к данным для этих полей — Объект.Контрагент и Объект.Договор соответственно. Также мы учтем, что для поля Договор уже установлено свойство СвязиПараметровВыбора, которое динамически фильтрует список договоров по владельцу (контрагенту), что полезно при ручном выборе.
Шаг 1: Добавляем обработчик события ПриИзменении для поля Контрагент.
Мы открываем форму документа в конфигураторе, находим элемент формы, привязанный к реквизиту Контрагент. В его свойствах, в разделе "События", мы создаем или выбираем процедуру КонтрагентПриИзменении. Эта процедура будет выполняться на клиенте и из неё мы будем вызывать нашу серверную функцию для поиска договора. В этой процедуре мы соберем все необходимые параметры документа, такие как организация, валюта и дата, чтобы передать их на сервер для точного отбора договоров.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
// Если контрагент очищен, очищаем и договор
Если Объект.Контрагент.Пустая() Тогда
Объект.Договор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
Возврат;
КонецЕсли;
// Собираем параметры текущего документа.
// Убедитесь, что эти реквизиты существуют в вашем документе (Объект)
// и корректно заполняются перед вызовом серверной функции.
Перем ОрганизацияДокумента Экспорт;
Перем ВалютаДокумента Экспорт;
Перем ДатаДокумента Экспорт;
Перем ИмяДокумента Экспорт;
ОрганизацияДокумента = Объект.Организация;
// Валюта документа может называться Валюта или ВалютаДокумента
// Уточните имя реквизита в вашем документе
ВалютаДокумента = Объект.Валюта;
ДатаДокумента = Объект.Дата;
// Получаем имя текущего документа, например, "ПоступлениеТоваровУслуг"
ИмяДокумента = Объект.Метаданные().Имя;
// Вызываем серверную функцию для получения договора, передавая все необходимые параметры
Объект.Договор = ПолучитьПодходящийДоговорНаСервере(
Объект.Контрагент,
ОрганизацияДокумента,
ВалютаДокумента,
ДатаДокумента,
ИмяДокумента
);
// Если договор не был найден, но контрагент выбран, можно вывести сообщение.
// Или если подставлен "первый из нескольких", тоже сообщить.
// Для этого ПолучитьПодходящийДоговорНаСервере должна возвращать не только договор,
// но и статус/сообщение.
Если Объект.Договор.Пустая() Тогда
Сообщить(СтрШаблон("Для контрагента %1 не найден подходящий договор.", Объект.Контрагент));
КонецЕсли;
КонецПроцедуры
Обратите внимание на передачу параметров ОрганизацияДокумента, ВалютаДокумента, ДатаДокумента и ИмяДокумента. Эти параметры критически важны для точного отбора договоров на стороне сервера.
Шаг 2: Создаем главную серверную функцию ПолучитьПодходящийДоговорНаСервере.
Эта функция будет оркестратором нашего процесса. Она определит необходимый ВидДоговора на основе имени документа и последовательно вызовет вспомогательные функции для поиска основного и затем единственного (или первого) договора. Мы делаем ее &НаСервереБезКонтекста для лучшей переиспользуемости и независимости от контекста конкретной формы.
&НаСервереБезКонтекста
Функция ПолучитьПодходящийДоговорНаСервере(Контрагент, Организация, ВалютаДокумента, ДатаДокумента, ИмяДокумента)
Если Контрагент.Пустая() Тогда
Возврат Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
КонецЕсли;
// Определяем необходимый вид договора на основе имени документа.
// Этот список нужно расширить в соответствии с вашими документами и видами договоров.
Перечисления.ВидыДоговоровКонтрагентов ВидДоговора = Неопределено;
Если ИмяДокумента = "ПоступлениеТоваровУслуг" Тогда
ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.СПоставщиком;
ИначеЕсли ИмяДокумента = "РеализацияТоваровУслуг" Тогда
ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.СПокупателем;
ИначеЕсли ИмяДокумента = "АвансовыйОтчет" Тогда // Пример для других документов
ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.Прочее;
КонецЕсли;
// 1. В первую очередь пытаемся найти основной договор, подходящий по всем параметрам.
СсылкаНаДоговор = ПолучитьОсновнойДоговор(Контрагент, Организация, ВидДоговора, ВалютаДокумента, ДатаДокумента);
Если Не СсылкаНаДоговор.Пустая() Тогда
Возврат СсылкаНаДоговор;
КонецЕсли;
// 2. Если основной договор не найден или не подходит, ищем единственный подходящий
// договор среди всех, или первый попавшийся, если их несколько.
СсылкаНаДоговор = ПолучитьЕдинственныйИлиПервыйДоговор(Контрагент, Организация, ВидДоговора, ВалютаДокумента, ДатаДокумента);
Возврат СсылкаНаДоговор;
КонецФункции // ПолучитьПодходящийДоговорНаСервере
Шаг 3: Реализуем вспомогательную функцию ПолучитьОсновнойДоговор.
Эта функция будет искать договор, помеченный как основной, в регистре сведений ОсновныеДоговорыКонтрагента. При этом мы будем проверять соответствие не только по контрагенту, но и по организации, виду договора, валюте и сроку действия. Для этого мы используем запросы с динамическим добавлением условий.
&НаСервереБезКонтекста
Функция ПолучитьОсновнойДоговор(Контрагент, Организация, ВидДоговора, ВалютаДокумента, ДатаДокумента)
Результат = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ОсновныеДоговорыКонтрагента.Договор
|ИЗ
| РегистрСведений.ОсновныеДоговорыКонтрагента КАК ОсновныеДоговорыКонтрагента
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
| ПО ОсновныеДоговорыКонтрагента.Договор = ДоговорыКонтрагентов.Ссылка
|ГДЕ
| ОсновныеДоговорыКонтрагента.Контрагент = &Контрагент
| И НЕ ДоговорыКонтрагентов.ДоговорПрисоединенныеФайлы.ПометкаУдаления"; // Проверяем, что сам договор не помечен на удаление
Запрос.УстановитьПараметр("Контрагент", Контрагент);
// Динамически добавляем условия для отбора по организации, виду договора, валюте и сроку действия.
// Использование ЗначениеЗаполнено() позволяет избежать ошибок, если параметры не переданы.
Если ЗначениеЗаполнено(Организация) Тогда
Запрос.Текст = Запрос.Текст + " И ДоговорыКонтрагентов.Организация = &Организация";
Запрос.УстановитьПараметр("Организация", Организация);
КонецЕсли;
Если ЗначениеЗаполнено(ВидДоговора) Тогда
Запрос.Tекст = Запрос.Текст + " И ДоговорыКонтрагентов.ВидДоговора = &ВидДоговора";
Запрос.УстановитьПараметр("ВидДоговора", ВидДоговора);
КонецЕсли;
Если ЗначениеЗаполнено(ВалютаДокумента) Тогда
Запрос.Текст = Запрос.Текст + " И ДоговорыКонтрагентов.ВалютаВзаиморасчетов = &ВалютаДокумента";
Запрос.УстановитьПараметр("ВалютаДокумента", ВалютаДокумента);
КонецЕсли;
Если ЗначениеЗаполнено(ДатаДокумента) Тогда
// Проверяем, что договор действует на дату документа.
// ДАТА(1, 1, 1) часто используется для обозначения "неограниченного" срока.
Запрос.Текст = Запрос.Текст + " И (ДоговорыКонтрагентов.ДатаНачала = ДАТА(1, 1, 1) ИЛИ ДоговорыКонтрагентов.ДатаНачала <= &ДатаДокумента)";
Запрос.Текст = Запрос.Текст + " И (ДоговорыКонтрагентов.ДатаОкончания = ДАТА(1, 1, 1) ИЛИ ДоговорыКонтрагентов.ДатаОкончания >= &ДатаДокумента)";
Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
КонецЕсли;
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() Тогда
Результат = Выборка.Договор;
КонецЕсли;
Возврат Результат;
КонецФункции // ПолучитьОсновнойДоговор
Шаг 4: Реализуем вспомогательную функцию ПолучитьЕдинственныйИлиПервыйДоговор.
Если основной договор не был найден или не подошел по условиям, эта функция будет искать среди всех остальных договоров контрагента, которые не помечены на удаление и соответствуют всем остальным фильтрам (организация, вид, валюта, срок действия). Если будет найден только один такой договор, мы подставим его. Если же таких договоров окажется несколько, мы можем подставить первый из них и сообщить пользователю о выборе.
&НаСервереБезКонтекста
Функция ПолучитьЕдинственныйИлиПервыйДоговор(Контрагент, Организация, ВидДоговора, ВалютаДокумента, ДатаДокумента)
Результат = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДоговорыКонтрагентов.Ссылка КАК Договор,
| ДоговорыКонтрагентов.Наименование КАК Наименование
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.Владелец = &Контрагент
| И НЕ ДоговорыКонтрагентов.ПометкаУдаления"; // Исключаем помеченные на удаление
Запрос.УстановитьПараметр("Контрагент", Контрагент);
// Динамически добавляем условия, аналогично функции ПолучитьОсновнойДоговор
Если ЗначениеЗаполнено(Организация) Тогда
Запрос.Текст = Запрос.Текст + " И ДоговорыКонтрагентов.Организация = &Организация";
Запрос.УстановитьПараметр("Организация", Организация);
КонецЕсли;
Если ЗначениеЗаполнено(ВидДоговора) Тогда
Запрос.Tекст = Запрос.Текст + " И ДоговорыКонтрагентов.ВидДоговора = &ВидДоговора";
Запрос.УстановитьПараметр("ВидДоговора", ВидДоговора);
КонецЕсли;
Если ЗначениеЗаполнено(ВалютаДокумента) Тогда
Запрос.Текст = Запрос.Текст + " И ДоговорыКонтрагентов.ВалютаВзаиморасчетов = &ВалютаДокумента";
Запрос.УстановитьПараметр("ВалютаДокумента", ВалютаДокумента);
КонецЕсли;
Если ЗначениеЗаполнено(ДатаДокумента) Тогда
Запрос.Tекст = Запрос.Текст + " И (ДоговорыКонтрагентов.ДатаНачала = ДАТА(1, 1, 1) ИЛИ ДоговорыКонтрагентов.ДатаНачала <= &ДатаДокумента)";
Запрос.Текст = Запрос.Текст + " И (ДоговорыКонтрагентов.ДатаОкончания = ДАТА(1, 1, 1) ИЛИ ДоговорыКонтрагентов.ДатаОкончания >= &ДатаДокумента)";
Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
КонецЕсли;
// Упорядочиваем для предсказуемого выбора "первого" договора, если их несколько.
// Например, по наименованию или по дате создания.
Запрос.Текст = Запрос.Текст + " УПОРЯДОЧИТЬ ПО ДоговорыКонтрагентов.Наименование";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Количество() = 1 Тогда
// Найден единственный подходящий договор
Выборка.Следующий();
Результат = Выборка.Договор;
ИначеЕсли Выборка.Количество() > 1 Тогда
// Найдено несколько договоров. Подставляем первый и рекомендуем пользователю проверить.
Выборка.Следующий();
Результат = Выборка.Договор;
// Здесь можно было бы вернуть статус для вывода сообщения на клиенте,
// например, через возвращаемую структуру: {Договор = Результат, Сообщение = "Найдено несколько договоров..."}
// Для упрощения примера, пока возвращаем только договор.
КонецЕсли;
Возврат Результат;
КонецФункции // ПолучитьЕдинственныйИлиПервыйДоговор
Разработанный нами алгоритм уже достаточно функционален, но мы всегда можем его улучшить и сделать более надежным и удобным для конечных пользователей. Давайте рассмотрим несколько важных аспектов.
Расширение СвязиПараметровВыбора: Мы уже упоминали свойство элемента формы СвязиПараметровВыбора для поля Договор. Изначально оно используется для отбора по владельцу (Отбор.Владелец(Объект.Контрагент)). Мы настоятельно рекомендуем расширить этот отбор, добавив в него все те же параметры, которые мы используем в нашем коде (Организация, ВидДоговора, Валюта). Это позволит пользователю при ручном открытии формы выбора договора сразу видеть только релевантные договоры, что значительно упрощает работу.
Гибкость и возможность ручной корректировки: Важно помнить, что автоматизация должна помогать, а не мешать. Пользователь всегда должен иметь возможность вручную изменить автоматически подставленный договор, если по каким-то причинам он не подходит. Не создавайте жестких ограничений, которые не позволяют исправить ошибку или выбрать альтернативный вариант.
Уведомления пользователя: Если алгоритм сталкивается с неопределенностью (например, найдено несколько подходящих договоров, и система выбрала первый), мы рекомендуем выдавать пользователю информационное сообщение. Это повышает прозрачность работы системы и акцентирует внимание на моменте, требующем потенциальной проверки. Как мы говорили, серверная функция может возвращать не только ссылку на договор, но и текстовое сообщение или статус для вывода на клиенте.
Альтернативный подход – автоматическое открытие формы выбора: Один из участников форума предложил отличную идею: если найдено несколько подходящих договоров, вместо автоматической подстановки первого, можно сразу открыть форму выбора договоров, уже отфильтрованную по всем параметрам. Это дает пользователю полный контроль над ситуацией и исключает неверные автоматические подстановки. Реализовать это можно, вызывая метод ПоказатьВыбор() для элемента формы Договор после того, как вы программно применили все необходимые отборы к списку выбора.
Учет "последнего используемого" договора: В некоторых бизнес-процессах наиболее релевантным считается последний используемый договор для данного контрагента и организации. Вы можете реализовать дополнительный регистр сведений, который будет хранить такую информацию, и в вашем алгоритме поиска отдавать приоритет такому договору. Это требует дополнительной логики записи в этот регистр при проведении документов.
В конечном итоге, эффективная автоматизация заполнения договора в 1С:Бухгалтерии 3.0 требует сочетания стандартных механизмов программы с тщательно продуманной пользовательской логикой. Мы должны учесть все специфические параметры и сценарии использования, чтобы система работала точно, минимизировала ошибки и действительно помогала пользователям выполнять свою работу быстрее и эффективнее.