При работе в устаревших, но все еще активно используемых конфигурациях на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с аппаратными и архитектурными ограничениями системы. Одной из таких классических задач является необходимость реализовать сложный многокритериальный выбор аналитики в документе. Например, когда в одном документе поступления содержатся товары, приходящие на разные физические склады, и для каждого склада (или даже для каждой строки) необходимо указать одного или нескольких ответственных кладовщиков, участвовавших в приемке.
В этой статье мы подробно проанализируем ситуацию, разберем архитектурные ограничения платформы 1С 7.7 и рассмотрим несколько эффективных практических способов реализации этой задачи: от простых и быстрых до сложных и максимально гибких решений.
---
Прежде чем перейти к написанию кода, определим исходные данные нашей бизнес-логики:
Кладовщики (или сотрудники), где для каждого элемента указан реквизит Склад (ссылка на справочник Склады).Номенклатура) закреплен базовый склад хранения.Основная сложность платформы 1С 7.7 заключается в том, что в отличие от 1С 8.х, здесь не существует механизма нескольких табличных частей в одном документе, а штатная табличная часть жестко привязана к структуре метаданных. Также стандартные экранные формы не позволяют легко использовать динамические таблицы значений без применения внешних компонент (таких как 1С++ или FormEx). Исходя из этого, выберем оптимальный путь решения.
---
Это самый простой и очевидный способ с точки зрения структуры метаданных. Мы добавляем новый реквизит табличной части документа Кладовщик с типом значения Справочник.Кладовщики.
При этом алгоритм выбора должен жестко контролировать соответствие склада товара и склада кладовщика. Разберем, как реализовать фильтрацию списка выбора на лету.
Для этого в модуле формы документа переопределим предопределенную процедуру ПриНачалеВыбораЗначения():
Процедура ПриНачалеВыбораЗначения(ИдРеквизита, ФлагСтандартнойОбработки)
Если ИдРеквизита = "Кладовщик" Тогда
// Отключаем стандартный выбор справочника
ФлагСтандартнойОбработки = 0;
// Получаем склад из текущей строки номенклатуры
ТекНоменклатура = Номенклатура;
Если ПустоеЗначение(ТекНоменклатура) = 1 Тогда
Предупреждение("Сначала выберите номенклатуру в строке!");
Возврат;
КонецЕсли;
ТекСклад = ТекНоменклатура.Склад;
Если ПустоеЗначение(ТекСклад) = 1 Тогда
Предупреждение("У выбранной номенклатуры не указан склад хранения!");
Возврат;
КонецЕсли;
// Формируем список кладовщиков, закрепленных за этим складом
СпКладовщиков = СоздатьОбъект("СписокЗначений");
СпрКладовщики = СоздатьОбъект("Справочник.Кладовщики");
СпрКладовщики.ВыбратьЭлементы();
Пока СпрКладовщики.ПолучитьЭлемент() = 1 Цикл
// Проверяем соответствие склада
Если СпрКладовщики.Склад = ТекСклад Тогда
СпКладовщиков.ДобавитьЗначение(СпрКладовщики.ТекущийЭлемент(), Строка(СпрКладовщики.Наименование));
КонецЕсли;
КонецЦикла;
Если СпКладовщиков.РазмерСписка() = 0 Тогда
Предупреждение("Нет кладовщиков, закрепленных за складом: " + Строка(ТекСклад));
Возврат;
КонецЕсли;
// Показываем пользователю отфильтрованный список выбора
ВыбКладовщик = "";
Если СпКладовщиков.ВыбратьЗначение(ВыбКладовщик, "Выберите ответственного кладовщика", , 60, 1) = 1 Тогда
Кладовщик = ВыбКладовщик;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Преимущества: Простая реализация, данные хранятся в стандартной табличной части, легко строятся любые отчеты средствами обычных запросов 1С 7.7.
Недостатки: Если документ содержит сотни строк, пользователю придется заполнять кладовщика в каждой строке вручную, что крайне неэффективно. Этот способ подходит только тогда, когда за каждую конкретную строку товара отвечает ровно один человек.
---
Если нам не нужно привязывать кладовщика к конкретным строкам, а нужно просто указать список людей (например, "приемку осуществляли Петров и Сидоров со Склада №1, а также Иванов со Склада №2") в целом для документа, можно пойти путем сериализации списка значений в строковый реквизит шапки неограниченной длины.
Для этого создадим в шапке документа реквизит СписокКладовщиковСтр (тип "Строка", длина 0 - неограниченная). На саму форму документа поместим элемент диалога типа "СписокЗначений" с идентификатором сзКладовщики.
Рассмотрим код инициализации и сохранения списка в модуле формы документа:
Процедура ПриОткрытии()
// Восстанавливаем сохраненный список из строки неограниченной длины
Если ПустаяСтрока(СписокКладовщиковСтр) = 0 Тогда
ЗначениеИзСтрокиВнутр(СписокКладовщиковСтр, сзКладовщики);
КонецЕсли;
КонецПроцедуры
Процедура ПриЗаписи()
// Преобразуем объект СписокЗначений в строку внутреннего формата перед записью
СписокКладовщиковСтр = ЗначениеВСтрокуВнутр(сзКладовщики);
КонецПроцедуры
Для заполнения списка сзКладовщики на форму выводится кнопка "Добавить ответственного". При нажатии на нее мы динамически опрашиваем табличную часть документа, собираем уникальный список задействованных складов, фильтруем по ним кладовщиков и предлагаем пользователю выбрать сотрудника для добавления в список.
Преимущества: Чрезвычайно быстро работает, не требует создания новых объектов метаданных (подчиненных справочников/документов), список хранится непосредственно в самом документе.
Недостатки: Самый главный минус — невозможность делать прямые поисковые запросы по этим данным. Получить отчет "Сколько товаров принял кладовщик Иванов за месяц" стандартным запросом не получится. Вам придется перебирать все документы выборкой, восстанавливать список значений в коде и проверять наличие элемента. Этот способ подходит только для справочного отображения информации на форме документа.
---
Этот способ является наиболее классическим с точки зрения правильного проектирования реляционных баз данных. Мы создаем новый справочник СкладскиеВизы. Делаем его подчиненным нашему документу (в 1С 7.7 справочники могут быть подчинены документам) либо создаем в нем реквизит ДокументСсылка с типом значения Документ.ПоступлениеТоваров.
Реквизиты справочника СкладскиеВизы:
Склад (тип Справочник.Склады).Кладовщик (тип Справочник.Кладовщики).При открытии документа мы можем считывать записи из этого справочника во временную Таблицу Значений на форме, а при записи документа — очищать старые записи в подчиненном справочнике и записывать новые актуальные данные.
Проанализируем пример кода сохранения данных в подчиненный справочник при записи документа:
Процедура ПриЗаписи()
// Сначала удалим старые привязанные визы по данному документу
СпрВизы = СоздатьОбъект("Справочник.СкладскиеВизы");
СпрВизы.ИспользоватьВладельца(ТекущийДокумент());
СпрВизы.ВыбратьЭлементы();
Пока СпрВизы.ПолучитьЭлемент() = 1 Цикл
СпрВизы.Удалить(1); // Удаляем физически
КонецЦикла;
// Теперь записываем новые данные из экранной Таблицы Значений (тзОтветственные)
тзОтветственные.ВыбратьСтроки();
Пока тзОтветственные.ПолучитьСтроку() = 1 Цикл
СпрВизы.Новый();
СпрВизы.Владелец = ТекущийДокумент();
СпрВизы.Склад = тзОтветственные.Склад;
СпрВизы.Кладовщик = тзОтветственные.Кладовщик;
СпрВизы.Записать();
КонецЦикла;
КонецПроцедуры
Преимущества: Полная ссылочная целостность, поддержка стандартных механизмов транзакций, возможность легко строить любые отчеты по справочнику в разрезе документов, складов и кладовщиков.
Недостатки: Необходимость администрирования дополнительного справочника, небольшое замедление процесса записи из-за удаления/записи объектов БД.
---
Рассмотрим продвинутый и очень элегантный интерфейсный прием, который позволяет использовать штатную табличную часть документа 1С 7.7 "в два горла" (эмулируя вторую табличную часть).
Суть идеи заключается в следующем. В табличную часть документа мы добавляем колонку Кладовщик. Вся табличная часть у нас будет делиться на два типа строк:
Номенклатура заполнена, колонка Кладовщик пустая.Номенклатура пустая, колонка Кладовщик заполнена.На форму документа мы добавляем стандартные закладки "Товары" и "Кладовщики". В зависимости от выбранной закладки мы будем динамически фильтровать строки ТЧ и показывать пользователю либо только товары, либо только кладовщиков. Для этого мы заведем в модуле формы переменную ТабЧастьПолнаяТЗ, куда будем помещать полную таблицу перед фильтрацией.
Разберем по шагам реализацию этого метода в модуле формы документа:
Перем ТабЧастьПолнаяТЗ; // Здесь храним объединенные данные
Процедура ПриОткрытии()
// Инициализируем Таблицу Значений для хранения полной копии ТЧ
ТабЧастьПолнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
ВыгрузитьТабличнуюЧасть(ТабЧастьПолнаяТЗ);
// По умолчанию открываем первую закладку "Товары"
Форма.ИспользоватьЗакладки(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. Какой из них выбрать для вашей практической работы?