Как в 1С 7.7 при создании документа на основании открыть уже существующий документ и избежать дубликатов?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

В процессе разработки и кастомизации конфигураций на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с задачей автоматизации ввода одних документов на основании других. Типичный сценарий: на форме документа-источника (например, «Заказ» или «Реализация») размещается кнопка, при нажатии на которую должен создаваться подчиненный документ (например, «Счет» или «Счет-фактура»).

Однако при стандартной реализации через функцию ОткрытьФорму возникает проблема: пользователь может нажимать на кнопку неограниченное количество раз, что приводит к созданию множества одинаковых документов-дубликатов, привязанных к одному и тому же основанию. В этой статье мы подробно разберем, как спроектировать надежный механизм, который при нажатии кнопки будет проверять наличие уже созданного документа, открывать его в случае обнаружения или создавать новый только при его отсутствии.

В чем суть проблемы: анализ исходной ситуации

Рассмотрим стандартную реализацию, с которой часто начинают начинающие разработчики. На форме Документа 1 (источника) создается кнопка, на которую вешают следующую процедуру:


Процедура ОткрытьСчет()
    ОткрытьФорму("Документ.Счет", , ТекущийДокумент());
КонецПроцедуры

При таком подходе платформа 1С 7.7 послушно открывает форму нового документа «Счет» и передает контекст текущего документа в качестве параметра «Основание». В модуле формы «Счета» отрабатывает предопределенная процедура ВводНаОсновании, которая заполняет реквизиты шапки и табличной части из документа-источника.

Проблема заключается в том, что этот код никак не анализирует базу данных на предмет того, выполнялось ли это действие ранее. Если пользователь случайно или намеренно нажмет кнопку «Создать Счет» три раза, в системе появится три независимых счета на оплату по одной и той же сделке. Нам необходимо внедрить проверку существования подчиненного документа перед непосредственным вызовом формы.

Анализ механизма подчиненности в 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 Тогда
        ОткрытьФорму("Документ.Счет", , ТекущийДокумент());
    КонецЕсли;
КонецПроцедуры

Такой подход делает код чище, проще в поддержке и снижает вероятность возникновения ошибок при обновлении или доработке конфигурации другими программистами. Внедряя подобные механизмы контроля, вы значительно повышаете качество ведения учета и минимизируете риски появления дублей в информационной базе ваших пользователей.

← На главную