Как рассчитать среднюю цену запасов номенклатуры в документе перемещения 1С

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

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

Анализ математической модели расчета

Прежде чем переходить к написанию кода, разберем базовую формулу. Одной из главных ошибок начинающих является попытка усреднить цены из разных документов поступления (сложить все цены и поделить на их количество). Рассмотрим, почему это неверно. Если вы купили 1 единицу товара за 100 рублей и 100 единиц за 10 рублей, то средняя цена составит не 55 рублей, а примерно 10.89 рублей.

Поэтому средняя стоимость всегда рассчитывается по формуле:

Средняя цена = Общая сумма остатка / Общее количество остатка

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

Выбор источника данных: Регистры накопления vs Последовательности

Проанализируем ситуацию, описанную на форуме. Автор пытался использовать объект Последовательность. Это концептуальная ошибка. Последовательности в 1С предназначены для контроля хронологической правильности проведения документов (чтобы нельзя было списать товар задним числом, которого на тот момент не было). Они не хранят данные о суммах и количестве для оперативного получения остатков.

Для нашей задачи идеально подходит Регистр накопления (например, "Запасы" или "ТоварыНаСкладах"). Нам потребуется его виртуальная таблица Остатки. Она позволяет мгновенно получить актуальные данные на конкретный момент времени.

Реализация серверной функции для получения цены

Поскольку запрос к базе данных невозможно выполнить непосредственно на клиенте (в форме), нам необходимо создать серверную функцию. Мы будем использовать директиву &НаСервереБезКонтекста. Это наиболее эффективный метод, так как он не передает данные всей формы на сервер, а работает только с переданными параметрами.

Разберем пример кода функции, которая возвращает среднюю цену:


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

Важные моменты в этом коде:

  1. Использование ЕСТЬNULL: Если товара нет на складе, запрос вернет NULL. Функция ЕСТЬNULL заменяет это значение нулем, что предотвращает ошибки при расчетах.
  2. Проверка деления на ноль: Перед делением суммы на количество мы обязательно проверяем, что Выборка.Количество > 0. Это критически важно для стабильности системы.
  3. Объект Граница: Мы используем ВидГраницы.Исключая. Это позволяет получить остатки до того, как текущий документ "Перемещение" внесет свои изменения в регистр.

Обработка события изменения количества в форме

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

Создадим клиентскую процедуру для события КоличествоПриИзменении:


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

Оптимизация для больших документов

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

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

Пример структуры такого запроса:


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

Типичные ошибки, которых следует избегать

Проанализируем основные "грабли", на которые наступают разработчики при решении этой задачи:

  1. Запрос в цикле: Никогда не пишите запросы внутри цикла Для Каждого Строка ИЗ Объект.Товары на сервере. Это создает колоссальную нагрузку на СУБД. Всегда стремитесь получить данные одним запросом.
  2. Игнорирование складов: Средняя цена в 1С чаще всего рассчитывается в разрезе конкретного склада. Если вы забудете добавить условие по складу в параметры виртуальной таблицы остатков, вы получите "среднюю температуру по больнице" (среднюю цену по всей фирме), что приведет к искажению себестоимости при перемещении между удаленными филиалами.
  3. Расчет без учета характеристик: Если в вашей конфигурации используется учет по характеристикам (цвет, размер), их обязательно нужно включать в запрос. Иначе цена футболки размера S и размера XXL будет усреднена, что может быть неверно с точки зрения управленческого учета.

Заключение

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

← На главную