Как сделать опись документов по хранилищам в 1С с учетом копий и оригиналов

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

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

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

В чем заключалась проблема исходного алгоритма?

Давайте проанализируем ситуацию, с которой столкнулся автор темы. В его схеме при создании документа АрхивныйДокумент указывается основное Хранилище (для оригиналов), количество подлинников, а также ХранилищеКоп (для копий) и количество копий.

При попытке вывести опись по ответственному возникли следующие проблемы:

  1. Дублирование строк и некорректные наименования: В печатной форме для всех строк выводилось одно и то же хранилище «А123», хотя количественные показатели в строках различались.
  2. Разделение оригиналов и копий: Копии выводились отдельной строкой, хотя логически они должны были суммироваться с оригиналами, если относятся к одному и тому же хранилищу.
  3. Неэффективная фильтрация: Сложное и логически неверное условие ГДЕ в запросе с использованием множественных операторов ИЛИ, работающее крайне медленно на больших базах данных.

Выясним причины этих ошибок и разберем по шагам правильную архитектуру решения.

---

Шаг 1. Исправление логики вывода наименования хранилища

Рассмотрим подробнее код вывода данных в цикле, который использовал разработчик:


Хран = Справочники.Хранилища.НайтиПоРеквизиту("Ответственный", Объект.ТекОтв,,);
...
Пока Выборка.Следующий() Цикл
    Кол = Кол + 1;
    ОбластьЗначениеТабл.Параметры.НомХран = Хран.Наименование; // ОШИБКА!
    ОбластьЗначениеТабл.Параметры.КолЭкз = Выборка.КолвоПодл;
    ОбластьЗначениеТабл.Параметры.КолДок = Выборка.КолДок;
    ОбластьЗначениеТабл.Параметры.НомерПП = Кол;
    ТабДок.Вывести(ОбластьЗначениеТабл);
КонецЦикла;

В чем ошибка? Переменная Хран инициализируется один раз до выполнения запроса методом НайтиПоРеквизиту. В результате в цикле обхода результатов запроса в параметр НомХран каждый раз передавалось наименование одного и того же первого найденного хранилища.

Для исправления этой ошибки необходимо брать наименование хранилища непосредственно из текущей строки выборки запроса:

ОбластьЗначениеТабл.Параметры.НомХран = Выборка.Хранилище.Наименование;

---

Шаг 2. Разработка правильной структуры запроса (Объединение данных)

Если мы попытаемся выбрать оригиналы и копии в одном простом запросе, то неизбежно столкнемся с проблемой: оригиналы привязаны к полю Хранилище, а копии — к ХранилищеКоп. Это разные измерения. Попытка наложить условие ИЛИ приведет к каше в результатах.

Правильным архитектурным подходом в 1С для таких задач является использование конструкции ОБЪЕДИНИТЬ ВСЕ (UNION ALL) во временной таблице с последующей группировкой. Мы разобьем задачу на два этапа:

  1. Выберем все оригиналы документов по нужному ответственному лицу.
  2. Выберем все копии документов по этому же ответственному лицу.
  3. Объединим результаты во временную таблицу и выполним по ней итоговую группировку.

Рассмотрим, как будет выглядеть оптимальный запрос:


ВЫБРАТЬ
    АрхивныйДокумент.Хранилище КАК Хранилище,
    АрхивныйДокумент.Ссылка КАК ДокументСсылка,
    ЕСТЬNULL(АрхивныйДокумент.КолвоПодл, 0) КАК КоличествоЭкз
ПОМЕСТИТЬ ВТ_ДвиженияДокументов
ИЗ
    Документ.АрхивныйДокумент КАК АрхивныйДокумент
ГДЕ
    АрхивныйДокумент.Хранилище.Ответственный = &Ответственный
    И АрхивныйДокумент.КолвоПодл > 0

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    АрхивныйДокумент.ХранилищеКоп КАК Хранилище,
    АрхивныйДокумент.Ссылка КАК ДокументСсылка,
    ЕСТЬNULL(АрхивныйДокумент.КолвоКопий, 0) КАК КоличествоЭкз
ИЗ
    Документ.АрхивныйДокумент КАК АрхивныйДокумент
ГДЕ
    АрхивныйДокумент.ХранилищеКоп.Ответственный = &Ответственный
    И АрхивныйДокумент.КолвоКопий > 0
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ДвиженияДокументов.Хранилище КАК Хранилище,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ_ДвиженияДокументов.ДокументСсылка) КАК КоличествоНаименований,
    СУММА(ВТ_ДвиженияДокументов.КоличествоЭкз) КАК КоличествоЭкземпляров
ИЗ
    ВТ_ДвиженияДокументов КАК ВТ_ДвиженияДокументов
ГДЕ
    ВТ_ДвиженияДокументов.Хранилище <> ЗНАЧЕНИЕ(Справочник.Хранилища.ПустаяСсылка)
СГРУППИРОВАТЬ ПО
    ВТ_ДвиженияДокументов.Хранилище
АВТОУПОРЯДОЧИВАНИЕ

Преимущества этого запроса:

---

Шаг 3. Реализация программного вывода отчета

Теперь объединим наш запрос и логику вывода в печатную форму. Проанализируем готовый вариант функции формирования описи:


Функция СформироватьОписьДокументов() Экспорт

    ТабДок = Новый ТабличныйДокумент;
    
    // Получение макета
    НовОтчет = РеквизитФормыВЗначение("Объект");
    ОбъектМакет = НовОтчет.ПолучитьМакет("МакетУход");
    
    // Получение областей макета
    ОбластьШапка = ОбъектМакет.ПолучитьОбласть("Шапка");
    ОбластьШапкаТабл = ОбъектМакет.ПолучитьОбласть("ШапкаТабл");        
    ОбластьЗначениеТабл = ОбъектМакет.ПолучитьОбласть("ЗначениеТабл");
    ОбластьПункт2 = ОбъектМакет.ПолучитьОбласть("Пункт2");
    ОбластьПодвал = ОбъектМакет.ПолучитьОбласть("Подвал");
    
    // Заполнение параметров шапки и подвала
    ОбластьШапка.Параметры.ТекущаяДата = Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy") + "г.";
    ОбластьШапка.Параметры.ДолжностьОтпускник = Объект.ТекОтв.Должность;
    ОбластьШапка.Параметры.ФИООтпускник = Объект.ТекОтв.Наименование;
    ОбластьШапка.Параметры.ДолжностьПринимающий = Объект.НовОтветственный.Должность;
    ОбластьШапка.Параметры.ФИОПринимающий = Объект.НовОтветственный.Наименование;
    
    ОбластьПодвал.Параметры.ТекущаяДата = Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy") + "г.";
    ОбластьПодвал.Параметры.Отв = Объект.ТекОтв.Сокращенно;
    ОбластьПодвал.Параметры.Приним = Объект.НовОтветственный.Сокращенно;
    ОбластьПодвал.Параметры.НомПечатиПринимающего = Объект.ПечатьНовОтветственного;
    ОбластьПодвал.Параметры.НомПечатиОтпускника = Объект.ПечатьТекОтв;
    
    // Вывод статических областей
    ТабДок.Вывести(ОбластьШапка);
    ТабДок.Вывести(ОбластьШапкаТабл);
    
    // Создание и настройка запроса
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	АрхивныйДокумент.Хранилище КАК Хранилище,
        |	АрхивныйДокумент.Ссылка КАК ДокументСсылка,
        |	ЕСТЬNULL(АрхивныйДокумент.КолвоПодл, 0) КАК КоличествоЭкз
        |ПОМЕСТИТЬ ВТ_ДвиженияДокументов
        |ИЗ
        |	Документ.АрхивныйДокумент КАК АрхивныйДокумент
        |ГДЕ
        |	АрхивныйДокумент.Хранилище.Ответственный = &Ответственный
        |	И АрхивныйДокумент.КолвоПодл > 0
        |
        |ОБЪЕДИНИТЬ ВСЕ
        |
        |ВЫБРАТЬ
        |	АрхивныйДокумент.ХранилищеКоп КАК Хранилище,
        |	АрхивныйДокумент.Ссылка КАК ДокументСсылка,
        |	ЕСТЬNULL(АрхивныйДокумент.КолвоКопий, 0) КАК КоличествоЭкз
        |ИЗ
        |	Документ.АрхивныйДокумент КАК АрхивныйДокумент
        |ГДЕ
        |	АрхивныйДокумент.ХранилищеКоп.Ответственный = &Ответственный
        |	И АрхивныйДокумент.КолвоКопий > 0
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ_ДвиженияДокументов.Хранилище КАК Хранилище,
        |	ВТ_ДвиженияДокументов.Хранилище.Наименование КАК ХранилищеНаименование,
        |	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ_ДвиженияДокументов.ДокументСсылка) КАК КоличествоНаименований,
        |	СУММА(ВТ_ДвиженияДокументов.КоличествоЭкз) КАК КоличествоЭкземпляров
        |ИЗ
        |	ВТ_ДвиженияДокументов КАК ВТ_ДвиженияДокументов
        |ГДЕ
        |	ВТ_ДвиженияДокументов.Хранилище <> ЗНАЧЕНИЕ(Справочник.Хранилища.ПустаяСсылка)
        |СГРУППИРОВАТЬ ПО
        |	ВТ_ДвиженияДокументов.Хранилище,
        |	ВТ_ДвиженияДокументов.Хранилище.Наименование
        |АВТОУПОРЯДОЧИВАНИЕ";
        
    Запрос.УстановитьПараметр("Ответственный", Объект.ТекОтв);
    
    // Выполнение запроса и вывод данных в таблицу
    Выборка = Запрос.Выполнить().Выбрать();
    Кол = 0;
    
    Пока Выборка.Следующий() Цикл
        Кол = Кол + 1;
        
        // Заполняем параметры строки из выборки
        ОбластьЗначениеТабл.Параметры.НомерПП = Кол;
        ОбластьЗначениеТабл.Параметры.НомХран = Выборка.ХранилищеНаименование;
        ОбластьЗначениеТабл.Параметры.КолДок = Выборка.КоличествоНаименований;
        ОбластьЗначениеТабл.Параметры.КолЭкз = Выборка.КоличествоЭкземпляров;
        
        ТабДок.Вывести(ОбластьЗначениеТабл);
    КонецЦикла;
    
    // Вывод подвала
    ТабДок.Вывести(ОбластьПункт2);
    ТабДок.Вывести(ОбластьПодвал);
    
    Возврат ТабДок;

КонецФункции

---

Совет по оптимизации: Переход на регистры накопления

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

Для долгосрочной стабильной работы рекомендуется использовать классический паттерн проектирования 1С — Регистры накопления.

Выясним, как перевести решение на рельсы регистров:

  1. Создайте оборотный регистр накопления (или регистр остатков, если документы могут списываться/уничтожаться) под названием НаличиеДокументов.
  2. Добавьте в него измерения: Хранилище (СправочникСсылка.Хранилища) и Документ (ДокументСсылка.АрхивныйДокумент).
  3. Добавьте ресурс: Количество (Число).
  4. При проведении документа АрхивныйДокумент делайте движения по регистру:
    • Для оригиналов: Измерение Хранилище = Хранилище, Ресурс Количество = КолвоПодл.
    • Для копий: Измерение Хранилище = ХранилищеКоп, Ресурс Количество = КолвоКопий.
  5. В отчете вместо тяжелого запроса к документам обращайтесь к виртуальной таблице РегистрНакопления.НаличиеДокументов.Обороты (или Остатки).

Такой подход позволит базе данных мгновенно возвращать результат описи независимо от того, сколько миллионов документов было введено в систему за все годы работы.

← На главную