При разработке учетных систем на платформе 1С 8.3 одной из базовых задач является списание себестоимости товарно-материальных ценностей при их реализации. Начинающие разработчики часто пытаются рассчитывать себестоимость напрямую из документов поступления или привязывать суммы продаж к строкам расхода. Разберем правильный архитектурный подход к реализации списания по средней стоимости в модуле проведения документа.
Прежде чем приступать к кодированию, проанализируем различия двух методов расчета средней стоимости:
Типичная ошибка начинающего специалиста — попытка найти документ ПриходнаяНакладная и взять цену закупки непосредственно из его табличной части. В реальном учете товары могут поступать десятками документов по разным ценам, частично списываться и возвращаться поставщикам. Единственным достоверным источником актуального наличия и стоимости являются регистры — регистр накопления остатков или регистр бухгалтерии.
Для корректного списания себестоимости нам необходимо получить дебетовый остаток по счету учета номенклатуры (например, 41 «Товары» или субсчету «Товары на складах») в количественном и суммовом выражении на момент проведения документа.
Расчет себестоимости и формирование движений должны производиться исключительно в процедуре ОбработкаПроведения модуля объекта документа РасходнаяНакладная. Процедуры вроде ПередЗаписью для этого не подходят, поскольку документ еще не блокирует данные и не гарантирует транзакционную целостность движений.
Разберем типовой пошаговый алгоритм проведения расходного документа:
Остатки регистра бухгалтерии или регистра накопления, передавая в качестве границы времени МоментВремени() проводимого документа.Отказ = Истина.ОстатокСуммы / ОстатокКоличества * КоличествоСписания.Посмотрим на практический пример кода для документа «Расходная накладная», списывающего товар с регистра бухгалтерии (проводка Дт 90.02 «Себестоимость» — Кт 41.01 «Товары на складах»):
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Очищаем старые движения и включаем запись
Движения.ОсновнойРегистрБухгалтерии.Записывать = Истина;
Движения.ОсновнойРегистрБухгалтерии.Очистить();
// Подготавливаем таблицу товаров для анализа остатков
ТаблицаТовары = Товары.Выгрузить();
ТаблицаТовары.Свернуть("Номенклатура", "Количество, Сумма");
// Формируем запрос к остаткам регистра бухгалтерии
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокТовары.Номенклатура КАК Номенклатура,
| ДокТовары.Количество КАК КоличествоВДокументе,
| ЕСТЬNULL(Остатки.КоличествоОстатокДт, 0) КАК КоличествоОстаток,
| ЕСТЬNULL(Остатки.СуммаОстатокДт, 0) КАК СуммаОстаток
|ИЗ
| &ТаблицаТовары КАК ДокТовары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.ОсновнойРегистрБухгалтерии.Остатки(
| &МоментВремени,
| Счет = &СчетУчета,
| ,
| Субконто1 В (&СписокНоменклатуры) И Субконто2 = &Склад) КАК Остатки
| ПО ДокТовары.Номенклатура = Остатки.Субконто1";
Запрос.УстановитьПараметр("ТаблицаТовары", ТаблицаТовары);
Запрос.УстановитьПараметр("СписокНоменклатуры", ТаблицаТовары.ВыгрузитьКолонку("Номенклатура"));
Запрос.УстановитьПараметр("СчетУчета", ПланыСчетов.Основной.ТоварыНаСкладах);
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Проверяем наличие требуемого количества
Если Выборка.КоличествоОстаток < Выборка.КоличествоВДокументе Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон("Недостаточно товара %1! В наличии: %2, требуется: %3",
Выборка.Номенклатура, Выборка.КоличествоОстаток, Выборка.КоличествоВДокументе);
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
Если Отказ Тогда
Продолжить;
КонецЕсли;
// Расчет себестоимости с защитой от зависания копеек
Если Выборка.КоличествоВДокументе = Выборка.КоличествоОстаток Тогда
СебестоимостьСписания = Выборка.СуммаОстаток;
Иначе
СебестоимостьСписания = Окр((Выборка.СуммаОстаток / Выборка.КоличествоОстаток) * Выборка.КоличествоВДокументе, 2);
КонецЕсли;
// Формирование проводки списания себестоимости
Движение = Движения.ОсновнойРегистрБухгалтерии.Добавить();
Движение.Период = Дата;
Движение.СчетДт = ПланыСчетов.Основной.Себестоимость;
Движение.СчетКт = ПланыСчетов.Основной.ТоварыНаСкладах;
Движение.Сумма = СебестоимостьСписания;
Движение.КоличествоКт = Выборка.КоличествоВДокументе;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.Номенклатура] = Выборка.Номенклатура;
Движение.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.Склады] = Склад;
КонецЦикла;
КонецПроцедуры
В процессе отладки разработчики часто сталкиваются с ситуацией: приходная накладная проведена, но расходная упорно сообщает о недостатке товара. Выясним основные причины такого поведения:
12:00:00, не увидит движений приходной, записанной на 15:00:00 или на конец дня. Для корректного учета приход должен быть строго раньше реализации по временной шкале.Дата, платформа обращается к остаткам на начало секунды без учета позиции самого документа в последовательности. Необходимо передавать системный объект МоментВремени().УдалениеДвижений в значение УдалятьАвтоматически либо очищайте наборы записей перед выполнением запроса.Номенклатура. Если товар оприходован на «Основной склад», а списывается с «Оптового», остаток найден не будет.