Как правильно организовать учет заказов в регистре накопления и почему нельзя удалять записи напрямую?

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

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

Разбор типичной ошибки: почему ручное удаление записей не работает?

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

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


// ОШИБОЧНЫЙ ПРИМЕР: так делать категорически нельзя!
Процедура УдалитьИзРегистра()
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЗаказыПоставщикамОстатки.ЗаказПоставщику КАК ЗаказПоставщику,
        |   ЗаказыПоставщикамОстатки.Номенклатура КАК Номенклатура,
        |   ЗаказыПоставщикамОстатки.ЗаказаноОстаток КАК ЗаказаноОстаток
        |ИЗ
        |   РегистрНакопления.ЗаказыПоставщикам.Остатки КАК ЗаказыПоставщикамОстатки";
    
    Результат = Запрос.Выполнить().Выбрать();
    НаборЗаписей = РегистрыНакопления.ЗаказыПоставщикам.СоздатьНаборЗаписей;
    
    // Попытка установить отбор по регистратору, который еще не выбран из результата запроса
    НаборЗаписей.Отбор.Регистратор.Установить(Результат.ЗаказПоставщику); 
    НаборЗаписей.Прочитать();
    // ... дальнейшая попытка удаления записей
КонецПроцедуры

Проанализируем, почему этот подход ошибочен как синтаксически, так и архитектурно:

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

Правильное решение: балансовый метод учета «Приход-Расход»

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

Выясним, как должна выглядеть правильная схема работы с регистром ЗаказыПоставщикам:

Для реализации этой схемы нам необходимо правильно спроектировать структуру регистра накопления:

Настройка движений документов по шагам

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

В документе ЗаказПоставщику в процедуре ОбработкаПроведения код будет выглядеть следующим образом:


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

В документе ПоступлениеТоваровУслуг, который вводится на основании заказа, нам обязательно нужно списывать эти ожидания. Код проведения по регистру с видом движения Расход примет следующий вид:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    // Проверяем, заполнен ли заказ-основание в документе поступления
    Если ЗначениеЗаполнено(ЗаказПоставщику) Тогда
        Для Каждого ТекСтрокаТовары Из Товары Цикл
            Движение = Движения.ЗаказыПоставщикам.ДобавитьРасход();
            Движение.Период = Дата;
            Движение.ЗаказПоставщику = ЗаказПоставщику;
            Движение.Номенклатура = ТекСтрокаТовары.Номенклатура;
            Движение.Количество = ТекСтрокаТовары.Количество;
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

Как правильно получать недопоставленные товары (анализ остатков)

После того как мы организовали движения «Приход-Расход», нам потребуется получать информацию о том, какие товары еще не дошли или пришли частично. Разберем типичную ошибку при написании запроса к остаткам.

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


// ПРИМЕР С ОШИБКАМИ: низкая производительность и неверная логика
"ВЫБРАТЬ
|   ЗаказыОстатки.ЗаказПоставщику КАК ЗаказПоставщику,
|   ЗаказыОстатки.Номенклатура КАК Номенклатура
|ИЗ
|   РегистрНакопления.ЗаказыПоставщикам.ОстаткиИОбороты КАК ЗаказыОстатки
|ГДЕ
|   ЗаказыОстатки.КоличествоПриход <> ЗаказыОстатки.КоличествоРасход"

Проанализируем, почему этот запрос крайне неэффективен:

  1. Высокая нагрузка на СУБД. Таблица ОстаткиИОбороты является самой ресурсоемкой для расчета. Системе приходится вычислять как срезы на начало и конец периода, так и все обороты внутри него. Если нам нужны только текущие невыполненные заказы, нам категорически не нужны обороты.
  2. Проблема временных интервалов. Если заказ был сделан в прошлом месяце, а мы запрашиваем таблицу оборотов за текущую неделю, этот заказ просто не попадет в выборку, и мы потеряем важные данные по недопоставкам.

Правильное решение: Использовать только виртуальную таблицу Остатки. Запрос получается очень легким, быстрым и надежным. Нам достаточно выбрать записи, у которых ресурс КоличествоОстаток больше нуля.

Посмотрим на пример правильного запроса для получения остатков по конкретному заказу:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ЗаказыОстатки.Номенклатура КАК Номенклатура,
    |   ЗаказыОстатки.КоличествоОстаток КАК ОсталосьПоставить
    |ИЗ
    |   РегистрНакопления.ЗаказыПоставщикам.Остатки(&Период, ЗаказПоставщику = &Заказ) КАК ЗаказыОстатки
    |ГДЕ
    |   ЗаказыОстатки.КоличествоОстаток > 0";

Запрос.УстановитьПараметр("Период", Новый Граница(МоментВремени(), ВидГраницы.Исключая));
Запрос.УстановитьПараметр("Заказ", СсылкаНаЗаказ);
РезультатЗапроса = Запрос.Выполнить();

Важный профессиональный нюанс: Использование класса «Граница»

Обратите внимание на то, как передается параметр периода в виртуальную таблицу остатков: Новый Граница(МоментВремени(), ВидГраницы.Исключая). Это критически важный момент при проведении документов или расчете остатков "на лету".

Если мы передадим в качестве периода просто дату или МоментВремени() без явного указания границы, то при проведении документа поступления система может захватить движения, которые делает этот же самый документ в эту же секунду. Использование объекта Граница с видом ВидГраницы.Исключая гарантирует, что мы получим остатки по регистру строго до того, как наш текущий документ запишет свои движения в базу данных. Это исключает двоение данных и логические коллизии при проведении.

Решение проблемы «зависших» остатков при частичной поставке

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

В профессиональных конфигурациях (1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей) эта проблема решается следующими механизмами:

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

← На главную