При разработке собственных конфигураций на платформе 1С:Предприятие 8.3 начинающие разработчики часто сталкиваются с задачей разделения ввода данных. Например, в документе создаются три табличные части: в первую (Товары) пользователь вносит номенклатуру, во вторую (Цены) — стоимости, а в третьей (Итог) должны автоматически объединяться данные из первых двух таблиц.
На практике синхронизация строк между несколькими табличными частями содержит множество подводных камней: пользователь может поменять строки местами, отсортировать их, удалить строку из середины списка или ввести повторяющиеся позиций. Проанализируем основные архитектурные подходы и программные способы решения этой задачи.
Прежде чем писать программный код для синхронизации трех таблиц, проанализируем структуру данных с точки зрения проектирования. Если в документе между товаром и ценой существует жесткая связь «один к одному», хранение этих данных в трех разных табличных частях в метаданных считается антипаттерном.
С точки зрения оптимизации базы данных и упрощения кода правильнее создать **одну табличную часть** Товары с реквизитами Товар и Цена. Если бизнес-требования требуют развести ввод товаров и цен по разным вкладкам или элементам интерфейса, реализуем это на уровне управляемой формы:
Товары с колонками Товар и Цена.Товар.Товар (для информации в режиме «только чтение») и Цена.При таком подходе данные физически хранятся в одном месте. Третья табличная часть Итог становится вообще не нужной, так как итоговые данные уже находятся в единой таблице объекта.
Если специфика бизнес-логики действительно требует наличия трех независимых табличных частей, наиболее надежным решением является применение ключей связи. Именно этот подход используется в типовых конфигурациях 1С (Управление торговлей, ERP, Комплексная автоматизация).
Разберем реализацию этого механизма по шагам:
Товары, Цены, Итог) добавляем скрытый реквизит КлючСвязи с типом Строка (для сохранения GUID) или Число.КлючСвязи.КлючСвязи.Рассмотрим пример кода в модуле управляемой формы при добавлении строки в табличную часть товаров:
&НаКлиенте
Процедура ТоварыПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
Если ОтменаРедактирования Или Не НоваяСтрока Тогда
Возврат;
КонецЕсли;
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
// Если ключ связи еще не сформирован, генерируем его
Если Не ЗначениеЗаполнено(ТекущаяСтрока.КлючСвязи) Тогда
ТекущаяСтрока.КлючСвязи = Строка(Новый УникальныйИдентификатор);
КонецЕсли;
СинхронизироватьСтрокиПоКлючу(ТекущаяСтрока.КлючСвязи, ТекущаяСтрока.Товар);
КонецПроцедуры
&НаКлиенте
Процедура СинхронизироватьСтрокиПоКлючу(Знач КлючСвязи, Знач Номенклатура)
// Синхронизация с ТЧ Цены
Отбор = Новый Структура("КлючСвязи", КлючСвязи);
НайденныеСтрокиЦены = Объект.Цены.НайтиСтроки(Отбор);
Если НайденныеСтрокиЦены.Количество() = 0 Тогда
СтрокаЦены = Объект.Цены.Добавить();
СтрокаЦены.КлючСвязи = КлючСвязи;
Иначе
СтрокаЦены = НайденныеСтрокиЦены[0];
КонецЕсли;
// Синхронизация с ТЧ Итог
НайденныеСтрокиИтог = Объект.Итог.НайтиСтроки(Отбор);
Если НайденныеСтрокиИтог.Количество() = 0 Тогда
СтрокаИтог = Объект.Итог.Добавить();
СтрокаИтог.КлючСвязи = КлючСвязи;
Иначе
СтрокаИтог = НайденныеСтрокиИтог[0];
КонецЕсли;
// Заполняем реквизиты
СтрокаИтог.Товар = Номенклатура;
КонецПроцедуры
Для корректной работы системы не забудем также обработать событие ПослеУдаления для первых двух табличных частей, чтобы при удалении строки с товаром автоматически удалялись связанные строки из других таблиц по КлючуСвязи.
Отслеживание всех действий пользователя на форме (перемещение строк кнопками «Вверх»/«Вниз», сортировка, интерактивное удаление) требует написания большого объема кода. Чтобы обезопасить себя от рассинхронизации данных, применим подход полной сборки итоговой таблицы в событии ПередЗаписью модуля объекта.
Выясним, как собрать данные из двух табличных частей с помощью запроса к табличным частям в памяти:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПро проведения)
// При повторной записи полностью очищаем итоговую табличную часть
Итог.Очистить();
// Выполняем запрос к табличным частям объекта
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧТовары.НомерСтроки КАК НомерСтроки,
| ТЧТовары.Товар КАК Товар,
| ТЧТовары.КлючСвязи КАК КлючСвязи
|ПОМЕСТИТЬ ВТТовары
|ИЗ
| &ТЧТовары КАК ТЧТовары
|;
|
|ВЫБРАТЬ
| ТЧЦены.Цена КАК Цена,
| ТЧЦены.КлючСвязи КАК КлючСвязи
|ПОМЕСТИТЬ ВТЦены
|ИЗ
| &ТЧЦены КАК ТЧЦены
|;
|
|ВЫБРАТЬ
| ВТТовары.Товар КАК Товар,
| ЕСТЬНУЛЛ(ВТЦены.Цена, 0) КАК Цена,
| ВТТовары.КлючСвязи КАК КлючСвязи
|ИЗ
| ВТТовары КАК ВТТовары
| ЛЕВОЕ СОЕДИНЕНИЕ ВТЦены КАК ВТЦены
| ПО ВТТовары.КлючСвязи = ВТЦены.КлючСвязи
|УПОРЯДОЧИТЬ ПО
| ВТТовары.НомерСтроки";
Запрос.УстановитьПараметр("ТЧТовары", Товары.Выгрузить());
Запрос.УстановитьПараметр("ТЧЦены", Цены.Выгрузить());
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Итог.Загрузить(Результат.Выгрузить());
КонецЕсли;
КонецПроцедуры
Для быстрой перенос данных между совпадающими по именованию строками также удобно использовать системную функцию ЗаполнитьЗначенияСвойств():
// Пример заполнения реквизитов одной строки из другой
ЗаполнитьЗначенияСвойств(СтрокаНазначения, СтрокаИсточника);
Мы рассмотрели различные пути решения задачи связывания табличных частей в 1С 8.3: