При оформлении документов реализации или заказов клиентов периодически возникает расхождение сумм на одну копейку из-за различных алгоритмов округления цен, построчного расчета НДС или скидок. Для решения этой проблемы разработчики нередко создают внешние обработки, позволяющие точечно корректировать итоговые суммы строк табличной части. Разберем по шагам, как правильно организовать чтение данных из документа во внешнюю обработку, изменить значения и корректно перезаписать их обратно в документ базы данных, избежав типичных ошибок при обходе строк.
Рассмотрим структуру типовой внешней обработки на управляемых формах. Обычно на форме обработки размещается реквизит Документ (ссылка на документ, например, ДокументСсылка.ЗаказКлиента) и табличная часть Товары, в которую копируются строки для ручного или автоматического редактирования.
Чтобы считать текущие строки выбранного документа, выполним запрос на сервере и заполним коллекцию формы:
&НаКлиенте
Процедура Заполнить(Команда)
ЗаполнитьТабличнуюЧастьНаСервере();
ПоказатьОповещениеПользователю("Данные документа успешно загружены");
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТабличнуюЧастьНаСервере()
Объект.Товары.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказКлиентаТовары.НомерСтроки КАК НомерСтроки,
| ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
| ЗаказКлиентаТовары.Сумма КАК Сумма,
| ЗаказКлиентаТовары.СтавкаНДС КАК СтавкаНДС,
| ЗаказКлиентаТовары.СуммаНДС КАК СуммаНДС,
| ЗаказКлиентаТовары.СуммаСНДС КАК СуммаСНДС
|ИЗ
| Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
|ГДЕ
| ЗаказКлиентаТовары.Ссылка = &Ссылка
|УПОРЯДОЧИТЬ ПО
| НомерСтроки";
Запрос.УстановитьПараметр("Ссылка", Объект.Документ);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
КонецПроцедуры
Проанализируем типичные логические и синтаксические ошибки, с которыми сталкиваются разработчики при попытке перенести измененные данные обратно в документ:
Объект.Документ.Записать()) приведет к ошибке. Для изменения документа в базе необходимо сначала получить объект данных с помощью метода ПолучитьОбъект().
// ОШИБОЧНЫЙ КОД:
Для Каждого СтрДок Из ДокОбъект.Товары Цикл
Для Каждого СтрОбр Из Объект.Товары Цикл
СтрДок.Сумма = СтрОбр.Сумма; // В каждой строке документа останется сумма из самой последней строки обработки!
КонецЦикла;
КонецЦикла;
В приведенном выше фрагменте на каждой итерации внешнего цикла внутренний цикл проходит до конца, поэтому абсолютно всем строкам документа присваивается значение из последней строки таблицы обработки.
Рассмотрим два надежных способа обновления данных в объекте документа.
Способ 1. Построчное обновление с сопоставлением по ключу связи или номеру строки
Если в документе не удалялись и не добавлялись новые строки, а только корректировались суммы, можно синхронизировать строки по их порядковому номеру НомерСтроки либо по номенклатуре/ключу связи:
&НаСервере
Процедура ЗаписатьИзмененияВДокументПострочно()
Если Не ЗначениеЗаполнено(Объект.Документ) Тогда
ВызватьИсключение "Не выбран документ для корректировки!";
КонецЕсли;
ДокОбъект = Объект.Документ.ПолучитьОбъект();
Для Каждого СтрОбр Из Объект.Товары Цикл
// Поиск соответствующей строки в табличной части документа
НайденнаяСтрока = ДокОбъект.Товары.Найти(СтрОбр.НомерСтроки, "НомерСтроки");
Если НайденнаяСтрока <> Неопределено Тогда
НайденнаяСтрока.Сумма = СтрОбр.Сумма;
НайденнаяСтрока.СуммаНДС = СтрОбр.СуммаНДС;
НайденнаяСтрока.СуммаСНДС = СтрОбр.СуммаСНДС;
КонецЕсли;
КонецЦикла;
// Записываем документ с проведением или без в зависимости от задачи
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецПроцедуры
Способ 2. Полная перезагрузка табличной части методом Загрузить()
Если структура таблицы на форме обработки полностью совпадает с реквизитами табличной части документа, можно выгрузить данные в объект ТаблицаЗначений и разом перезаписать табличную часть с помощью метода Загрузить():
&НаСервере
Процедура ЗаписатьИзмененияТаблицей()
ДокОбъект = Объект.Документ.ПолучитьОбъект();
// Выгружаем данные табличной части формы обработки в ТаблицуЗначений
ТаблицаДляЗагрузки = Объект.Товары.Выгрузить();
// Загружаем подготовленную таблицу напрямую в объект документа
ДокОбъект.Товары.Загрузить(ТаблицаДляЗагрузки);
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецПроцедуры
Выясним важные нюансы, которые необходимо учитывать при корректировке копеек в типовых конфигурациях:
Сумма = Количество * Цена - Скидка. Если принудительно изменить только реквизит Сумма или СуммаСНДС, не изменив цену или скидку, то при следующем открытии документа пользователем и любом интерактивном пересчете типовой код вернет исходные рассчитанные значения. Поэтому копейку безопаснее списывать через реквизит ручной скидки (СуммаРучнойСкидки) или корректировать цену.ПолучитьОбъект() и его последующей записи следите, чтобы документ не был в этот момент заблокирован другим сеансом для редактирования, иначе платформа вернет ошибку оптимистической блокировки.ЗаполнениеТабличнойЧасти, что позволит вызывать корректировку непосредственно из открытой формы документа.