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