Как правильно организовать учет остатков товаров в 1С: в справочнике или через регистры накопления?

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

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

Почему нельзя хранить остатки в реквизитах справочника?

Проанализируем ключевые архитектурные проблемы, которые неизбежно возникнут при попытке списывать количество прямо из справочника:

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

Прямое изменение реквизита: разбор наивного подхода

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


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

Посмотрим на недостатки данного кода: если документ перезаписать без проведения несколько раз или изменить в нем номенклатуру, алгоритм начнет накапливать системные погрешности. Поэтому стандартная парадигма 1С всегда строится по схеме: Документы -> Регистры -> Отчеты.

Правильная архитектура: регистры накопления

Разберем по шагам правильное создание структуры складского учета в конфигураторе:

  1. Справочник Товары. Хранит исключительно статическую информацию о позиции (наименование, артикул, единицу измерения). Никаких реквизитов остатков в нем быть не должно.
  2. Регистр накопления ОстаткиТоваров. Устанавливаем вид регистра: Остатки.
    Добавляем Измерения: Товар (тип СправочникСсылка.Товары).
    Добавляем Ресурсы: Количество (тип Число, длина 15, точность 3).
  3. Документы поступления и списания. Создаем документы ПриходТовара (или ПоступлениеТоваров) и документ РасходТовара (или РеализацияТоваров). Для каждого документа на вкладке Движения отмечаем регистр накопления ОстаткиТоваров.

Проведение документов и формирование движений

Рассмотрим заполнение движений в модуле документа расхода в обработчике ОбработкаПроведения. Движения формируются в контексте транзакции проведения:


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

Для приходного документа используется метод ДобавитьПриход(). Платформа самостоятельно управляет итогами регистра: пересчитывает актуальные остатки и очищает записи при отмене проведения документа.

Контроль отрицательных остатков

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


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

Как увидеть остатки в справочнике и отчетах?

Если пользователю необходимо видеть текущий остаток прямо в форме списка справочника Товары, сам реквизит в структуру объекта не добавляется. Для этого настраивается свойство ДинамическийСписок формы списка: включается произвольный запрос, в котором таблица Справочник.Товары связывается левым соединением с виртуальной таблицей РегистрНакопления.ОстаткиТоваров.Остатки().

Также важно разделять складские документы и документы намерений: например, документ Заказ не списывает физический остаток, а резервирует товар через отдельный регистр ЗаказыПокупателей. Фактическое списание со склада оформляется отдельным документом отгрузки.

← На главную