При работе с системой 1С часто возникает задача по формированию отчетов, которые должны представлять данные в определенном, удобном для анализа виде. Одна из таких ситуаций — необходимость вывести в отчете каждого ответственного за выполнение задания отдельной строкой, даже если в исходном документе за это задание отвечали несколько сотрудников. Давайте подробно разберем, как решить эту задачу, основываясь на принципах нормализации данных и возможностях Системы Компоновки Данных (СКД) в 1С.
Представим исходную ситуацию. У нас есть документ, который фиксирует выполнение заданий. В нем строка задания содержит несколько полей для ответственных и их результатов, например:
Если на основании такого документа мы создаем движения в регистре накопления, сохраняя все эти поля как отдельные реквизиты, то в отчете мы столкнемся с тем, что информация об Ответственном1, Ответственном2 и Ответственном3 будет выводиться в разных колонках. Желаемый же результат — каждый ответственный за задание должен быть представлен отдельной строкой в отчете. Например:
Рассмотрим пошагово, как достичь такого представления данных.
Первым и самым важным шагом является правильное проектирование структуры регистра накопления. Чтобы каждый ответственный отображался отдельной строкой, данные в регистре должны быть нормализованы. Это означает, что каждая запись (движение) в регистре должна относиться только к одному ответственному.
Для нашей задачи регистр накопления должен иметь следующую структуру:
Задание (ссылка на элемент справочника или строковое поле, описывающее задание)Номенклатура (ссылка на элемент справочника Номенклатура)Ответственный (ссылка на элемент справочника ФизическиеЛица или Пользователи — одно поле для ответственного!)Материал, Подразделение).КоличествоОбработанного (числовое поле, хранящее объем работы, выполненной конкретным ответственным)КоличествоБрака).Такая структура регистра гарантирует, что при запросе данных по измерению Ответственный каждая уникальная комбинация Задания, Номенклатуры и Ответственного будет формировать отдельную строку. Это именно то, что нам требуется для отчета.
После того как мы определили правильную структуру регистра накопления, следующим шагом будет изменение логики формирования движений в процедуре ОбработкаПроведения нашего документа-регистратора. Вместо того чтобы создавать одно движение с несколькими полями для ответственных, мы будем создавать отдельное движение для каждого ответственного.
Рассмотрим подробнее по шагам, как это сделать:
ОбработкаПроведения нам необходимо обратиться к коллекции движений нашего регистра. Допустим, наш регистр накопления называется "КонтролерыОТК".Ответственный1. Если да, это означает, что первый ответственный выполнял работу. Затем аналогично проверяем Ответственный2 и Ответственный3.Добавить() коллекции движений создает новую запись, которую мы затем заполняем.Задание, Номенклатура, Материал, копируем из текущей строки табличной части документа.Ответственный заполняем значением из соответствующего поля документа (СтрокаТЧ.Ответственный1, СтрокаТЧ.Ответственный2 и т.д.).КоличествоОбработанного заполняем значением из соответствующего поля документа (СтрокаТЧ.КоличествоОбработанного1, СтрокаТЧ.КоличествоОбработанного2 и т.д.).Регистратор, которое автоматически связывает движение с документом, заполнится само при записи движений.Давайте посмотрим на пример кода для процедуры ОбработкаПроведения:
// В процедуре ОбработкаПроведения документа
// Устанавливаем режим записи движений регистра.
// Важно использовать РежимЗаписиДвижений.Записывать,
// чтобы движения создавались явно, а не перезаписывались все.
Движения.КонтролерыОТК.Записывать = Истина;
// Очищаем существующие движения по этому регистратору, чтобы избежать дублирования
// при повторном проведении документа.
Движения.КонтролерыОТК.Очистить();
// Перебираем каждую строку табличной части документа, например, "ТабличнаяЧастьЗадания"
Для Каждого СтрокаТЧ Из ДокументОбъект.ТабличнаяЧастьЗадания Цикл
// Проверяем, заполнен ли первый ответственный
Если ЗначениеЗаполнено(СтрокаТЧ.Ответственный1) Тогда
// Создаем новое движение для первого ответственного
Движение = Движения.КонтролерыОТК.Добавить();
// Заполняем общие измерения из строки табличной части
// Метод ЗаполнитьЗначенияСвойств копирует свойства, если их имена совпадают.
// Здесь мы явно указываем, какие свойства копировать, чтобы не скопировать лишнее,
// а также чтобы обеспечить копирование полей, которые могут отличаться от имен измерений
// регистра (например, если в ТЧ поле "ЗаданиеНомер", а в регистре "Задание").
// Для простоты примера, мы предполагаем, что поля документа названы так же, как измерения регистра.
Движение.Задание = СтрокаТЧ.Задание;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
// ... другие общие измерения из строки ТЧ ...
// Устанавливаем специфичные для данного ответственного измерения и ресурсы
Движение.Ответственный = СтрокаТЧ.Ответственный1;
Движение.КоличествоОбработанного = СтрокаТЧ.КоличествоОбработанного1;
// Если есть дополнительные ресурсы (например, брак), добавляем их
// Движение.КоличествоБрака = СтрокаТЧ.КоличествоБрака1;
КонецЕсли;
// Проверяем, заполнен ли второй ответственный
Если ЗначениеЗаполнено(СтрокаТЧ.Ответственный2) Тогда
// Создаем новое движение для второго ответственного
Движение = Движения.КонтролерыОТК.Добавить();
// Заполняем общие измерения
Движение.Задание = СтрокаТЧ.Задание;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
// ... другие общие измерения из строки ТЧ ...
// Устанавливаем специфичные для второго ответственного
Движение.Ответственный = СтрокаТЧ.Ответственный2;
Движение.КоличествоОбработанного = СтрокаТЧ.КоличествоОбработанного2;
// Движение.КоличествоБрака = СтрокаТЧ.КоличествоБрака2;
КонецЕсли;
// Проверяем, заполнен ли третий ответственный (если он есть)
Если ЗначениеЗаполнено(СтрокаТЧ.Ответственный3) Тогда
// Создаем новое движение для третьего ответственного
Движение = Движения.КонтролерыОТК.Добавить();
// Заполняем общие измерения
Движение.Задание = СтрокаТЧ.Задание;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
// ... другие общие измерения из строки ТЧ ...
// Устанавливаем специфичные для третьего ответственного
Движение.Ответственный = СтрокаТЧ.Ответственный3;
Движение.КоличествоОбработанного = СтрокаТЧ.КоличествоОбработанного3;
// Движение.КоличествоБрака = СтрокаТЧ.КоличествоБрака3;
КонецЕсли;
КонецЦикла;
Обратите внимание на использование метода ЗаполнитьЗначенияСвойств(), о котором упоминалось в обсуждении. Если имена полей в табличной части документа и измерениях регистра совпадают, то этот метод значительно упрощает код, автоматически копируя значения. Однако, если есть небольшие различия в именовании или требуется выборочное копирование, лучше явно присваивать значения, как показано в примере выше, или использовать третий параметр метода ЗаполнитьЗначенияСвойств() для перечисления копируемых свойств.
Теперь, после проведения документа, одна строка табличной части может породить столько движений, сколько ответственных было заполнено в этой строке. Например, если в строке Задания 1 были заполнены Ответственный1 и Ответственный2, то в регистре будет создано две отдельные записи для одного и того же Задания 1, но с разными ответственными.
Хотя текущая структура документа с колонками Ответственный1, Ответственный2, Ответственный3 может быть рабочей, она не является самой гибкой. Что, если в будущем появится необходимость добавить четвертого или пятого ответственного? Придется изменять структуру документа и код проведения. Более элегантным и гибким решением, как было предложено в обсуждении, является использование дополнительной табличной части в документе.
Представим, что наш документ будет иметь две табличные части:
НомерСтроки (системное поле, уникальный идентификатор строки)ЗаданиеНоменклатураКоличествоПродукцииНомерСтрокиЗадания (ссылка на НомерСтроки основной табличной части)ОтветственныйКоличествоОбработанногоТакая структура позволяет связать любое количество ответственных с каждой строкой основного задания. Механизм КлючСтроки в 1С часто используется для установления таких связей между строками табличных частей.
При проведении документа с такой структурой, логика будет выглядеть следующим образом:
НомерСтрокиЗадания).Примерная логика кода для проведения документа с дополнительной табличной частью:
// В процедуре ОбработкаПроведения документа
Движения.КонтролерыОТК.Записывать = Истина;
Движения.КонтролерыОТК.Очистить();
// Итерируем по основной табличной части документа
Для Каждого СтрокаОсновнойТЧ Из ДокументОбъект.ТабличнаяЧастьЗадания Цикл
// Теперь нам нужно найти всех ответственных, привязанных к этой конкретной строке задания.
// Если табличные части связаны по полю "КлючСтроки" основной ТЧ и "КлючСтрокиЗадания" дополнительной ТЧ:
Для Каждого СтрокаОтветственного Из ДокументОбъект.ОтветственныеПоСтрокеЗадания Цикл
Если СтрокаОтветственного.КлючСтрокиЗадания = СтрокаОсновнойТЧ.КлючСтроки Тогда
// Создаем новое движение для каждого ответственного
Движение = Движения.КонтролерыОТК.Добавить();
// Заполняем общие измерения из основной строки задания
Движение.Задание = СтрокаОсновнойТЧ.Задание;
Движение.Номенклатура = СтрокаОсновнойТЧ.Номенклатура;
// ... другие общие измерения ...
// Заполняем специфичные для ответственного измерения и ресурсы из строки ответственного
Движение.Ответственный = СтрокаОтветственного.Ответственный;
Движение.КоличествоОбработанного = СтрокаОтветственного.КоличествоОбработанного;
// Если есть дополнительные ресурсы (например, брак), добавляем их
// Движение.КоличествоБрака = СтрокаОтветственного.КоличествоБрака;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Такой подход обеспечивает максимальную гибкость и упрощает поддержку решения в долгосрочной перспективе.
После того как данные корректно нормализованы и записаны в регистр накопления, построение отчета становится относительно простой задачей, которая выполняется с помощью Системы Компоновки Данных (СКД) в 1С.
Давайте разберем по шагам, как настроить отчет в СКД:
РегистрНакопления.КонтролерыОТК.Обороты, так как нам нужны обобщенные данные за период.Авто или Регистратор.
Пример запроса:
ВЫБРАТЬ
КонтролерыОТКОбороты.Задание,
КонтролерыОТКОбороты.Номенклатура,
КонтролерыОТКОбороты.Ответственный,
КонтролерыОТКОбороты.КоличествоОбработанногоОборот КАК КоличествоОбработанного,
КонтролерыОТКОбороты.Регистратор
ИЗ
РегистрНакопления.КонтролерыОТК.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК КонтролерыОТКОбороты
Обратите внимание на параметр Авто в виртуальной таблице .Обороты(). Именно он позволяет получить поле Регистратор в списке доступных полей запроса, как это было упомянуто в обсуждении.
Задание, Номенклатура, Ответственный, КоличествоОбработанного и Регистратор.ЗаданиеНоменклатураОтветственныйВнутри каждой группировки мы можем выводить детальные записи или только итоговые значения по ресурсам. Для получения требуемого результата, нам потребуется вывести детальные записи после группировки по Ответственный.
&НачалоПериода и &КонецПериода, используемых в запросе, настраиваем параметры на закладке "Параметры", чтобы пользователь мог задавать период формирования отчета.После всех этих настроек отчет будет выводить данные именно так, как нам нужно: для каждого задания и номенклатуры будет список ответственных, и каждый ответственный будет представлен отдельной строкой со своим объемом выполненной работы. А благодаря полю Регистратор, пользователи смогут кликнуть по документу в отчете и перейти непосредственно в него для просмотра или редактирования.
Если ЗначениеЗаполнено(СтрокаТЧ.ОтветственныйX) Тогда корректно работают. В крайнем случае, можно ввести специальное "физическое лицо" или "пользователя" типа "Без ответственного" и присваивать его в случае отсутствия реального ответственного, если требуется видеть все задания, даже те, у которых нет явных исполнителей.Таким образом, комбинируя правильное проектирование регистра накопления с соответствующей логикой формирования движений в процедуре проведения документа и мощными возможностями Системы Компоновки Данных, мы получаем универсальное и гибкое решение для вывода каждого ответственного отдельной строкой в отчете 1С.