При разработке механизмов проведения документов в системе «1С:Предприятие 8.3» одной из самых распространенных задач является организация партионного учета. Начинающие разработчики часто сталкиваются с трудностями при формировании движений по регистру накопления, расчете себестоимости и контроле отрицательных остатков. В данной статье мы подробно проанализируем ситуацию, разберем причины возникновения ошибок и построим правильный алгоритм проведения документа списания.
Рассмотрим ситуацию, возникшую в коде автора темы. В процессе цикла обхода партий при заполнении движений регистра возникала критическая ошибка: {Документ.РасходнаяНакладная.МодульОбъекта(100)}: Значение не является значением объектного типа (Количество). Проанализируем фрагмент кода, который к этому привел:
Если ВыборкаДетальныеЗаписи.КоличествоОстаток <> 0 Тогда
Движение = Себестоимость; // Ошибка здесь!
КонецЕсли;
Движение.Количество = Списать;
Выясним причину: в языке 1С переменная Движение изначально была инициализирована как объект записи регистра (результат метода Добавить()). Однако в условии Если разработчик по ошибке присвоил этой переменной числовое значение (переменную Себестоимость). В результате объект был «затерт» числом, и при следующей попытке обратиться к свойству Движение.Количество система закономерно выдала ошибку, так как у числа нет свойств.
Как исправить: необходимо обращаться к конкретному реквизиту объекта. Правильный вариант выглядит так: Движение.Сумма = Себестоимость;.
В процессе обсуждения было отмечено, что в коде перепутаны «старая» и «новая» методики контроля. Давайте разберем их подробнее, чтобы понимать, какой подход использовать в учебных и реальных задачах.
БлокировкаДанных), иначе в многопользовательском режиме возможны ошибки.В учебных задачах (например, по методике Радченко) чаще используется «старая» методика с запросом остатков в момент проведения. Рассмотрим ее реализацию для партионного учета.
Рассмотрим по шагам, как правильно организовать списание по партиям. Нам необходимо не только списать общее количество, но и распределить его по документам поступления в хронологическом порядке.
Шаг 1. Подготовка данных документа. Проанализируем ситуацию: в табличной части документа может быть несколько строк с одинаковой номенклатурой. Прежде чем обращаться к регистру, данные документа нужно сгруппировать с помощью запроса и поместить во временную таблицу.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| ТЧ.Склад КАК Склад,
| СУММА(ТЧ.Количество) КАК КоличествоВДокументе
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТабличнаяЧасть КАК ТЧ
|СГРУППИРОВАТЬ ПО
| ТЧ.Номенклатура,
| ТЧ.Склад
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура, Склад";
Запрос.УстановитьПараметр("ТабличнаяЧасть", Товары.Выгрузить());
Обратите внимание на использование ИНДЕКСИРОВАТЬ ПО. Это значительно ускоряет соединение временной таблицы с таблицами регистра в базе данных.
Шаг 2. Получение остатков в разрезе партий. Теперь соединим нашу временную таблицу с виртуальной таблицей Остатки регистра накопления ТоварыНаСкладах. Для реализации FIFO критически важно добавить сортировку по дате партии (регистратору).
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Склад КАК Склад,
| ВТ_Товары.КоличествоВДокументе КАК КоличествоВДокументе,
| Остатки.Партия КАК Партия,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
| ЕСТЬNULL(Остатки.СуммаОстаток, 0) КАК СуммаОстаток
|ИЗ
| ВТ_Товары КАК ВТ_Товары
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментВремени,
| (Номенклатура, Склад) В (ВЫБРАТЬ Номенклатура, Склад ИЗ ВТ_Товары)) КАК Остатки
|ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура И ВТ_Товары.Склад = Остатки.Склад
|УПОРЯДОЧИТЬ ПО
| Партия.Дата ВОЗР"; // Это и есть FIFO
Шаг 3. Обход результата и распределение. Используем обход по группировкам. На верхнем уровне — Номенклатура (сколько всего нужно списать), на нижнем — Партии (сколько есть в наличии).
ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНоменклатура.Следующий() Цикл
// Проверяем общий остаток по всем партиям
Если ВыборкаНоменклатура.КоличествоОстаток < ВыборкаНоменклатура.КоличествоВДокументе Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не хватает товара: " + ВыборкаНоменклатура.Номенклатура;
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
Если Отказ Тогда Продолжить; КонецЕсли;
ОсталосьСписать = ВыборкаНоменклатура.КоличествоВДокументе;
ВыборкаПартии = ВыборкаНоменклатура.Выбрать();
Пока ВыборкаПартии.Следующий() И ОсталосьСписать > 0 Цикл
СписываемИзПартии = Мин(ОсталосьСписать, ВыборкаПартии.КоличествоОстаток);
// Расчет себестоимости (пропорционально количеству)
Себестоимость = ?(ВыборкаПартии.КоличествоОстаток = СписываемИзПартии,
ВыборкаПартии.СуммаОстаток,
ВыборкаПартии.СуммаОстаток / ВыборкаПартии.КоличествоОстаток * СписываемИзПартии);
// Формируем движение
Движение = Движения.ТоварыНаСкладах.ДобавитьРасход();
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаПартии.Номенклатура;
Движение.Склад = ВыборкаПартии.Склад;
Движение.Партия = ВыборкаПартии.Партия;
Движение.Количество = СписываемИзПартии;
Движение.Сумма = Себестоимость;
ОсталосьСписать = ОсталосьСписать - СписываемИзПартии;
КонецЦикла;
КонецЦикла;
Проанализируем формулу расчета себестоимости. В коде часто встречается деление: СуммаОстаток / КоличествоОстаток * Списываем. Здесь кроются две опасности:
?(КоличествоОстаток = 0, 0, ...) или условие.Если СписываемИзПартии = КоличествоОстаток Тогда Списать Всю СуммуОстаток. Это гарантирует полную очистку партии.Мы выяснили, что для корректной работы партионного учета необходимо:
Соблюдение этих правил позволит вам создавать надежные и производительные механизмы учета в любых конфигурациях на платформе 1С:Предприятие 8.3.