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