Как реализовать выбор и сохранение нескольких ответственных (кладовщиков) в разрезе складов в документе на 1С 7.7?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

При работе в устаревших, но все еще активно используемых конфигурациях на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с аппаратными и архитектурными ограничениями системы. Одной из таких классических задач является необходимость реализовать сложный многокритериальный выбор аналитики в документе. Например, когда в одном документе поступления содержатся товары, приходящие на разные физические склады, и для каждого склада (или даже для каждой строки) необходимо указать одного или нескольких ответственных кладовщиков, участвовавших в приемке.

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

---

Постановка задачи и анализ ограничений платформы 1С 7.7

Прежде чем перейти к написанию кода, определим исходные данные нашей бизнес-логики:

  1. Имеется справочник Кладовщики (или сотрудники), где для каждого элемента указан реквизит Склад (ссылка на справочник Склады).
  2. В карточке номенклатуры (справочник Номенклатура) закреплен базовый склад хранения.
  3. В документе "Поступление" (или любом другом приходном/расходном документе) заполняется табличная часть с перечнем номенклатуры.
  4. Задача: После заполнения табличной части предоставить пользователю возможность выбрать только тех кладовщиков, склады которых фигурируют в позициях номенклатуры данного документа. Выбранных кладовщиков необходимо привязать к документу для последующего анализа (в отчетах, журналах и т.д.).

Основная сложность платформы 1С 7.7 заключается в том, что в отличие от 1С 8.х, здесь не существует механизма нескольких табличных частей в одном документе, а штатная табличная часть жестко привязана к структуре метаданных. Также стандартные экранные формы не позволяют легко использовать динамические таблицы значений без применения внешних компонент (таких как 1С++ или FormEx). Исходя из этого, выберем оптимальный путь решения.

---

Вариант 1. Реквизит "Кладовщик" непосредственно в табличной части документа

Это самый простой и очевидный способ с точки зрения структуры метаданных. Мы добавляем новый реквизит табличной части документа Кладовщик с типом значения Справочник.Кладовщики.

При этом алгоритм выбора должен жестко контролировать соответствие склада товара и склада кладовщика. Разберем, как реализовать фильтрацию списка выбора на лету.

Для этого в модуле формы документа переопределим предопределенную процедуру ПриНачалеВыбораЗначения():


Процедура ПриНачалеВыбораЗначения(ИдРеквизита, ФлагСтандартнойОбработки)
    Если ИдРеквизита = "Кладовщик" Тогда
        // Отключаем стандартный выбор справочника
        ФлагСтандартнойОбработки = 0;
        
        // Получаем склад из текущей строки номенклатуры
        ТекНоменклатура = Номенклатура;
        Если ПустоеЗначение(ТекНоменклатура) = 1 Тогда
            Предупреждение("Сначала выберите номенклатуру в строке!");
            Возврат;
        КонецЕсли;
        
        ТекСклад = ТекНоменклатура.Склад;
        Если ПустоеЗначение(ТекСклад) = 1 Тогда
            Предупреждение("У выбранной номенклатуры не указан склад хранения!");
            Возврат;
        КонецЕсли;
        
        // Формируем список кладовщиков, закрепленных за этим складом
        СпКладовщиков = СоздатьОбъект("СписокЗначений");
        СпрКладовщики = СоздатьОбъект("Справочник.Кладовщики");
        
        СпрКладовщики.ВыбратьЭлементы();
        Пока СпрКладовщики.ПолучитьЭлемент() = 1 Цикл
            // Проверяем соответствие склада
            Если СпрКладовщики.Склад = ТекСклад Тогда
                СпКладовщиков.ДобавитьЗначение(СпрКладовщики.ТекущийЭлемент(), Строка(СпрКладовщики.Наименование));
            КонецЕсли;
        КонецЦикла;
        
        Если СпКладовщиков.РазмерСписка() = 0 Тогда
            Предупреждение("Нет кладовщиков, закрепленных за складом: " + Строка(ТекСклад));
            Возврат;
        КонецЕсли;
        
        // Показываем пользователю отфильтрованный список выбора
        ВыбКладовщик = "";
        Если СпКладовщиков.ВыбратьЗначение(ВыбКладовщик, "Выберите ответственного кладовщика", , 60, 1) = 1 Тогда
            Кладовщик = ВыбКладовщик;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Преимущества: Простая реализация, данные хранятся в стандартной табличной части, легко строятся любые отчеты средствами обычных запросов 1С 7.7.

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

---

Вариант 2. Хранение списка выбранных ответственных в шапке через сериализацию (ЗначениеВСтрокуВнутр)

Если нам не нужно привязывать кладовщика к конкретным строкам, а нужно просто указать список людей (например, "приемку осуществляли Петров и Сидоров со Склада №1, а также Иванов со Склада №2") в целом для документа, можно пойти путем сериализации списка значений в строковый реквизит шапки неограниченной длины.

Для этого создадим в шапке документа реквизит СписокКладовщиковСтр (тип "Строка", длина 0 - неограниченная). На саму форму документа поместим элемент диалога типа "СписокЗначений" с идентификатором сзКладовщики.

Рассмотрим код инициализации и сохранения списка в модуле формы документа:


Процедура ПриОткрытии()
    // Восстанавливаем сохраненный список из строки неограниченной длины
    Если ПустаяСтрока(СписокКладовщиковСтр) = 0 Тогда
        ЗначениеИзСтрокиВнутр(СписокКладовщиковСтр, сзКладовщики);
    КонецЕсли;
КонецПроцедуры

Процедура ПриЗаписи()
    // Преобразуем объект СписокЗначений в строку внутреннего формата перед записью
    СписокКладовщиковСтр = ЗначениеВСтрокуВнутр(сзКладовщики);
КонецПроцедуры

Для заполнения списка сзКладовщики на форму выводится кнопка "Добавить ответственного". При нажатии на нее мы динамически опрашиваем табличную часть документа, собираем уникальный список задействованных складов, фильтруем по ним кладовщиков и предлагаем пользователю выбрать сотрудника для добавления в список.

Преимущества: Чрезвычайно быстро работает, не требует создания новых объектов метаданных (подчиненных справочников/документов), список хранится непосредственно в самом документе.

Недостатки: Самый главный минус — невозможность делать прямые поисковые запросы по этим данным. Получить отчет "Сколько товаров принял кладовщик Иванов за месяц" стандартным запросом не получится. Вам придется перебирать все документы выборкой, восстанавливать список значений в коде и проверять наличие элемента. Этот способ подходит только для справочного отображения информации на форме документа.

---

Вариант 3. Использование подчиненного справочника

Этот способ является наиболее классическим с точки зрения правильного проектирования реляционных баз данных. Мы создаем новый справочник СкладскиеВизы. Делаем его подчиненным нашему документу (в 1С 7.7 справочники могут быть подчинены документам) либо создаем в нем реквизит ДокументСсылка с типом значения Документ.ПоступлениеТоваров.

Реквизиты справочника СкладскиеВизы:

При открытии документа мы можем считывать записи из этого справочника во временную Таблицу Значений на форме, а при записи документа — очищать старые записи в подчиненном справочнике и записывать новые актуальные данные.

Проанализируем пример кода сохранения данных в подчиненный справочник при записи документа:


Процедура ПриЗаписи()
    // Сначала удалим старые привязанные визы по данному документу
    СпрВизы = СоздатьОбъект("Справочник.СкладскиеВизы");
    СпрВизы.ИспользоватьВладельца(ТекущийДокумент());
    СпрВизы.ВыбратьЭлементы();
    Пока СпрВизы.ПолучитьЭлемент() = 1 Цикл
        СпрВизы.Удалить(1); // Удаляем физически
    КонецЦикла;
    
    // Теперь записываем новые данные из экранной Таблицы Значений (тзОтветственные)
    тзОтветственные.ВыбратьСтроки();
    Пока тзОтветственные.ПолучитьСтроку() = 1 Цикл
        СпрВизы.Новый();
        СпрВизы.Владелец = ТекущийДокумент();
        СпрВизы.Склад = тзОтветственные.Склад;
        СпрВизы.Кладовщик = тзОтветственные.Кладовщик;
        СпрВизы.Записать();
    КонецЦикла;
КонецПроцедуры

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

Недостатки: Необходимость администрирования дополнительного справочника, небольшое замедление процесса записи из-за удаления/записи объектов БД.

---

Вариант 4. Композитная (совмещенная) табличная часть

Рассмотрим продвинутый и очень элегантный интерфейсный прием, который позволяет использовать штатную табличную часть документа 1С 7.7 "в два горла" (эмулируя вторую табличную часть).

Суть идеи заключается в следующем. В табличную часть документа мы добавляем колонку Кладовщик. Вся табличная часть у нас будет делиться на два типа строк:

  1. Строки с товаром: колонка Номенклатура заполнена, колонка Кладовщик пустая.
  2. Строки с кладовщиками: колонка Номенклатура пустая, колонка Кладовщик заполнена.

На форму документа мы добавляем стандартные закладки "Товары" и "Кладовщики". В зависимости от выбранной закладки мы будем динамически фильтровать строки ТЧ и показывать пользователю либо только товары, либо только кладовщиков. Для этого мы заведем в модуле формы переменную ТабЧастьПолнаяТЗ, куда будем помещать полную таблицу перед фильтрацией.

Разберем по шагам реализацию этого метода в модуле формы документа:


Перем ТабЧастьПолнаяТЗ; // Здесь храним объединенные данные

Процедура ПриОткрытии()
    // Инициализируем Таблицу Значений для хранения полной копии ТЧ
    ТабЧастьПолнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
    ВыгрузитьТабличнуюЧасть(ТабЧастьПолнаяТЗ);
    
    // По умолчанию открываем первую закладку "Товары"
    Форма.ИспользоватьЗакладки(1);
    Форма.Закладки.ДобавитьЗначение("Товары");
    Форма.Закладки.ДобавитьЗначение("Кладовщики");
    
    // Настраиваем видимость колонок для первой закладки
    ПереустановитьВидимостьКолонок("Товары");
КонецПроцедуры

Процедура ПереустановитьВидимостьКолонок(ТекЗакладка)
    Если ТекЗакладка = "Товары" Тогда
        Форма.Номенклатура.Видимость(1);
        Форма.Количество.Видимость(1);
        Форма.Цена.Видимость(1);
        Форма.Кладовщик.Видимость(0);
    ИначеЕсли ТекЗакладка = "Кладовщики" Тогда
        Форма.Номенклатура.Видимость(0);
        Форма.Количество.Видимость(0);
        Форма.Цена.Видимость(0);
        Форма.Кладовщик.Видимость(1);
    КонецЕсли;
КонецПроцедуры

Процедура ПриВыбореЗакладки(Ном, Знач)
    // 1. Сохраняем текущие изменения с формы в полную ТЗ
    // Для этого определяем, на какой закладке мы сейчас стояли перед переключением
    ТекЗакладка = Форма.Закладки.ПолучитьЗначение(Форма.Закладки.ТекущаяСтрока());
    
    // Считываем то, что сейчас на экране
    ВременнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
    ВыгрузитьТабличнуюЧасть(ВременнаяТЗ);
    
    // Синхронизируем изменения с полной ТЗ
    Если ТекЗакладка = "Товары" Тогда
        // Удаляем из полной ТЗ все строки товаров и заменяем их измененными с экрана
        Пока ТабЧастьПолнаяТЗ.КоличествоСтрок() > 0 Цикл
            // Находим строки товаров
            Индекс = 0;
            Если ТабЧастьПолнаяТЗ.НайтиЗначение("", Индекс, "Кладовщик") = 1 Тогда
                ТабЧастьПолнаяТЗ.УдалитьСтроку(Индекс);
            Иначе
                Прервать;
            EndIf;
        КонецЦикла;
        // Добавляем обновленные строки товаров
        ВременнаяТЗ.ВыбратьСтроки();
        Пока ВременнаяТЗ.ПолучитьСтроку() = 1 Цикл
            ТабЧастьПолнаяТЗ.НоваяСтрока();
            ТабЧастьFullКопироватьРеквизиты(ВременнаяТЗ, ТабЧастьПолнаяТЗ);
        КонецЦикла;
    Иначе
        // То же самое проделываем для строк кладовщиков
        // (удаляем из полной ТЗ строки, где заполнено поле Кладовщик)
        // и дописываем обновленные строки с экрана
    КонецЕсли;
    
    // 2. Очищаем ТЧ на форме и загружаем в нее отфильтрованные данные под новую закладку
    УдалитьСтроки();
    ТабЧастьПолнаяТЗ.ВыбратьСтроки();
    Пока ТабЧастьПолнаяТЗ.ПолучитьСтроку() = 1 Цикл
        Если Знач = "Товары" Тогда
            Если ПустоеЗначение(ТабЧастьПолнаяТЗ.Кладовщик) = 1 Тогда
                НоваяСтрока();
                Кладовщик = "";
                Номенклатура = ТабЧастьПолнаяТЗ.Номенклатура;
                Количество = ТабЧастьПолнаяТЗ.Количество;
                Цена = ТабЧастьПолнаяТЗ.Цена;
            КонецЕсли;
        ИначеЕсли Знач = "Кладовщики" Тогда
            Если ПустоеЗначение(ТабЧастьПолнаяТЗ.Кладовщик) = 0 Тогда
                НоваяСтрока();
                Кладовщик = ТабЧастьПолнаяТЗ.Кладовщик;
                Номенклатура = "";
                Количество = 0;
                Цена = 0;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    // Меняем видимость колонок
    ПереустановитьВидимостьКолонок(Знач);
КонецПроцедуры

Процедура ПриЗаписи()
    // Перед физической записью документа на диск мы должны принудительно объединить 
    // все данные обратно в штатную табличную часть документа
    УдалитьСтроки();
    ТабЧастьПолнаяТЗ.ВыбратьСтроки();
    Пока ТабЧастьПолнаяТЗ.ПолучитьСтроку() = 1 Цикл
        НоваяСтрока();
        Номенклатура = ТабЧастьПолнаяТЗ.Номенклатура;
        Количество = ТабЧастьПолнаяТЗ.Количество;
        Цена = ТабЧастьПолнаяТЗ.Цена;
        Кладовщик = ТабЧастьПолнаяТЗ.Кладовщик;
    КонецЦикла;
КонецПроцедуры

Преимущества: Полное отсутствие избыточности в данных, пользователю кажется, что в документе есть две независимые полноценные табличные части ("Товары" и "Ответственные"), возможность легкого построения отчетов, так как кладовщики привязаны к документу напрямую через строки его ТЧ.

Недостатки: Сложная программная логика синхронизации данных. Требует высокой квалификации разработчика для отладки всех возможных кейсов (например, если пользователь закроет документ без сохранения или удалит строку товара в процессе работы).

---

Резюме и рекомендации по выбору решения

Мы подробно проанализировали четыре принципиально разных подхода к решению этой задачи в среде 1С 7.7. Какой из них выбрать для вашей практической работы?

← На главную