В процессе разработки и кастомизации конфигураций на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с задачей автоматизации ввода одних документов на основании других. Типичный сценарий: на форме документа-источника (например, «Заказ» или «Реализация») размещается кнопка, при нажатии на которую должен создаваться подчиненный документ (например, «Счет» или «Счет-фактура»).
Однако при стандартной реализации через функцию ОткрытьФорму возникает проблема: пользователь может нажимать на кнопку неограниченное количество раз, что приводит к созданию множества одинаковых документов-дубликатов, привязанных к одному и тому же основанию. В этой статье мы подробно разберем, как спроектировать надежный механизм, который при нажатии кнопки будет проверять наличие уже созданного документа, открывать его в случае обнаружения или создавать новый только при его отсутствии.
Рассмотрим стандартную реализацию, с которой часто начинают начинающие разработчики. На форме Документа 1 (источника) создается кнопка, на которую вешают следующую процедуру:
Процедура ОткрытьСчет()
ОткрытьФорму("Документ.Счет", , ТекущийДокумент());
КонецПроцедуры
При таком подходе платформа 1С 7.7 послушно открывает форму нового документа «Счет» и передает контекст текущего документа в качестве параметра «Основание». В модуле формы «Счета» отрабатывает предопределенная процедура ВводНаОсновании, которая заполняет реквизиты шапки и табличной части из документа-источника.
Проблема заключается в том, что этот код никак не анализирует базу данных на предмет того, выполнялось ли это действие ранее. Если пользователь случайно или намеренно нажмет кнопку «Создать Счет» три раза, в системе появится три независимых счета на оплату по одной и той же сделке. Нам необходимо внедрить проверку существования подчиненного документа перед непосредственным вызовом формы.
Для решения нашей задачи мы должны использовать встроенный в платформу механизм поиска подчиненных документов. В 1С 7.7 связь между документами организуется через реквизиты типа «Документ». Когда в подчиненном документе (в нашем случае это «Счет») заполняется реквизит, хранящий ссылку на документ-основание (например, реквизит ДокументОснование), платформа автоматически выстраивает дерево подчиненности.
Для работы с этой структурой в языке 1С 7.7 предназначен объект типа Документ и его метод ВыбратьПодчиненныеДокументы. Давайте проанализируем синтаксис этого метода:
ВыбратьПодчиненныеДокументы(«НачалоПериода», «КонецПериода», «ДокументОснование»)
Параметры метода позволяют ограничить выборку временным интервалом, а также жестко указать документ, для которого мы ищем подчиненные связи. Если мы передаем в качестве третьего параметра ссылку на наш текущий документ-основание, платформа выберет исключительно те документы в базе данных, у которых в реквизитах ссылки указан наш документ.
Прежде чем написать итоговый код, важно разобрать критические ошибки, которые часто допускают разработчики при написании подобных проверок на форумах (включая сообщения из темы-источника).
Во-первых, обратите внимание на синтаксис определения вида документа. В сообщении 17 на форуме была предложена конструкция:
Если Документы.Вид = "Счет" Тогда
В платформе 1С 7.7 это гарантированно приведет к ошибке времени выполнения. Дело в том, что Вид() — это метод объекта, а не его свойство. Правильное написание всегда требует использования круглых скобок: Если Документы.Вид() = "Счет" Тогда.
Во-вторых, необходимо учитывать состояние самого документа-основания. Метод ВыбратьПодчиненныеДокументы работает исключительно с базой данных (физическими таблицами на диске). Если пользователь только что создал Документ 1, заполнил его реквизиты на экране, но еще не нажал кнопку «Записать» (или «Провести»), этого документа физически нет в базе данных. Попытка найти подчиненные документы для незаписанного объекта вернет пустой результат, а при попытке открыть форму нового счета платформа выдаст ошибку, так как ссылка на владельца еще не сформирована.
Поэтому перед поиском обязательно нужно делать проверку метода Выбран():
Если Выбран() = 0 Тогда
Предупреждение("Документ необходимо записать перед выпиской счета!");
Возврат;
КонецЕсли;
В-третьих, нельзя забывать про человеческий фактор и пометку удаления. Представим ситуацию: пользователь создал счет на основании реализации, но затем передумал, пометил этот счет на удаление и решил выписать новый. Если наш алгоритм будет просто искать любой подчиненный счет, он найдет этот удаленный (неактивный) документ и предложит открыть его. Нам необходимо явно игнорировать документы, помеченные на удаление, с помощью метода ПометкаУдаления().
В-четвертых, правильная структура метаданных. Чтобы связь вообще установилась, в метаданных подчиненного документа («Счет») обязательно должен присутствовать реквизит шапки с типом «Документ» (общий или с ограничением по конкретному виду). При автозаполнении в процедуре ВводНаОсновании этот реквизит обязательно должен заполняться ссылкой на документ-источник. Например:
Процедура ВводНаОсновании(ДокОсн)
ДокументОснование = ДокОсн; // Заполняем реквизит связи
// далее идет заполнение остальных реквизитов формы...
КонецПроцедуры
С учетом всех проанализированных нюансов, давайте составим итоговый рабочий алгоритм для кнопки на форме документа-источника. Этот алгоритм решает все описанные проблемы: он проверяет запись текущего документа, ищет только проведенные или активные подчиненные счета, игнорирует помеченные на удаление документы и предлагает пользователю удобный выбор.
Разместим в модуле формы документа-источника следующий код обработчика кнопки:
Процедура ОткрытьСчет()
// Шаг 1. Проверяем, записан ли текущий документ в базу данных
Если Выбран() = 0 Тогда
Предупреждение("Текущий документ еще не записан! Сначала запишите документ.", 5);
Возврат;
КонецЕсли;
// Шаг 2. Инициализируем поиск подчиненных документов
ДокПодч = СоздатьОбъект("Документ");
// Выбираем все подчиненные документы без ограничения по периоду
ДокПодч.ВыбратьПодчиненныеДокументы(, , ТекущийДокумент());
СчетНайден = 0;
// Шаг 3. Обходим выборку подчиненных документов в цикле
Пока ДокПодч.ПолучитьДокумент() = 1 Цикл
// Проверяем, совпадает ли вид найденного документа с искомым "Счет"
Если ДокПодч.Вид() = "Счет" Тогда
// Проверяем, не помечен ли данный счет на удаление
Если ДокПодч.ПометкаУдаления() = 0 Тогда
// Активный документ найден, фиксируем ссылку и прерываем поиск
СчетНайден = 1;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Шаг 4. Анализируем результаты поиска и принимаем решение
Если СчетНайден = 1 Тогда
// Запрашиваем у пользователя подтверждение на открытие существующего документа
ТекстВопроса = "Счет по этому документу уже был создан ранее (" +
СокрЛП(ДокПодч.НомерДок) + " от " + ДокПодч.ДатаДок +
"). Открыть существующий счет?";
Ответ = Вопрос(ТекстВопроса, "Да+Нет", 60);
Если Ответ = "Да" Тогда
// Открываем форму существующего счета для просмотра или редактирования
ОткрытьФорму(ДокПодч.ТекущийДокумент());
КонецЕсли;
Иначе
// Если активных счетов не найдено — открываем форму создания нового счета
ОткрытьФорму("Документ.Счет", , ТекущийДокумент());
EndIf;
КонецПроцедуры
В крупных типовых конфигурациях 1С 7.7 (таких как «Комплексная конфигурация», «Производство+Услуги+Бухгалтерия» или «Торговля и Склад») разработчики фирмы «1С» уже позаботились о централизации подобных механизмов. Чтобы не писать один и тот же код поиска в десятках разных документов, в Глобальном модуле часто объявляются универсальные экспортные функции.
Если вы работаете в рамках типовой конфигурации, перед написанием собственного кода загляните в Глобальный модуль. Там вы можете обнаружить функции вида глНайтиПодчиненныйДокумент(Контекст, ВидДокумента) или аналогичные. Использование глобальных функций сократит ваш код на кнопке до одной-двух строк:
Процедура ОткрытьСчет()
// Пример вызова готового механизма из типовой конфигурации
Если глНайтиПодчиненныйДокумент(Контекст, "Счет") = 0 Тогда
ОткрытьФорму("Документ.Счет", , ТекущийДокумент());
КонецЕсли;
КонецПроцедуры
Такой подход делает код чище, проще в поддержке и снижает вероятность возникновения ошибок при обновлении или доработке конфигурации другими программистами. Внедряя подобные механизмы контроля, вы значительно повышаете качество ведения учета и минимизируете риски появления дублей в информационной базе ваших пользователей.