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