Тема: Разбирал тут календарь...

Создан:Vlad Sh 04/24/2007 06:31 PM
Модифицирован:Vlad Sh 04/25/2007 03:09 PM
Папка:
06. Разработка Notes-приложений
Тип сообщения:
Вопрос

Сообщение:

Задача стояла следующая: сделать в своей расписание по своей форме (со своими реквизитами), но чтобы исполнители не могли его менять. Как известно, стандартный механизм календаря предусматривает согласование (ACCEPT) времени у исполнителей, а это не подходит.

Собственно кроме некоторой полезной информации почти ничего получить не удалось...
Изложу здесь всё, что я нарыл по этой теме и где я остановился, может быть кто-то поможет? :)

Пошёл от формы Appointment с AppointmentType="3", остальное не подходит...


I. Визуальное получение свободного времени персон/ресурсов - работа с объектом Scheduler.
Кому будет интересно - см. подформу "(InviteeCheckSchedules)", с неё проще начинать - почти всё понятно.
Здесь одна тонкость:
- при открытии документа для начальной инициализации нужно вызывать Call uischedular.getscheduledata(2)
- при последующих Refresh - Call uischedular.getscheduledata(1)!
Это важно, т.к. без этого при добавлении новых персон/ресурсов никаких обновлений планировщика производиться не будет!
По ходу дела удивился примитивизму организации Onchange и Exiting полей дат.

Событие "Detail Display For" самого Scheduler'а так и не заработало. В книге Интертраста "Разработка приложений в Domino Designer R7" говорится, что на Right-Click на документе в планировщике будет выводиться информация из полей, определённых в Schedue Detail Item, но ничего похожего в LN6.5.1 добиться так и не удалось, хотя это д.б. и в R6...


II. Резервирование времени исполнителей.
а). обратился к базу busytime.nsf, в документах которой есть след поля:
SchedProfile (Data Type: Time/Date List or Range) - информация о рабочем времени персоны, попадает туда из их CalendarProfile.
И собственно 'интересные' поля и их значения (нечитаемы - запись в них производится закрытым механизмом Lotus):
DetailsList - Datatype: "??? 001E ???"; ...
EventList, EventListExt - Datatype: "Busy Time List"; похоже на то, что сюда записывается резервируемое время и соотв. ему события.

б). Вынесло на соотв. библиотеки: CSCalendarEntry, CSEventNotes, CSEventClass, Common.
Собственно самое интересное находится в последней. Даже не в ней, а в том, что она %INCLUDE "orgconst.lss", но подозреваю, что об этом мы никогда не узнаем :)


1. Инициализация.
Вся работа этих библиотек основана на хитром объекте:
'-------------------------------------------------------------------------------------------------
Dim BEobject As Variant ' Back-End Object

'Эти классы определены в orgconst.lss
If (nEventType = RESOURCE_EVENT) Then
Set BEobject = New NotesCSReservationDocument()
Else
Set BEobject = New NotesCSEventParticipantDocument(nEventType)
End If

'Инициализация!!! ND - NotesDocument - новый, сохранённый документ Notice или Appointment
Call BEobject.Init ( ND, 128 )
'-------------------------------------------------------------------------------------------------

Долго на этой строке скрипт вылетал.
Похожая ситуация: http://www-10.lotus.com/ldd/nd6forum.nsf/DateAllThreadedweb/71940361770432e885256f86007d1d32?OpenDocument
Экспериментальным путём установил, что для успешной инициализации в ТЕКУЩЕЙ БАЗЕ необходим профайл CalendarProfile! Т.е. либо база должна быть почтовым ящиком (очевидно, что работа этих объектов заточена на работу из почтовой БД), либо в ней должен быть этот профайл. Мы же, как известно, лёгких путей не ищем... :)


2. Действие SEND INVITATION (на базовый документ Meeting в п.я., выбранных в планировщике персон, отправляетя Notice-документ на подтверждение участия).
Вот собственно вся выжимка из этого действия:
'-------------------------------------------------------------------------------------------------
' A method for controlling UpdateDateTimeItemsWithZone
Sub UpdateDates()
Call m_beobject.UpdateDateTimeItemsWithZone()
End Sub

Call Me.m_beobject.PrepareToSave(1)

Sub CSDocSendPrimaryNotice(vBEObject As Variant, vCallBackObject As Variant, vContinue As Variant)

If Not(vBEObject.IsWorkflowEnabled) Then Exit Sub

Call vBEObject.InviteRooms()

If bSendUpdate Then
Call vBEObject.ActionHandler(ORS_MSGTYPE_UPDATEINFO, Cint(bSendNow), Cint(vCallBackObject.FieldsChanged))
Call vCallBackObject.AddToNotesDocumentArray()
End If

' first, prepare the backend document for sending
Err = 0
' Morph the cal entry into a notice
Call vBEObject.PreSendPrimaryRequest()

' invoke the (LS) callback object - mail the initial invitation
Call vCallBackObject.SendPrimaryRequest()
' Revert doc back to appt.
Call vBEObject.PostSendPrimaryRequest(Err <> 0)

End Sub

If (bCreateRepeat) Then Call m_beobject.CreateRepeatHierarchy()

Call m_beobject.MarkTempItems
'-------------------------------------------------------------------------------------------------

Надо заметить:
- что запустить PreSendPrimaryRequest и PostSendPrimaryRequest из своей базы так и не удалось, пишет что-то типа "undeclared method...". Неясно как это работает в почте.
- что никакой внятной информации в инете о методах этих "закрытых" объектов нет. Кроме пожалуй http://www-10.lotus.com/ldd/46dom.nsf/55c38d716d632d9b8525689b005ba1c0/5f6528557a758da18525695e005801e6?OpenDocument
там рассказывается как сделать repeating appointment, но мне этого не нужно.
- что во всех скриптах этих библиотек перед действием идёт PrepareToSave(1), а после MarkTempItems, зачем это надо - не понятно, пробовал не вызывать - результат тот же. Но раз такая традиция...


3. Действие ACCEPT (подтверждение пользователем его участия).
Здесь меня постигла вторая (после BEobject.Init) небольшая удача.
Выжимка из этого действия:
'-------------------------------------------------------------------------------------------------
' get a list of all unapplied notices for this meeting
vdata = m_beobject.GetUnappliedNotices()

...
Elseif (Me.m_nEventType = RESOURCE_EVENT) Then
Call UpdateDates()
Elseif Me.m_note.hasitem("tmpInviteeTimeChange") Then
Call UpdateDates()
...

strAction = ORS_MSGTYPE_ACCEPT
Call m_beobject.ActionHandler(strAction, Cint(bSendNow), Cint(m_UpdateInfoOverWrite))

Call Me.m_beobject.PrepareToSave(1)

' update busytime so that your busytime reflects the change
' if we're accepting a reschedule, then update busytime on the parent/instance note, not this note
If m_note.GetItemValue("Form")(0) = "Notice" Then
...
Else ' this is a calendar entry check the note to see if we need to update busytime
Call m_beobject.UpdateBusyTimeInfo(m_note.GetItemValue("BookFreeTime")(0) <> "1")
End If

Call m_beobject.MarkTempItems
'-------------------------------------------------------------------------------------------------

m_beobject.GetUnappliedNotices() у меня всегда почему-то возвращает пустоту

Есть ещё интересная функция BatchAcceptNotices, в которую передаются UNID'ы документов по форме Notice, т.е. запросов на подтверждение, вот её урезанный, относящийся к делу код:
'-------------------------------------------------------------------------------------------------
'nEventType:
Const CALENDAR_EVENT = 1
Const TASK_EVENT= 2
Const RESOURCE_EVENT = 3

Dim db As NotesDatabase
Dim ND As NotesDocument
Dim i As Integer
Dim bUpdateMain As Integer

Public BEobject As Variant ' Back-End Object

Dim strNoticetype As String

Set db = NS.CurrentDatabase

If Isarray( UNIDs )Then
For i = 0 To Ubound( UNIDs )
Set ND = db.GetDocumentByID( UNIDs(i) )

If (nEventType = RESOURCE_EVENT) Then
Set BEobject = New NotesCSReservationDocument()
Else
Set BEobject = New NotesCSEventParticipantDocument(nEventType)
End If

Call BEobject.Init ( ND, 128 )

Set po = CSGetMainEventObject(BEobject)
Set noteMain = po.document

If Not ND Is Nothing And Not po Is Nothing Then

' if this is a status update, we need to do some special handling
strNoticeType = Ucase(ND.GetItemValue("NoticeType")(0))

If strNoticeType = ORS_MSGTYPE_STATUSUPDATE Then
If ND.HasItem("OrgStatus") Then
strNoticeType = ND.GetItemValue("orgStatus")(0)
End If
End If

Select Case strNoticeType
Case ORS_MSGTYPE_REFRESHINFO
Call BEObject.ActionHandler( ORS_MSGTYPE_ADDCALENDAR, Cint(True), 0)
bUpdateMain = True
' Call ND.Save(True,False,True)
Case ORS_MSGTYPE_RESCHEDULE
' we process broadcasts differently than we do normal meetings
If ND.HasItem("Broadcast") Then
Call BEObject.ActionHandler( ORS_MSGTYPE_ADDCALENDAR, Cint(True), 0)
Else
Call BEObject.ActionHandler( ORS_MSGTYPE_ACCEPT, Cint(True), 0)
End If
' update Busytime
If (BEobject.context = CTX_REPEAT_MSG) Then
If BEobject.RepeatInstanceEvent.document.getitemvalue("NoticeType")(0) = "T" Then
Call BEobject.RepeatInstanceEvent.UpdateBusyTimeInfo(Cint(True))
Else
Call BEobject.RepeatInstanceEvent.UpdateBusyTimeInfo(Cint(True))
End If
Else
Call BEobject.ParentEvent.UpdateBusyTimeInfo(Cint(True))
End If
bUpdateMain = True
Case ORS_MSGTYPE_CONFIRMATION
Call BEobject.ActionHandler(ORS_MSGTYPE_ADDCALENDAR, Cint(True), 0)
bUpdateMain = True
Case ORS_MSGTYPE_CANCEL
Call BEobject.DeCommitEvent(ORS_MSGTYPE_CANCEL)
Case ORS_MSGTYPE_STATUSREMOVED
Call BEobject.DeCommitEvent("5")
bUpdateMain = True
End Select

If (bUpdateMain) And m_session.CurrentDatabase.IsCurrentAccessPublicWriter Then
If note.HasItem("OrgRepeat") Then
Call CSDocSaveRepeatingEntry(EventObject.BEObject, Cint(0))
End If
End If
End If
Next
End If
'-------------------------------------------------------------------------------------------------


Стоит заметить, что:
- UpdateBusyTimeInfo для ParentEvent запустить так и не удалось - какая-то ошибка, сейчас не помню;
- на самом деле после изучения поведения методов ActionHandler и UpdateBusyTimeInfo пришёл к выводу, что оно идентично для нижеперечисленных действий (для других не тестировал - пока не было надобности):
ORS_MSGTYPE_INVITE "I" CONST STRING
ORS_MSGTYPE_ACCEPT "A" CONST STRING
ORS_MSGTYPE_CONFIRMATION "N" CONST STRING
это значение содержится в поле NoticeType. Более того если при создании документа прописать это поле самостоятельно, то UpdateBusyTimeInfo будет действовать именно так, как указано в этом поле.
Updated 25.04.2007: Единственная существенная разница этих флагов - это отображение определённых Actions при открытии документа Notice. Если выбирать "N", то пользователь не сможет внести изменения в документ с помощью Actions и/или прямого редактирования - это как раз то, что нужно.

Обязательные поля документов Notice (если какое-то не указать, то BEobject.Init() будет вылетать с ошибкой типа "Field can not located..."):
'-------------------------------------------------------------------------------------------------
$BusyName Data Type: Text; имя пользователя, у которого будет резервироваться время
$BusyPriority Data Type: Text; для обычных Meetings это "2"

NoticeType Data Type: Text; тип данного Notice, его значения:
"I" - Invitation (в стадии приглашения)
"A" - новый, по которому решение ещё не принято
"N" - Confirmed (подтверждён)

Создаются отдельные Notice для:
RequiredAttendees Data Type: Text, Field Flags: SUMMARY NAMES; содержит имена персон, обязательных на данном мероприятии
RoomToReserve Data Type: Text, Field Flags: SUMMARY NAMES; содержит имена помещений
Resources Data Type: Text, Field Flags: SUMMARY NAMES; содержит имена ресурсов

Информация о датах-времени, по которой будет производиться резервирование времени
StartDate, EndDate Data Type: Time/Date; дата без времени
StartDateTime, EndDateTime Data Type: Time/Date; дата с временем
StartTime, EndTime Data Type: Time/Date; время без даты
StartTimeZone, EndTimeZone Data Type: Text; значение типа "Z=-2$DO=1$DL=3 -1 1 10 -1 1$ZX=33$ZN=GTB"

CalendarDateTime Data Type: Time/Date; = StartDateTime, только в новом документе (NoticeType="A")

Form
Chair Data Type: Text, Field Flags: SUMMARY NAMES; содержит имя персоны, создававшей Meetings

В этих полях прописывается UNID "самого себя". Если создавать эти документы из своей базы и прописать другой, то BEobject.Init() будет вылетать с Err=4670, описания на которую нет.
ApptUNID Data Type: Text; по этому полю (UNID'у) Scheduler определяет, подтвердил пользователь своё участие в мероприятии или нет
$Orig Data Type: Response Reference List; то же самое, что и ApptUNID (где-то читал, что его оставили для совместимости клиентов старых версий)

OrgTable значение "C0" - для резервирования времени для персон
SequenceNum Data Type: Number; 1
UpdateSeq Data Type: Number; 1, 2 в Confirmed-документе
'-------------------------------------------------------------------------------------------------

Прочие (необязательные) поля:
FormToUse Data Type: Text; "Notice", есть только в Confirmed-документах
$ExpandGroups Data Type: Text; значение "3"
$WIModified Data Type: Text, "$S" - только в новом документе (NoticeType="A")
StorageRequiredNames Data Type: Text, Field Flags: SUMMARY NAMES; "1"
'-------------------------------------------------------------------------------------------------


Т.е. вполне достаточно создать документ Notice или Appointment (это не важно) в почтовой БД пользователя с необходимыми полями (одним из трёх вышеперечисленных значений NoticeType) + натравить на него UpdateBusyTimeInfo и время прекрасно резервируется!



ПРОБЛЕМЫ:

1. Если документы для резервирования (Notice) создавать не в своей базе, а в базе пользователя, то он может преспокойно удалить их и время опять станет не занятым, освободится :( Получается, что UpdateBusyTimeInfo и ActionHandler, хотя и сохраняет документы в busytime.nsf, но резервируют время не жёстко!
Сначала думал, что удаление документа в UI сбрасывает резервирование (похожий скрипт в Querydocumentundelete имеется), но не только! Если удалить Notice через Back-End, то время всё равно высвобождается секунд через 10-15 :( похоже задача Sched старается.
Была идея запретить удаление таких документов в п.я. пользователей (потом чистить агентом при прошествии события), смотрел Querydocumentundelete - там такая возможность не предусмотрена или я не нашёл?! А править дизайн стандартной почты не хочется... Какие будут варианты, подскажИте?!

2. Если Notice для резервирования времени других пользователей создавать в своей базе, то UpdateBusyTimeInfo и ActionHandler не резервируют время :(
Пробовал сделать базу типа "mailfile" + добавить необходимых элементов дизайна - не получается - не резервирует.
Пробовал зарезервировать время для Qwner-пользователя (владельца базы из CalendarProfile текущей базы) - резервирует! А для других - нет!
Игрался с видами... кстати инфу о том, в каком виде Лотус (задача Sched) просматривает документы для подтверждения резервирования, нигде не мог найти. Экспериментальным путём (поочерёдным удалением видов) определил, что это вид "($ApptUNID)"... но это не разрешает проблемы.


Какие будут идеи? Может кто-то уже подобное делал?
Пробовал сделать всё по правилам, в своей базе: сначала Appointment, потом для него Notice (в этой же базе) - всё равно то же самое - для не-Qwner-пользователя не резервирует :(
Please Help!


Иерархия документов данной дискуссии:
Разбирал тут календарь... (Vlad Sh) (24.04.2007 18:31:29)
.... Маленькая поправка: orgconst.lss имеется в программном каталоге Notes вместе со всякими lsconst.lss и т.п.... (Vladimir A. Panov; InterTrust) (25.04.2007 16:14:25)
........ pdf'ник однозначно хорош! (Vlad Sh) (25.04.2007 19:53:57)
........ Наткнулся ещё на одну неприятную вещь (+) (Vlad Sh) (26.04.2007 12:26:35)
............ notesSession.FreeTimeSearch (Vladimir A. Panov; InterTrust) (26.04.2007 15:33:42)
................ Точно; заработало! Что-то типа этого (+) (Vlad Sh) (27.04.2007 14:47:23)
.... А если в одной базе создать несколько календарных профилей (для разных пользователей), будет для них резервироваться время по этой базе?... (Vladimir A. Panov; InterTrust) (25.04.2007 16:35:56)
........ А это идея! (Vlad Sh) (25.04.2007 19:41:40)
............ Все идеи Ваши, а у меня просто свой интерес, но пока чисто "теоретический", до экспериментов руки не доходят (Vladimir A. Panov; InterTrust) (25.04.2007 21:54:41)


Разработчикам и администраторам: курсы, книги, сертификация