Microsoft Dynamics CRM 2011 | Time-Offs – find the right pattern

In meinem heutigen Artikel möchte ich mich mit der Erstellung von Time-Offs (arbeitsfreie Zeiten) z.B. aus einem Plug-in heraus beschäftigen. Arbeitsfreie Zeiten sind ein sehr flexibles Hilfsmittel und werden selbstverständlich auch bei der Planung, z.B. von Services mit berücksichtigt.

TimeOff_User_WorkingHourWährend sich im SDK nur spärlich Beispiele rund um den Arbeits-zeitenkalender eines Benutzers finden, so gibt es in einer Recherche über eine Suchmaschine Eurer Wahl schon mehr Treffer. Mit einem solchen Beispiel habe ich den Eintrag am 11ten erzeugt.

Am 10ten habe ich hingegen den von mir ausgearbeiteten Pattern verwendet. Augenscheinlich sind zunächst erst einmal beide Einträge gleich.

TimeOff_Detail_TimeOff11thSchaut man sich den Eintrag am 11ten jedoch im Detail an, so stellt man in fest, dass die Zeit hier nicht korrekt dargestellt wird.

Verwunderlich, angesichts der Tatsache, dass der Eintrag doch vermeintlich korrekt von 13:00 – 13:30 Uhr erzeugt wurde.

TimeOff_Detail_TimeOff10thIm Vergleich dazu stellt sich in der Detail-ansicht mit dem von mir verwendeten Pattern auch die Uhrzeit korrekt dar.

Was also mag der Unterschied zwischen den beiden Versionen sein?

Bevor ich zu den Unterschieden komme, möchte ich Euch einen Tipp geben, wie ich mein Schema ermitteln konnte. Dazu habe ich das SOAP Logger Tool verwendet, welches sich im SDK – Client-Ordner befindet, sowie ein optischer Vergleich in MS Excel unter Verwendung der zugehörigen SQL-Kalender-Abfrage.

TimeOff_Differences_ExcelZunächst habe ich dabei eine Arbeitsfreie Zeit über die Ober-fläche erzeugt. Diese findet Ihr in der Zeile 2 bzw. 3 dargestellt. Anschließend habe ich einen Vergleich mit den im Internet gefundenen Schema getätigt und mich so den von CRM intern verwendeten Angaben angeglichen.

Die korrekte Anlage findet Ihr in den Zeilen 10 bzw. 11.

Dabei fallen folgende Dinge auf:

Eine Arbeitsfreie Zeit besteht immer aus zwei Kalenderregeln. Die erste Regel beinhaltet den Pattern, die zweite Regel kümmert sich um die tatsächliche Dauer der Auszeit

Im 1. Pattern finden wir in der StartTime jedoch nicht die tatsächliche Uhrzeit des Beginns. Stattdessen finden wir hier das Datum der Auszeit mit der Zeitangabe Mitternacht.

Im 1. Pattern unter Duration findet sich nicht etwa die Dauer (in meinem Fall 30), sondern ein weitaus höherer Wert. Dafür bleiben Offset, TimeCode, IsSimple und Effort ungesetzt.

Im 1. Pattern findet sich darüber hinaus unter TimeZoneCode die Zeitzoneninformation des Benutzers für den der Eintrag erstellt wurde.

Im 1. Pattern wird kein SubCode (in unserem Fall 6 für Vacation) gesetzt.

Im 2. Pattern wird der Pattern nicht etwa wiederholt,  hingegen finden wir wichtige Ergänzungen, wie etwa:

– TimeCode

– IsSimple

– Offset

– SubCode

– Duration

Letztere beinhaltet in diesem Fall die tatsächliche Dauer der Auszeit.

Dies wirft einige Fragen auf. Zum Beispiel warum die Uhrzeit bei der StartTime auf Mitternacht gesetzt ist.

Schaut man sich den dazu passenden Wert der Duration an, so liegt dieser bei 810. Dies ist ein Offset-Wert.
810 / 60 = 13,5. Diesen Wert muss man nunmehr in Relation zu Mitternacht sehen, also 13,5 Stunden nach Mitternacht. Dies setzt aber nicht etwa den Anfang der Arbeitsfreien Zeit auf 13:30 Uhr ! Vielmehr wird damit das Ende der Arbeitsfreien Zeit errechnet.

Die Dauer der Arbeitsfreien Zeit von 30 (Minuten) hingegen findet sich in der zweiten Kalenderregel, die in diesem Fall über die InnerCalendarId dem Pattern zugewiesen wird.

In Kombination mit dem dort befindlichen TimeZoneCode wird damit um 13:00 Uhr eine Arbeitsfreie Zeit von 13:00 bis 13:30 Uhr erzeugt.

Daher habe ich folgenden Code-Pattern für die Erzeugung einer Arbeitsfreien Zeit abgeleitet:

var currentUserSettings = service.RetrieveMultiple(
new QueryExpression(UserSettings.EntityLogicalName)
{
ColumnSet = new ColumnSet(„localeid“, „timezonecode“),
Criteria = new FilterExpression
{
Conditions =
{
   new ConditionExpression(„systemuserid“, ConditionOperator.Equal, _currentUserId )
}
}
}).Entities[0].ToEntity<UserSettings>();

_timeZoneCode = currentUserSettings.TimeZoneCode;

Dieser Code dient nur dazu, den TimeZoneCode des Benutzers zu ermitteln für den wir unseren Kalendereintrag erstellen wollen.

// Get the calendar id of the user
    Entity systemUserEntity = slos.Retrieve(„systemuser“, _currentUserId, new ColumnSet(new String[] { „calendarid“, }));
    // Retrieve the calendar of the user
    Entity userCalendarEntity = slos.Retrieve(„calendar“, ((Microsoft.Xrm.Sdk.EntityReference)(systemUserEntity.Attributes[„calendarid“])).Id, new ColumnSet(true));
    // Retrieve the calendar rules defined in the calendar
    EntityCollection calendarRules = (EntityCollection)userCalendarEntity.Attributes[„calendarrules“];
    // Duration for Offset = (starttime.hour * 60) + startdate.Minute + duration
    var startdate = new DateTime(2013, 8, 11, 13, 0, 0, DateTimeKind.Utc);
    var offsetRule = (startdate.Hour * 60) + startdate.Minute + 30;
    // Create a new inner calendar
    Entity newInnerCalendar = new Entity(„calendar“);
    newInnerCalendar.Attributes[„businessunitid“] = new EntityReference(„businessunit“, ((Microsoft.Xrm.Sdk.EntityReference)(userCalendarEntity[„businessunitid“])).Id);
    newInnerCalendar.Attributes[„name“] = „CUSTOM Time Off“;
    Guid innerCalendarId = service.Create(newInnerCalendar);
    // Create a new calendar rule and assign the inner calendar id to it
    Entity calendarRule = new Entity(„calendarrule“);
    calendarRule.Attributes[„description“] = „Time Off Rule“;
    calendarRule.Attributes[„duration“] = offsetRule;
    calendarRule.Attributes[„extentcode“] = 2;
    calendarRule.Attributes[„pattern“] = „FREQ=DAILY;INTERVAL=1;COUNT=1“;
    calendarRule.Attributes[„rank“] = 0;
    calendarRule.Attributes[„timezonecode“] = _timeZoneCode;
    calendarRule.Attributes[„innercalendarid“] = new EntityReference(„calendar“, innerCalendarId);
    // starting at midnight given day 
    calendarRule.Attributes[„starttime“] = new DateTime(2013, 8, 11,0,0,0,DateTimeKind.Utc);
    calendarRules.Entities.Add(calendarRule);
    // assign all the calendar rule back to the user calendar
    userCalendarEntity.Attributes[„calendarrules“] = calendarRules;
    // update the user calendar entity that has the new rule
    service.Update(userCalendarEntity);
    Entity calendarRule1 = new Entity(„calendarrule“);
    // set Duration (now Duration only)
    calendarRule1.Attributes[„duration“] = 30;
    calendarRule1.Attributes[„issimple“] = true;
    // create offset from given startdate
    var offset = (startdate.Hour * 60) + startdate.Minute;
    calendarRule1.Attributes[„offset“] = offset;
    calendarRule1.Attributes[„rank“] = 0;
    // subcode 6= vacation
    calendarRule1.Attributes[„subcode“] = 6;
    // time code 2 = unavailable
    calendarRule1.Attributes[„timecode“] = 2;
    calendarRule1.Attributes[„timezonecode“] = -1;
    calendarRule1.Attributes[„extentcode“] = 2;
    calendarRule1.Attributes[„calendarid“] = new EntityReference(„calendar“, innerCalendarId);
    EntityCollection innerCalendarRules = new EntityCollection();
    innerCalendarRules.EntityName = „calendarrule“;
    innerCalendarRules.Entities.Add(calendarRule1);
    newInnerCalendar.Attributes[„calendarrules“] = innerCalendarRules;
    newInnerCalendar.Attributes[„calendarid“] = innerCalendarId;
    slos.Update(newInnerCalendar);

Wie Ihr an dem Schlüsselwort “slos” erkennen könnt, habe ich den Code mit Hilfe des SOAP Logger Tools getestet. Die wichtigsten Stellen habe ich hervorgehoben und möchte sie nachfolgend erklären, da sich mein Pattern hierin von anderen gefundenen Mustern unterscheidet.

Zunächst setze ich das Datum und die Uhrzeit zu dem ich meine Auszeit wünsche. Berücksichtigt dabei, dass der Server in UTC Zeit rechnet. Im Feld StartTime setze ich daher immer den UTC Wert. Bei meinem dynamischen Wert StartDate hingegen kommt es darauf an, ob ich ein Datumswert im Server erzeuge oder einen Wert aus einem DateTime-Feld einer CRM Entität übernehme. Bei Letzterem belasst diesen mit .ToLocalTime() bei der lokalen Zeit, um den unterschiedlichen Zeitzonen gerecht zu werden.

Hinweis: In CRM unterscheiden wir zwischen

– Zeitzone, die sich der Benutzer als Anzeige in seinen persönlichen Optionen eingestellt hat

– Zeitzone, in der ein Systemadministrator für diesen Benutzer seinen Arbeitszeitenkalender erstellt hat und

– UTC Zeit, mit der der Server intern rechnet bzw. in der DatumZeit-Feldwerte gespeichert werden.

Danach errechne ich mir den Offset-Wert, also das Ende, wann meine Auszeit aufhören und der “normale” Arbeitszeitenkalender fortgeschrieben wird. Dabei hole ich mir die Stunden, rechne diese in Minuten um und addiere evtl. vorhandene Minuten und zusätzlich den Wert der Dauer meiner Auszeit (30).

Für die innere Kalenderregel verwende ich in der Duration nur die tatsächliche Dauer (30) und errechne mir den Beginn (Offset) der Arbeitsfreien Zeit. Dies erfolgt wiederum aus den Stunden der gegebenen Anfangszeit umgerechnet in Minuten + etwaiger Minuten. Diesen Wert setze ich dann im Offset.

In meinem Beispiel komme ich auf einen Wert von 780, der sich aus der Rechnung 13 * 60 + 0 ergibt. Für CRM ergibt sich damit die Anfangszeit von 13:00 Uhr.

Und zu guter Letzt setze ich den TimeZoneCode der inneren Kalenderregel auf –1.

Dieses Beispiel habe ich jedoch noch detaillierter betrachtet und mich der Frage gewidmet: Was passiert denn eigentlich, wenn man die Dauer der Arbeitsfreien Zeit verlängern wollte? Also z.B. den Wert um 30 Minuten erhöhen will.

Die Auswirkungen sehen wir in der Excel-Darstellung in den Zeilen 6 und 7. Tatsächlich wird nicht nur der innere Kalender einer Aktualisierung unterzogen, sprich hier der Duration Wert von 60 auf 90 angehoben. Nein, auch der Offset-Wert des Patterns muss sich anpassen und damit von 960 auf 990 ändern.

Wird der letzte Schritt vergessen, ist der Kalender des Benutzers im Anschluss unbrauchbar und in der Oberfläche zeigt sich eine Fehlermeldung.

Ich hoffe, Euch mit diesem Beispiel einmal mehr die Philosophie des Arbeitszeitenkalenders näher gebracht zu haben und wünsche Euch viel Spaß bei der Umsetzung Eurer Projekte.

Technorati Tags:

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s