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