Книга знаний

1С:Предприятие

Формирование книги учета доходов и расходов (КУДИР) в 1С v7.7

Реализация в 1С v7.7 (комплексная конфигурация) книги учета доходов и расходов (КУДИР). Способы оптимизации.Автор статьи: Женёк | Редакторы:
Последняя редакция №3 от 14.08.07 | История
URL: http://kb.mista.ru/article.php?id=577

Ключевые слова: Книга учета доходов и расходов, КУДИР


   Некоторая часть пользователей 1С работает на упрощенной системе налогообложения (УСН) и, в принципе, имея небольшой товарооборот, обходится обособленной конфигурацией для версии 7.7 – 1С:УСН. Но встречаются такие случаи, когда фирма имеет большой товарооборот и желает видеть всю отчетность, которая необходима для ведения торговой деятельности. В этой же базе необходимо видеть и проводки (что очень удобно для бухгалтера), здесь же и начислять зарплату. Для этой цели подходит 1С:Комплексная конфигурация. Но, конфигурация имеет огромный недостаток, который и будет рассмотрен в этой статье – это абсолютно неоптимизированный процесс работы с КУДИР.
   Под «неоптимизированным процессом работы» понимается не то, что разработчики не правильно разработали алгоритм формирования КУДИР, а то, что реализовали его очень медленным и не эффективным.
   Результаты использования такой КУДИР: проведение документа «Формирование КУДИР» ночами, формирование отчета и его распечатка – сутками.

   Оптимизация процесса формирования КУДИР достаточно трудно реализуемая задача, так как сама по себе КУДИР, в соответствии с нашим законодательством, содержит в себе много тонкостей. И процесс оптимизации с первичной отладкой может занять 1-2 недели (с учетом того, что придется не только оптимизировать, но и заниматься другими направлениями), да еще к этому тестирование в работе.
   Оптимизация состоит из 3-х частей, причем каждая из них решает определенный круг проблем, возникающих при работе с КУДИР:
   1) изменение модуля отчета «КнигаУчетаДоходовИРасходов» - уменьшает время формирования отчета КУДИР;
   2) изменение структуры метаданных регистра «УчетОплатРасходовУСН» - позволяет решить проблему перепроведения базы, а если точнее – расчета итогов на начало периода (например, на 1-ое число месяца) и при большом количестве записей в регистре – несколько ускорить проведение документа;
   3) изменение модуля проведения документа «КнигаУчетаДоходовИРасходов» - значительно уменьшает время проведения документа.


1. Изменение модуля отчета «КнигаУчетаДоходовИРасходов».

Источник долгого формирования и долгой распечатки отчета кроется в обработке большого объема информации из регистра «УчетРасходовУСН» (не путайте с «УчетОплатРасходовУСН»), ну и как следствие, нехватки памяти. Кроме этого необходимо модифицировать часть кода, дабы функции Док.Выбран() и ПустоеЗначение(Док) – выполняются с разной скоростью, а вот выполнение их 1 млн.раз в цикле – дает значительное приемущество в приоритете выбора одной из них.
   1) Так как расшифровка данных по КУДИР достаточно большая и не всегда оправдывает свою необходимость, предлагается ее просто убрать. Для этого просто комментируем строки, которые  работают с объектами «Расшифровка» и «ТаблРасшифровки». Таким образом, размер mxl-файла уменьшается примерно в 100-1000 раз (напримере, в мое случае с 1Гб до 1Мб).
   2) Для модификации модуля необходимо провести замер производительности через отладчик – выявить узлы кода, которые выполняются достаточно часто и достаточно долго:
   а) Док.Выбран() заменить на ПустоеЗначение(Док) – разница в скорости 5-6 раз;
   б) условие, которое встречается в цикле
Если ДокОплаты.ГруппаРасхода = Перечисление.ГруппыРасходовПриУСН.Товары Тогда

выполняется дольше, чем если использовать следующую конструкцию:
до цикла:
ГРТовары = Перечисление.ГруппыРасходовПриУСН.Товары;

внутри цикла:
Если (ДокОплаты.ГруппаРасхода = ГРТовары) Тогда

   в) необходимо учесть, что сложные условия выполняются дольше, чем они же, только разбитые на части. Связано это с тем, что для выполнения условия необходимо рассчитать все логические значения, заданные в условии:
Например, код
Если ((А = Б) или (В = Г)) Тогда
    Счетчик = Счетчик + 1;
КонецЕсли;

будет всегда выполнять проверку «А=Б» и «В=Г». Такую конструкцию можно заменить на следующую:
Если А=Б Тогда
    Счетчик = Счетчик + 1;
ИначеЕсли В=Г Тогда
    Счетчик = Счетчик + 1;
КонецЕсли;

   При этом лучше определить вероятность того, какое условие срабатывает чаще. Если В=Г срабатывает в 90% случае, а А=Б – в 40%, тогда лучше проверять сначала «В=Г», а «А=Б» проверять в оставшихся 10% случаев.
   г) поиск внутри таблицы значений одного и того же значения в 1С версии 7.7 не реализовано. Чтобы сделать это – необходимо перебирать всю таблицу значений. Например, есть ТЗ, в которой один из столбцов имеет числовые значения в следующем порядке {1, 2, 3, 2, 1, 3}. Необходимо выбрать из этой таблицы определенные значения. Если, например, нужно найти цифру «2» - перебираем всю ТЗ и ищем в ней строки с цифрой «2». Чтобы ускорить алгоритм, необходимо ТЗ отсортировать по этому полю, затем выполнить поиск цифры «2» и далее перебирать этот столбец до тех пор, пока значение не изменится на другое. Пример кода (ТЗ уже создана и имеет колонку «Цифра»):
ТЗ.Сортировать(«Цифра»);
….
ЗначениеДляПоиска = 2;
НСтр = 0;
Если ТЗ.НайтиЗначение(ЗначениеДляПоиска, НСтр, «Цифра») = 1 Тогда
    Пока НСтр <= ТЗ.КоличествоСтрок() Цикл
        Если ТЗ.ПолучитьЗначение(НСтр, "Цифра") <> ЗначениеДляПоиска Тогда
            Прервать;
        КонецЕсли;
        //…. Обработчик строки
        НСтр = НСтр + 1;
    КонецЦикла;
КонецЕсли;

Если необходимо получить итог по другим полям, где в записях «Цифра» = «2», тогда можно использовать следующий механизм:
ТЗ.Сортировать(«Цифра»);
….
ЗначениеДляПоиска = 2;
НСтр = 0;
Если ТЗ.НайтиЗначение(ЗначениеДляПоиска, НСтр, «Цифра») = 1 Тогда
    НСтр2 = НСтр;
    Пока НСтр2 <= ТЗ.КоличествоСтрок() Цикл
        НСтр2 = НСтр2 + 1;
        Если ТЗ.ПолучитьЗначение(НСтр2, "Цифра") <> ЗначениеДляПоиска Тогда
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;
ТЗВременная = СоздатьОбъект(«ТаблицаЗначений»);
ТЗ.Выгрузить(ТЗВременная, НСтр, НСтр21);


   В результате в ТЗВременная будут выгружены все строки, которые в поле «Цифра» содержат значение «2».
   В КУДИР очень важным моментом является порядок сортировки ТаблицЗначений, полученных из регистра. Поэтому, если использовать метод, указанный выше, необходимо после получения итогов добавить поле «НомерСтр», заполнить его номером строки таблицы значений и заменить строку
ТЗ.Сортировать(«Цифра»);

на
ТЗ.Сортировать(«Цифра, НомерСтр»);

   Данный способ на маленьких таблицах будет работать медленнее, но на больших в несколько тысяч строк, да еще и если выборка из них производится в цикле – будет давать прирост в несколько раз.
   
   Используя вот эти четыре направления оптимизации – получаем ускорение формирования отчета КУДИР в несколько десятков раз (в моем случае примерно в 25 раз).

2. Изменение структуры метаданных регистра «УчетОплатРасходовУСН».

   Практическим путем удалось установить, что из двух регистров «УчетРасходовУСН» и «УчетОплатРасходовУСН», используемых для КУДИР, наиболее медленным является «УчетОплатРасходовУСН». Структура их схожа, но в последнем присутствует два дополнительных поля: «ДокументОплатыПоставщику» и «ДокументОплатыПокупателя». Оба имеют тип «Документ». Именно наличие этих двух полей приводит 1С при перепроведении и пересчете остатков практически к зависанию – система «борется» с индексами и итогами.
   С учетом того, что данный регистр используется только как временный для расчета значений КУДИР, то в связи с изменением структуры, отчет «КнигаУчетаДоходовИРасходов» модифицировать не придется. Необходимо только изменить структуру регистра и модуль проведения документа «КнигаУчетаДоходовИРасходов».
   Опять же практическим путем было выяснено, что скорость работы регистра увеличится, если сменить тип этих полей на справочник определенного типа. В связи с этим меняем существующую структуру конфигурации:
   1) Добавляем новый справочник: «КУДИР_ДокОплаты», у которого отключены поля «Код» и «Наименование». В справочнике будет только одно поле: «ДокОплаты» типа «Документ». Признак «Сортировка» должен быть у поля обязательно включен!!! Сюда будут записываться все документы, которые должны были бы попасть в поля «ДокументОплатыПоставщику» и «ДокументОплатыПокупателя»;
   2) В регистре «УчетОплатРасходовУСН» тип полей «ДокументОплатыПоставщику» и «ДокументОплатыПокупателя» заменяем на «Справочник.ДокОплаты».
Для корректной работы с таким регистром в модуле проведения необходимо произвести некоторые изменения. Во-первых, перед записью в регистр необходимо произвести подмену документа на элемент справочника (при этом, чтобы не множить элементы, необходимо сначала осуществить поиск элемента с таким значением документа, а если уж не нашли, то создать такой элемент). Во-вторых, при получении итогов (или при обработке итогов) – выполнить обратную подмену: элемент справочника заменить на документ.
   В результате такой модификации скорость проведения документа «КнигаУчетаДоходовИРасходов» увеличивается (при больших объемах регистра), и при этом получаем базу, которая будет не будет зависать при перепроведении.

3. Изменение модуля проведения документа «КнигаУчетаДоходовИРасходов».

Все изменения здесь практически такие же, как в п.1. Например, следующую процедуру можно оптимизировать:
Процедура УстановитьЗначениеАтрибута(РегУСН, ИмяАтрибута, Значение)
    Если (Метаданные.Регистр(РегУСН.Вид()).Измерение(ИмяАтрибута).Выбран() = 1) ИЛИ 
         (Метаданные.Регистр(РегУСН.Вид()).Ресурс   (ИмяАтрибута).Выбран() = 1) ИЛИ 
         (Метаданные.Регистр(РегУСН.Вид()).Реквизит (ИмяАтрибута).Выбран() = 1) Тогда
         
         Если ИмяАтрибута = "ЭлементРасхода" Тогда
            // Реквизит неопределенного типа
             РегУСН.НазначитьТип(ИмяАтрибута, ТипЗначенияСтр(Значение) + "." + Значение.Вид());
         КонецЕсли;
         РегУСН.УстановитьАтрибут(ИмяАтрибута, Значение);
    КонецЕсли;
КонецПроцедуры // УстановитьЗначениеАтрибута()

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

Здесь СпРегУСН и СпРегОплУСН – это списки значений, полученные следующим образом:
Функция ВернутьАтрибутыРегистра(ВидРег);
    СЗ = СоздатьОбъект("СписокЗначений");
    Для сч = 1 По Метаданные.Регистр(ВидРег).Измерение() Цикл
        СЗ.ДобавитьЗначение(Метаданные.Регистр(ВидРег).Измерение(сч).Идентификатор);
    КонецЦикла;
    Для сч = 1 По Метаданные.Регистр(ВидРег).Ресурс() Цикл
        СЗ.ДобавитьЗначение(Метаданные.Регистр(ВидРег).Ресурс(сч).Идентификатор);
    КонецЦикла;
    Для сч = 1 По Метаданные.Регистр(ВидРег).Реквизит() Цикл
        СЗ.ДобавитьЗначение(Метаданные.Регистр(ВидРег).Реквизит(сч).Идентификатор);
    КонецЦикла;
    Возврат СЗ;
КонецФункции;

СпРегУСН = ВернутьАтрибутыРегистра("УчетРасходовУСН");
СпРегОплУСН = ВернутьАтрибутыРегистра("УчетОплатРасходовУСН");

   Так как значений в регистр записывается просто огромное количество, модификация процедуры «УстановитьЗначениеАтрибута» оказывает уже некоторое влияние на ускорение проведения документа.

   Но сама причина медленного проведения КУДИР заключается в том, что при проведении обращение к временным итогам регистров осуществляется постоянно, да еще и в циклах!!! Поэтому нужно как можно оптимизировать эти части модуля.

   В модуле проведения документа используется процедура «РассчитатьРегистрыПо», используемая для временного расчета регистров по текущий документ включительно. У нее есть очень интересная особенность: если документ делает движения по рассчитанным регистрам, то итоги, во временном расчете динамически изменяются. На примере это выглядит так. Есть регистр с измерением «Номенклатура» и ресурсом «Сумма». Допустим, по номенклатуре «А» на момент проведения документа была сумма «10.00». При проведении документа в регистр записывается приход по «А» в сумме «15.00». При этом, еще не окончив проведение документа, но обратившись к временному итогу по «А» будет получено значение «25.00».
   Такой способ проведения позволяет использовать уже записанные в регистр при проведении документа записи для расчета данных.
   Именно это и составляет основную трудность при оптимизации модуля проведения.
   Основные задержки при проведении выпадают на строки:
        ВремКУДИР.ВыгрузитьИтоги(ТаблИтогов, 1, 1);
        ОплатаКУДИР.ВыгрузитьИтоги(ТабОплат, 1, 1);

   С помощью этих строк выгружаются итоги по определенным документам, группам расходов, элементам (устанавливаются предварительно фильтром).
   Для оптимизации этих строк необходимо копию итогов регистров хранить в таблицах значений. Обеспечивается это следующим образом: перед началом проведения документа итоги регистров выгружаются в таблицы значений. При добавлении значений в регистр, необходимо параллельно осуществлять добавление данных в эти таблицы значений. А при запросе данных – выгружать из этих таблиц те строки, которые задаются фильтром. При этом необходимо пользоваться п.1(г) при работе с ТЗ.

PS. Пожалуй это основные моменты. Если будут какие-то неясности - статью допишу. А так стучитесь в аську, мыльтесь на мыло!

Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2025 | Mista.ru

Яндекс.Метрика