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