Как настроить автоматическую скидку на товар с наименьшей ценой в чеке в 1С:УТ 11?

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

Разберем детально вопрос, касающийся настройки автоматической скидки на товар с наименьшей ценой в чеке или документе реализации в конфигурации 1С:Управление торговлей 11 (УТ 11). Автор темы столкнулся с необходимостью применения скидки в 10% на одну строку с товаром, имеющим минимальную единичную цену, если в документе присутствует два или более товаров.

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

Определение и уточнение задачи

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

  1. Скидка должна применяться на товар, который имеет наименьшую единичную цену среди всех товаров в документе (чеке или реализации).
  2. Размер скидки составляет 10%.
  3. Скидка применяется на всю строку выбранного товара. Например, если товар2 с ценой 50 руб. за единицу и количеством 2 штуки имеет наименьшую цену, скидка 10% будет применена на общую сумму этой строки (100 руб.), что составит 10 руб.
  4. Если в чеке три товара и более, скидка все равно применяется только на один товар с наименьшей ценой. Если несколько товаров имеют одинаковую минимальную цену, скидка может быть применена к первому найденному.
  5. Скидка должна быть автоматической, то есть применяться без участия пользователя.

Такая логика скидок достаточно популярна в розничной торговле, например, акция "купи два товара, третий с меньшей ценой в подарок" или "скидка на самый дешевый товар в чеке".

Анализ типовой системы скидок в 1С:УТ 11

Действительно, 1С:Управление торговлей 11 обладает весьма гибкой и мощной системой настройки скидок, что отличает ее от предыдущих версий, таких как 10.3. Эта система позволяет настраивать различные виды скидок и условий их применения:

  1. Типы скидок: Мы можем задавать процентные или фиксированные скидки на товар, а также автоматические скидки, учитывающие наценку.
  2. Применение по объектам: Скидки могут быть привязаны к конкретным клиентам, группам клиентов, сегментам номенклатуры, видам цен и так далее.
  3. Автоматические скидки по условиям: Это ключевой механизм для нашей задачи. В УТ 11 можно создавать автоматические скидки, которые применяются при выполнении определенных условий. Стандартные условия могут быть связаны с:
    • Объемом продаж (количество или сумма проданного товара) по партнеру или по соглашению.
    • Типом или сегментом номенклатуры.
    • Датой и временем продажи.
    • Минимальной суммой или количеством товаров в документе.
    • Использование бонусных баллов.

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

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

Почему без программирования не обойтись?

Как было справедливо отмечено в обсуждении на форуме (сообщения 1, 5, 13), реализовать такую скидку без программирования невозможно. Чудес не бывает, и "гибкая система скидок" в УТ 11 сама по себе является результатом программной реализации, но не предоставляет универсального конструктора для *любой* мыслимой логики.

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

  1. Получить доступ ко всем строкам табличной части документа (чек ККМ или реализация).
  2. Итерировать по этим строкам, то есть пройтись по каждой из них.
  3. Для каждой строки определить единичную цену товара.
  4. Сравнить единичные цены всех товаров, чтобы найти минимальное значение.
  5. Идентифицировать ту строку или строки, которые соответствуют этой минимальной цене.
  6. Рассчитать сумму скидки на основе суммы выбранной строки.
  7. Применить рассчитанную скидку к документу или конкретной строке.
  8. Учесть потенциальные изменения в документе (удаление строк, изменение количества) и обеспечить пересчет скидки.

Все эти действия представляют собой алгоритмическую логику, которая не может быть выражена через стандартные настройки `УсловияПредоставленияСкидок` или другие типовые элементы пользовательского интерфейса УТ 11.

Варианты реализации: Подключение пользовательской логики

Поскольку без программирования обойтись нельзя, мы рассмотрим два основных подхода к реализации этой функциональности:

1. Использование внешней обработки или расширения конфигурации (рекомендуемый подход)

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

Преимущества:

  1. Сохранение типовой конфигурации: Мы не вносим изменения в основной код, что минимизирует риски и упрощает поддержку.
  2. Легкость обновления: Обновления типовой конфигурации проходят безболезненно, так как наша логика находится отдельно.
  3. Изолированность кода: Пользовательский код легко отлаживать и модифицировать, не затрагивая остальные части системы.

Механизм подключения:

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

Пошаговый алгоритм реализации во внешней обработке/расширении:

  1. Определение события для расчета скидки

    Мы должны выбрать подходящий момент для выполнения нашего алгоритма. Это может быть:

    • Событие `ПриИзмененииСтрокиТабличнойЧасти` или `ПередЗаписью`/`ПослеЗаписи` для объектов `Документ.РеализацияТоваровУслуг` или `Документ.ЧекККМ`. Этот подход позволит пересчитывать скидку каждый раз при изменении состава документа.
    • Подключение обработчика к системе автоматических скидок. 1С:УТ 11 позволяет регистрировать произвольные условия предоставления скидок, которые могут быть реализованы через внешние модули. Это может быть сделано через создание нового вида автоматической скидки с типом "Произвольный алгоритм" и привязкой к нашей внешней обработке/модулю расширения.
    • Вызов процедуры расчета по специальной кнопке "Рассчитать скидки", которую мы можем добавить в форму документа. Однако это не соответствует требованию "автоматической" скидки.

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

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

    Наш программный модуль должен получить доступ к объекту документа (например, `ДокументОбъект.РеализацияТоваровУслуг` или `ДокументОбъект.ЧекККМ`) и его табличной части `Товары`.

  3. Поиск товара с минимальной единичной ценой

    Этот шаг является центральным для нашей задачи:

    1. Инициализируем переменные для хранения минимальной цены и ссылки на строку, к которой будет применена скидка.
    2. Итерируем по всем строкам табличной части `Товары` документа.
    3. Для каждой строки мы должны определить единичную цену товара. Если в поле `Цена` хранится цена за единицу, используем ее. Если `Цена` - это общая сумма по строке, а `Количество` - количество единиц в строке, то единичная цена = `Цена` / `Количество`. Важно убедиться, что количество не равно нулю, чтобы избежать деления на ноль.
    4. Сравниваем текущую единичную цену с ранее найденной минимальной ценой. Если текущая цена меньше, обновляем минимальную цену и запоминаем текущую строку как "строку для скидки".
    5. Если в документе меньше двух товаров, скидка не применяется согласно условию "на вторую строку".
  4. Расчет суммы скидки

    Как только мы определили `СтрокаДляСкидки`, мы рассчитываем 10% от ее общей суммы:

    СуммаСкидки = СтрокаДляСкидки.Сумма * 10 / 100;

  5. Применение скидки

    Применить скидку можно несколькими способами:

    • Применение к конкретной строке: Мы можем изменить поля `СуммаСкидки` и `Сумма` непосредственно в выбранной строке табличной части. Это может потребовать знания структуры табличной части `Товары` и полей, отвечающих за скидки (`ПроцентАвтоматическойСкидки`, `СуммаСкидкиВДокументе` и т.п.).
    • Применение общей скидки на документ: Более гибкий вариант - установить общую сумму скидки для всего документа, а затем вызвать типовую процедуру пересчета скидок (например, `РассчитатьСкидки()`, если такой метод доступен для объекта документа). Система УТ 11 способна распределить общую скидку документа по строкам согласно своим правилам, что часто бывает более предпочтительным с точки зрения интеграции с типовой логикой. В сообщении 10 автор упоминал, что "можно на весь чек эти 6 руб раскидать", что подтверждает приемлемость такого варианта.

    Важно: При применении скидки необходимо обеспечить, чтобы она была видна пользователю в форме документа и корректно сохранялась в базе данных.

  6. Обработка граничных случаев и пересчет

    Необходимо предусмотреть следующие сценарии:

    • Меньше двух товаров: Скидка не применяется.
    • Несколько товаров с одинаковой минимальной ценой: Наше решение будет применять скидку к первой найденной строке с такой ценой. Если требуется другой подход (например, к последней, или распределить между ними), алгоритм поиска необходимо скорректировать.
    • Изменение или удаление строк: Как справедливо было отмечено в сообщении 3, менеджеры могут быть ушлыми. Если первую строку, которая была дороже, удалят, а скидка останется на другой строке, это приведет к некорректности. Поэтому крайне важно, чтобы наш алгоритм скидки пересчитывался при каждом изменении состава документа (добавлении/удалении строк, изменении количества или цены). Это обеспечивается правильным выбором события для вызова нашей процедуры расчета.

Примерная структура кода (псевдокод)

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


// Примерный код в модуле внешней обработки или расширения.
// Предположим, что эта процедура вызывается из обработчика события
// "ОбработкаАвтоматическихСкидок" или аналогичного,
// передающего объект документа для работы.

Процедура РассчитатьСкидкуНаМинимальныйТовар(ДокументОбъект, ПараметрыРасчета) Экспорт

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

    Если ДокументОбъект.Товары.Количество() < 2 Тогда // Условие: скидка на второй товар и более
        // Если скидка была, ее нужно снять.
        // Здесь должна быть логика очистки, если скидка была ранее применена
        // и не была снята типовым механизмом.
        Возврат; // Скидка не применяется
    КонецЕсли;

    // Инициализируем переменные для поиска минимальной цены
    МинимальнаяЦенаЕдиницы = Неопределено;
    СтрокаДляСкидки = Неопределено;
    ИндексСтрокиДляСкидки = -1; // Для запоминания индекса строки

    // Шаг 2: Поиск товара с минимальной единичной ценой
    Для каждого ИН_Строка Из ДокументОбъект.Товары Цикл
        // Проверяем, что количество не равно нулю, чтобы избежать деления на ноль
        Если ИН_Строка.Количество = 0 Тогда
            Продолжить;
        КонецЕсли;

        // Предполагаем, что поле "Цена" в строке табличной части - это цена за единицу товара.
        // Если "Цена" - это сумма за строку, а "Количество" - число единиц, то
        // ТекущаяЦенаЕдиницы = ИН_Строка.Сумма / ИН_Строка.Количество;
        ТекущаяЦенаЕдиницы = ИН_Строка.Цена; 
        
        // Убедимся, что сравниваем только положительные цены
        Если ТекущаяЦенаЕдиницы < 0 Тогда
            Продолжить;
        КонецЕсли;

        Если МинимальнаяЦенаЕдиницы = Неопределено ИЛИ ТекущаяЦенаЕдиницы < МинимальнаяЦенаЕдиницы Тогда
            МинимальнаяЦенаЕдиницы = ТекущаяЦенаЕдиницы;
            СтрокаДляСкидки = ИН_Строка; 
            // Запоминаем индекс для прямого доступа, если понадобится изменить строку по индексу
            ИндексСтрокиДляСкидки = ДокументОбъект.Товары.Индекс(ИН_Строка);
        КонецЕсли;
    КонецЦикла;

    Если СтрокаДляСкидки = Неопределено Тогда
        Возврат; // Товары не найдены или все с нулевым количеством/отрицательной ценой
    КонецЕсли;

    // Шаг 3: Расчет суммы скидки
    ПроцентСкидки = 10;
    
    // Сумма строки, на которую будет применена скидка
    СуммаСтрокиККорректировке = СтрокаДляСкидки.Количество * СтрокаДляСкидки.Цена; 

    // Рассчитываем сумму скидки
    СуммаСкидки = СуммаСтрокиККорректировке * ПроцентСкидки / 100;

    // Шаг 4: Применение скидки
    // Этот шаг сильно зависит от того, как УТ11 управляет скидками в строках и документе.
    // Ниже представлены два концептуальных варианта.

    // Вариант А: Изменение полей в строке табличной части напрямую.
    // Этот подход может быть сложным, так как УТ11 может пересчитывать эти поля.
    // Обычно в документах есть специальные поля для автоматических скидок.
    // Например:
    // СтрокаДляСкидки.СуммаАвтоматическойСкидки = СуммаСкидки;
    // СтрокаДляСкидки.СуммаСУчетомСкидок = СуммаСтрокиККорректировке - СуммаСкидки;
    // Или, если в строке есть поле ПроцентСкидки:
    // СтрокаДляСкидки.ПроцентСкидкиРучной = 0; // Сбрасываем ручную скидку, если она есть
    // СтрокаДляСкидки.ПроцентАвтоматическойСкидки = ПроцентСкидки;
    // СтрокаДляСкидки.Сумма = СуммаСтрокиККорректировке - СуммаСкидки;

    // Вариант Б: Применение скидки через механизм управления скидками документа.
    // Это более предпочтительный путь, так как он использует типовые механизмы.
    // Для этого нужно найти, как в УТ11 "зарегистрировать" автоматическую скидку для документа.
    //
    // В упрощенном виде, если бы документ имел свойство для общей скидки и метод пересчета:
    // ДокументОбъект.ОбщаяСуммаСкидки = ДокументОбъект.ОбщаяСуммаСкидки + СуммаСкидки;
    // ДокументОбъект.РассчитатьСкидки(); // Вызов типового метода пересчета скидок документа

    // В реальной УТ11 мы бы работали с коллекцией "АвтоматическиеСкидки"
    // или вызывали специализированные процедуры из общих модулей,
    // например, `ЦенообразованиеИСкидкиСервер.ПрименитьАвтоматическиеСкидки()`.
    // Нам нужно создать объект "АвтоматическаяСкидка", указать ее параметры
    // (процент, сумму, к чему применяется - к строке или к документу)
    // и добавить его в список применяемых скидок.
    
    // Пример с гипотетическим объектом АвтоматическаяСкидка
    // и использованием предопределенного условия (если бы такое было настроено)
    // или прямым добавлением в список рассчитанных скидок для документа.
    
    // Пусть мы нашли строку ДокументОбъект.Товары[ИндексСтрокиДляСкидки]
    // Мы можем модифицировать ее поля для скидки.
    
    // Важно: если мы напрямую меняем поля строки, необходимо убедиться,
    // что это не будет перезаписано типовым механизмом скидок.
    // Иногда требуется отключить типовой механизм или внедрить свою логику
    // в его работу через точки расширения.

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

    // Пример (концептуальный) для внешнего модуля, который регистрирует скидку:
    // Если ПараметрыРасчета.ИспользуетсяМеханизмСкидок Тогда
    //     НоваяСкидка = ПараметрыРасчета.КоллекцияСкидок.Добавить();
    //     НоваяСкидка.ВидСкидки = "Процент";
    //     НоваяСкидка.Процент = ПроцентСкидки;
    //     НоваяСкидка.НазначениеСкидки = "НаСтроку";
    //     НоваяСкидка.ИндексСтроки = ИндексСтрокиДляСкидки;
    //     НоваяСкидка.Сумма = СуммаСкидки;
    // КонецЕсли;

    // Для текущего упрощенного примера, предположим, что мы можем напрямую
    // скорректировать сумму строки, а типовой механизм УТ11 затем пересчитает
    // итоги документа. Это самый простой способ, но не всегда надежный.
    
    ДокументОбъект.Товары[ИндексСтрокиДляСкидки].СуммаСкидкиРучной = СуммаСкидки;
    ДокументОбъект.Товары[ИндексСтрокиДляСкидки].ПроцентСкидкиРучной = ПроцентСкидки;
    // Важно: если есть поле "Сумма" в строке, которая уже учитывает скидки
    // ДокументОбъект.Товары[ИндексСтрокиДляСкидки].Сумма = СуммаСтрокиККорректировке - СуммаСкидки;
    // Или, если нужно просто показать процент
    // ДокументОбъект.Товары[ИндексСтрокиДляСкидки].ПроцентАвтоматическойСкидки = ПроцентСкидки;

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

КонецПроцедуры

2. Изменение типовой конфигурации (менее предпочтительный подход)

Этот вариант подразумевает внесение изменений непосредственно в код типовой конфигурации УТ 11. Суть изменений будет та же, что и во внешней обработке, но код будет встроен в модули объектов документов (`Документ.РеализацияТоваровУслуг`, `Документ.ЧекККМ`), или в общие модули, отвечающие за расчет скидок (например, `ЦенообразованиеИСкидкиСервер`).

Недостатки:

  1. Сложности при обновлении: При каждом обновлении типовой конфигурации нам придется вручную переносить внесенные изменения, что трудоемко и чревато ошибками.
  2. Потеря поддержки: Измененная конфигурация перестает быть типовой, что может усложнить получение поддержки от 1С или партнеров.
  3. Риск ошибок: Внесение изменений в сложный код типовой системы без глубокого понимания может привести к непредсказуемым ошибкам.

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

Прочие рекомендации и важные аспекты

  1. Тщательное тестирование

    После реализации скидки крайне важно провести исчерпывающее тестирование на различных сценариях:

    • Документы с двумя, тремя и более товарами.
    • Случаи, когда несколько товаров имеют одинаковую минимальную цену.
    • Изменение количества или цены товаров в документе.
    • Добавление и удаление строк.
    • Отмена проведения документа и повторное проведение.
    • Проверка отображения скидки в печатных формах и отчетах.
  2. Производительность

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

  3. Пользовательский интерфейс

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

  4. Совместимость с другими скидками

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

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

← На главную