По ночам должны работать роботы…
По ночам должны работать роботы…
Наверное каждый из вас сталкивался с тем, что нужно что-то сделать в базе в монопольном режиме. И многие сталкивались с тем, что, чтобы сделать это, нужно выгонять всех пользователей, у которых много работы, или сидеть и делать это по ночам.
Восстановление последовательности, удаление помеченных на удаление объектов да и просто какие-то операции, требующие того, чтобы вся работа была закончена.
Фирма 1С не позаботилась о бедных пользователях, так давайте предоставим эту возможность… роботу. Точнее автоматической обработке.
Итак, данная статья написана на примере конфигурации ТиС. В нее включены следующие разделы:
1. Функция восстановления последовательностей.
2. Функция удаления помеченных элементов справочников и документов.
3. Организация автоматического запуска нашей обработки. Функция восстановления последовательностей.
Итак, последовательность. В 1С последовательность – это цепочка документов проведенных по порядку, в зависимости от даты и времени. Граница последовательности – последний документ, проведенный без нарушения последовательности. Следовательно, для восстановления последовательности мы должны перепровести все документы, входящие в последовательность, по порядку, начиная с последнего в последовательности.
Кстати, в ТиС есть стандартная Функция глВосстановлениеПоследовательности(), можете использовать и ее, но своя рубашка ближе к телу, а доверия фирма 1С давно уже не оправдывает :)
Моя функция приведена ниже с комментариями, я думаю их будет достаточно для понимания.
Функция ВосстановлениеПоследовательностей() Экспорт
Перем СтараяПозицияТА;
СтараяПозицияТА = ПолучитьПозициюТА();
БылиОшибкиПроведения = 0;
Документ=СоздатьОбъект("Документ");
Объект=СоздатьОбъект("Документ");
//получим наименьшую дату границ последовательностей
ВыбДата = Последовательность.ПолучитьАтрибут(Метаданные.Последовательность(1)).ПолучитьДату();
Для nn=1 По Метаданные.Последовательность() Цикл
Дат = Последовательность.ПолучитьАтрибут(Метаданные.Последовательность(nn)).ПолучитьДату();
ВыбДата = Мин(Дат,ВыбДата);
КонецЦикла;
//выберем документы
Документ.ВыбратьДокументы(ВыбДата,);
Пока (Документ.ПолучитьДокумент()>0) и (БылиОшибкиПроведения = 0) Цикл
//пропускаем непроведенные документы
Если Документ.Проведен()=0 Тогда
Продолжить;
КонецЕсли;
ТекДок = Документ.ТекущийДокумент();
//проверим на принадлежность к последовательностям
Принадлежит=0;
Для nn=1 По Метаданные.Последовательность() Цикл
Принадлежит=Макс(Принадлежит,ТекДок.ПринадлежитПоследовательности(Метаданные.Последовательность(nn)));
КонецЦикла;
// если оперативный документ находится за ТА, то ТА надо передвинуть в любом случае
Если (ТекДок.СравнитьТА()>0) или (Принадлежит = 1) Тогда
БылаПозиция = ПолучитьПозициюТА();
УстановитьТАНа(ТекДок);
Если БылаПозиция = ПолучитьПозициюТА() Тогда
// не удалось поменять (например, были открытые документы)
БылиОшибкиПроведения = 1;
Продолжить;
КонецЕсли;
КонецЕсли;
Если Принадлежит=1 Тогда
Объект.НайтиДокумент(ТекДок);
//пытаемся перепровести
Попытка
Если Объект.Провести() = 0 Тогда
БылиОшибкиПроведения = 1;
КонецЕсли;
Исключение
глСообщениеПроведения("Не удалось провести документ "+Строка(Объект)+"
|Ошибка: "+ОписаниеОшибки(), Объект.ТекущийДокумент(),,,1);
БылиОшибкиПроведения = 1;
КонецПопытки;
КонецЕсли;
//сохраним последний документ, для позиционирования потом на нем последовательностей
ПослДок = Документ.ТекущийДокумент();
КонецЦикла;
//позиционируем последовательности на последнем перепроведенном документе
Для nn=1 По Метаданные.Последовательность() Цикл
Последовательность.ПолучитьАтрибут(Метаданные.Последовательность(nn)).Установить(ПослДок);
КонецЦикла;
//устанавливаем старую позицию ТА
Если СтараяПозицияТА <> ПолучитьПозициюТА() Тогда
УстановитьТАПо(СтараяПозицияТА);
КонецЕсли;
//возвращаем 1 если ошибок не было, а если были - возвращаем документ, на котором была ошибка
Возврат ?(БылиОшибкиПроведения = 0,1,Объект.ТекущийДокумент());
КонецФункции // ВосстановлениеПоследовательностей()
Функция удаления помеченных элементов справочников и документов.
Далее на повестке дня у нас – удаление помеченных элементов. Есть такие, кто не будет читать эту статью, а просто переберет элементы и напишет Элемент.Удалить(1). Но наверное есть и те, кто так уже сделал и кому интересно, а как же все сделать правильно и избежать тех проблем, которые у них возникли :)
А правильный способ – это удалять только те элементы, на которые после удаления не останется ссылок в базе. То есть если ссылок совсем нет, или ссылающиеся элементы будут также удалены.
Для этого мы воспользуемся процедурой НайтиСсылки и получим таблицу значений, где будут Объекты, Ссылки и комментарии. Перед этим получим список помеченных элементов. Но полученую таблицу еще нужно обработать. То есть выделить те элементы, которые все-таки можно удалять и те, которые нельзя.
Ниже функция, которая получилась у меня.
Функция ИнфаОбЭлементе(Эл)
Стр = "";
Если ТипЗначенияСтр(Эл) = "Справочник" Тогда
Стр = "Справочник." + Эл.Вид() + " " + Эл.Код + " " + Эл.Наименование;
ИначеЕсли ТипЗначенияСтр(Эл) = "Документ" Тогда
Стр = "Документ Номер: " + Эл.НомерДок + " " + глНазваниеДокументаВжурнале(Эл);
КонецЕсли;
Возврат(Стр);
КонецФункции //ИнфаОбЭлементе()
Функция УдалениеПомеченых()
Спис = СоздатьОбъект("СписокЗначений");
ЧистСпис = СоздатьОбъект("СписокЗначений");
ТабСсыл = СоздатьОбъект("ТаблицаЗначений");
ОтчТекст = СоздатьОбъект("Текст");
//перебираем все документы
Док = СоздатьОбъект("Документ");
Док.ВыбратьДокументы();
Пока Док.ПолучитьДокумент() = 1 Цикл
Если Док.ПометкаУдаления() = 1 Тогда
Спис.ДобавитьЗначение(Док.ТекущийДокумент());
КонецЕсли;
КонецЦикла;
//перебираем все справочники
Запрос = СоздатьОбъект("Запрос");
Для nn=1 По Метаданные.Справочник() Цикл
ТекстЗапроса = "
|Обрабатывать ПомеченныеНаУдаление;
|Поз = Справочник."+Метаданные.Справочник(nn).Идентификатор+".ТекущийЭлемент;
|Группировка Поз;";
Запрос.Выполнить(ТекстЗапроса);
Пока Запрос.Группировка(1) = 1 Цикл
//проверим на пометку, чтобы не вылезли непомеченные группы
Если Запрос.Поз.ПометкаУдаления() = 1 Тогда
Спис.ДобавитьЗначение(Запрос.Поз);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Если Спис.РазмерСписка() > 0 Тогда
//находим ссылки
НайтиСсылки(Спис,ТабСсыл);
//создаем чистый список элементов, которые можно удалить
//и оставим в полученом те, которые удалить нельзя
Пока 1=1 Цикл
ФормЗак = 1;
Тзн = 0;
Для nn = 1 По Спис.РазмерСписка() Цикл
Тзн = Тзн + 1;
Если Тзн > Спис.РазмерСписка() Тогда
Прервать;
КонецЕсли;
//если на элемент нет ссылок
Если ТабСсыл.НайтиЗначение(Спис.ПолучитьЗначение(Тзн),,"Объект") = 0 Тогда
//если на элемент нет ссылок
//чистим таблицу
Пока 1=1 Цикл
Нстр = 0;
Если ТабСсыл.НайтиЗначение(Спис.ПолучитьЗначение(Тзн),Нстр,"Ссылка") = 1 Тогда
ТабСсыл.УдалитьСтроку(Нстр);
Иначе
Прервать;
КонецЕсли;
КонецЦикла;
//переносим значение в чистый список
ЧистСпис.ДобавитьЗначение(Спис.ПолучитьЗначение(Тзн));
//удалим ссылку и уменьшим значение текущей позиции
Спис.УдалитьЗначение(Тзн);
Тзн = Тзн - 1;
//поставим пометку продолжения обработки
ФормЗак = 0;
КонецЕсли;
КонецЦикла;
//если ничего не осталось, прерываем цикл
Если ФормЗак = 1 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
ОтчТекст = СоздатьОбъект("Текст");
Если ЧистСпис.РазмерСписка() > 0 Тогда
//удалим те, которые можно
Для nn = 1 По ЧистСпис.РазмерСписка() Цикл
Эл = ЧистСпис.ПолучитьЗначение(nn);
МЭл = СоздатьОбъект(""+ТипЗначенияСтр(Эл)+"."+Эл.Вид());
Если ТипЗначенияСтр(Эл) = "Справочник" Тогда
МЭл.НайтиЭлемент(Эл);
ИнфЭл = ИнфаОбЭлементе(МЭл.ТекущийЭлемент());
ИначеЕсли ТипЗначенияСтр(Эл) = "Документ" Тогда
МЭл.НайтиДокумент(Эл);
ИнфЭл = ИнфаОбЭлементе(МЭл.ТекущийДокумент());
КонецЕсли;
Попытка
МЭл.Удалить(1);
ОтчТекст.ДобавитьСтроку(" Удалили " + ИнфЭл);
Исключение
ОтчТекст.ДобавитьСтроку(" Не удалось удалить " + ИнфЭл);
КонецПопытки;
КонецЦикла;
Иначе
ОтчТекст.ДобавитьСтроку(" Удалять нечего!");
КонецЕсли;
//переберем те, которые удалять нельзя
Если Спис.РазмерСписка() > 0 Тогда
ОтчТекст.ДобавитьСтроку("");
ОтчТекст.ДобавитьСтроку(" На эти элементы есть ссылки:");
Для nn = 1 По Спис.РазмерСписка() Цикл
ОтчТекст.ДобавитьСтроку(" "+ИнфаОбЭлементе(Спис.ПолучитьЗначение(nn)));
КонецЦикла;
КонецЕсли;
КонецЕсли;
Возврат(ОтчТекст);
КонецФункции //УдалениеПомеченых()
Функция вернет текстовый отчет об удаленных и неудаляемых элементах. Кстати, функция по скорости нисколько не уступает стандартной, так что можно на ее основе сделать копию стандартной обработки удаления.
Организация автоматического запуска нашей обработки.
Как же теперь добиться автоматического выполнения этих функция?
Пошагово расспишу, как это сделал я, а вы уж сами решайте, как делать вам.
1. Завел нового пользователя Service. Имя лучше на латинице задавать, ниже опишу почему. Пароль лучше не самый простой, любопытных пользователей много и им будет очень интересно, зачем это тут.
2. В глобальном модуле в процедуре ПриНачалеРаботыСистемы открытие нового периода сделал с условием «Если ИмяПользователя() <> "Service" Тогда», ибо решил доверить его нашему боту.
3. В конце процедуры ПриНачалеРаботыСистемы прописал:
Если ИмяПользователя() = "Service" Тогда
Если Константа. АвтоматическиеРаботы = 1 Тогда
ОткрытьФорму("Отчет",,""+КаталогИБ()+"Автомат.ert");
КонецЕсли;
ЗавершитьРаботуСистемы(0);
КонецЕсли;
4. Добавил константу АвтоматическиеРаботы со значением 1 или 0, и дал права на ее редактирование нужным людям (себе и еще раз себе :] )
5. С установленной в 0 константой зашел в базу под новой учеткой и позакрывал всякую ерунду (Советы дня и пр.), а также проверил, чтобы не выскакивала какая-нибудь незапланированная лабуда.
6. Также в каталог базы кинул bat файл, с таким текстом:
del *.cdx
"C:\Program Files\1Cv77\BIN\1CV7s.exe" ENTERPRISE /M /DD:\1C\DB /NService /P159
Замечу, сначала удаляются все файлы cdx, чтобы при монопольном запуске не выдавалось противное окошко с вопросом, затем запускается 1С в нужной базе с нужным юзером (если бы имя юзера было на кириллице, вас бы поджидал большой облом :-} ) .
7. Проверил работоспособность вручную, затем создал новое задание в планировщике, поставил на 1 ночи, ибо работают у нас и до полуночи некоторые. Текст обработки Автомат.ert (соответственно к нему еще надо приписать приведенные выше функции)
//*******************************************
Функция ТекВремя(Сп=0) Экспорт
Часов = 0; Минут = 0; Секунд = 0;
ТекущееВремя(Часов,Минут,Секунд);
Возврат(?(Сп=0,"",""+ТекущаяДата()+" ")+Формат(Часов,"Ч(0)2")+":"+Формат(Минут,"Ч(0)2")+":"+Формат(Секунд,"Ч(0)2"));
КонецФункции
//*******************************************
Процедура ПриОткрытии()
Если Константа.АвтоматическиеРаботы = 1 Тогда
Текст = СоздатьОбъект("Текст");
path = ""+КаталогИБ()+"avtomat.txt";
Если ФС.СуществуетФайл(path) = 1 Тогда
Текст.Открыть(path);
КонецЕсли;
Текст.ДобавитьСтроку("");
Текст.ДобавитьСтроку("");
Текст.ДобавитьСтроку("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
Текст.ДобавитьСтроку("Начало - "+ТекВремя(1));
Текст.Записать(path);
Текст.ДобавитьСтроку("");
Если МонопольныйРежим()=0 Тогда
Текст.ДобавитьСтроку("Был немонопольный режим - ТА не перенесли, последовательности не востановили!");
Иначе
Текст.ДобавитьСтроку("Начинаем переносить ТА "+ТекВремя());
Текст.Записать(path);
//переносим ТА
Если РабочаяДата() > ПолучитьДатуТА() Тогда
УстановитьТАНа(РабочаяДата());
Текст.ДобавитьСтроку("Перенесли ТА "+ТекВремя());
Иначе
Текст.ДобавитьСтроку("Переносить ТА не потребовалось "+ТекВремя());
КонецЕсли;
//восстанавливаем последовательности
Текст.ДобавитьСтроку("");
Текст.ДобавитьСтроку("Начали восстановление последовательностей "+ТекВремя());
Текст.Записать(path);
Рез = ВосстановлениеПоследовательностей();
Если Рез = 1 Тогда
Текст.ДобавитьСтроку("Закончили успешно "+ТекВремя());
Иначе
Текст.ДобавитьСтроку("Потерпели неудачу на документе "+глНазваниеДокументаВжурнале(Рез)+" "+ТекВремя());
КонецЕсли;
Текст.Записать(path);
//удаляем помеченые
ВремТекст = УдалениеПомеченых();
Если ВремТекст.КоличествоСтрок() > 0 Тогда
Текст.ДобавитьСтроку("");
Текст.ДобавитьСтроку("Провели удаление помеченных элементов "+ТекВремя());
Для nn = 1 По ВремТекст.КоличествоСтрок() Цикл
Текст.ДобавитьСтроку(ВремТекст.ПолучитьСтроку(nn));
КонецЦикла;
КонецЕсли;
КонецЕсли;
Текст.Записать(path);
КонецЕсли;
СтатусВозврата(0);
Возврат;
КонецПроцедуры
|