Как правильно реализовать списание товара по партиям (FIFO) и исправить ошибку объектного типа в 1С 8.3

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

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

Разбор ошибки «Значение не является значением объектного типа»

Рассмотрим ситуацию, возникшую в коде автора темы. В процессе цикла обхода партий при заполнении движений регистра возникала критическая ошибка: {Документ.РасходнаяНакладная.МодульОбъекта(100)}: Значение не является значением объектного типа (Количество). Проанализируем фрагмент кода, который к этому привел:


Если ВыборкаДетальныеЗаписи.КоличествоОстаток <> 0 Тогда
    Движение = Себестоимость; // Ошибка здесь!
КонецЕсли;
Движение.Количество = Списать;

Выясним причину: в языке 1С переменная Движение изначально была инициализирована как объект записи регистра (результат метода Добавить()). Однако в условии Если разработчик по ошибке присвоил этой переменной числовое значение (переменную Себестоимость). В результате объект был «затерт» числом, и при следующей попытке обратиться к свойству Движение.Количество система закономерно выдала ошибку, так как у числа нет свойств.

Как исправить: необходимо обращаться к конкретному реквизиту объекта. Правильный вариант выглядит так: Движение.Сумма = Себестоимость;.

Выбор методики контроля остатков

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

  1. Старая методика: проверка остатков выполняется до записи движений в базу. Сначала мы запрашиваем остатки из регистра, сравниваем их с количеством в документе, и если товара не хватает — прерываем проведение. Важно: этот метод требует установки явной блокировки данных (объект БлокировкаДанных), иначе в многопользовательском режиме возможны ошибки.
  2. Новая методика: документ сначала записывает свои движения (уменьшает остаток), а затем выполняется запрос к остаткам, чтобы проверить, не появились ли отрицательные значения. Этот метод считается более производительным и современным для управляемых форм.

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

Алгоритм реализации партионного учета (FIFO)

Рассмотрим по шагам, как правильно организовать списание по партиям. Нам необходимо не только списать общее количество, но и распределить его по документам поступления в хронологическом порядке.

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


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

Запрос.УстановитьПараметр("ТабличнаяЧасть", Товары.Выгрузить());

Обратите внимание на использование ИНДЕКСИРОВАТЬ ПО. Это значительно ускоряет соединение временной таблицы с таблицами регистра в базе данных.

Шаг 2. Получение остатков в разрезе партий. Теперь соединим нашу временную таблицу с виртуальной таблицей Остатки регистра накопления ТоварыНаСкладах. Для реализации FIFO критически важно добавить сортировку по дате партии (регистратору).


Запрос.Текст = 
"ВЫБРАТЬ
|	ВТ_Товары.Номенклатура КАК Номенклатура,
|	ВТ_Товары.Склад КАК Склад,
|	ВТ_Товары.КоличествоВДокументе КАК КоличествоВДокументе,
|	Остатки.Партия КАК Партия,
|	ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
|	ЕСТЬNULL(Остатки.СуммаОстаток, 0) КАК СуммаОстаток
|ИЗ
|	ВТ_Товары КАК ВТ_Товары
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментВремени, 
|	(Номенклатура, Склад) В (ВЫБРАТЬ Номенклатура, Склад ИЗ ВТ_Товары)) КАК Остатки
|ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура И ВТ_Товары.Склад = Остатки.Склад
|УПОРЯДОЧИТЬ ПО
|	Партия.Дата ВОЗР"; // Это и есть FIFO

Шаг 3. Обход результата и распределение. Используем обход по группировкам. На верхнем уровне — Номенклатура (сколько всего нужно списать), на нижнем — Партии (сколько есть в наличии).


ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаНоменклатура.Следующий() Цикл
    
    // Проверяем общий остаток по всем партиям
    Если ВыборкаНоменклатура.КоличествоОстаток < ВыборкаНоменклатура.КоличествоВДокументе Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Не хватает товара: " + ВыборкаНоменклатура.Номенклатура;
        Сообщение.Сообщить();
        Отказ = Истина;
    КонецЕсли;

    Если Отказ Тогда Продолжить; КонецЕсли;

    ОсталосьСписать = ВыборкаНоменклатура.КоличествоВДокументе;
    ВыборкаПартии = ВыборкаНоменклатура.Выбрать();

    Пока ВыборкаПартии.Следующий() И ОсталосьСписать > 0 Цикл
        
        СписываемИзПартии = Мин(ОсталосьСписать, ВыборкаПартии.КоличествоОстаток);
        
        // Расчет себестоимости (пропорционально количеству)
        Себестоимость = ?(ВыборкаПартии.КоличествоОстаток = СписываемИзПартии, 
                          ВыборкаПартии.СуммаОстаток, 
                          ВыборкаПартии.СуммаОстаток / ВыборкаПартии.КоличествоОстаток * СписываемИзПартии);

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

        ОсталосьСписать = ОсталосьСписать - СписываемИзПартии;
    КонецЦикла;
КонецЦикла;

Важные нюансы при расчете себестоимости

Проанализируем формулу расчета себестоимости. В коде часто встречается деление: СуммаОстаток / КоличествоОстаток * Списываем. Здесь кроются две опасности:

Итоги

Мы выяснили, что для корректной работы партионного учета необходимо:

  1. Тщательно следить за типами данных в переменных и не присваивать числа объектам записей.
  2. Использовать запросы с группировкой и сортировкой по дате партии для реализации FIFO.
  3. Корректно рассчитывать себестоимость, учитывая ситуацию «списания последней партии».
  4. Не забывать про контроль отрицательных остатков и блокировки данных при использовании «старой» методики проведения.

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

← На главную