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