При загрузке заказов клиентов или накладных из внешних файлов (например, Excel) нередко возникают ситуации, когда в табличную часть попадают позиции с нулевой ценой (подарки, сувениры, рекламная продукция или бонусы). Однако проведение документа с нулевой стоимостью строки часто блокируется типовыми проверками конфигурации, либо вызывает ошибки при фискализации чека на ККТ (драйверы касс по 54-ФЗ не позволяют регистрировать нулевые позиции). В связи с этим возникает задача: установить для таких позиций минимальную цену в 1 рубль, а общую сумму документа уменьшить на соответствующую величину за счет других позиций, чтобы итоговый взаиморасчет с клиентом остался неизменным.
Разберем основные риски, математические нюансы и программные алгоритмы решения этой задачи в 1С.
Прежде чем переходить к алгоритмам, проанализируем ключевые сложности, возникающие при ручном изменении цен и сумм:
Цена на 1 рубль в строке, где Количество = 5, то сумма строки уменьшится на 5 рублей (Количество * Цена), а не на 1 рубль. Корректировать цены напрямую можно только в строках с единичным количеством, либо необходимо оперировать реквизитом Сумма.СуммаНДС, СуммаСНДС, СуммаРучнойСкидки, Всего.Рассмотрим прямолинейный подход: сначала подсчитаем количество строк с нулевой ценой и установим им цену 1, а затем последовательно обойдем остальные строки документа и снимем накопленную разницу с первых подходящих позиций.
Посмотрим на пример реализации алгоритма:
// Шаг 1. Находим нулевые строки и задаем им цену 1
СчетчикКоррекции = 0;
Для Каждого Стр Из Заказ.Товары Цикл
Если Стр.Цена = 0 Тогда
СчетчикКоррекции = СчетчикКоррекции + 1;
Стр.Цена = 1;
Стр.Сумма = Стр.Цена * Стр.Количество;
КонецЕсли;
КонецЦикла;
// Шаг 2. Уменьшаем цены в строках-донорах
Для Каждого Стр Из Заказ.Товары Цикл
// Работаем только со строками, где цена позволяет произвести уменьшение
Если Цел(Стр.Цена) > 1 Тогда
Если Цел(Стр.Цена - СчетчикКоррекции) >= 1 Тогда
Стр.Цена = Стр.Цена - СчетчикКоррекции;
Стр.Сумма = Стр.Цена * Стр.Количество;
СчетчикКоррекции = 0;
Иначе
// Если остатка цены не хватает на полное списание, забираем доступную часть
СчетчикКоррекции = СчетчикКоррекции - (Стр.Цена - 1);
Стр.Цена = 1;
Стр.Сумма = Стр.Цена * Стр.Количество;
КонецЕсли;
Если СчетчикКоррекции = 0 Тогда
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Ограничение метода: этот алгоритм корректно работает в случаях, когда количество в строках равно 1. Если количество больше единицы, правильнее оперировать суммами строк и скидками.
Разберем математически корректный и универсальный способ. Мы фиксируем исходную итоговую сумму документа, устанавливаем нулевым позициям минимальную сумму, а затем пропорционально уменьшаем суммы всех строк заказа через корректирующий коэффициент. Погрешность округления в копейках относим на строку с максимальной суммой.
Рассмотрим код заполнения и пересчета табличной части:
СуммаКоррекции = 0;
// Обход выборки или строк внешнего файла
Для Каждого Стр Из ДанныеЗагрузки Цикл
НовСтрока = Заказ.Товары.Добавить();
НовСтрока.Номенклатура = Стр.Номенклатура;
НовСтрока.Количество = Стр.Количество;
НовСтрока.КоличествоУпаковок = Стр.Количество;
НовСтрока.СтавкаНДС = Перечисления.СтавкиНДС.БезНДС;
Если Стр.Цена = 0 Тогда
// Устанавливаем минимальную стоимость
НовСтрока.Цена = 1;
НовСтрока.Сумма = НовСтрока.Цена * НовСтрока.Количество;
СуммаКоррекции = СуммаКоррекции + НовСтрока.Сумма;
Иначе
НовСтрока.Цена = Стр.Цена;
НовСтрока.СуммаРучнойСкидки = Стр.СуммаСкидки;
НовСтрока.Сумма = Стр.Сумма;
КонецЕсли;
КонецЦикла;
// Если были добавлены нулевые строки, пересчитываем суммы строк
Если СуммаКоррекции > 0 И Заказ.Товары.Итог("Сумма") > 0 Тогда
// Исходная сумма документа, которая должна остаться неизменной
ИтоговаяСуммаДокумента = Заказ.Товары.Итог("Сумма") - СуммаКоррекции;
КоэффициентКорректировки = ИтоговаяСуммаДокумента / Заказ.Товары.Итог("Сумма");
СтрокаСМаксимальнойСуммой = Неопределено;
Для Каждого СтрокаЗаказа Из Заказ.Товары Цикл
// Пропорционально уменьшаем сумму строки
СтрокаЗаказа.Сумма = Окр(СтрокаЗаказа.Сумма * КоэффициентКорректировки, 2);
// Пересчитываем цену от новой суммы (если количество > 0)
Если СтрокаЗаказа.Количество > 0 Тогда
СтрокаЗаказа.Цена = СтрокаЗаказа.Сумма / СтрокаЗаказа.Количество;
КонецЕсли;
// Ищем строку с наибольшей суммой для отнесения погрешности
Если СтрокаСМаксимальнойСуммой = Неопределено
ИЛИ СтрокаЗаказа.Сумма > СтрокаСМаксимальнойСуммой.Сумма Тогда
СтрокаСМаксимальнойСуммой = СтрокаЗаказа;
КонецЕсли;
КонецЦикла;
// Устраняем разницу округления
РазницаИзЗаОкруглений = ИтоговаяСуммаДокумента - Заказ.Товары.Итог("Сумма");
Если СтрокаСМаксимальнойСуммой <> Неопределено И РазницаИзЗаОкруглений <> 0 Тогда
СтрокаСМаксимальнойСуммой.Сумма = СтрокаСМаксимальнойСуммой.Сумма + РазницаИзЗаОкруглений;
Если СтрокаСМаксимальнойСуммой.Количество > 0 Тогда
СтрокаСМаксимальнойСуммой.Цена = СтрокаСМаксимальнойСуммой.Сумма / СтрокаСМаксимальнойСуммой.Количество;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Проанализируем другие способы отражения бесплатных товаров в типовых конфигурациях 1С:
СуммаРучнойСкидки или ПроцентРучнойСкидки заносится 100%. В результате сумма позиции равна 0, но базовая цена сохранена.Внутреннее потребление товаров (списание на расходы/рекламу). Это исключает налоговые риски, связанные с безвозмездной передачей по НДС.