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