Почему программно записанное значение Числа в реквизит Определяемого типа документа 1С не сохраняется?

Программист 1С v8.3 (Управляемые формы) 1С:Комплексная автоматизация Торговля и дистрибуция
← На главную

При работе с документами в 1С:Предприятии часто возникает необходимость программно изменять значения их реквизитов. Однако иногда мы сталкиваемся с ситуацией, когда установленное нами значение, казалось бы, не сохраняется в базе данных, или, что еще более коварно, перезаписывается другим значением без видимых ошибок. Именно с такой проблемой столкнулись коллеги, пытаясь записать числовое значение в реквизит СуммаДокумента определяемого типа в документе «Реализация» в конфигурации «Комплексная автоматизация 2».

В этой статье мы подробно разберем эту ситуацию, рассмотрим причины возникновения проблемы и предложим различные подходы к ее решению, а также проанализируем нюансы каждого из них. Мы выясним, почему определяемые типы обычно не являются источником таких проблем, и какую роль играет отладчик в поиске истинной причины.

Анализируем исходную проблему и первые шаги

Изначально задача заключалась в том, чтобы программно присвоить значение реквизиту СуммаДокумента документа «Реализация» на основе итога по табличной части «Товары». Предположим, что мы имеем ссылку на документ и хотим получить его объект, изменить реквизит и сохранить. Давайте посмотрим на код, который был предложен в начале обсуждения:


РеализацияОбъект = Выборка.Ссылка.ПолучитьОбъект;
РеализацияОбъект.СуммаДокумента = РеализацияОбъект.Товары.Итог("Сумма");
РеализацияОбъект.СуммаВзаиморасчетов = РеализацияОбъект.Товары.Итог("Сумма");

На первый взгляд, код выглядит логичным: мы получаем объект документа, присваиваем значения его реквизитам. Однако, как было справедливо отмечено коллегами, в этом коде отсутствует критически важный шаг для сохранения изменений в базе данных.

  1. Получение объекта документа: Строка РеализацияОбъект = Выборка.Ссылка.ПолучитьОбъект; корректно извлекает объект документа из базы данных в оперативную память. Все дальнейшие изменения происходят с этим объектом в памяти.
  2. Присвоение значений реквизитам: Строки РеализацияОбъект.СуммаДокумента = РеализацияОбъект.Товары.Итог("Сумма"); и РеализацияОбъект.СуммаВзаиморасчетов = РеализацияОбъект.Товары.Итог("Сумма"); успешно изменяют значения соответствующих реквизитов объекта в оперативной памяти.
  3. Отсутствие сохранения: Главная проблема в данном фрагменте кода заключается в том, что после изменения объекта в памяти его новые значения не были записаны обратно в базу данных. Для этого необходимо явно вызвать метод Записать() у объекта документа.

Без вызова метода Записать() все изменения, сделанные программно, останутся только в текущем сеансе работы с объектом в памяти и будут потеряны, как только объект перестанет быть доступным или будет очищен сборщиком мусора.

Первое исправление: вызов метода "Записать()"

Естественным шагом для исправления ситуации является добавление вызова метода Записать(). Кроме того, хорошей практикой является использование блока Попытка...Исключение...КонецПопытки для перехвата возможных ошибок при записи.

Рассмотрим доработанный фрагмент кода:


РеализацияОбъект = Выборка.Ссылка.ПолучитьОбъект;
РеализацияОбъект.СуммаДокумента = РеализацияОбъект.Товары.Итог("Сумма");
РеализацияОбъект.СуммаВзаиморасчетов = РеализацияОбъект.Товары.Итог("Сумма");
Попытка
    РеализацияОбъект.Записать;
Исключение
    Сообщить(ОписаниеОшибки);
КонецПопытки;

Этот код уже намного ближе к правильному решению. Мы получаем объект, изменяем его реквизиты, а затем пытаемся записать эти изменения в базу данных. В случае возникновения ошибок при записи (например, нарушения уникальности, ошибок валидации данных), они будут перехвачены и выведены с помощью Сообщить(ОписаниеОшибки), что позволит нам диагностировать проблему.

Однако, как показала дальнейшая отладка автора темы, даже с вызовом Записать() значение все равно не сохранялось так, как ожидалось. Это привело нас к более глубокому пониманию механизмов работы документов в 1С.

Диагностика истинной причины: пересчет при записи

После добавления метода Записать() и проверки через отладчик выяснилось, что присвоенное значение действительно присутствует в реквизите РеализацияОбъект.СуммаДокумента непосредственно перед вызовом Записать(). Но после завершения операции записи (иногда даже без явных ошибок) значение снова оказывалось нулевым или некорректным.

Истинная причина проблемы, как выяснил автор темы, заключалась в том, что «при записи документа СуммаДокумента пересчитывается и получается 0». Это очень распространенная ситуация в 1С:Предприятии, особенно в типовых конфигурациях.

Документы в 1С обладают мощным механизмом обработки событий, который срабатывает при различных операциях с документом. Наиболее важными для нашего случая являются:

  1. ПередЗаписью: Этот обработчик события вызывается непосредственно перед тем, как объект документа будет записан в базу данных. Это идеальное место для выполнения проверок, автоматического расчета реквизитов, заполнения полей или корректировки данных перед их сохранением.
  2. ПриЗаписи: Этот обработчик вызывается после того, как данные документа уже записаны в базу данных, но до того, как будет завершена транзакция записи. Здесь часто формируются движения по регистрам, или выполняются другие операции, которые должны быть частью транзакции записи.
  3. ОбработкаПроведения: При проведении документа (т.е. записи его с фиксацией движений по регистрам) вызывается этот обработчик. В нем обычно содержится основная логика формирования движений.

В типовых конфигурациях, таких как «Комплексная автоматизация», реквизиты типа СуммаДокумента или СуммаВзаиморасчетов очень часто пересчитываются автоматически в обработчиках событий ПередЗаписью или ОбработкаПроведения (если документ проводится) на основе данных табличных частей (например, «Товары»). Если в вашей логике в табличной части «Товары» нет записей или их суммы равны нулю, или же есть какая-то другая логика, обнуляющая эти суммы, то программно установленное вами значение будет перезаписано.

Суть проблемы: Мы программно устанавливаем значение, но встроенная логика документа при записи или проведении игнорирует наше значение и устанавливает свое собственное, часто обнуляя его или пересчитывая. Определяемые типы здесь ни при чем, они лишь стандартизируют тип данных, но не влияют на логику их обработки.

Решение 1: Временное отключение логики документа через "ОбменДанными.Загрузка = Истина"

Одним из способов "протолкнуть" значение и проигнорировать стандартную логику пересчета документа является использование флага ОбменДанными.Загрузка = Истина. Этот флаг предназначен для сценариев обмена данными (например, при импорте из других систем), когда мы доверяем входящим данным и не хотим, чтобы документ выполнял свои обычные проверки и автоматические расчеты, которые могут изменить эти данные.

Рассмотрим, как это выглядит в коде:


РеализацияОбъект = Выборка.Ссылка.ПолучитьОбъект;
РеализацияОбъект.СуммаДокумента = РеализацияОбъект.Товары.Итог("Сумма");
РеализацияОбъект.СуммаВзаиморасчетов = РеализацияОбъект.Товары.Итог("Сумма");
Попытка
    // Устанавливаем флаг, который отключает стандартные обработчики ПередЗаписью/ПриЗаписи
    РеализацияОбъект.ОбменДанными.Загрузка = Истина;
    РеализацияОбъект.Записать;
Исключение
    Сообщить(ОписаниеОшибки);
КонецПопытки;
// Важно: если объект планируется использовать далее, или передавать другим функциям,
// флаг ОбменДанными.Загрузка следует вернуть в Истина, если не предполагается
// дальнейшее поведение по его отключению. В данном случае, после записи, объект обычно
// либо уничтожается, либо переполучается.

Преимущества этого метода:

  1. Простота реализации: Достаточно одной строки кода перед вызовом Записать(), чтобы игнорировать большинство стандартных проверок и перерасчетов.
  2. Сохранение заданного значения: Позволяет сохранить точное значение, которое мы присвоили реквизиту, не позволяя внутренней логике документа его изменить.
  3. Полезность в сценариях обмена: Идеально подходит для задач миграции данных, импорта из внешних систем, где мы хотим сохранить данные "как есть".

Недостатки и риски:

  1. Пропуск важной логики: Установка ОбменДанными.Загрузка = Истина отключает не только пересчет суммы, но и всю другую логику, которая выполняется в обработчиках ПередЗаписью, ПриЗаписи и, в некоторых случаях, ОбработкаПроведения (хотя в последнем случае есть свои нюансы). Это может привести к записи документа с некорректными данными, если в этих обработчиках выполняются важные проверки целостности данных, формирование других реквизитов, учет специфических условий и т.д.
  2. Неустойчивость решения при пользовательской работе: Как правильно заметил один из участников обсуждения, если пользователь впоследствии откроет этот документ и вручную запишет его (или проведет), то флаг ОбменДанными.Загрузка не будет установлен, и вся стандартная логика документа сработает вновь. Это означает, что ваше программно установленное значение СуммаДокумента снова будет перезаписано. Таким образом, это решение является скорее временным обходным путем или применимо только в очень специфических сценариях.
  3. Нарушение бизнес-логики: Использование этого флага может нарушить встроенную бизнес-логику конфигурации, что в долгосрочной перспективе может привести к расхождениям в данных или некорректному поведению системы.

Мы должны применять этот метод с большой осторожностью, понимая все его последствия. Если цель — постоянное изменение логики расчета реквизита, то этот подход не является лучшим долгосрочным решением.

Решение 2: Корректировка логики в обработчиках событий документа (Рекомендуемый подход)

Если мы хотим, чтобы реквизит СуммаДокумента всегда содержал определенное значение, которое отличается от того, что рассчитывает типовая логика конфигурации, то наиболее правильным и устойчивым решением будет изменение самой этой типовой логики. Этот подход более сложный, так как требует модификации конфигурации, но он обеспечивает надежное и предсказуемое поведение документа.

Разберем по шагам, что необходимо сделать:

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

Этот подход является наиболее надежным, так как он интегрирует вашу бизнес-логику непосредственно в поведение документа. Документ будет всегда вести себя предсказуемо, независимо от того, кто его записывает — пользователь вручную или программный код.

Роль определяемых типов

В начале обсуждения был упомянут реквизит «Определяемого типа», который в данном случае был «Число». Важно понимать, что определяемые типы в 1С:Предприятии влияют на структуру данных, но не на логику их обработки или сохранения.

Определяемые типы используются для создания общих, переиспользуемых типов данных, которые могут быть применены к различным реквизитам, параметрам функций, переменным и т.д. Например, определяемый тип СуммаДокументаКА может быть определен как Число с длиной 15 и точностью 2. Затем этот определяемый тип может быть использован для реквизита СуммаДокумента в «Реализации», СуммаПоступления в «ПоступленииТоваровУслуг» и т.д. Это помогает стандартизировать правила для числовых полей, содержащих суммы, по всей конфигурации.

В контексте нашей проблемы, тот факт, что реквизит СуммаДокумента имеет определяемый тип Число, не является причиной не сохранения значения. Проблема заключалась именно в механизмах записи документа и его внутренних обработчиках событий, а не в самом типе данных.

Важность отладки как основного инструмента

Как показал опыт автора темы, ключевую роль в выявлении истинной причины проблемы сыграл отладчик 1С:Предприятия. Именно пошаговое выполнение кода, проверка значений реквизитов до и после вызова метода Записать(), а также вход в обработчики событий документа (таких как ПередЗаписью) позволили обнаружить, что значение обнуляется или перезаписывается внутренней логикой конфигурации.

Мы настоятельно рекомендуем использовать отладчик для любой подобной ситуации:

  1. Установите точку останова непосредственно перед строкой, где вы присваиваете значение реквизиту.
  2. Проверьте значение реквизита после присвоения. Убедитесь, что оно корректно.
  3. Установите точку останова на строке РеализацияОбъект.Записать;.
  4. Выполните пошагово, входя в метод Записать() (обычно по F11), чтобы проследить весь путь выполнения кода, включая обработчики событий модуля объекта.
  5. Внутри обработчиков событий (например, ПередЗаписью) снова проверяйте значение вашего реквизита. Вы увидите, в какой момент и каким кодом оно изменяется.

Это самый эффективный способ понять, что происходит "под капотом" документа и найти точное место, где нужно внести изменения.

В заключение, проблема не сохранения программно установленного значения в реквизите документа 1С, когда оно перезаписывается, является классической. Ее решение требует понимания механизмов событий документа и аккуратного использования отладчика. Выбор между временным обходом с помощью ОбменДанными.Загрузка = Истина и более фундаментальной корректировкой логики в модуле объекта зависит от конкретных требований проекта и долгосрочных планов по поддержке конфигурации. В большинстве случаев, для устойчивого и предсказуемого поведения, предпочтительнее модифицировать логику в обработчиках событий документа.

← На главную