Как правильно сформировать движения по двум регистрам накопления с разными ценами

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

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача, когда один документ должен отражать хозяйственную операцию сразу в нескольких регистрах накопления. Типичный пример — перемещение товара между управленческими контурами, где списание происходит по одной стоимости (например, себестоимости), а поступление — по другой (например, трансфертной или розничной цене). Разберем детально, как реализовать такую логику в модуле документа для управляемого приложения.

Постановка задачи и выбор стратегии

Представим ситуацию: у нас есть документ, в табличной части которого указана номенклатура, количество и две цены (Цена1 и Цена2). При проведении документа нам необходимо:

  1. Списать товар из регистра ОстаткиТоваров1 по Цена1.
  2. Оприходовать товар в регистр ОстаткиТоваров2 по Цена2.

Для реализации этой задачи мы воспользуемся процедурой ОбработкаПроведения в модуле документа. Мы рассмотрим "классический" способ формирования движений, который является наиболее надежным и понятным для сопровождения.

Использование конструктора движений

Как справедливо отмечено в обсуждении, для новичков и для ускорения работы рекомендуется использовать Конструктор движений. Чтобы вызвать его, необходимо зайти в объект конфигурации "Документ", перейти на закладку "Движения" и нажать кнопку "Конструктор движений".

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

Проектирование алгоритма проведения

Проанализируем основные этапы, которые должны быть в нашей процедуре ОбработкаПроведения:

  1. Подготовка движений: Установка признака записи для нужных регистров.
  2. Блокировка данных: Предотвращение изменения остатков другими пользователями в момент проведения.
  3. Цикл обхода данных: Чтение табличной части документа.
  4. Формирование записей: Добавление строк в наборы записей регистров.

Реализация программного кода

Рассмотрим пример кода, который решает поставленную задачу. Мы будем использовать объектную модель доступа к движениям документа через свойство Движения.


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	
	// 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";

Запрос.УстановитьПараметр("Регистратор", Ссылка);
Результат = Запрос.Выполнить();

Если Не Результат.Пустой() Тогда
	Отказ = Истина;
	// Вывод сообщений пользователю об ошибке
КонецЕсли;

Нюансы интерфейса "Такси"

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

Для этого в цикле можно передавать индекс строки:

Сообщение = Новый СообщениеПользователю; Сообщение.Текст = "Недостаточно товара!"; Сообщение.Поле = "СписокТоваров[" + НомерСтроки + "].Количество"; Сообщение.Сообщить();

Резюме

Формирование движений по нескольким регистрам — это стандартная процедура, требующая внимательности к деталям. Основные правила: один цикл по данным, обязательная блокировка ресурсов и корректное управление флагами записи. Использование двух разных цен в одном цикле позволяет минимизировать нагрузку на сервер и сделать код максимально прозрачным.

← На главную