Детальные записи макета СКД: не потерять исходные строки в табличном отчёте

Учебные задачи

У трёх собственных продаж разные коды строк. Получаем настоящие детальные записи скомпилированной оси, проверяем зависимости наборов, readonly-шапку/подвал/тело/порядок и полный вывод 750/50/500 по реальным кодам.

Где и как запускать

Полный серверный общий модуль УчебныйМакетТаблицы85 собственной новой базы. Отдельный полный модуль формы Пульт ниже. Не выполнялся в 1С.

Подготовка

  1. Создайте НОВУЮ отдельную файловую учебную базу 8.5.1.1343: имя конфигурации УчебныеМакетыТаблиц85, совместимость НеИспользовать, управляемое приложение. Без рабочих данных, расширений, обменов и подписок с внешними действиями. Здесь 1С не запускалась: runtimeVerifiedScenarios=0.

  2. Общие модули → УчебныйМакетТаблицы85: Сервер=Да, Клиент=Нет, Вызов сервера=Нет, Привилегированный=Нет, Глобальный=Нет. Вставьте полный основной модуль ОДНОГО выбранного урока, включая все функции ниже ВыполнитьПример. Не объединяйте два модуля с одинаковыми именами процедур.

  3. Обработки → УчебныйПросмотрТаблицы85 → управляемая форма Пульт. Создайте команду Выполнить, назначьте одноимённую клиентскую процедуру её действием и добавьте кнопку. Ниже полный отдельный модуль Пульт. F7 и обновление только новой учебной базы; серверный обработчик вызывает общий модуль без передачи Макет или ТабличныйДокумент в клиент.

  4. Каждый запуск заново создаёт свою Local-схему, настройки, макет, процессор и табличный документ в памяти. Нет чтения документов/справочников/регистров, записи БД, файлов, настоящей сети, печати и сохранения пользовательских настроек.

  5. Учебные продажи: Север/Книга 750, Север/Ручка 50, Юг/Книга 500. Во втором уроке у каждой исходной записи свой КодСтроки guide-sale-1/2/3. Подписи магазинов/товаров/кодов должны остаться уникальными на соответствующей оси; проверка намеренно прекращается при неоднозначной подписи.

  6. Поставьте серверную точку останова перед последним Сообщить в ВыполнитьПример; откройте просмотр значений Документ либо ДоПереноса/ПослеПереноса/Восстановленный. Сверьте реальные пересечения и оформление. Статические проверки не подтверждают будущую отрисовку или расчёт в 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. В строковой группировке не задаём ПоляГруппировки: это детальные записи, а не группировка по уникальному коду. Для вывода выбираем непустое нересурсное поле КодСтроки. В колонках группируем Наименование, ресурс таблицы — Сумма; три исходные строки не должны свернуться в общий итог.

  2. После реальной компиляции находим одну свою таблицу и обходим её строковую ось. Проверяем тип каждого элемента: только ЗаписиТаблицыМакетаКомпоновкиДанных становятся объектами урока. Новый для этого типа не выдумывается; при отсутствии настоящих записей опыт останавливается.

  3. Для каждого элемента оси Индекс проверяется на -1/границы, а Получить(Индекс) должен вернуть тот же объект. Это настоящая коллекция ТелоГруппировкиТаблицыМакетаКомпоновкиДанных, не настройки структуры отчёта.

  4. Имя/Идентификатор и ОтборЗаписей диагностируют именно скомпилированную часть. Фильтр не заменяется случайной строкой языка запросов; имена/идентификаторы не переименовываются отдельно от связанных частей.

  5. НаборыДанных и ОбязательныеНаборыДанных — readonly-массивы имён. Каждое фактическое имя разрешаем в настоящем Макет.НаборыДанных.Найти; технические имена генератора не угадываются из имени Local-источника.

  6. МакетШапки/МакетПодвала детальных записей являются readonly-объектами МакетГруппировкиТаблицыМакетаКомпоновкиДанных. Через их .Макет проверяем действительные строковые ссылки на области; контракт отличается от четырёх строковых свойств самой таблицы.

  7. Readonly Тело и Порядок не присваиваются. Читаем количество частей и фактические выражения упорядочивания. Настройка сортировки КодСтроки задаётся до компиляции на уровне настроек, а не выдуманным выражением готового макета.

  8. КоличествоЗаписей=-1 означает полный вывод, а не минус одну исходную строку. ПроцентЗаписей=100 задаёт полный процент вывода. Читаем прежние значения, меняем только эту собственную свежую часть и проверяем установленное состояние.

  9. Свежий процессор формирует реальный ТабличныйДокумент. Находим guide-sale-1/2/3 по строкам и Книга/Ручка по колонкам, требуем три разных строки и проверяем 750/50/500 в соответствующих пересечениях. Отсутствующие сочетания не заполняем ожидаемыми числами.

Как проверить результат

  1. Будущий тест: F7 → собственный Пульт → Выполнить. Реальный строковый макет должен содержать хотя бы один объект ЗаписиТаблицыМакетаКомпоновкиДанных; иначе сработает исключение и урок не считается выполненным.

  2. У каждой найденной части установлен полный вывод -1/100, а каждое имя зависимого набора найдено в настоящем макете. Пустой массив обязательных наборов сам по себе не ошибка.

  3. В реальном документе три разных строки: guide-sale-1/Книга=750, guide-sale-2/Ручка=50, guide-sale-3/Книга=500. Остальные три сочетания пусты либо 0. Сам источник по-прежнему содержит три строки и 1300.

  4. Просмотр документа и отладчик должны подтвердить выбранные коды, области и фактические зависимости. Статическая проверка сигнатур не доказывает наличие строк или отрисовку в будущем запуске.

Ожидаемый результат

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

На что обратить внимание

  1. Не заменяйте детальные записи группировкой по коду: это другой тип скомпилированной части. Поле КодСтроки здесь выбирается для вывода, но не добавляется в ПоляГруппировки.

  2. КоличествоЗаписей — ограничение вывода части макета, не число строк контрольного запроса. Полный процент вывода не отменяет реальные отборы источника; урок не меняет ОтборЗаписей вручную.

  3. У записей шапка/подвал readonly и объектные; у самой таблицы ссылки строковые и записываемые. Не присваивайте readonly-массивы, тело или порядок.

  4. Спорные native-сигнатуры Сдвинуть/Удалить тела табличной группировки здесь не вызываются и не считаются покрытыми. Содержательные уроки не добавляются путём угадывания аргументов.

  5. Проверка ограничена своей маленькой неиерархической схемой. Имена, позиции ячеек и вид областей реальной конфигурации могут отличаться. Нет фактического запуска; runtimeVerifiedScenarios=0.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.