Как сделать так, чтобы при заполнении одной табличной части регистр делал приход, а при заполнении другой — расход?

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

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

В этой статье мы подробно разберем, как решить эту задачу двумя способами: с помощью интерактивного встроенного инструмента платформы (Конструктор движений) и путем написания кода вручную в модуле объекта документа. Также мы выясним причины типичных ошибок, из-за которых система может «выдавать ошибку» при проведении.

Теоретическая база: Виды регистров и типы движений

Прежде чем переходить к реализации, проанализируем ситуацию с точки зрения архитектуры метаданных 1С. В системе существует два типа регистров накопления:

  1. Остатки — хранят информацию о текущих остатках ресурсов (например, товаров на складах или взаиморасчетов) в разрезе измерений. Для этого типа регистров принципиально важно направление движения: увеличение остатка (Приход) или его уменьшение (Расход).
  2. Обороты — фиксируют только факт совершения операции за период (например, объем продаж). У таких регистров нет понятия «приход» или «расход», они лишь накапливают числовые показатели.

Направление движения для регистра остатков задается системным перечислением ВидДвиженияНакопления, которое имеет ровно два значения: ВидДвиженияНакопления.Приход (обозначается знаком плюс «+») и ВидДвиженияНакопления.Расход (обозначается знаком минус «-»).

Способ 1: Настройка через Конструктор движений («без кода»)

Многие начинающие разработчики ошибочно полагают, что Конструктор движений позволяет привязать к регистру только одну табличную часть. На самом деле инструмент обладает скрытой, но очень полезной функциональностью. Разберем по шагам, как настроить систему визуальными средствами:

  1. Откроем дерево метаданных в конфигураторе, найдем наш документ и перейдем на вкладку «Движения».
  2. Убедимся, что в списке регистров напротив нашего регистра накопления (например, ТоварыНаСкладах) установлена галочка. Это означает, что документ является регистратором для данного регистра.
  3. Нажмем кнопку «Конструктор движений».
  4. В открывшемся окне в левой колонке «Регистры» выделим наш регистр накопления.
  5. Настроим первую табличную часть (Приход):
    • В поле «Тип движения регистра» выберем значение Приход.
    • В поле «Табличная часть» выберем первую табличную часть (например, ТоварыПриход).
    • Нажмем кнопку «Заполнить выражения». Система автоматически сопоставит реквизиты табличной части с измерениями и ресурсами регистра по совпадению имен. При необходимости настроим связи вручную.
  6. Настроим вторую табличную часть (Расход):
    • Внимание: чтобы связать тот же регистр со второй табличной частью, найдем в левой колонке над списком регистров кнопку «Добавить» (иконка с зеленым плюсом).
    • Нажмем ее. В списке появится еще одна строка с именем нашего регистра.
    • Выделим эту новую строку.
    • В поле «Тип движения регистра» укажем значение Расход.
    • В поле «Табличная часть» выберем вторую табличную часть (например, ТоварыРасход).
    • Снова нажмем кнопку «Заполнить выражения» для сопоставления полей расходной части.
  7. Нажмем кнопку «ОК» внизу окна конструктора.

Платформа автоматически сгенерирует весь необходимый программный код в модуле объекта документа в процедуре ОбработкаПроведения. Этот способ идеален, так как минимизирует вероятность совершить синтаксическую ошибку.

Способ 2: Программное заполнение движений в коде

Рассмотрим подробнее программный метод. Если нам требуется реализовать сложную логику (например, делать движения только при выполнении определенных условий или выполнять предварительные расчеты), то писать код придется вручную.

Посмотрим на пример реализации в процедуре ОбработкаПроведения модуля объекта документа. Мы будем использовать обход коллекций строк наших табличных частей во вложенных циклах:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Обязательно взводим флаг автоматической записи движений при завершении транзакции проведения
    Движения.ТоварыНаСкладах.Записывать = Истина;
    
    // Очищаем движения, если документ проводится повторно
    Движения.ТоварыНаСкладах.Очистить();
    
    // 2. Обработка ПРИХОДНОЙ табличной части
    Для Каждого ТекСтрокаПриход Из ТоварыПриход Цикл
        
        // Создаем новую запись в наборе движений регистра
        НовоеДвижение = Движения.ТоварыНаСкладах.ДобавитьПриход();
        
        // Заполняем обязательные свойства движения
        НовоеДвижение.Период = Дата;
        
        // Заполняем измерения регистра
        НовоеДвижение.Номенклатура = ТекСтрокаПриход.Номенклатура;
        НовоеДвижение.Склад        = Склад;
        
        // Заполняем ресурсы регистра
        НовоеДвижение.Количество   = ТекСтрокаПриход.Количество;
        
    КонецЦикла;
    
    // 3. Обработка РАСХОДНОЙ табличной части
    Для Каждого ТекСтрокаРасход Из ТоварыРасход Цикл
        
        // Создаем новую запись со знаком Расход
        НовоеДвижение = Движения.ТоварыНаСкладах.ДобавитьРасход();
        
        // Заполняем обязательные свойства движения
        НовоеДвижение.Период = Дата;
        
        // Заполняем измерения регистра
        НовоеДвижение.Номенклатура = ТекСтрокаРасход.Номенклатура;
        НовоеДвижение.Склад        = Склад;
        
        // Заполняем ресурсы регистра
        НовоеДвижение.Количество   = ТекСтрокаРасход.Количество;
        
    КонецЦикла;

КонецПроцедуры

Обратите внимание на использование методов ДобавитьПриход() и ДобавитьРасход(). Это специализированные методы коллекции движений, которые не только добавляют новую строку в набор, но и автоматически устанавливают свойство ВидДвижения в соответствующее значение.

В качестве альтернативы можно использовать стандартный метод Добавить(), но тогда вид движения придется назначать явно:


НовоеДвижение = Движения.ТоварыНаСкладах.Добавить();
НовоеДвижение.ВидДвижения = ВидДвиженияНакопления.Расход;

Выясним причину: почему система может «выдавать ошибку»?

Проанализируем ситуацию, когда разработчик вроде бы написал код или настроил конструктор, но при попытке проведения программа аварийно завершает работу с ошибкой. Наиболее частыми причинами сбоев являются три фактора:

1. Ошибка типа регистра. Самая частая ошибка новичков — попытка установить свойство ВидДвижения для регистра накопления, который имеет тип «Обороты». У оборотного регистра нет остатков, а значит, физически отсутствует реквизит направления движения. Попытка выполнить такой код вызовет ошибку времени выполнения: «Поле объекта не обнаружено (ВидДвижения)». Решение: убедитесь, что ваш регистр имеет вид «Остатки».

2. Ошибка типов данных при заполнении свойств. Если в измерение регистра с типом СправочникСсылка.Номенклатура попытаться передать строковое значение из табличной части (например, текстовое наименование товара вместо ссылки), платформа выдаст ошибку несоответствия типов при записи набора движений в базу данных.

3. Отсутствие флага записи. Если вы заполняете движения программно, но забыли указать Движения.ИмяРегистра.Записывать = Истина;, транзакция проведения завершится успешно, но в физические таблицы базы данных движения так и не запишутся.

Используя предложенные методы, вы сможете гибко управлять поведением регистров накопления и без труда реализовывать бизнес-логику любой сложности, распределяя потоки данных из разных интерфейсных форм документа.

← На главную