Почему не записывается «Результат» в журнал расчетов 1С 7.7 и как это исправить?

Программист 1С v7.7
← На главную

При работе со сложными периодическими расчетами в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с поведением, которое кажется нелогичным: при записи записи в журнал расчетов пользовательские реквизиты (например, «Тариф», «Количество») успешно сохраняются в базе данных, а системное поле Результат остается пустым или сбрасывается в ноль. При этом отладка показывает, что расчет внутри кода происходит корректно и переменная содержит нужное числовое значение.

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

---

Выясняем причину: как движок расчетов 1С 7.7 управляет системными полями

Для того чтобы понять суть проблемы, нам необходимо проанализировать архитектуру компоненты «Расчет» в 1С 7.7. В отличие от обычных справочников или документов, ЖурналРасчетов управляется специализированным расчетным движком платформы.

Реквизиты журнала расчетов делятся на две категории:

  1. Пользовательские (дополнительные) реквизиты: такие как «Тариф», «КолПроп» или «Количество». Платформа ничего не знает об их назначении, поэтому она никогда не пытается их изменить или рассчитать самостоятельно. Они записываются и считываются стандартным образом.
  2. Системные реквизиты: к ним относятся Результат, Рассчитана, Фиксирована, ПериодРегистрации и другие. Эти поля жестко завязаны на внутреннюю логику расчетного механизма.

Когда вы выполняете в коде конструкцию:


ЖрнКв.УстановитьРеквизит("Результат", Результат);
ЖрнКв.УстановитьРеквизит("Рассчитана", 1);
ЖрнКв.Записать();

значение действительно записывается во временный буфер и даже может физически попасть в базу данных на короткое время. Однако, как только управление возвращается к документу или обработке проведения, система инициализирует общий расчет записей журнала расчетов (например, вызывается метод Рассчитать()).

В этот момент движок расчетов берет каждую запись, обращается к назначенному ей Виду Расчета (в нашем случае — «ПоКоличеству») и запускает его собственный Модуль расчета. Если этот модуль пуст, не содержит формулы или рассчитывает значение по-другому, платформа вычисляет пустой результат и перезаписывает им ваше сохраненное вручную значение. При этом «Тариф» и «Количество» остаются нетронутыми, так как расчетный движок их не обрабатывает.

---

Способ 1. Архитектурно правильное решение — перенос вычислений в Модуль расчета

В концепции 1С 7.7 расчеты не должны производиться в модуле проведения документа или во внешних глобальных процедурах. Правильный подход — разделить подготовку данных и сам расчет. Рассмотрим этот процесс по шагам.

Шаг 1: В глобальной процедуре или модуле документа мы только подготавливаем исходные данные для расчета. Мы записываем в журнал расчетов «Тариф» и «Количество», но сам результат не вычисляем и не пишем:


// В глобальном модуле или модуле документа
Процедура глЗаполнитьИсходныеДанныеТКО(ЛицСчет, ЖрнКв) Экспорт
    Тариф = ЖрнКв.ВидТарифа.Тариф.Получить(глНачПериода);
    КолПроп = ЛицСчет.КолПроп.Получить(глНачПериода); 
    
    // Записываем только исходные параметры для расчета
    ЖрнКв.УстановитьРеквизит("Тариф", Тариф);
    ЖрнКв.УстановитьРеквизит("Количество", КолПроп);
    // Снимаем признак расчета, чтобы движок знал, что запись нужно пересчитать
    ЖрнКв.УстановитьРеквизит("Рассчитана", 0); 
    ЖрнКв.Записать(); 
КонецПроцедуры

Шаг 2: Открываем в Конфигураторе свойства нашего вида расчета (в данном случае ПоКоличеству) и переходим в его Модуль расчета. Пишем там лаконичную и понятную платформе формулу:


// Модуль расчета вида расчета "ПоКоличеству"
Тариф = ПолучитьРеквизит("Тариф");
Количество = ПолучитьРеквизит("Количество");

// Записываем результат напрямую в системную переменную расчета
Результат = Тариф * Количество;

При таком подходе, когда система вызовет метод ЖрнКв.Рассчитать(), платформа сама обратится к модулю расчета, возьмет подготовленные вами «Тариф» и «Количество», перемножит их и запишет итоговое значение в поле Результат, установив флаг Рассчитана = 1.

---

Способ 2. Использование системного реквизита «Фиксирована» (быстрый обход проблемы)

Если проект слишком сложный, логика расчетов запутана и переносить код в модули расчета видов расчета сейчас нецелесообразно, можно принудительно запретить расчетному движку перезаписывать наше значение.

Для этого в платформе 1С 7.7 существует специальный системный флаг — Фиксирована. Если установить его значение в 1, движок расчетов при проходе по журналу расчетов будет полностью игнорировать эту запись, считая ее результат окончательным и не подлежащим автоматическому пересчету.

Давайте посмотрим, как модифицировать код исходной процедуры с использованием этого флага:


// Модифицированный фрагмент кода с фиксацией результата
Если ЖрнКв.ВидРасч = ВидРасчета.ПоКоличеству Тогда       
    Тариф = ЖрнКв.ВидТарифа.Тариф.Получить(глНачПериода);
    КолПроп = ЛицСчет.КолПроп.Получить(глНачПериода); 
    
    Результат = КолПроп * Тариф;
    
    ЖрнКв.УстановитьРеквизит("Тариф", Тариф);
    ЖрнКв.УстановитьРеквизит("Количество", КолПроп);
    ЖрнКв.УстановитьРеквизит("Результат", Результат);
    
    // Обязательные системные реквизиты для ручной фиксации:
    ЖрнКв.УстановитьРеквизит("Рассчитана", 1);
    ЖрнКв.УстановитьРеквизит("Фиксирована", 1); // Запрещаем пересчет движком!
    
    ЖрнКв.Записать(); 
КонецЕсли;

Важное предупреждение: Использование флага Фиксирована решает локальную проблему, но лишает вас преимуществ автоматического перерасчета. Если в будущем изменятся исходные данные (например, тариф задним числом), система не сможет автоматически пересчитать эту строку журнала расчетов, так как она заблокирована.

---

Способ 3. Устранение сбоя курсора при записи в цикле выборки

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


// ВНИМАНИЕ: Так писать код в 1С 7.7 крайне не рекомендуется!
Пока ЖрнКв.ПолучитьЗапись() = 1 Цикл
    ...
    ЖрнКв.Записать(); 
КонецЦикла;

В архитектуре баз данных DBF и SQL, используемых в 1С 7.7, позиционирование на записи (курсор выборки) происходит динамически. Модификация и сохранение текущей записи непосредственно в цикле выборки сбивает внутренний указатель платформы. Это приводит к непредсказуемым результатам: записи могут пропускаться, обрабатываться дважды или записываться с искажением системных полей (таких как Результат).

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


Процедура глРассчитатьЛицСчетТКО(ЛицСчет) Экспорт
    Перем Сальдо, ВидСчетчика;
    
    // Создаем основной объект для чтения и выборки записей
    ЖрнКв = СоздатьОбъект("ЖурналРасчетов.КвартплатаТКО");
    // Создаем вспомогательный объект исключительно для записи данных
    ЖрнКвЗапись = СоздатьОбъект("ЖурналРасчетов.КвартплатаТКО");
    
    ЖрнКв.ВыбратьЗаписиПоОбъекту(ЛицСчет.ТекущийЭлемент(), глНачПериода, глКонПериода);
    Сальдо = глРассчитатьСальдоТКО(ЛицСчет);           
    
    Пока ЖрнКв.ПолучитьЗапись() = 1 Цикл
        // Проверяем вид документа-регистратора
        Если ЖрнКв.Документ.Вид() = "РасчетКвартплатыТКО" Тогда  
            
            // Исправляем синтаксическую ошибку автора (добавляем пропущенное "Если")
            Если ЖрнКв.ВидРасч.ВходитВГруппу(ГруппаРасчетов.Начисления) = 1 Тогда    
                
                Если ЖрнКв.ВидРасч = ВидРасчета.ПоКоличеству Тогда      
                    Тариф = ЖрнКв.ВидТарифа.Тариф.Получить(глНачПериода);
                    КолПроп = ЛицСчет.КолПроп.Получить(глНачПериода); 
                    Результат = КолПроп * Тариф;
                    
                    // Позиционируем объект записи на текущую запись выборки
                    ЖрнКвЗапись.НайтиЗапись(ЖрнКв.ТекущаяЗапись());
                    
                    // Производим изменения через вспомогательный объект
                    ЖрнКвЗапись.УстановитьРеквизит("Тариф", Тариф);
                    ЖрнКвЗапись.УстановитьРеквизит("Количество", КолПроп);
                    ЖрнКвЗапись.УстановитьРеквизит("Результат", Результат);
                    ЖрнКвЗапись.УстановитьРеквизит("Рассчитана", 1);
                    ЖрнКвЗапись.Записать(); 
                КонецЕсли;    
            КонецЕсли;    
            
            Если ЖрнКв.ВидРасч.ВходитВГруппу(ГруппаРасчетов.Сальдо) = 1 Тогда
                Стр = ""; Кол = "";
                Сальдо.НайтиЗначение(ЖрнКв.ВидТарифа, Стр, Кол);
                
                // Позиционируем объект записи
                ЖрнКвЗапись.НайтиЗапись(ЖрнКв.ТекущаяЗапись());
                
                Если Стр = 0 Тогда 
                    ЖрнКвЗапись.УстановитьРеквизит("Результат", 0);
                    ЖрнКвЗапись.УстановитьРеквизит("Рассчитана", 1);
                    ЖрнКвЗапись.Записать(); 
                Иначе    
                    Сальдо.ПолучитьСтрокуПоНомеру(Стр);
                    ЖрнКвЗапись.УстановитьРеквизит("Результат", Сальдо.СальдоК);
                    ЖрнКвЗапись.УстановитьРеквизит("Рассчитана", 1);
                    ЖрнКвЗапись.Записать(); 
                КонецЕсли;
            КонецЕсли;    
        КонецЕсли;    
    КонецЦикла;    
КонецПроцедуры

Использование раздельных объектов для обхода базы данных и непосредственной перезаписи реквизитов гарантирует стабильность работы СУБД, исключает появление взаимных блокировок транзакций и предотвращает порчу системных индексов журнала расчетов.

← На главную