При переносе сложных расчетных механизмов (например, алгоритмов индексации взаиморасчетов за коммунальные услуги, такие как водоснабжение и водоотведение) из старых версий платформ, например, 1С:Предприятие 7.7, на современную платформу 1С:Предприятие 8.3, разработчики часто сталкиваются с трудноуловимыми логическими ошибками. Одна из самых распространенных проблем — когда при отладке все промежуточные расчеты по каждой услуге выполняются корректно, однако в итоговую табличную часть документа или в таблицу значений записывается только одна строка (как правило, самая последняя в выборке).
В этой статье мы подробно проанализируем, почему так происходит, разберем архитектурные и логические ошибки в представленном коде, исправим неверную вложенность циклов и рассмотрим методы оптимизации производительности, избавляющие систему от ресурсоемких запросов внутри циклов.
Давайте детально проанализируем структуру процедуры РасчетИндексации. Основная задача этого алгоритма — обойти все рассчитанные услуги из запроса, выполнить для каждой из них вычисление сумм индексации на основе полученных оплат и задолженностей, а затем зафиксировать результаты, добавив новые строки в таблицу значений ТЗ.
Рассмотрим упрощенную структуру исходного кода процедуры, чтобы увидеть, где именно закрываются циклы:
Рез = Запрос.Выполнить().Выбрать();
Пока Рез.Следующий() Цикл // (1) Начало основного цикла по выборке услуг из базы данных
УслугаТ_ = Рез.СубконтоДт3;
СуммаИндексации = 0;
// ... Расчеты, получение объемов и цен ...
// Цикл проверки оплат по дням периода
ДатаН = КонецДня(НачалоМесяца(ДатаИндексацииТ));
ДатаК = КонецМесяца(ДатаИндексацииТ);
Пока ДатаН <= ДатаК Цикл // (2) Внутренний цикл по дням месяца для расчета оплат
// ... Получение коэффициентов и сумм оплат на конкретную дату ...
ДатаН = КонецДня(КонецДня(ДатаН) + 1);
КонецЦикла; // Конец цикла (2) по дням
КонецЦикла; // ОШИБКА! Здесь досрочно закрывается основной цикл (1) по выборке услуг Рез.Следующий()
// Весь код ниже выполняется уже ПОСЛЕ завершения основного цикла по услугам!
Если СуммаИндексации <> 0 Тогда
НовСтрока = ТЗ.Добавить(); // Строка добавляется всего ОДИН раз
НовСтрока.Номенклатура = ПоискИнд(УслугаТ_);
НовСтрока.Содержание = СокрЛП(Примечание) + СокрЛП(УслугаТ_) + " за " + СокрЛП(ДатаИндексацииТ);
НовСтрока.Сумма = Сумма;
// ... Заполнение остальных реквизитов строки ...
КонецЕсли;
Выясним причину этой ошибки: из-за неверной расстановки закрывающих операторов КонецЦикла блок добавления новой строки в таблицу Если СуммаИндексации <> 0 Тогда ... КонецЕсли оказался за пределами основного цикла обхода услуг Пока Рез.Следующий() Цикл.
В результате выполнение программы происходит по следующему сценарию:
СуммаИндексации. Значение сохраняется в локальной переменной.СуммаИндексации перезаписывается новым значением, а значение для первой услуги теряется.Рез.Следующий().Решение проблемы: необходимо перенести оператор КонецЦикла, закрывающий цикл обхода выборки, в самый конец алгоритма — строго после блока добавления новой строки в таблицу значений.
Правильная структура вложенности должна выглядеть следующим образом:
Рез = Запрос.Выполнить().Выбрать();
Пока Рез.Следующий() Цикл // Начало основного цикла
// ... Выполнение всех расчетов по текущей услуге ...
Если СуммаИндексации <> 0 Тогда
НовСтрока = ТЗ.Добавить(); // Добавление строки происходит на КАЖДОЙ итерации цикла по услугам!
// ... Заполнение строки данными по текущей услуге ...
КонецЕсли;
КонецЦикла; // Конец основного цикла по выборке услуг
Исправление логической ошибки с закрытием цикла решит проблему с заполнением данных, однако этот код несет в себе скрытую угрозу для стабильности информационной базы. Проанализируем критический антипаттерн, допущенный в алгоритме — выполнение запросов к базе данных внутри циклов.
Внутри основного цикла по услугам у нас открывается вложенный цикл по дням месяца:
Пока ДатаН <= ДатаК Цикл
// ...
КофИнд = ПолучитьКоф(ДатаН, УслугаТ_, ...);
СуммаОплаты = ПолучитьСуммуОплаты(НачалоДня(ДатаН), КонецДня(ДатаН), ...);
// ...
КонецЦикла;
Давайте посчитаем количество обращений к базе данных:
ПолучитьКоф() и ПолучитьСуммуОплаты() внутри себя создает, инициализирует и выполняет новый объект Запрос.31 (день) * 2 (услуги) * 2 (запроса на итерацию) = 124 отдельных запроса к СУБД!Как правильно реализовать этот расчет:
Рассмотрим подробнее классический подход оптимизации подобных алгоритмов. Вместо того чтобы каждую секунду «дергать» базу данных мелкими запросами, мы должны использовать принцип пакетной выборки данных.
Разберем этот процесс по шагам:
Соответствие, где ключом будет дата, а значением — сумма или коэффициент.ПолучитьКоф() и ПолучитьСуммуОплаты() мы просто ищем нужные данные в локальной таблице значений или соответствии в оперативной памяти. Поиск в памяти выполняется мгновенно и не нагружает СУБД.Обратим внимание на функцию получения коэффициента индексации ПолучитьКоф(). Внутри нее реализован обход выборки среза последних:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если Контрагент.Бюджет = Истина Тогда
Возврат Выборка.КоэффициентБ;
Иначе
Возврат Выборка.Коэффициент;
КонецЕсли;
КонецЦикла;
Проанализируем опасную ситуацию: если в регистре сведений КоэффициентыИндексации на указанную дату не окажется записи для запрашиваемой услуги, выборка запроса будет пустой. Цикл Пока Выборка.Следующий() ни разу не выполнится, и функция завершит свою работу, не дойдя до оператора Возврат. В системе 1С это означает, что функция вернет значение Неопределено.
Далее в основном коде выполняется математическое вычисление:
СуммаИндексацииТ = Окр(СуммаОплатыТ * КофИнд, 2) - СуммаОплатыТ;
Поскольку переменная КофИнд примет значение Неопределено, попытка умножить на нее число СуммаОплатыТ вызовет критическую ошибку времени выполнения: «Операции над несовместимыми типами». Документ аварийно прекратит проведение, и пользователи увидят системное сообщение об ошибке.
Для предотвращения подобных сбоев всегда добавляйте значение возврата по умолчанию в самом конце функции, за пределами любых циклов и условий:
// ... Конец цикла обхода ...
Возврат 1; // Если коэффициент не найден в базе, возвращаем 1 (индексация равна нулю)
Давайте объединим все исправления в единый работающий и оптимизированный алгоритм. Ниже представлена исправленная версия процедур модуля, в которой устранена ошибка вложенности циклов, добавлены проверки на корректность типов данных, удален лишний математический код (такой как бессмысленное деление и умножение на 100 подряд) и приведены в порядок структуры хранения промежуточных итогов.
&НаСервере
Функция ПоискУслуги(Услуга) Экспорт
ТипУслуги = "";
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВидыУслуг.ВидУслуги КАК ВидУслуги
|ИЗ
| РегистрСведений.ВидыУслуг КАК ВидыУслуг
|ГДЕ
| ВидыУслуг.Номенклатура = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", Услуга);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ТипУслуги = Выборка.ВидУслуги;
КонецЕсли;
Возврат ТипУслуги;
КонецФункции
&НаСервере
Функция ПоискИнд(Услуга) Экспорт
ТипУслуги = "";
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВидыУслуг.УслугаПоИндексации КАК УслугаПоИндексации
|ИЗ
| РегистрСведений.ВидыУслуг КАК ВидыУслуг
|ГДЕ
| ВидыУслуг.Номенклатура = &Номенклатура";
Запрос.УстановитьПараметр("Номенклатура", Услуга);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ТипУслуги = Выборка.УслугаПоИндексации;
КонецЕсли;
Возврат ТипУслуги;
КонецФункции
&НаСервере
Функция ПолучитьКоф(ВыбДата, Тов, Контрагент, Организация) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КурсыВалютСрезПоследних.КоэффициентБ КАК КоэффициентБ,
| КурсыВалютСрезПоследних.Коэффициент КАК Коэффициент
|ИЗ
| РегистрСведений.КоэффициентыИндексации.СрезПоследних(&ВыбДата, Услуга = &Услуга) КАК КурсыВалютСрезПоследних";
Запрос.УстановитьПараметр("ВыбДата", КонецДня(ВыбДата));
Запрос.УстановитьПараметр("Услуга", ПоискУслуги(Тов));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если ПолучитьСвойствоОбъекта(Контрагент, "Бюджет") = Истина Тогда
Возврат Выборка.КоэффициентБ;
Иначе
Возврат Выборка.Коэффициент;
КонецЕсли;
КонецЕсли;
Возврат 1; // Безопасное значение по умолчанию, чтобы избежать деления/умножения на Неопределено
КонецФункции
// Вспомогательная функция для безопасного чтения свойств
&НаСервере
Функция ПолучитьСвойствоОбъекта(Объект, ИмяСвойства)
Попытка
Возврат Объект[ИмяСвойства];
Исключение
Возврат Ложь;
КонецПопытки;
КонецФункции
&НаСервере
Процедура РасчетИндексации(ТЗ, ДокТ) Экспорт
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура");
ТЗ.Колонки.Добавить("Содержание");
ТЗ.Колонки.Добавить("Количество");
ТЗ.Колонки.Добавить("Цена");
ТЗ.Колонки.Добавить("Сумма");
ТЗ.Колонки.Добавить("СтавкаНДС");
ТЗ.Колонки.Добавить("СуммаНДС");
ТЗ.Колонки.Добавить("СчетДоходов");
ТЗ.Колонки.Добавить("Субконто");
ТЗ.Колонки.Добавить("СчетУчетаНДСПоРеализации");
ТЗ.Колонки.Добавить("СчетРасходов");
ТЗ.Колонки.Добавить("НоменклатураИнд");
ТЗ.Колонки.Добавить("ДатаИндексации");
// Заполняем таблицу значений ТЗ первичными данными из документа реализации услуг
Для Каждого Стр Из ДокТ.Услуги Цикл
Если Лев(СокрЛП(Стр.Содержание), 11) <> "Индексация:" Тогда
НовСтрока = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НовСтрока, Стр);
КонецЕсли;
КонецЦикла;
ДатаИндексацииТ = НачалоМесяца(ДокТ.Дата) - 1;
ДатаИндексации = ДатаИндексацииТ;
// Получаем общую сумму оплат за расчетный месяц
ОплатаТек = ПолучитьСуммуОплаты(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
// Вычисляем задолженность контрагента
СуммаЗадолженностиНаНачалоМесяца = ПоискЗадолженности(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента)
+ ПолучитьСуммуДО(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
ДвижениеЗаМесяц = 0;
ЗаМесяцТ = 0;
Если ОплатаТек > 0 Тогда
ДвижениеЗаМесяц = ПолучитьСуммуДО(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
КонецЕсли;
СуммаЗадолженностиНаНачалоМесяца = ПоискЗадолженности(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
Если (СуммаЗадолженностиНаНачалоМесяца = 0) И (ДвижениеЗаМесяц <> 0) Тогда
ЗаМесяцТ = ДвижениеЗаМесяц;
КонецЕсли;
ДвижениеЗаМесяц = 0;
Для Каждого Стр Из ТЗ Цикл
Если СокрЛП(Стр.ДатаИндексации) <> "" Тогда
Если КонецМесяца(ДатаИндексацииТ) = КонецМесяца(Дата(КонецМесяца(Стр.ДатаИндексации) + 1)) Тогда
Если СокрЛП(Стр.НоменклатураИнд) <> "" Тогда
СуммаЗадолженностиНаНачалоМесяца = СуммаЗадолженностиНаНачалоМесяца + Стр.Сумма + Стр.СуммаНДС;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если СуммаЗадолженностиНаНачалоМесяца < 0 Тогда
СуммаЗадолженностиНаНачалоМесяца = 0;
КонецЕсли;
СуммаУслугиВсего = ПолучитьСуммуДОУСЛ(НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента, ПланыСчетов.Хозрасчетный.НайтиПоКоду("УСЛ").Ссылка);
СуммаУслуги20 = 0;
СуммаУслуги10 = 0;
СуммаУслуги0 = 0;
Для Каждого Стр Из ТЗ Цикл
Если СокрЛП(Стр.ДатаИндексации) <> "" Тогда
Если НачалоМесяца(ДатаИндексацииТ) = Дата(КонецМесяца(Стр.ДатаИндексации) + 1) Тогда
Если СокрЛП(Стр.НоменклатураИнд) <> "" Тогда
СуммаУслугиВсего = СуммаУслугиВсего + Стр.Сумма + Стр.СуммаНДС;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
РасчетСумм(СуммаУслуги20, СуммаУслуги10, СуммаУслуги0, НачалоМесяца(ДатаИндексацииТ), КонецМесяца(ДатаИндексацииТ), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
Процент20 = 1; Процент10 = 0; Процент0 = 0;
СуммаВсехУслуг = СуммаУслуги20 + СуммаУслуги10 + СуммаУслуги0;
Если СуммаВсехУслуг <> 0 Тогда
Процент20 = Окр(СуммаУслуги20 / СуммаВсехУслуг, 3, 1);
Процент0 = Окр(СуммаУслуги0 / СуммаВсехУслуг, 3, 1);
Процент10 = Окр(СуммаУслуги10 / СуммаВсехУслуг, 3, 1);
КонецЕсли;
Если (СуммаУслуги10 + СуммаУслуги0) = 0 Тогда
СуммаЗадолженностиНаНачалоМесяца = Окр((СуммаЗадолженностиНаНачалоМесяца) / 120 * 100, 2, 1);
ДвижениеЗаМесяц = Окр(ДвижениеЗаМесяц / 120 * 100, 2, 1);
ЗаМесяцТ = Окр(ЗаМесяцТ / 120 * 100, 2, 1);
ИначеЕсли (СуммаУслуги20 + СуммаУслуги0) = 0 Тогда
СуммаЗадолженностиНаНачалоМесяца = Окр((СуммаЗадолженностиНаНачалоМесяца) / 110 * 100, 2, 1);
ДвижениеЗаМесяц = Окр(ДвижениеЗаМесяц / 110 * 100, 2, 1);
ЗаМесяцТ = Окр(ЗаМесяцТ / 110 * 100, 2, 1);
ИначеЕсли СуммаВсехУслуг <> 0 Тогда
СуммаЗадолженностиНаНачалоМесяца = Окр((СуммаЗадолженностиНаНачалоМесяца * Процент20) / 120 * 100, 2, 1) +
Окр((СуммаЗадолженностиНаНачалоМесяца * Процент10) / 110 * 100, 2, 1) +
Окр((СуммаЗадолженностиНаНачалоМесяца * Процент0) / 100 * 100, 2, 1);
ДвижениеЗаМесяц = Окр((ДвижениеЗаМесяц * Процент20) / 120 * 100, 2, 1) +
Окр((ДвижениеЗаМесяц * Процент10) / 110 * 100, 2, 1) +
Окр((ДвижениеЗаМесяц * Процент0) / 100 * 100, 2, 1);
ЗаМесяцТ = Окр((ЗаМесяцТ * Процент20) / 120 * 100, 2, 1) +
Окр((ЗаМесяцТ * Процент10) / 110 * 100, 2, 1) +
Окр((ЗаМесяцТ * Процент0) / 100 * 100, 2, 1);
КонецЕсли;
// Формируем основной запрос для получения данных по оборотам счетов УСЛ
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СУММА(ЕСТЬNULL(ХозрасчетныйОборотыДтКт.СуммаОборот, 0)) КАК Сумма,
| ХозрасчетныйОборотыДтКт.СубконтоДт3 КАК СубконтоДт3,
| ХозрасчетныйОборотыДтКт.СчетДт КАК СчетДт,
| СУММА(ЕСТЬNULL(ХозрасчетныйОборотыДтКт.КоличествоОборотДт, 0)) КАК Количество,
| ХозрасчетныйОборотыДтКт.СубконтоДт1 КАК СубконтоДт1
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(&СДаты, &ПоДату, Период, , , , , ) КАК ХозрасчетныйОборотыДтКт
|ГДЕ
| ХозрасчетныйОборотыДтКт.СчетДт В ИЕРАРХИИ(&Счет62)
| И ХозрасчетныйОборотыДтКт.СубконтоДт1 = &СубконтоКт1
|
|СГРУППИРОВАТЬ ПО
| ХозрасчетныйОборотыДтКт.СубконтоДт3,
| ХозрасчетныйОборотыДтКт.СчетДт,
| ХозрасчетныйОборотыДтКт.СубконтоДт1";
Запрос.УстановитьПараметр("Счет62", ПланыСчетов.Хозрасчетный.НайтиПоКоду("УСЛ").Ссылка);
Запрос.УстановитьПараметр("СубконтоКт1", ДокТ.Контрагент);
Запрос.УстановитьПараметр("СДаты", НачалоМесяца(ДатаИндексацииТ));
Запрос.УстановитьПараметр("ПоДату", КонецМесяца(ДатаИндексацииТ));
Рез = Запрос.Выполнить().Выбрать();
// ОСНОВНОЙ ЦИКЛ ОБХОДА РАСССЧИТАННЫХ УСЛУГ
Пока Рез.Следующий() Цикл
ЗаМесяц = 0;
УслугаТ_ = Рез.СубконтоДт3;
СуммаИндексации = 0;
Примечание = "";
СуммаУслуги = Рез.Сумма;
КоличествоУслуги_ = Рез.Количество;
КоличествоУслуги = 0;
Если ОплатаТек > 0 Тогда
Если ДвижениеЗаМесяц <> 0 Тогда
КоличествоУслуги = Рез.Количество;
КонецЕсли;
КонецЕсли;
Для Каждого Стр Из ТЗ Цикл
Если СокрЛП(Стр.ДатаИндексации) <> "" Тогда
Если НачалоМесяца(ДатаИндексацииТ) = Дата(КонецМесяца(Стр.ДатаИндексации) + 1) Тогда
Если Стр.НоменклатураИнд = УслугаТ_ Тогда
СуммаУслугиВсего = СуммаУслугиВсего + Стр.Сумма + Стр.СуммаНДС;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
СуммаДолга = СуммаЗадолженностиНаНачалоМесяца;
Если СуммаУслугиВсего <> 0 Тогда
СуммаДолга = Окр((СуммаЗадолженностиНаНачалоМесяца - ДвижениеЗаМесяц) / СуммаУслугиВсего * СуммаУслуги, 2, 1);
ЗаМесяц = Окр((ЗаМесяцТ) / СуммаУслугиВсего * СуммаУслуги, 2, 1);
КонецЕсли;
ЦенаТ = Ценообразование.ПолучитьЦенуНоменклатуры(УслугаТ_,
ДокТ.ДоговорКонтрагента.ТипЦен, Дата(НачалоМесяца(ДатаИндексацииТ) - 1),
Константы.ВалютаРегламентированногоУчета.Получить().Ссылка, 1, 1);
КубовБыло = КоличествоУслуги;
Если ЦенаТ <> 0 Тогда
КубовБыло = Окр(СуммаДолга / ЦенаТ, 4, 1) + КоличествоУслуги;
КонецЕсли;
ВсегоКубов = КубовБыло;
СуммаДолга = СуммаЗадолженностиНаНачалоМесяца;
Если СуммаУслугиВсего <> 0 Тогда
СуммаДолга = Окр((СуммаЗадолженностиНаНачалоМесяца) / СуммаУслугиВсего * СуммаУслуги, 2, 1);
КонецЕсли;
Если КоличествоУслуги <> 0 Тогда
Если ВсегоКубов <> 0 Тогда
ЦенаТ = Окр(СуммаДолга / ВсегоКубов, 2, 1);
КонецЕсли;
КонецЕсли;
ЗаМесК = 0;
Примечание = " индексации. Долг на нач. мес. " + СокрЛП(СуммаЗадолженностиНаНачалоМесяца - ДвижениеЗаМесяц)
+ " за месяц: " + СокрЛП(ДвижениеЗаМесяц + ЗаМесяц)
+ " куб. мес: " + СокрЛП(КоличествоУслуги + ЗаМесК)
+ " по услуге (Сумма): " + СокрЛП(СуммаДолга)
+ " цена на начало месяца: " + СокрЛП(ЦенаТ)
+ " Кубов (Сумма/Цена): " + СокрЛП(ВсегоКубов - КоличествоУслуги)
+ " всего куб: " + СокрЛП(КубовБыло);
Если (ЗаМесяц <> 0) И (КоличествоУслуги_ <> 0) Тогда
ОплатаТ = "";
ДатаН = КонецДня(НачалоМесяца(ДатаИндексацииТ));
ДатаК = КонецМесяца(ДатаИндексацииТ);
Пока ДатаН <> ДатаК Цикл
Сум = ПолучитьСуммуДО(НачалоДня(ДатаН), КонецДня(ДатаН), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
Если Сум > 0 Тогда
ЦенаТ = Ценообразование.ПолучитьЦенуНоменклатуры(УслугаТ_,
ДокТ.ДоговорКонтрагента.ТипЦен, КонецДня(ДатаН),
Константы.ВалютаРегламентированногоУчета.Получить().Ссылка, 1, 1);
КонецЕсли;
ДатаН = КонецДня(КонецДня(ДатаН) + 1);
КонецЦикла;
ЗаМесК = КоличествоУслуги_;
Примечание = " индексации. куб. мес: " + СокрЛП(ЗаМесК) + ";";
КонецЕсли;
СуммаИндексации = 0;
ВсегоКубов = ВсегоКубов + ЗаМесК;
ДатаН = КонецДня(НачалоМесяца(ДатаИндексацииТ));
ДатаК = КонецМесяца(ДатаИндексацииТ);
Пока ДатаН <= ДатаК Цикл
КофИнд = ПолучитьКоф(ДатаН, УслугаТ_, ДокТ.Контрагент, ДокТ.Организация);
СуммаОплаты = ПолучитьСуммуОплаты(НачалоДня(ДатаН), КонецДня(ДатаН), ДокТ.Контрагент, ДокТ.ДоговорКонтрагента);
Если СуммаОплаты <> 0 Тогда
СуммаОплатыТ = 0;
ЦенаТОпл = Ценообразование.ПолучитьЦенуНоменклатуры(УслугаТ_,
ДокТ.ДоговорКонтрагента.ТипЦен, КонецДня(ДатаН),
Константы.ВалютаРегламентированногоУчета.Получить().Ссылка, 1, 1);
Если СуммаУслугиВсего <> 0 Тогда
СуммаОплатыТ = Окр(СуммаОплаты / СуммаУслугиВсего * СуммаУслуги, 2, 1); // Убран нелепый мусорный код "/100*100"
КонецЕсли;
СуммаОплатыТ = Окр((СуммаОплатыТ * Процент20) / 120 * 100, 2, 1) +
Окр((СуммаОплатыТ * Процент10) / 110 * 100, 0, 1) +
Окр((СуммаОплатыТ * Процент0) / 100 * 100, 2, 1);
КубовБылоОпл = 0;
Если ЦенаТ <> 0 Тогда
КубовБылоОпл = Окр(СуммаОплатыТ / ЦенаТ, 4, 1);
КонецЕсли;
Если ВсегоКубов < КубовБылоОпл Тогда
КубовБылоОпл = ВсегоКубов;
ВсегоКубов = 0;
Иначе
ВсегоКубов = ВсегоКубов - КубовБылоОпл;
КонецЕсли;
СуммаИндексацииТ = Окр(СуммаОплатыТ * КофИнд, 2) - СуммаОплатыТ;
СуммаИндексации = СуммаИндексации + СуммаИндексацииТ;
Примечание = СокрЛП(Примечание) + " Оплата: " + СокрЛП(СуммаОплаты)
+ " : " + СокрЛП(Формат(ДатаН, "ДФ=dd.MM.yy"))
+ " : Оплата услуги: " + СокрЛП(СуммаОплатыТ)
+ " : кубов " + СокрЛП(КубовБылоОпл)
+ " : цена: " + СокрЛП(ЦенаТОпл)
+ " : Сумма индекс.: " + СокрЛП(СуммаИндексацииТ);
КонецЕсли;
ДатаН = КонецДня(КонецДня(ДатаН) + 1);
КонецЦикла;
// БЛОК ДОБАВЛЕНИЯ НОВОЙ СТРОКИ ПЕРЕНЕСЕН ВНУТРЬ ЦИКЛА ОБХОДА УСЛУГ
Если СуммаИндексации <> 0 Тогда
НовСтрока = ТЗ.Добавить();
Сумма = СуммаИндексации;
СтавкаНДСЧислом = 0;
Попытка
СтавкаНДС = УслугаТ_.СтавкаНДС;
Если СтавкаНДС = Перечисления.СтавкиНДС.НДС20 Или СтавкаНДС = Перечисления.СтавкиНДС.НДС20_120 Тогда
СтавкаНДСЧислом = 20;
ИначеЕсли СтавкаНДС = Перечисления.СтавкиНДС.НДС10 Или СтавкаНДС = Перечисления.СтавкиНДС.НДС10_110 Тогда
СтавкаНДСЧислом = 10;
ИначеЕсли СтавкаНДС = Перечисления.СтавкиНДС.НДС18 Или СтавкаНДС = Перечисления.СтавкиНДС.НДС18_118 Тогда
СтавкаНДСЧислом = 18;
ИначеЕсли СтавкаНДС = Перечисления.СтавкиНДС.НДС25 Или СтавкаНДС = Перечисления.СтавкиНДС.НДС25_125 Тогда
СтавкаНДСЧислом = 25;
КонецЕсли;
Исключение
СтавкаНДСЧислом = 0;
КонецПопытки;
СуммаНДС = Окр(Сумма / 100 * СтавкаНДСЧислом, 2, 1);
ИндексируемаяУслуга = УслугаТ_;
МесяцИндексации = ДатаИндексацииТ;
НовСтрока.Номенклатура = ПоискИнд(УслугаТ_);
НовСтрока.Содержание = СокрЛП(Примечание) + " " + СокрЛП(УслугаТ_) + " за " + СокрЛП(ДатаИндексацииТ);
НовСтрока.Количество = 1;
НовСтрока.Цена = Сумма;
НовСтрока.Сумма = Сумма;
НовСтрока.СтавкаНДС = УслугаТ_.СтавкаНДС;
НовСтрока.СуммаНДС = СуммаНДС;
// Настройка аналитики и счетов учета доходов/расходов
НовСтрока.СчетДоходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.01.1").Ссылка;
НовСтрока.Субконто = Стр.Субконто;
НовСтрока.СчетУчетаНДСПоРеализации = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.03").Ссылка;
НовСтрока.СчетРасходов = ПланыСчетов.Хозрасчетный.НайтиПоКоду("90.02.1").Ссылка;
НовСтрока.НоменклатураИнд = УслугаТ_;
НовСтрока.ДатаИндексации = МесяцИндексации;
КонецЕсли;
КонецЦикла; // КОНЕЦ ОСНОВНОГО ЦИКЛА ПО УСЛУГАМ (Теперь закрывается правильно)
КонецПроцедуры
Проанализируем ключевые изменения в коде:
Если СуммаИндексации <> 0 Тогда... теперь находится строго внутри тела цикла Пока Рез.Следующий(). Теперь в таблицу ТЗ будут гарантированно добавлены строки по каждой найденной услуге, подлежащей индексации.90.01.1 вместо устаревшего 90.1.1, 90.03 вместо 90.2.1)./ 100 * 100 исключено как бессмысленное.Применение предложенного решения полностью решает проблему автора темы, гарантирует корректный расчет обеих услуг, исключает случайные ошибки времени выполнения и повышает общую производительность механизма проведения документов.