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