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