Книга знаний

1С:Предприятие / Объекты конфигурации / Регистры

Волшебная последовательность документов: перепроведение без шума и пыли (статья)

Чтобы сделать процесс проведения документов в многопользовательском режиме работы 1С:Предприятие 7.7 более приятным и комфортным для пользователей, можно применить, как минимум, два способа – вставлять паузы между последовательными проведениями документов в цикле, и использовать «интеллектуальную» последовательность проведения, наподобие последовательности с измерениями в системе «1С:Предприятие» версии 8.0, чтобы минимизировать количество проводимых документов. Автор статьи:
Последняя редакция №1 от 17.10.05
URL: http://kb.mista.ru/article.php?id=53

Паузы между проведениями документов


Первый способ – самый нехитрый, и заключается в том, что для того, чтобы транзакции пользователей не «висели» в бесконечном (или, скорее, конечном) цикле, а могли все-таки выполниться, алгоритм перепроведения документов должен включать паузы. Т.е. обработка должна проводить документы не все время, а с промежутками. Тогда пользователи смогут «вставить» свои собственные транзакции, и продолжить, без значительных помех, свою работу.  

К сожалению, в 1С нет аналога системной функции sleep, которая в системе Windows (и, кстати, изначально - в Unix) делает паузу в миллисекундах, не нагружая при этом процессор (точнее, процессор все равно работает, но при этом выполняются другие задачи в многозадачной операционной среде). Для этого, если вы не ищете легких путей, используйте внешнюю компоненту, которая реализует системный вызов sleep().

Пример внешней компоненты с исходным кодом можно скачать по адресу:
http://x-romix.narod.ru/Sleep.rar

Если у вас лекарственная идиосинкразия к внешним компонентам и COM-объектам, есть и другие способы сделать паузу. Один из самых простых - Предупреждение() с таймаутом в секундах (второй параметр метода Предупреждение):

Процедура Проведение()
 док=СоздатьОбъект(«Документ»);
 док.ВыбратьДокументы(НачДата, КонДата);
 Пока док.ПолучитьДокумент()=1 Цикл
   док.Провести();
   Предупреждение(«Ждем 3 секунды…», 3);
 КонецЦикла;
КонецПроцедуры

Для организации цикла ожидания можно также воспользоваться недокументированной функцией _GetPerformanceCounter() – это счетчик времени в миллисекундах, или документированной – ТекущееВремя() – с точностью до секунды. Но при этом потребуется «крутить» цикл без пауз, который, как известно, потребляет 100% ресурсов одного процессора (что может сказаться на производительности работы пользователей, например, в терминальном режиме).
Выборочное перепроведение
Второй способ, который вы можете применить – заранее исключает из проведения те документы, движения по которым при перепроведении заведомо не будут изменяться. Легко ли выявить такие документы алгоритмически? Давайте это оценим.

Пример №1


Предположим, что пользователь исправил документ недельной давности, в котором изменил ошибочно введенную сумму авансового платежа по договору №121 с контрагентом ООО «Томагавк-сервис» со 100000 рублей на 10000 рублей (у оператора дрогнула рука, и вместо одного нуля сами собой ввелись два).

Что при этом изменится? А изменятся при этом счета-фактуры «на аванс» и пятая графа счета-фактуры, где указан платежно-расчетный документ списанного аванса (неправильное их заполнение может послужить поводом к отказу от налоговых вычетов по НДС).

Налоговые инспектора уже просекли фишку, что эти документы чувствительны к любым правкам «задним числом», и страшное налоговое преступление перед государством здесь с наибольшей вероятностью будет раскрыто.

Документы по другим контрагентам и договорам при этом перепроводить и перепечатывать нет необходимости, что уже само по себе – большое облегчение.

Пример №2


Рассмотрим другой пример – списание товара по средней себестоимости. Предположим, что на склад поступило 100 кг товара «редиска свежая» на сумму 5000 рублей. Средняя цена составит, в нашем примере, 50 рублей за килограмм редиски. Списание по средней себестоимости постепенно сводит к нулю товарный и стоимостной запас редиски. Но что будет, если пользователь (например, обнаружив ошибку) изменит документ прихода (например, поставит сумму 6000 рублей)? А произойдет при этом следующее: товарный запас при полном списании уйдет в 0, а по денежным суммам будет числиться положительное или отрицательное… гм… сальдо. Чтобы этого не случилось, документы списания необходимо перепровести, и тогда нехорошее сальдо тоже уйдет в 0. Перепроводить при этом нужно не все документы, а только те из них, где фигурирует данная редиска на данном складе. Другие документы перепроводить, очевидно, не требуется.

Реализация алгоритма


В системе 1С:Предприятие 8.0 появился механизм Последовательностей с измерениями, который и реализует такую возможность. К сожалению, переход на версию 8.0 затруднен – там «все несовместимо» с 1С версии 7.7. Поэтому, поюзать 8-ку придется только мысленно, реализовав похожий механизм в 7.7 посредством, как вы уже, наверное, догадались, головы, рук и хвоста.

Чтобы сэмулировать последовательность, нам потребуется справочник, где мы будем хранить ссылки на документы, требующие перепроведения. Я назвал его ЭмуляцияПоследовательности, и его реквизиты – это Наименование (куда я пишу позицию документа для правильной его выборки «сверху вниз») и ссылка на документ (поле с типом «документ неопределенного вида»).

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

1)    В процессе проведения документа выявить, по каким движениям (измерениям последовательности) появились различия. Пример кода с достаточно простым алгоритмом этого действия приведен ниже.
2)    Просмотреть более поздние (NB!) движения в разрезе выявленных различий. (при помощи методов УстановитьЗначениеФильтра и ВыбратьДвижения).
3)    Ссылки на выявленные по этим движениям документы запомнить в справочнике ЭмуляцияПоследовательности.
4)    Исключить текущий документ из этого справочника, если он там уже был.

Фоновой обработке, которая восстанавливает последовательность, потребуется периодически «просыпаться» и просматривать справочник ЭмуляцияПоследовательности на предмет наличия в нем документов. И перепроводить их (желательно, с паузами) «сверху вниз» (т.е. в порядке расположения документов на временной оси, для чего я пишу в наименование элемента справочника текстовую строку из ПолучитьПозицию()).

Почему именно справочник?


Хотелось бы сразу же ответить на вопрос, почему я применяю справочник для хранения ссылок на документы, а не выставляю пометки в самом документе. Дело в том, что в многопользовательской среде часть документов может быть заблокирована. А такие вещи лучше отслеживать не внутри транзакции проведения (и откатывать всю транзакцию, если не удалось поставить, предположим, одну из сотни пометок), а обработкой (которая смотрит на справочник, и спокойно может остановиться и подождать минуту-две, пока «заклинивший» документ не освободят). Кроме того, извлечение ссылок на документы из справочника – более быстрая процедура, чем попытка сделать это поиском по документам.

Пример реализации


В качестве примера этого алгоритма, приведу модуль проведения тестового документа ПриходнаяНакладная, который содержит реквизиты Склад, Товар и Сумма. Аналогичный алгоритм  можно применить, с небольшими отличиями, для документов и регистров любого вида. Строки исходного кода, которые, вероятнее всего, придется при этом скорректировать, помечены комментарием с тремя звездочками: «//***». Количество потенциальных корректировок можно уменьшить, работая с регистром через метаданные, но это уменьшило бы понятность и читабельность примера (предлагаю читателю реализовать этот режим работы самостоятельно – ссылки на коммерческие решения опубликую). :-)

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

/////////////////////////////////////////////////////////////////////////
перем г_тзДвижения; //Переменная хранит таблицу значений, в которой мы будем
//записывать старые движения документа, потом вычитать из них новые движения и,
//таким образом, получать между ними разницу.

/////////////////////////////////////////////////////////////////////////
Процедура пЗапомнитьДокументДляПерепроведения(прм_док)
   //Процедура запоминает документ, переданный в качестве параметра прм_док,
   //в справочнике ЭмуляцияПоследовательности.
   
   спр=СоздатьОбъект("Справочник.ЭмуляцияПоследовательности");
   Если спр.НайтиПоРеквизиту("Документ",прм_док,1)=0 Тогда
       спр.Новый();
       спр.Документ = прм_док;
       стрПозиция=прм_док.ПолучитьПозицию(); //32-символьная позиция документа
       спр.Позиция = стрПозиция;
       спр.Записать();
   КонецЕсли;
КонецПроцедуры    // ЗапомнитьДокументДляПерепроведения

/////////////////////////////////////////////////////////////////////////
Процедура пУдалитьТекущийДокументИзПоследовательности()
   //Процедура удаляет текущий документ из справочника
   //ЭмуляцияПоследовательности, если он там есть.
   
   спр=СоздатьОбъект("Справочник.ЭмуляцияПоследовательности");
   Если спр.НайтиПоРеквизиту("Документ",ТекущийДокумент(),1)=1 Тогда
       спр.Удалить(1);
   КонецЕсли;
КонецПроцедуры    // УдалитьТекущийДокументИзПоследовательности

/////////////////////////////////////////////////////////////////////////
Процедура пИскатьДокументыВниз(прм_Товар, прм_Склад) //***
   //Процедура просматривает движения ниже текущего документа, у которых
   //измерения совпадают с параметрами процедуры.
   //Если такие движения есть, то запоминает соответствующие документы в
   //справочнике при помощи пЗапомнитьДокументДляПерепроведения().
       
       рег=СоздатьОбъект("Регистр.ОстаткиТовара"); //***
       рег.УстановитьЗначениеФильтра("Товар",прм_Товар); //***
       рег.УстановитьЗначениеФильтра("Склад",прм_Склад); //***
       рег.ВыбратьДвижения(ТекущийДокумент(),);
       док1=ТекущийДокумент();
       Пока рег.ПолучитьДвижение()=1 Цикл
           док=рег.ТекущийДокумент();
           Если док=док1 Тогда
               Продолжить;
           КонецЕсли;
           Сообщить(" --- Выявлен документ для перепроведения: "+док,"!");
           пЗапомнитьДокументДляПерепроведения(док);
           док1=док;
       КонецЦикла; //по движениям регистра
   
КонецПроцедуры    // ИскатьДокументыВниз



/////////////////////////////////////////////////////////////////////////
Процедура пЗапомнитьСтарыеДвижения()
   //Процедура запоминает движения документа в глобальной таблице значений.
   тз=г_тзДвижения;
   
   рег=СоздатьОбъект("Регистр.ОстаткиТовара");//***
   
   тз.НоваяКолонка("Товар","Справочник.Товары");//***
   тз.НоваяКолонка("Склад","Справочник.Склады");//***
   тз.НоваяКолонка("СуммаРуб","Число", 14,2); //***
   
   рег.ВыбратьДвиженияДокумента(ТекущийДокумент());
   Пока рег.ПолучитьДвижение()=1 Цикл
       тз.НоваяСтрока();
       тз.Товар=рег.Товар; //***
       тз.Склад=рег.Склад; //***
       тз.СуммаРуб=рег.СуммаРуб; //***
   КонецЦикла;    
КонецПроцедуры    // ЗапомнитьДвижения


/////////////////////////////////////////////////////////////////////////
Процедура пВычестьНовыеДвижения()
   //Процедура вычитает из ранее запомненных "старых" движений документа
   //новые движения, чтобы выявить различия.
   
   тз=г_тзДвижения;
   
   рег=СоздатьОбъект("Регистр.ОстаткиТовара"); //***
   
   рег.ВыбратьДвиженияДокумента(ТекущийДокумент());
   Пока рег.ПолучитьДвижение()=1 Цикл
       тз.НоваяСтрока();
       тз.Товар=рег.Товар; //***
       тз.Склад=рег.Склад; //***
       тз.СуммаРуб=-рег.СуммаРуб; //сумма - с обратным знаком//***
   КонецЦикла;    
   
   тз.Свернуть("Склад,Товар","СуммаРуб"); //***
   //теперь ненулевые значения говорят о различиях между тем, что было
   //и тем, что стало
КонецПроцедуры    // ЗапомнитьДвижения


   
/////////////////////////////////////////////////////////////////////////
Процедура пОбработкаРазличий()
   //Процедура просматривает таблицу значений, в которую мы ранее записали старые движения,
   //и вычли из них новые, на предмет ненулевых сумм. Если таковые есть, то вызывает
   //пИскатьДокументыВниз() для соответствующих измерений.
   тз=г_тзДвижения;
   
   тз.ВыбратьСтроки();
   Пока тз.ПолучитьСтроку() = 1 Цикл
       Если тз.СуммаРуб=0 Тогда//***
           Продолжить;
       КонецЕсли;    
       
       Сообщить("Зафиксировано различие по измерениям "+тз.Товар+" "+тз.Склад,"!"); //***
       пИскатьДокументыВниз(тз.Товар, тз.Склад); //***
       
   КонецЦикла;// по строкам тз, где различия
КонецПроцедуры    // пОбработкаРазличий


/////////////////////////////////////////////////////////////////////////
Процедура ОбработкаПроведения() //Предопределенная процедура 1С
   
   г_тзДвижения=СоздатьОбъект("ТаблицаЗначений");
   

       //Запомним старые движения регистра в таблицу значений
   пЗапомнитьСтарыеДвижения();
   
   //Очистим движения.
   ОчиститьДвижения(); //встроенный метод 1С
   
   //Выполним новые движения.
   
ВыбратьСтроки();
   Пока ПолучитьСтроку() = 1 Цикл

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

КонецПроцедуры

///////////////////////////////////////////////////////////////////////
Процедура ОбработкаУдаленияПроведения() //Предопределенная процедура 1С
   Сообщить("-----------");
   Сообщить("Удаление движений документа: "+ТекущийДокумент(),"!");
   спр=СоздатьОбъект("Справочник.ЭмуляцияПоследовательности");
   ВыбратьСтроки();
   Сообщить("Удаление движений по измерениям "+Товар+" "+Склад,"!"); //***
   Пока ПолучитьСтроку() = 1 Цикл
       пИскатьДокументыВниз(Товар,Склад); //***
   КонецЦикла;
   пУдалитьТекущийДокументИзПоследовательности();
КонецПроцедуры

Альтернатива


Заметим, что этот документ несколько сложнее «простого человеческого» документа с модулем проведения наподобие

Процедура ОбработкаПроведения()
 ВыбратьСтроки();
 Пока ПолучитьСтроку() = 1 Цикл
   Регистр.ОстаткиТовара.Склад = Склад;
   Регистр.ОстаткиТовара.Товар = Товар;
   Регистр.ОстаткиТовара.Количество = Количество;
   Регистр.ОстаткиТовара.СуммаРуб = СуммаРуб;
   Регистр.ОстаткиТовара.ДвижениеПриходВыполнить();
 КонецЦикла;
КонецПроцедуры

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



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

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