Как реализовать расчет себестоимости с учетом дополнительных расходов и построить отчет о прибыли

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

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

Проектирование структуры данных

Для корректного решения задачи нам понадобятся два регистра накопления. Разберем назначение каждого из них:

  1. ОстаткиНоменклатуры (тип: Остатки). Этот регистр является «хранилищем» текущей себестоимости.
    • Измерения: Аквариум, ПородаРыб.
    • Ресурсы: Количество, Стоимость.
  2. Продажи (тип: Обороты). Здесь мы будем фиксировать финансовый результат.
    • Измерения: Аквариум, ПородаРыб.
    • Ресурсы: Количество, СуммаПродажи, Себестоимость.

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

Реализация документа «Покупка рыб»

Этот документ отвечает за первичный приход актива. Здесь все стандартно: мы просто увеличиваем количество и стоимость в регистре остатков.


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

Реализация документа «Покупка корма» (Распределение затрат)

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

Алгоритм проведения будет следующим:

  1. Получаем текущие остатки рыб в разрезе пород для указанного аквариума.
  2. Вычисляем общее количество рыб в аквариуме.
  3. Распределяем стоимость корма пропорционально количеству каждой породы.
  4. Делаем движение Приход в регистр ОстаткиНоменклатуры, где Количество = 0, а Стоимость = РаспределеннаяСумма.

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

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

Реализация документа «Продажа рыб»

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


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

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

        // Расчет себестоимости по средней
        Если Выборка.Количество = Выборка.КоличествоОстаток Тогда
            СебестоимостьСписания = Выборка.СтоимостьОстаток;
        Иначе
            СебестоимостьСписания = (Выборка.СтоимостьОстаток / Выборка.КоличествоОстаток) * Выборка.Количество;
        КонецЕсли;

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

        // 2. Движение по продажам (Оборот)
        ДвижениеПродажи = Движения.Продажи.Добавить();
        ДвижениеПродажи.Период = Дата;
        ДвижениеПродажи.Аквариум = Выборка.Аквариум;
        ДвижениеПродажи.ПородаРыб = Выборка.ПородаРыб;
        ДвижениеПродажи.Количество = Выборка.Количество;
        ДвижениеПродажи.СуммаПродажи = Выборка.СуммаПродажи;
        ДвижениеПродажи.Себестоимость = СебестоимостьСписания;
    КонецЦикла;
КонецПроцедуры

Построение отчета о прибыли

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

Проанализируем необходимые поля:

В настройках отчета добавим группировку по Аквариуму, а внутри нее — по Породе рыб. Благодаря тому, что корм был включен в СебестоимостьСписания при проведении продажи, отчет выведет корректные данные: сумма продажи Мечерота за вычетом его закупочной цены и его доли съеденного корма.

Резюме: Ключ к решению задач на распределение затрат — понимание того, что дополнительные расходы должны «накапливаться» в остатках активов. Метод списания по средней с проверкой на последнюю единицу гарантирует отсутствие «зависших» остатков и корректный расчет прибыли в любом управленческом отчете.

← На главную