Волшебная последовательность документов: перепроведение без шума и пыли (статья)Чтобы сделать процесс проведения документов в многопользовательском режиме работы 1С:Предприятие 7.7 более приятным и комфортным для пользователей, можно применить, как минимум, два способа – вставлять паузы между последовательными проведениями документов в цикле, и использовать «интеллектуальную» последовательность проведения, наподобие последовательности с измерениями в системе «1С:Предприятие» версии 8.0, чтобы минимизировать количество проводимых документов.
| | Автор статьи: romix | Редакторы: Волшебник Последняя редакция №6 от 16.02.06 | История URL: http://kb.mista.ru/article.php?id=53 | |
Ключевые слова: восстановление, последовательность, перепроведение, документ, пакетное, групповое
Замечание (16.02.2006, romix)
Более компактная и быстродействующая реализация механизма "интеллектуальных последовательностей" приведена по этой ссылке:
Книга знаний: Асинхронные события 1С: полезные алгоритмы
Таким образом, вы можете не читать, что написано здесь ниже. Главка "Паузы между проведениями документов" вынесена в отдельную статью.
Книга знаний: Перепроведение документов в 1С:Предприятие без блокировки других пользователей
Выборочное перепроведение
Второй способ, который вы можете применить – заранее исключает из проведения те документы, движения по которым при перепроведении заведомо не будут изменяться. Легко ли выявить такие документы алгоритмически? Давайте это оценим.
Пример №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 – нет.
Ссылка на пример конфигурации из этой статьи для скачивания:
http://x-romix.narod.ru/consecution.rar (17К), качать *левой* кнопкой мыши.
|