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