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

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет Сфера услуг, туризм и социальный сектор
← На главную

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

В этой статье мы подробно проанализируем, почему так происходит, разберем архитектурные и логические ошибки в представленном коде, исправим неверную вложенность циклов и рассмотрим методы оптимизации производительности, избавляющие систему от ресурсоемких запросов внутри циклов.

Причина №1: Логическая ошибка вложенности циклов (смещение оператора КонецЦикла)

Давайте детально проанализируем структуру процедуры РасчетИндексации. Основная задача этого алгоритма — обойти все рассчитанные услуги из запроса, выполнить для каждой из них вычисление сумм индексации на основе полученных оплат и задолженностей, а затем зафиксировать результаты, добавив новые строки в таблицу значений ТЗ.

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


Рез = Запрос.Выполнить().Выбрать();
Пока Рез.Следующий() Цикл // (1) Начало основного цикла по выборке услуг из базы данных
    
    УслугаТ_ = Рез.СубконтоДт3;
    СуммаИндексации = 0;
    
    // ... Расчеты, получение объемов и цен ...
    
    // Цикл проверки оплат по дням периода
    ДатаН = КонецДня(НачалоМесяца(ДатаИндексацииТ));
    ДатаК = КонецМесяца(ДатаИндексацииТ);
    Пока ДатаН <= ДатаК Цикл // (2) Внутренний цикл по дням месяца для расчета оплат
        
        // ... Получение коэффициентов и сумм оплат на конкретную дату ...
        
        ДатаН = КонецДня(КонецДня(ДатаН) + 1);
    КонецЦикла; // Конец цикла (2) по дням
    
КонецЦикла; // ОШИБКА! Здесь досрочно закрывается основной цикл (1) по выборке услуг Рез.Следующий()

// Весь код ниже выполняется уже ПОСЛЕ завершения основного цикла по услугам!
Если СуммаИндексации <> 0 Тогда
    НовСтрока = ТЗ.Добавить(); // Строка добавляется всего ОДИН раз
    НовСтрока.Номенклатура = ПоискИнд(УслугаТ_);
    НовСтрока.Содержание = СокрЛП(Примечание) + СокрЛП(УслугаТ_) + " за " + СокрЛП(ДатаИндексацииТ);
    НовСтрока.Сумма = Сумма;
    // ... Заполнение остальных реквизитов строки ...
КонецЕсли;

Выясним причину этой ошибки: из-за неверной расстановки закрывающих операторов КонецЦикла блок добавления новой строки в таблицу Если СуммаИндексации <> 0 Тогда ... КонецЕсли оказался за пределами основного цикла обхода услуг Пока Рез.Следующий() Цикл.

В результате выполнение программы происходит по следующему сценарию:

  1. Программа заходит в цикл по выборке услуг. Сначала обрабатывается первая услуга (например, Водопотребление).
  2. Для нее рассчитывается СуммаИндексации. Значение сохраняется в локальной переменной.
  3. Программа переходит к следующему шагу цикла выборки. Обрабатывается вторая услуга (Водоотведение). Локальная переменная СуммаИндексации перезаписывается новым значением, а значение для первой услуги теряется.
  4. Выборка данных завершается, и программа выходит из цикла Рез.Следующий().
  5. И только теперь, один-единственный раз, срабатывает условие добавления строки в таблицу значений. Естественно, в нее записываются те данные, которые остались в переменных на момент последней итерации цикла (то есть данные по Водоотведению). Данные по водопотреблению попросту пропадают.

Решение проблемы: необходимо перенести оператор КонецЦикла, закрывающий цикл обхода выборки, в самый конец алгоритма — строго после блока добавления новой строки в таблицу значений.

Правильная структура вложенности должна выглядеть следующим образом:


Рез = Запрос.Выполнить().Выбрать();
Пока Рез.Следующий() Цикл // Начало основного цикла
    
    // ... Выполнение всех расчетов по текущей услуге ...
    
    Если СуммаИндексации <> 0 Тогда
        НовСтрока = ТЗ.Добавить(); // Добавление строки происходит на КАЖДОЙ итерации цикла по услугам!
        // ... Заполнение строки данными по текущей услуге ...
    КонецЕсли;
    
КонецЦикла; // Конец основного цикла по выборке услуг

Причина №2: Катастрофическое падение производительности из-за запросов в цикле

Исправление логической ошибки с закрытием цикла решит проблему с заполнением данных, однако этот код несет в себе скрытую угрозу для стабильности информационной базы. Проанализируем критический антипаттерн, допущенный в алгоритме — выполнение запросов к базе данных внутри циклов.

Внутри основного цикла по услугам у нас открывается вложенный цикл по дням месяца:


Пока ДатаН <= ДатаК Цикл
    // ...
    КофИнд = ПолучитьКоф(ДатаН, УслугаТ_, ...);
    СуммаОплаты = ПолучитьСуммуОплаты(НачалоДня(ДатаН), КонецДня(ДатаН), ...);
    // ...
КонецЦикла;

Давайте посчитаем количество обращений к базе данных:

Как правильно реализовать этот расчет:

Рассмотрим подробнее классический подход оптимизации подобных алгоритмов. Вместо того чтобы каждую секунду «дергать» базу данных мелкими запросами, мы должны использовать принцип пакетной выборки данных.

Разберем этот процесс по шагам:

  1. Перед запуском циклов обработки мы делаем один большой запрос, который получает сразу все оплаты, начисления и коэффициенты за весь необходимый период (месяц) по конкретному контрагенту и его договору.
  2. Результаты этого запроса мы выгружаем во временную таблицу значений в оперативной памяти или преобразуем в структуру Соответствие, где ключом будет дата, а значением — сумма или коэффициент.
  3. Внутри циклов мы больше не обращаемся к базе данных. Вместо вызова функций ПолучитьКоф() и ПолучитьСуммуОплаты() мы просто ищем нужные данные в локальной таблице значений или соответствии в оперативной памяти. Поиск в памяти выполняется мгновенно и не нагружает СУБД.

Причина №3: Опасность аварийного завершения работы программы (ошибка типа Неопределено)

Обратим внимание на функцию получения коэффициента индексации ПолучитьКоф(). Внутри нее реализован обход выборки среза последних:


Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
   Если Контрагент.Бюджет = Истина Тогда
        Возврат Выборка.КоэффициентБ;
   Иначе
        Возврат Выборка.Коэффициент;
   КонецЕсли;
КонецЦикла;

Проанализируем опасную ситуацию: если в регистре сведений КоэффициентыИндексации на указанную дату не окажется записи для запрашиваемой услуги, выборка запроса будет пустой. Цикл Пока Выборка.Следующий() ни разу не выполнится, и функция завершит свою работу, не дойдя до оператора Возврат. В системе 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").Ссылка;
            НовСтрока.НоменклатураИнд = УслугаТ_;
            НовСтрока.ДатаИндексации = МесяцИндексации;
        КонецЕсли;     
        
    КонецЦикла; // КОНЕЦ ОСНОВНОГО ЦИКЛА ПО УСЛУГАМ (Теперь закрывается правильно)
    
КонецПроцедуры    

Проанализируем ключевые изменения в коде:

Применение предложенного решения полностью решает проблему автора темы, гарантирует корректный расчет обеих услуг, исключает случайные ошибки времени выполнения и повышает общую производительность механизма проведения документов.

← На главную