Как эффективно разместить и рассчитать среднюю закупочную цену в 1С без партионного учета?

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

Разберем ситуацию, когда требуется организовать учет средней закупочной цены в самописной конфигурации 1С на управляемых формах (УФ) при отсутствии партионного учета. Основная задача — выводить среднюю цену в табличной части документов и корректно пересчитывать её при поступлениях и выбытиях товаров.

Предположим, у нас есть обычный регистр накопления остатков, но нет механизма учета по партиям (FIFO, LIFO). Пользователи хотят видеть среднюю цену закупки, которая обновляется при каждом поступлении. Это ставит перед нами несколько ключевых вопросов: где хранить эту цену, как её корректно рассчитывать и, самое главное, как обеспечить стабильность расчетов при повторном проведении документов, чтобы избежать искажений данных.

Оптимальное место для хранения средней цены

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

  1. Создание нового регистра сведений:

    Мы создадим новый регистр, например, с именем СредниеЦеныЗакупки. Этот регистр будет хранить информацию о средней цене для каждой номенклатурной позиции на определенный момент времени.

  2. Структура регистра сведений:

    • Измерения:

      Измерениями регистра будут те аналитики, в разрезе которых мы хотим хранить среднюю цену. Обязательным измерением является Номенклатура (тип СправочникСсылка.Номенклатура).

      Если в вашей системе используется учет по характеристикам, мы также должны включить измерение ХарактеристикаНоменклатуры (тип СправочникСсылка.ХарактеристикиНоменклатуры, если такой справочник есть). Это позволит хранить и получать среднюю цену для каждой конкретной характеристики товара.

      Наличие этих измерений гарантирует, что каждая запись в регистре будет однозначно привязана к определенной позиции товара.

    • Ресурс:

      Ресурс будет хранить саму среднюю цену. Назовем его СредняяЦенаЗакупки с типом Число и достаточной точностью (например, длина 15, точность 2).

    • Периодичность:

      Критически важный параметр для нашего случая. Мы установим периодичность По позиции регистратора.

      Данная периодичность означает, что каждая запись в регистре сведений будет привязана не просто к дате, а к конкретному моменту времени и документу-регистратору, который это движение создал. Это позволяет нам иметь точную историю изменения средней цены после каждого значимого события (поступления). Если выбрать периодичность "В пределах дня", мы потеряем детализацию изменений в течение одного дня, что может быть критично для корректного расчета скользящей средней, особенно если поступления и выбытия происходят часто.

Механизм расчета средней цены: метод скользящей средней

Пользователи хотят, чтобы "из разных цен закупки высвечивалась средняя цена", которая обновляется при поступлениях. Наиболее подходящим методом для реализации этого требования без партионного учета является метод скользящей (или взвешенной) средней цены.

Принцип работы скользящей средней при поступлении товара:

При проведении каждого документа поступления (например, ПоступлениеТоваровУслуг) мы будем пересчитывать среднюю цену для поступившей номенклатуры. Формула для расчета скользящей средней выглядит следующим образом:

НоваяСредняяЦена = (ТекущийОстатокКоличества * ТекущаяСредняяЦена + КоличествоПоступления * ЦенаПоступления) / (ТекущийОстатокКоличества + КоличествоПоступления)

Разберем каждый компонент этой формулы и откуда мы их получаем:

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

    Для этого нам потребуется сделать запрос к регистру накопления остатков на дату и время, предшествующие проведению текущего документа.

  2. ТекущаяСредняяЦена: Это средняя цена закупки, которая была актуальна для данного товара непосредственно до проведения текущего документа поступления. Эту информацию мы получаем из нашего нового регистра сведений СредниеЦеныЗакупки, используя срез последних на дату и время, предшествующие проведению текущего документа.

  3. КоличествоПоступления: Количество товара, которое поступает по текущему документу. Это значение берется непосредственно из табличной части документа поступления.

  4. ЦенаПоступления: Цена за единицу товара, указанная в текущем документе поступления.

Пример по шагам:

Рассмотрим пример, приведенный на форуме, и как он будет работать с нашим подходом:

Важно: Этот расчет и запись движения в регистр сведений СредниеЦеныЗакупки должны происходить в обработчике проведения (ОбработкаПроведения) вашего документа поступления.

Влияние продаж (выбытия) на среднюю цену

Один из ключевых вопросов на форуме звучал так: "как пересчитать среднюю цену, когда нет партионного учета?" и "Ага, а минусовать этот регистр как будешь?". Здесь важно понимать специфику метода скользящей средней без партионного учета.

При использовании метода скользящей средней, продажи (выбытие) не изменяют саму среднюю цену за единицу оставшегося товара. Они лишь уменьшают количество товара и общую стоимость остатка на складе.

Когда мы проводим документ реализации или списания, товар списывается по текущей средней цене, которая была актуальна на момент проведения этого документа (или до последнего поступления). То есть, мы берем срез последних из нашего регистра сведений СредниеЦеныЗакупки на дату и время проведения документа реализации и используем эту цену для расчета стоимости списанного товара.

Например, после второго поступления средняя цена "Телефона" стала 106.67 руб. Если мы продадим 1 штуку:

Таким образом, регистр сведений СредниеЦеныЗакупки хранит только среднюю цену, сформированную последним поступлением, и не "минусуется" при продажах. Продажи лишь используют эту цену для оценки выбытия.

Решение проблем с повторным проведением документов

На форуме справедливо был поднят вопрос: "что-то ерунда получается, когда проводишь пару документов все красиво, а если провести повторно один и тот же документ, то все плывет". Это очень распространенная и серьезная проблема при расчете себестоимости или подобных зависимых метрик в 1С, когда движения документа влияют на данные, на основе которых сам документ рассчитывает движения.

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

Ключевой принцип (сообщение 10): Документ не должен зависеть от движений, которые сам и делает. Это означает, что при каждом проведении (и перепроведении) документа расчеты должны производиться на "чистых" данных, как если бы этого документа до этого не существовало.

Алгоритм корректного проведения документов с расчетом средней цены:

Для обеспечения стабильности и корректности расчетов, особенно при многократном перепроведении документов, необходимо строго соблюдать следующую последовательность действий в обработчике проведения (ОбработкаПроведения) документа поступления:

  1. Отмена (удаление) предыдущих движений документа:

    Перед тем как начать расчеты, мы должны полностью очистить все движения, которые текущий документ создал при своем предыдущем проведении. Это касается всех регистров, в которые документ делает движения – как регистра остатков (Движения.ТоварыНаСкладах.Очистить()), так и нашего регистра сведений (Движения.СредниеЦеныЗакупки.Очистить()).

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

  2. Получение актуальных данных на "момент до":

    После очистки предыдущих движений, мы выполняем запросы для получения ТекущегоОстаткаКоличества из регистра накопления и ТекущейСреднейЦены из регистра сведений. Очень важно получить эти "срезы последних" на дату и время проведения текущего документа, чтобы отразить состояние базы *до* того, как этот документ начнет свои новые движения.

    Пример получения среза последних из регистра сведений:

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

    Аналогично для получения остатков из регистра накопления.

  3. Расчет и запись новых движений:

    Только после получения "чистых" данных мы производим расчет НовойСреднейЦены по формуле скользящей средней и формируем новые движения для регистра остатков и для регистра сведений СредниеЦеныЗакупки. Эти движения записываются с датой и временем текущего документа.

    Пример формирования движения в регистр сведений:

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

    Не забываем также сформировать движения по регистру накопления остатков.

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

В системах без партионного учета, где себестоимость рассчитывается по средней, особенно важен контроль отрицательных остатков. Если товар продается, когда его физически нет на складе (в регистре остатков), расчет средней цены может стать некорректным или привести к нулевым ценам выбытия. Мы должны предусмотреть проверки и не допускать проведения документов реализации при отсутствии остатков или принимать управленческое решение о списании по нулевой цене/цене последней закупки. Для этой задачи можно использовать обработку для контроля остатков перед проведением реализации.

Отображение средней цены в табличной части документа

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


// В модуле объекта документа или в модуле менеджера
// Функция для получения средней цены для конкретной номенклатуры
Функция ПолучитьСреднююЦенуЗакупки(НоменклатураСсылка, ДатаДокумента) Экспорт
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    СредниеЦеныЗакупкиСрезПоследних.СредняяЦенаЗакупки
        |ИЗ
        |    РегистрСведений.СредниеЦеныЗакупки.СрезПоследних(&ДатаДокумента, Номенклатура = &Номенклатура) КАК СредниеЦеныЗакупкиСрезПоследних";

    Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
    Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);

    Результат = Запрос.Выполнить().Выбрать();
    Если Результат.Следующий() Тогда
        Возврат Результат.СредняяЦенаЗакупки;
    Иначе
        Возврат 0; // Или другая логика, если цена не найдена
    КонецЕсли;
КонецФункции

// В обработчике ПриИзменении (или аналогичном) поля Номенклатура табличной части
// На клиентском или серверном вызове, в зависимости от контекста формы УФ
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    Если Элемент.ТекущиеДанные <> Неопределено Тогда
        ТекущаяСтрока = Элемент.ТекущиеДанные;
        Если ЗначениеЗаполнено(ТекущаяСтрока.Номенклатура) Тогда
            // Предполагаем, что ПолучитьСреднююЦенуЗакупки выполняется на сервере
            СредняяЦена = ПолучитьСреднююЦенуЗакупки(ТекущаяСтрока.Номенклатура, Объект.Дата);
            ТекущаяСтрока.СредняяЦенаПродажи = СредняяЦена; // Или куда нужно вывести
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

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

Учетные политики и настройки

Даже в самописной конфигурации полезно предусмотреть механизм настройки метода расчета себестоимости. В типовых решениях 1С (таких как 1С:УНФ) выбор метода (средняя за месяц, ФИФО) часто определяется учетной политикой организации. Мы можем реализовать аналогичную настройку через константу или регистр сведений, чтобы при необходимости можно было легко переключить логику расчета или добавить новые методы.

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

← На главную