У трёх собственных продаж разные коды строк. Получаем настоящие детальные записи скомпилированной оси, проверяем зависимости наборов, readonly-шапку/подвал/тело/порядок и полный вывод 750/50/500 по реальным кодам.
Где и как запускать
Полный серверный общий модуль УчебныйМакетТаблицы85 собственной новой базы. Отдельный полный модуль формы Пульт ниже. Не выполнялся в 1С.
Подготовка
Создайте НОВУЮ отдельную файловую учебную базу 8.5.1.1343: имя конфигурации УчебныеМакетыТаблиц85, совместимость НеИспользовать, управляемое приложение. Без рабочих данных, расширений, обменов и подписок с внешними действиями. Здесь 1С не запускалась: runtimeVerifiedScenarios=0.
Общие модули → УчебныйМакетТаблицы85: Сервер=Да, Клиент=Нет, Вызов сервера=Нет, Привилегированный=Нет, Глобальный=Нет. Вставьте полный основной модуль ОДНОГО выбранного урока, включая все функции ниже ВыполнитьПример. Не объединяйте два модуля с одинаковыми именами процедур.
Обработки → УчебныйПросмотрТаблицы85 → управляемая форма Пульт. Создайте команду Выполнить, назначьте одноимённую клиентскую процедуру её действием и добавьте кнопку. Ниже полный отдельный модуль Пульт. F7 и обновление только новой учебной базы; серверный обработчик вызывает общий модуль без передачи Макет или ТабличныйДокумент в клиент.
Каждый запуск заново создаёт свою Local-схему, настройки, макет, процессор и табличный документ в памяти. Нет чтения документов/справочников/регистров, записи БД, файлов, настоящей сети, печати и сохранения пользовательских настроек.
Учебные продажи: Север/Книга 750, Север/Ручка 50, Юг/Книга 500. Во втором уроке у каждой исходной записи свой КодСтроки guide-sale-1/2/3. Подписи магазинов/товаров/кодов должны остаться уникальными на соответствующей оси; проверка намеренно прекращается при неоднозначной подписи.
Поставьте серверную точку останова перед последним Сообщить в ВыполнитьПример; откройте просмотр значений Документ либо ДоПереноса/ПослеПереноса/Восстановленный. Сверьте реальные пересечения и оформление. Статические проверки не подтверждают будущую отрисовку или расчёт в 1С.
Процедура ВыполнитьПример() Экспорт
ПроверитьСвойКонтекст();
Схема = Новый СхемаКомпоновкиДанных;
Источник = Схема.ИсточникиДанных.Добавить();
Источник.Имя = "УчебныеПродажи";
Источник.ТипИсточникаДанных = "Local";
Набор = Схема.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
Набор.Имя = "СвоиТриСтроки";
Набор.ИсточникДанных = Источник.Имя;
Набор.АвтоЗаполнениеДоступныхПолей = Истина;
Набор.Запрос = "ВЫБРАТЬ ""guide-sale-1"" КАК КодСтроки, ""Север"" КАК Магазин,
|""Книга"" КАК Наименование, 3 КАК Количество, 750 КАК Сумма
|ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ ""guide-sale-2"", ""Север"", ""Ручка"", 1, 50
|ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ ""guide-sale-3"", ""Юг"", ""Книга"", 2, 500";
Для Каждого ИмяРесурса Из СтрРазделить("Сумма,Количество", ",") Цикл
Ресурс = Схема.ПоляИтога.Добавить();
Ресурс.ПутьКДанным = ИмяРесурса;
Ресурс.Выражение = "Сумма(" + ИмяРесурса + ")";
КонецЦикла;
Настройки = Схема.НастройкиПоУмолчанию;
ТаблицаНастроек = Настройки.Структура.Добавить(Тип("ТаблицаКомпоновкиДанных"));
ТаблицаНастроек.Имя = "СвоиДетальныеПродажиПоТоварам";
ТаблицаНастроек.Использование = Истина;
РесурсВыбора = ТаблицаНастроек.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
РесурсВыбора.Поле = Новый ПолеКомпоновкиДанных("Сумма");
РесурсВыбора.Использование = Истина;
ДеталиНастроек = ТаблицаНастроек.Строки.Добавить();
ДеталиНастроек.Имя = "СвоиИсходныеСтроки";
ДеталиНастроек.Использование = Истина;
КодВыбора = ДеталиНастроек.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
КодВыбора.Поле = Новый ПолеКомпоновкиДанных("КодСтроки");
КодВыбора.Использование = Истина;
ПорядокНастроек = ДеталиНастроек.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
ПорядокНастроек.Поле = Новый ПолеКомпоновкиДанных("КодСтроки");
ПорядокНастроек.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Возр;
ПорядокНастроек.Использование = Истина;
ТоварыНастроек = ТаблицаНастроек.Колонки.Добавить();
ТоварыНастроек.Имя = "СвоиТовары";
ТоварыНастроек.Использование = Истина;
ПолеТовара = ТоварыНастроек.ПоляГруппировки.Элементы.Добавить(Тип("ПолеГруппировкиКомпоновкиДанных"));
ПолеТовара.Поле = Новый ПолеКомпоновкиДанных("Наименование");
ПолеТовара.ТипГруппировки = ТипГруппировкиКомпоновкиДанных.Элементы;
ПолеТовара.Использование = Истина;
КонтрольныйЗапрос = Новый Запрос;
КонтрольныйЗапрос.Текст = Набор.Запрос;
ИсходныеСтроки = КонтрольныйЗапрос.Выполнить().Выгрузить();
Если ИсходныеСтроки.Количество() <> 3 Или ИсходныеСтроки.Итог("Сумма") <> 1300 Тогда
ВызватьИсключение "Изменились собственные учебные данные";
КонецЕсли;
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = Компоновщик.Выполнить(Схема, Настройки);
ТаблицаМакета = НайтиСвоюТаблицу(Макет.Тело);
Детали = Новый Массив;
СобратьСвоиДетальныеЗаписи(ТаблицаМакета.Строки, Детали);
Если Детали.Количество() = 0 Тогда
ВызватьИсключение "В строковой оси собственной таблицы должны быть реальные детальные записи";
КонецЕсли;
Для Каждого Записи Из Детали Цикл
Сообщить("Имя детальных записей: " + Записи.Имя + "; идентификатор=" + Записи.Идентификатор);
Сообщить("Прежнее ограничение количества: " + Строка(Записи.КоличествоЗаписей));
Сообщить("Прежний процент вывода: " + Строка(Записи.ПроцентЗаписей));
Сообщить("Фактическое выражение отбора: " + Записи.ОтборЗаписей);
ПроверитьСвоиИменаНаборов(Макет, Записи.НаборыДанных);
ПроверитьСвоиИменаНаборов(Макет, Записи.ОбязательныеНаборыДанных);
Если ТипЗнч(Записи.МакетШапки) <> Тип("МакетГруппировкиТаблицыМакетаКомпоновкиДанных")
Или ТипЗнч(Записи.МакетПодвала) <> Тип("МакетГруппировкиТаблицыМакетаКомпоновкиДанных") Тогда
ВызватьИсключение "Шапка и подвал записей таблицы являются объектами, не строками";
КонецЕсли;
ПроверитьСсылкуНаОбласть(Макет, Записи.МакетШапки.Макет);
ПроверитьСсылкуНаОбласть(Макет, Записи.МакетПодвала.Макет);
Сообщить("Частей readonly-тела: " + Строка(Записи.Тело.Количество()));
Сообщить("Выражений фактического порядка: " + Строка(Записи.Порядок.Количество()));
Для Каждого Выражение Из Записи.Порядок Цикл
Сообщить("Реальный порядок: " + Выражение.Выражение + "; направление=" + Строка(Выражение.ТипУпорядочивания));
КонецЦикла;
Записи.КоличествоЗаписей = -1;
Записи.ПроцентЗаписей = 100;
Если Записи.КоличествоЗаписей <> -1 Или Записи.ПроцентЗаписей <> 100 Тогда
ВызватьИсключение "В собственном макете должен быть разрешён полный вывод записей";
КонецЕсли;
КонецЦикла;
Документ = СформироватьСвойДокумент(Макет);
ПроверитьСвоиДетальныеЯчейки(Документ);
Если ИсходныеСтроки.Количество() <> 3 Или ИсходныеСтроки.Итог("Сумма") <> 1300 Тогда
ВызватьИсключение "Настройки вывода не должны менять собственный константный источник";
КонецЕсли;
Сообщить("Три настоящие детальные строки найдены в результате: guide-sale-1/2/3; суммы 750/50/500 проверены по пересечениям товаров.");
КонецПроцедуры
Функция НайтиСвоюТаблицу(ТелоМакета)
Найденные = Новый Массив;
Для Каждого Часть Из ТелоМакета Цикл
Если ТипЗнч(Часть) = Тип("ТаблицаМакетаКомпоновкиДанных") Тогда
Найденные.Добавить(Часть);
КонецЕсли;
КонецЦикла;
Если Найденные.Количество() <> 1 Тогда
ВызватьИсключение "Нужна ровно одна собственная таблица в простом макете";
КонецЕсли;
Возврат Найденные.Получить(0);
КонецФункции
Процедура ПроверитьСсылкуНаОбласть(Макет, ИмяОбласти)
Если ТипЗнч(ИмяОбласти) <> Тип("Строка") Тогда
ВызватьИсключение "Ссылка на макет области должна быть строкой";
КонецЕсли;
Если ИмяОбласти = "" Тогда
Сообщить("Необязательный макет области отсутствует");
Возврат;
КонецЕсли;
ОписаниеОбласти = Макет.Макеты.Найти(ИмяОбласти);
Если ОписаниеОбласти = Неопределено Тогда
ВызватьИсключение "Нельзя выдумывать техническое имя отсутствующей области: " + ИмяОбласти;
КонецЕсли;
Сообщить("Реальная ссылка на область: " + ИмяОбласти);
КонецПроцедуры
Функция СформироватьСвойДокумент(СвойМакет)
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(СвойМакет);
Документ = Новый ТабличныйДокумент;
Вывод = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
Вывод.ОтображатьПроцентВывода = Ложь;
Вывод.УстановитьДокумент(Документ);
РезультатВывода = Вывод.Вывести(Процессор, Ложь);
Если ТипЗнч(РезультатВывода) <> Тип("ТабличныйДокумент") Тогда
ВызватьИсключение "Не получен собственный табличный документ";
КонецЕсли;
Возврат РезультатВывода;
КонецФункции
Процедура ПроверитьМалыйДокумент(Документ)
Если ТипЗнч(Документ) <> Тип("ТабличныйДокумент") Или Документ.ВысотаТаблицы < 1
Или Документ.ШиринаТаблицы < 1 Или Документ.ВысотаТаблицы > 200 Или Документ.ШиринаТаблицы > 100 Тогда
ВызватьИсключение "Проверка рассчитана только на свой небольшой документ";
КонецЕсли;
КонецПроцедуры
Функция НайтиСвоюКоординату(Документ, Подпись, ПоСтроке)
ПроверитьМалыйДокумент(Документ);
Найдено = 0;
Для НомерСтроки = 1 По Документ.ВысотаТаблицы Цикл
Для НомерКолонки = 1 По Документ.ШиринаТаблицы Цикл
Ячейка = Документ.Область(НомерСтроки, НомерКолонки);
Если СокрЛП(Ячейка.Текст) = Подпись Тогда
Координата = ?(ПоСтроке, НомерСтроки, НомерКолонки);
Если Найдено <> 0 И Найдено <> Координата Тогда
ВызватьИсключение "Неоднозначная подпись своей оси: " + Подпись;
КонецЕсли;
Найдено = Координата;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Если Найдено = 0 Тогда
ВызватьИсключение "В реальном документе не найдена подпись " + Подпись;
КонецЕсли;
Возврат Найдено;
КонецФункции
Функция ПрочитатьСвоеЧисло(Ячейка)
Если Ячейка.СодержитЗначение Тогда
Значение = Ячейка.Значение;
Если ТипЗнч(Значение) = Тип("Число") Тогда
Возврат Значение;
КонецЕсли;
Возврат Неопределено;
КонецЕсли;
Текст = СтрЗаменить(СтрЗаменить(СокрЛП(Ячейка.Текст), Символ(160), ""), " ", "");
Если Текст = "" Тогда
Возврат Неопределено;
КонецЕсли;
ЕстьЦифра = Ложь;
Для Позиция = 1 По СтрДлина(Текст) Цикл
Знак = Сред(Текст, Позиция, 1);
Если СтрНайти("0123456789", Знак) > 0 Тогда
ЕстьЦифра = Истина;
ИначеЕсли СтрНайти(",.-", Знак) = 0 Тогда
Возврат Неопределено;
КонецЕсли;
КонецЦикла;
Если Не ЕстьЦифра Тогда
Возврат Неопределено;
КонецЕсли;
Возврат Число(Текст);
КонецФункции
Процедура ПроверитьСвоюЯчейку(Документ, НомерСтроки, НомерКолонки, Ожидаемое)
Значение = ПрочитатьСвоеЧисло(Документ.Область(НомерСтроки, НомерКолонки));
Если Значение = Неопределено Тогда
ВызватьИсключение "Нет числа в ожидаемом пересечении собственной матрицы";
КонецЕсли;
Если Значение <> Ожидаемое Тогда
ВызватьИсключение "Неверное число в пересечении: " + Строка(Значение);
КонецЕсли;
КонецПроцедуры
Процедура ПроверитьСвоеПустоеПересечение(Документ, НомерСтроки, НомерКолонки)
Ячейка = Документ.Область(НомерСтроки, НомерКолонки);
Значение = ПрочитатьСвоеЧисло(Ячейка);
Если Значение <> Неопределено Тогда
Если Значение <> 0 Тогда
ВызватьИсключение "В отсутствующем сочетании не должно быть ненулевой продажи";
КонецЕсли;
ИначеЕсли СокрЛП(Ячейка.Текст) <> "" Тогда
ВызватьИсключение "Отсутствующая продажа должна быть пустой или числовым нулём, не посторонним текстом";
КонецЕсли;
КонецПроцедуры
Процедура СобратьСвоиДетальныеЗаписи(ТелоОси, Найденные)
Для Каждого Часть Из ТелоОси Цикл
Индекс = ТелоОси.Индекс(Часть);
Если Индекс < 0 Или Индекс >= ТелоОси.Количество() Тогда
ВызватьИсключение "Элемент обхода должен принадлежать реальной оси своего макета";
КонецЕсли;
Если ТелоОси.Получить(Индекс) <> Часть Тогда
ВызватьИсключение "Индекс и Получить должны вернуть ту же часть реальной оси";
КонецЕсли;
Если ТипЗнч(Часть) = Тип("ЗаписиТаблицыМакетаКомпоновкиДанных") Тогда
Найденные.Добавить(Часть);
ИначеЕсли ТипЗнч(Часть) = Тип("ГруппировкаТаблицыМакетаКомпоновкиДанных") Тогда
СобратьСвоиДетальныеЗаписи(Часть.Тело, Найденные);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Процедура ПроверитьСвоиИменаНаборов(Макет, Имена)
Если ТипЗнч(Имена) <> Тип("Массив") Тогда
ВызватьИсключение "Нужен настоящий readonly-массив имён наборов записей";
КонецЕсли;
Для Каждого ИмяНабора Из Имена Цикл
Если ТипЗнч(ИмяНабора) <> Тип("Строка") Тогда
ВызватьИсключение "Имя скомпилированного набора должно быть строкой";
КонецЕсли;
НайденныйНабор = Макет.НаборыДанных.Найти(ИмяНабора);
Если НайденныйНабор = Неопределено Тогда
ВызватьИсключение "Записи ссылаются на отсутствующий реальный набор " + ИмяНабора;
КонецЕсли;
Сообщить("Реальная зависимость детальных записей: " + ИмяНабора);
КонецЦикла;
КонецПроцедуры
Процедура ПроверитьСвоиДетальныеЯчейки(Документ)
Книга = НайтиСвоюКоординату(Документ, "Книга", Ложь);
Ручка = НайтиСвоюКоординату(Документ, "Ручка", Ложь);
Если Книга = Ручка Тогда
ВызватьИсключение "Оси товаров не должны совпадать";
КонецЕсли;
Первая = НайтиСвоюКоординату(Документ, "guide-sale-1", Истина);
Вторая = НайтиСвоюКоординату(Документ, "guide-sale-2", Истина);
Третья = НайтиСвоюКоординату(Документ, "guide-sale-3", Истина);
Если Первая = Вторая Или Вторая = Третья Или Первая = Третья Тогда
ВызватьИсключение "Три исходные записи не должны склеиться в одну строку";
КонецЕсли;
ПроверитьСвоюЯчейку(Документ, Первая, Книга, 750);
ПроверитьСвоюЯчейку(Документ, Вторая, Ручка, 50);
ПроверитьСвоюЯчейку(Документ, Третья, Книга, 500);
ПроверитьСвоеПустоеПересечение(Документ, Первая, Ручка);
ПроверитьСвоеПустоеПересечение(Документ, Вторая, Книга);
ПроверитьСвоеПустоеПересечение(Документ, Третья, Ручка);
КонецПроцедуры
Процедура ПроверитьСвойКонтекст()
Если Метаданные.РежимСовместимости <> Метаданные.СвойстваОбъектов.РежимСовместимости.НеИспользовать Тогда
ВызватьИсключение "Нужен собственный стенд УчебныеМакетыТаблиц85 платформы 8.5.1.1343";
КонецЕсли;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Учебный вывод не должен выполняться внутри чужой транзакции";
КонецЕсли;
КонецПроцедурыУчебныйПросмотрТаблицы85.Пульт
УчебныеМакетыТаблиц85 → Обработки → УчебныйПросмотрТаблицы85 → Формы → Пульт → Модуль
Полный модуль собственной управляемой формы Пульт; не выполнялся.
&НаКлиенте
Процедура Выполнить(Команда)
ВыполнитьНаСервере();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере()
УчебныйМакетТаблицы85.ВыполнитьПример();
КонецПроцедурыРазбор по шагам
В строковой группировке не задаём ПоляГруппировки: это детальные записи, а не группировка по уникальному коду. Для вывода выбираем непустое нересурсное поле КодСтроки. В колонках группируем Наименование, ресурс таблицы — Сумма; три исходные строки не должны свернуться в общий итог.
После реальной компиляции находим одну свою таблицу и обходим её строковую ось. Проверяем тип каждого элемента: только ЗаписиТаблицыМакетаКомпоновкиДанных становятся объектами урока. Новый для этого типа не выдумывается; при отсутствии настоящих записей опыт останавливается.
Для каждого элемента оси Индекс проверяется на -1/границы, а Получить(Индекс) должен вернуть тот же объект. Это настоящая коллекция ТелоГруппировкиТаблицыМакетаКомпоновкиДанных, не настройки структуры отчёта.
Имя/Идентификатор и ОтборЗаписей диагностируют именно скомпилированную часть. Фильтр не заменяется случайной строкой языка запросов; имена/идентификаторы не переименовываются отдельно от связанных частей.
НаборыДанных и ОбязательныеНаборыДанных — readonly-массивы имён. Каждое фактическое имя разрешаем в настоящем Макет.НаборыДанных.Найти; технические имена генератора не угадываются из имени Local-источника.
МакетШапки/МакетПодвала детальных записей являются readonly-объектами МакетГруппировкиТаблицыМакетаКомпоновкиДанных. Через их .Макет проверяем действительные строковые ссылки на области; контракт отличается от четырёх строковых свойств самой таблицы.
Readonly Тело и Порядок не присваиваются. Читаем количество частей и фактические выражения упорядочивания. Настройка сортировки КодСтроки задаётся до компиляции на уровне настроек, а не выдуманным выражением готового макета.
КоличествоЗаписей=-1 означает полный вывод, а не минус одну исходную строку. ПроцентЗаписей=100 задаёт полный процент вывода. Читаем прежние значения, меняем только эту собственную свежую часть и проверяем установленное состояние.
Свежий процессор формирует реальный ТабличныйДокумент. Находим guide-sale-1/2/3 по строкам и Книга/Ручка по колонкам, требуем три разных строки и проверяем 750/50/500 в соответствующих пересечениях. Отсутствующие сочетания не заполняем ожидаемыми числами.
Как проверить результат
Будущий тест: F7 → собственный Пульт → Выполнить. Реальный строковый макет должен содержать хотя бы один объект ЗаписиТаблицыМакетаКомпоновкиДанных; иначе сработает исключение и урок не считается выполненным.
У каждой найденной части установлен полный вывод -1/100, а каждое имя зависимого набора найдено в настоящем макете. Пустой массив обязательных наборов сам по себе не ошибка.
В реальном документе три разных строки: guide-sale-1/Книга=750, guide-sale-2/Ручка=50, guide-sale-3/Книга=500. Остальные три сочетания пусты либо 0. Сам источник по-прежнему содержит три строки и 1300.
Просмотр документа и отладчик должны подтвердить выбранные коды, области и фактические зависимости. Статическая проверка сигнатур не доказывает наличие строк или отрисовку в будущем запуске.
Ожидаемый результат
Будущий результат: полный табличный отчёт по трём реальным учебным записям, с проверкой каждой строки и её товарного пересечения. Скомпилированные зависимости показаны без фиктивных имён. В 1С не проверялось.
На что обратить внимание
Не заменяйте детальные записи группировкой по коду: это другой тип скомпилированной части. Поле КодСтроки здесь выбирается для вывода, но не добавляется в ПоляГруппировки.
КоличествоЗаписей — ограничение вывода части макета, не число строк контрольного запроса. Полный процент вывода не отменяет реальные отборы источника; урок не меняет ОтборЗаписей вручную.
У записей шапка/подвал readonly и объектные; у самой таблицы ссылки строковые и записываемые. Не присваивайте readonly-массивы, тело или порядок.
Спорные native-сигнатуры Сдвинуть/Удалить тела табличной группировки здесь не вызываются и не считаются покрытыми. Содержательные уроки не добавляются путём угадывания аргументов.
Проверка ограничена своей маленькой неиерархической схемой. Имена, позиции ячеек и вид областей реальной конфигурации могут отличаться. Нет фактического запуска; runtimeVerifiedScenarios=0.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.