понедельник, 17 августа 2009 г.

1С: Журнал регистраций

По долгу слу...работы возникла необходимость обработать журнал регистраций: нужно получить время входа и время выхода пользователя из системы, штатными средствами выполнить это не удается. Вот решил поделиться способом работы с журналом регистраций, т.к. в гугле мало что нарыл по этому поводу. Итак структура mlg строки в файле mlg (КаталогИБ()+"\SYSLOG\1cv7.mlg")Date;Time;UserName;TypeOpen;TypeOperation;Operation;Category;PC-Name;ObjId;Comment
Все строки упорядочены по паре (дата, время) от младшего к старшему (оно и логично это ведь лог файл по своей сути). Теперь по каждому блоку.


Date - Дата в формате: YYYYMMDD
Time - время в формате: HH:MM:SS
UserName - имя пользователя как он определен в конфигураторе
TypeOpen - тип открытия:
  • E - Предприятие
  • C - Конфигуратор
  • M - Монитор
  • D - Отладчик
TypeOperation - Типы операций
  • Accs – счета.
      Operations:
    • AccNew - Записан новый счет
    • AccDel - Счет удален
    • AccMarkDel - Счет помечен на удаление
    • AccUnmarkDel - со счета снята пометка удаления
  • Archive – сохранение/восстановление.
      Operations:
    • ArchiveStart - Начало архивирования
    • ArciveEnd - Конец архивирования
  • CJ – журнал расчетов.
      Operations:
    • CJEditRec - Отредактирован результат расчета в журнале расчетов
  • Const – константы.
      Operations:
    • ConstWrite - Записано значение константы.
    • ConstDel - Удалено значение константы.
  • CorrProv – корректные проводки.
      Operations:
    • CorrProvNew - Записана новая корректная проводка
    • CorrProvEdit - Изменение корректной проводки
    • CorrProvDel - Удалена корректная проводка
  • Distr – распределенная ИБ.
      Operations:
    • DistBatchErr - Ошибка автообмена в пакетном режиме
    • DistDnldBeg - Начата выгрузка изменений данных
    • DistDnldSuc - Выгрузка изменений данных успешно завершена
    • DistDnlFail - Выгрузка изменений данных не выполнена
    • DistDnlErr - Ошибка выгрузки изменений данных
    • DistUplBeg - Начата загрузка изменений данных
    • DistUplSuc - Загрузка изменений данных успешно завершена
    • DistUplFail - Загрузка изменений данных не выполнена
    • DistUplErr - Ошибка загрузки изменений данных
    • DistUplStatus - Загрузка изменений данных
    • DistDnldPrimBeg - Первичная выгрузка периферийной ИБ
    • DistDnldPrimSuc - Первичная выгрузка периферийной ИБ успешно завершена
    • DistInit - Распределенная ИБ инициализирована
    • DistPIBCreat - Создана периферийная ИБ
    • DistAEParam - Изменены параметры автообмена
  • Docs – документы.
      Operations:
    • DocNew - Документ создан
    • DocOpen - Документ открыт
    • DocWrite - Документ записан
    • DocWriteNew - Записан новый документ
    • DocNotWrite - Документ не записан
    • DocPassed - Документ проведен
    • DocBackPassed - Документ проведен задним числом
    • DocNotPassed - Документ не проведен
    • DocMakeNotPassed - Документ сделан не проведенным
    • DocWriteAndPassed - Документ записан и проведен
    • DocWriteAndRepassed - Документ записан и проведен задним числом
    • DocTimeChanged - Изменено время документа
    • DocOperOn - Проводки включены
    • DocOperOff - Проводки выключены
    • DocMarkDel - Документ помечен на удаление
    • DocUnmarkDel - Пометка на удаление документа снята
    • DocDel - Документ удален
  • Grbgs – общие события.
      Operations:
    • GrbgNewPerBuhTot - Бухгалтерские итоги рассчитаны
    • GrbgRclcAllBuhTot - Полный пересчет бухгалтерских итогов
    • GrbgSyntaxErr - Синтаксическая ошибка
    • GrbgRuntimeErr - Ошибка времени выполнения
  • Refs – справочники.
      Operations:
    • RefNew - Записан новый элемент справочника
    • RefWrite - Элемент справочника записан
    • RefUnmarkDel - С элемента справочника снята пометка на удаление
    • RefDel - Элемент справочника удален
    • RefMarkDel - Элемент справочника помечен на удаление
    • RefGrpMove - Элемент справочника перенесен в другую группу
    • RefAttrWrite - Значение реквизита справочника записано
    • RefAttrDel - Значение реквизита справочника удалено
  • Restruct – конфигурация.
      Operations:
    • RestructSaveMD - Запись измененной конфигурации
    • RestructStart - Начало реструктуризации
    • RestructCopy - Начато копирование результатов реструктуризации
    • RestructAcptEnd - Реструктуризация завершена
    • RestructStatus - Статус реструктуризации
    • RestructAnalys - Анализ информации
    • RestructStartWarn - Предупреждение
    • RestructErr - Ошибка при реструктуризации
    • RestructCritErr - Критическая ошибка при реструктуризации
  • Sys – сеанс.
      Operations:
    • OpenSession - Подключение
    • CloseSession - Отключение
  • TmplOpers – типовые операции.
      Operations:
    • TmplOperNew - Записана новая типовая операция
    • TmplOperWrite - Типовая операция записана
    • TmplOperGrpMove - Типовая операция перенесена в другую группу
  • UpDown – выгрузка/загрузка данных.
      Operations:
    • UpDownDnldToFile - Выгрузка ИБ
    • UpDownDnldSuc - Выгрузка ИБ успешно завершена
    • UpDownUplFromFile - Загрузка ИБ
    • UpDownUplSuc - Загрузка ИБ успешно завершена
  • Users – другие события.
      Operations:
    • UserMsg - Дополнительное событие
  • UsrDef – пользователи.
      Operations:
    • UsrDefChng - Изменение списка пользователей
Возможно есть еще что-то, инфу брал отсюда [1] т.к. мне всего-лишь требовалось
узнать дату и время входа и выхода из системы.
Так дальше по формату:
Category - Категория события
  1. - администрирование
  2. - изменение данных
  3. - информация
  4. - предупреждение
  5. - ошибка
PC-Name - Имя компьютера (dns-имя)
ObjId - Id объекта в виде Тип(ID_вида)ID_объекта [2]
Comment - Комментарий к событию

Такс формат более менее расписал.
Часто бывает нужно выбрать информацию о изменениях за период, я делаю это методом половинного деления:

//_________разбираем строку по составляющим__________
Функция РазобратьСтроку(Стр)
  Если ПустоеЗначение(Стр)=1 Тогда
      Возврат СоздатьОбъект("СписокЗначений");
  КонецЕсли;
  Спис = СоздатьОбъект("СписокЗначений");
  Стр = СтрЗаменить(Стр,",","°");
  Стр = СтрЗаменить(Стр,";",""",""");
  Стр = """" + Стр + """";
  Спис.ИзСтрокиСРазделителями(Стр);
  Возврат Спис
КонецФункции//РазобратьСтроку(Стр)

//Получаем заданный диапазон
//Где НачДата - Это начало искомого диапазона
//    КонДата - Конец искомого диапазона
//    Журнал - Открытый файл в текстовом режиме
Функция ПолучитьДиапазон(НачДата,КонДата,Журнал)
  НачНашли = 0;
  КонНашли = 0;
  //Начальные данные
  Нач = 1;
  Кон = Журнал.КоличествоСтрок()-1;
  //Для сходимости разнесем концы в противоположные
  ПредНач = Кон;
  ПредКон = Нач;  //Получаем первые данные
  Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Нач));
  дНач = Спис.ПолучитьЗначение(1);
  дНач = Дата(Лев(дНач,4),Сред(дНач,5,2),Прав(дНач,2));
  Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Кон));
  дКон = Спис.ПолучитьЗначение(1);
  дКон = Дата(Лев(дКон,4),Сред(дКон,5,2),Прав(дКон,2));

  //Ну а дальше МПИ, с небольшой эвристикой, необходимо найти начало дня
  Пока (дНач <> НачДата) или (дКон <> КонДата) Цикл
      //Смотрим что делить
      нНач = 0;
      Если дНач <> НачДата Тогда 
          //Надо сместиться вправо на половину от текущего положения
          нНач = Цел((Нач + ПредНач)/2);
          //можно даже навешать эвристику,
          //какая дата нужна и на сколько процентов сместиться надо
      КонецЕсли;

     //Если пересчитывалось т.е. дНач <> НачДата
     Если нНач <> 0 Тогда
         Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(нНач));
         ддНач = Спис.ПолучитьЗначение(1);
         ддНач = Дата(Лев(ддНач,4),Сред(ддНач,5,2),Прав(ддНач,2));
         //смотрим в какой области ответ
         Если ддНач > НачДата Тогда    //Дата слева
             ПредНач = нНач;
         ИначеЕсли ддНач < НачДата Тогда    //Дата справа
             Нач = нНач;
             КонецЕсли;
             дНач = ддНач;
         Иначе   //Если мы уже нашли начало а конец еще нет
             //Смещаемся влево чтобы потом меньше бегать
             Если НачНашли = 0 Тогда
                 Нач = Нач - 1;
                 Если Нач <= 0 Тогда
                      Нач = 1;
                 КонецЕсли;
                 Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Нач));
                 ддНач = Спис.ПолучитьЗначение(1);
                 ддНач = Дата(Лев(ддНач,4),Сред(ддНач,5,2),Прав(ддНач,2));           
                 //если промахнулись
                 Если (ддНач <> НачДата) ИЛИ (Нач = 1)  Тогда
                     НачНашли = 1;
                     Нач = Нач + 1;
                 КонецЕсли;
             КонецЕсли;
        КонецЕсли;
  Если (НачНашли = 0) и (ПредНач - Нач <= 1) Тогда
        Нач = -1;
        НачНашли = 1;
  КонецЕсли;
  нКон = 0;
  Если дКон <> КонДата Тогда
       //Надо сместиться вправо на половину от текущего положения
       нКон = Цел((Кон - ПредКон)/2); //можно даже навешать эвристику,
         //какая дата нужна и на сколько процентов сместиться надо
  КонецЕсли;

//Если пересчитывалось т.е. дКон <> КонДата
Если нКон <> 0 Тогда
   Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(нКон));
   ддКон = Спис.ПолучитьЗначение(1);
   ддКон = Дата(Лев(ддКон,4),Сред(ддКон,5,2),Прав(ддКон,2));
   //смотрим в какой области ответ
   Если ддКон > КонДата Тогда    //Дата слева
       ПредКон = нКон;
   ИначеЕсли ддКон < КонДата Тогда    //Дата справа
       Кон = нКон;
   КонецЕсли;
       дКон = ддКон;
   Иначе   //Если мы уже нашли конец а начало еще нет   
   //Смещаемся вправо чтобы потом меньше бегать
       Если КонНашли = 0 Тогда
            Кон = Кон + 1;
            Если Кон >= Журнал.КоличествоСтрок() Тогда
                Кон = Журнал.КоличествоСтрок() - 1;
            КонецЕсли;
            Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Кон));
            ддКон = Спис.ПолучитьЗначение(1);
            ддКон = Дата(Лев(ддНач,4),Сред(ддКон,5,2),Прав(ддКон,2));
            //если промахнулись
            Если (ддКон <> КонДата) или (Кон = Журнал.КоличествоСтрок() - 1) Тогда
                КонНашли = 1;
                Кон = Кон - 1;
           КонецЕсли;
       КонецЕсли;
   КонецЕсли;
Если (КонНашли = 0) и (Кон - ПредКон <= 1) Тогда
     Кон = -1;
     КонНашли = 1;
 КонецЕсли;
 КонецЦикла; //Попали в область каждой из даты
//нужно найти начало даты 
Флаг = 0;
Пока Флаг = 0 Цикл
    Если НачНашли = 0 Тогда
         Нач = Нач - 1;
         Если Нач <= 0 Тогда
              Нач = 1;
         КонецЕсли;
         Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Нач));
         ддНач = Спис.ПолучитьЗначение(1);
         ддНач = Дата(Лев(ддНач,4),Сред(ддНач,5,2),Прав(ддНач,2));
         //если промахнулись
         Если (ддНач <> НачДата) или (Нач = 1) Тогда
             НачНашли = 1;
             Нач = Нач + 1;
         КонецЕсли;
     КонецЕсли;
  Если КонНашли = 0 Тогда
      Кон = Кон + 1;
      Если Кон <= 0 Тогда
          Кон = Журнал.КоличествоСтрок() - 1;
      КонецЕсли;
      Спис = РазобратьСтроку(Журнал.ПолучитьСтроку(Кон));
      ддКон = Спис.ПолучитьЗначение(1);
      ддКон = Дата(Лев(ддНач,4),Сред(ддКон,5,2),Прав(ддКон,2));
      //если промахнулись
      Если (ддКон <> КонДата) ИЛИ (Кон = Журнал.КоличествоСтрок() - 1)  Тогда
          КонНашли = 1;
          Кон = Кон - 1;
      КонецЕсли;
   КонецЕсли;
 Если КонНашли + НачНашли = 2 Тогда
     Флаг = 1;
 КонецЕсли;
КонецЦикла;
Спис = СоздатьОбъект("СписокЗначений");
Спис.ДобавитьЗначение(Нач,Нач);
Спис.ДобавитьЗначение(Кон,Кон);
Возврат Спис;КонецФункции;//ПолучитьДиапазон
Намеренно не пишу разбор объекта т.к. эту информацию можно почерпнуть в [2]

Список источников.

  1. Формат файла mlg
  2. Внутренние идентификаторы объектов
Дополнения и критика приветствуется.

10 комментариев:

  1. TypeOpen M - это Монитор, а не монопольный вход, а D - это Отладчик, а так очень помогло, автору спасибо большое

    ОтветитьУдалить
  2. Спасибо за замечание, поправил

    ОтветитьУдалить
  3. Кстати эту же задачу я позже переделал с подсправочником Учет времени (т.к. задача усложнилась - рабочий промежуток изменился и стал с 11.00-00.00). Бывало пользователи выходили позже указанного промежутка, а вносить модификации в данный механизм не представлялось возможным.

    ОтветитьУдалить
  4. Только начина программировать на 1С

    подскажите плиз как мне внедрить этот код в 1С?

    т.е. в какое место его положить?
    и наверное нужно еще дописывать?
    ИмяФайла = КаталогИБ()+"Syslog\1cv7.mlg";

    не соображу никак)))

    ОтветитьУдалить
  5. Мне нужно чтоб в Отчете формировалась работа пользователей...

    ОтветитьУдалить
  6. Советую сделать так:
    Завести справочник - Учет времени:
    * Сотрудник
    * ДатаСобытия
    * ВремяСобытия
    * ТипСобытия (либо число, либо перечисление "вход/выход").

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

    а при завершении системы соответственно, то же самое но, ТипСобытия = ...Выход;

    Отчет это цикл по данному справочнику.

    ОтветитьУдалить
  7. В таком случае будет формироваться отчет за текущий день, так ведь?

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

    Ваш код ведь это реализует, так ведь?
    Как его внедрить?
    кстати источник [2] не доступен.

    P.S.
    С Новым Годом :)

    ОтветитьУдалить
  8. Сделал как Вы посоветовали. В принципе сработало.

    Не получается только делать выборку по Дате

    СпрУчвр.ВыбратьЭлементы(НачДата,КонДата)

    Ругается
    Вот так:
    ------------
    Пока СпрУчвр.ДатаСобытия.ВыбратьЭлементы(НачДата,КонДата) = 1 Цикл
    {Отчет.УчетВремени.Форма.Модуль(32)}: Значение не представляет агрегатный объект (ВыбратьЭлементы)
    ------------

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

    Пока Спр.ПолучитьЭлемент() = 1 Цикл
    КонецЦикла
    ...
    КонецЦикла

    Либо через систему запросов

    ТекстЗапроса = "ДатаСобытия = Справочник.УчетВремени.ДатаСобытия;
    Сотрудник = Справочник.УчетВремени.Сотрудник;
    Условие(Сотрудник = ВыбСотрдудник);
    Условие((ДатаСобытия >= НачДата) и (ДатаСобытия <= КонДата));
    // и помоему группировку надо сделать..."

    Пример пишу не проверяя, на коленке в прямом смысле этого слова

    Наверное сделаю отдельно заметку по этой теме...

    ОтветитьУдалить
  10. Через систему запросов не получилось.

    А вот через цикл все заработало.
    Огромное Вам человеческое спасибо!

    Если бы у меня было 2 кг мандаринов, то я с Вами поделился бы)))))

    ОтветитьУдалить