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