Почему вместо суммовой скидки в 1С применяется скидка в процентах и как это исправить?

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

При доработке документов продажи или рабочего места кассира разработчики нередко сталкиваются с ситуацией: оператор вводит фиксированную сумму ручной скидки (например, 500 рублей), но программа рассчитывает результат так, словно была указана скидка в размере 500%. В результате сумма документа принимает абсурдные или отрицательные значения. Разберем детально, почему возникает эта ошибка, проанализируем исходный программный код и рассмотрим правильные подходы к расчету суммовой скидки.

Анализ проблемы и разбор исходного кода

Выясним причину некорректного поведения. В исходном фрагменте обработчика была написана следующая логика:


Если ВидСкидкиНаценки = "Сумма" Тогда
    Если ТекущаяСтрока.Номенклатура.ВидНоменклатуры.Код = "ЦУ0000008" Тогда
        Пароль = ПолучитьФорму("ФормаСменыПароля").ОткрытьМодально();
        Если Пароль = "280317" Тогда 
            ТекущаяСтрока.ПроцентСкидкиНаценки = ЗначениеСкидкиНаценки; 
        Иначе
            ЗначениеСкидкиНаценки = 0;
        КонецЕсли;
    КонецЕсли;
КонецЕсли;

Проанализируем этот алгоритм по шагам:

  1. Прямая логическая ошибка присвоения: в условии проверяется ветка ВидСкидкиНаценки = "Сумма", однако в теле условия числу 500 (которое пользователь ввел как рубли) напрямую присваивается реквизит ТекущаяСтрока.ПроцентСкидкиНаценки. Платформа вполне закономерно трактует значение 500 как 500%.
  2. Некорректная обработка ветки «Иначе»: если пароль введен неверно, обнуляется только локальная переменная ЗначениеСкидкиНаценки, в то время как скидка в самой строке документа не сбрасывается. Логичнее явно обнулять реквизит строки.
  3. Потеря контекста выбора из диалогового окна: форма ввода значения возвращает только само число через Закрыть(ВернутьЗначение()). Переменные Вид («Сумма» или «Процент») и Тип («Скидка» или «Наценка») остаются внутри формы и теряются при ее закрытии, если они не передаются вызывающему коду в явном виде.

Вариант 1. Пересчет суммы скидки в процент (если в табличной части нет отдельного реквизита суммы скидки)

Во многих типовых и нетиповых конфигурациях на обычных формах табличная часть «Товары» содержит только реквизиты Количество, Цена, ПроцентСкидкиНаценки и Сумма. В такой архитектуре колонка для хранения фиксированной суммы скидки по конкретной строке отсутствует.

Рассмотрим, как правильно пересчитать введенную сумму в эквивалентный процент:


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

// Обязательно вызываем процедуру пересчета сумм строки или документа
РассчитатьСуммуТабличнойЧасти(ТекущаяСтрока);

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

Вариант 2. Корректный возврат параметров из вспомогательной формы ввода

Посмотрим на код функции закрытия формы ввода скидки, где выбирается режим расчета («Процент» или «Сумма») и знак («Скидка» или «Наценка»):


Функция ВернутьЗначение()
    ЗначениеСкидкиНаценки = ОбщегоНазначения.ПривестиСтрокуКЧислу(Значение, Истина);
    
    Если ЗначениеСкидкиНаценки = Неопределено Тогда
        ЗначениеСкидкиНаценки = 0;
    КонецЕсли;
    
    Если Тип = "Наценка" Тогда
        ЗначениеСкидкиНаценки = -ЗначениеСкидкиНаценки;
    КонецЕсли;
    
    // Возвращаем структуру с полным контекстом операции
    Результат = Новый Структура;
    Результат.Вставить("Вид", Вид);           // "Сумма" или "Процент"
    Результат.Вставить("Тип", Тип);           // "Скидка" или "Наценка"
    Результат.Вставить("Значение", ЗначениеСкидкиНаценки);
    
    Возврат Результат;
КонецФункции

Теперь в вызывающей форме документа мы можем однозначно разобрать полученную структуру и применить скидку в зависимости от выбранного режима:


ПараметрыСкидки = ФормаВвода.ОткрытьМодально();

Если ТипЗнч(ПараметрыСкидки) = Тип("Структура") Тогда
    Если ПараметрыСкидки.Вид = "Сумма" Тогда
        СтоимостьСтроки = ТекущаяСтрока.Количество * ТекущаяСтрока.Цена;
        Если СтоимостьСтроки > 0 Тогда
            ТекущаяСтрока.ПроцентСкидкиНаценки = (ПараметрыСкидки.Значение / СтоимостьСтроки) * 100;
        КонецЕсли;
    Иначе
        ТекущаяСтрока.ПроцентСкидкиНаценки = ПараметрыСкидки.Значение;
    КонецЕсли;
    
    // Выполняем пересчет сумм по строке
    РассчитатьСуммуТабличнойЧасти(ТекущаяСтрока);
КонецЕсли;

Архитектурные замечания и рекомендации по чистоте кода

Разберем еще несколько критических моментов, которые присутствовали в исходной реализации:

  1. Хардкод паролей и кодов справочников: условие ТекущаяСтрока.Номенклатура.ВидНоменклатуры.Код = "ЦУ0000008" крайне ненадежно. При переносе данных, изменении кодировки или случайной правке кода вида номенклатуры в режиме «1С:Предприятие» условие перестанет выполняться. Для подобных проверок следует использовать предопределенные элементы, константы или регистры сведений с настройками скидок. Хранение паролей в виде открытого текста в коде (Если Пароль = "280317") нарушает базовые правила информационной безопасности — проверку прав на установку ручных скидок следует производить через типовые механизмы ролей или дополнительные права пользователей.
  2. Отказ от модальных вызовов: метод ОткрытьМодально() является устаревшим и блокирует работу веб-клиента. В современных конфигурациях открытие форм ввода организуется асинхронно через ПоказатьВводЧисла(...) или модальные окна заменяются на немодальные с передачей параметров через объект ОписаниеОповещения.
← На главную