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