Microsoft Dynamics CRM 2011 | Update von Kalenderregeln?

In meinem letzten Artikel zum Thema Arbeitszeitenkalender bin ich auf die Abfragen eingegangen. In meinem heutigen Artikel möchte ich auf weitere Besonderheiten und das Erstellen von neuen Arbeitszeitenkalendern eingehen.

New_TimeOff_UserCalendarWie schon zuvor erwähnt ist der Arbeitszeitenkalender im CRM nur begrenzt über SDK Methoden erreichbar und birgt einige Besonderheiten. Bevor ich daher die Frage nach dem Update von Kalenderregeln beantworte, möchte ich zuvor auf einige Besonderheiten näher eingehen.

So wird im System üblicherweise ein allgemeingültiger Arbeitszeitenkalender einmalig bei Einrichtung des CRM Systems bzw. des Benutzers im CRM definiert. Dieser Kalender ist als ID (CalendarId) bei dem Benutzer hinterlegt und kann mit SDK Methoden auch abgefragt werden.

Legt man nunmehr eine Arbeitsfreie Zeit an, so wird diese nicht etwa in die bestehende Kalendervorlage kopiert, sondern ein zusätzlicher Kalender (für diesen Tag) inkl. der Regel für die Arbeitsfreie Zeit (Kalenderregel) angelegt.

WorkingHour_SQLQuery_UserCalendarAnalysiert man den Benutzerkalender mit Hilfe einer SQL-Abfrage, so stellt man fest, dass für jede Arbeitsfreie Zeit eine neue Regel angelegt wird, die wiederum Bestandteil eines neuen Kalenders ist. Jede Arbeitsfreie Zeit besteht also aus einem neuen Kalender (neue CalendarId) + einer neuen Kalenderregel (neue CalendarRuleId)

WorkingHour_BreakNeben Arbeitsfreien Zeiten, können jedoch pro Tag beliebig viele Pausen die Arbeitszeit unterbrechen. Bevor man sich daher dem Update möglicher Kalenderregeln widmen kann, muss man den Arbeitszeitenkalender auch diesbezüglich analysieren.

WorkingHour_SQLQuery_GettingNewCalendar_And_BreakRuleAuch hier hilft eine SQL- Abfrage. Legt man per SDK einen neuen Arbeitszeiten-kalender an, so werden die Pausen mit einem SubCode 4 (Break) in der Datenbank angelegt. Wurden mehrere Pausen definiert, so finden sich in der Datenbank unter einer CalendardId mehrere Einträge. Zum Einen der so genannte Pattern, der die generelle Anfangs- und Endzeit definiert (SubCode 1), sowie beliebig viele Pauseneinträge (SubCode 4) mit unterschiedlichen CalendarRuleIds. Diese verweisen jedoch immer auf den gleichen Kalender (CalendarId). Der Aufbau ist also etwas anders, als bei den Arbeitsfreien Zeiten, wo ein Eintrag jeweils einen neue Kalender (CalendarId) zur Folge hat.

// Retrieve the working hours of the current and other users.
QueryMultipleSchedulesRequest scheduleRequest = new QueryMultipleSchedulesRequest();
scheduleRequest.ResourceIds = new Guid[1];
scheduleRequest.ResourceIds[0] = _currentUserId;
//scheduleRequest.ResourceIds[1] = MyGuid;
scheduleRequest.Start = DateTime.Today.AddDays(1);
scheduleRequest.End = DateTime.Today.AddDays(2);
scheduleRequest.TimeCodes = new TimeCode[] { TimeCode.Available, TimeCode.Unavailable };
QueryMultipleSchedulesResponse scheduleResponse = (QueryMultipleSchedulesResponse)slos.Execute(scheduleRequest);

Mit diesem Code ist es uns möglich, mehrere Kalender von Benutzern abzufragen. Wir verwenden dabei sowohl den TimeCode.Available, als auch den TimeCode.Unavailable.

Die Antwort für Arbeitsfreie Zeiten (verkürzt) sieht dann in etwa so aus:

<b:TimeInfo>

<b:ActivityStatusCode>-1</b:ActivityStatusCode>

<b:CalendarId>4d491aae-c7f6-e211-97f4-000c299a9455</b:CalendarId>

<b:DisplayText />

<b:Effort>0</b:Effort>

<b:End>2013-07-27T13:00:00Z</b:End>

<b:IsActivity>false</b:IsActivity>

<b:SourceId>4e491aae-c7f6-e211-97f4-000c299a9455</b:SourceId>

<b:SourceTypeCode>4004</b:SourceTypeCode>

<b:Start>2013-07-27T12:00:00Z</b:Start>

<b:SubCode>Vacation</b:SubCode>

<b:TimeCode>Unavailable</b:TimeCode>

</b:TimeInfo>

und für Arbeitszeitregeln mit Pausen (verkürzt) in etwa so:

<b:TimeInfo>

<b:ActivityStatusCode>-1</b:ActivityStatusCode>

<b:CalendarId>4e5f0668-1fdc-e211-9795-000c299a9455</b:CalendarId>

<b:DisplayText />

<b:Effort>0</b:Effort>

<b:End>2013-07-27T11:00:00Z</b:End>

<b:IsActivity>false</b:IsActivity>

<b:SourceId>505f0668-1fdc-e211-9795-000c299a9455</b:SourceId>

<b:SourceTypeCode>4004</b:SourceTypeCode>

<b:Start>2013-07-27T10:30:00Z</b:Start>

<b:SubCode>Break</b:SubCode>

<b:TimeCode>Unavailable</b:TimeCode>

</b:TimeInfo>

Eure Aufmerksamkeit möchte ich jeweils auf das Attribut SourceId lenken, mit dem wir in der Lage sind, unsere Objekte eindeutig zu identifizieren. Dieses Attribut enthält jeweils die GUID der angelegten Kalenderregel.

Leider jedoch ist es per SDK nicht möglich mit Retrieve, RetrieveMultiple, Create oder Update direkt die Kalenderregeln anzusprechen. Deshalb stellt sich die Frage, ob dennoch ein Update besagter Regeln möglich ist? Denn in der Praxis möchten wir z.B. Pausen den tatsächlichen Pausenzeiten hin anpassen. Vielleicht wollen wir auch dynamisch Arbeitsfreie Zeiten anlegen.

Zwei Faktoren geben den Ausschlag, ob wir die Kalenderregel updaten können:

a) Wir benötigen pro Tag einen Kalender mit allen Kalenderregeln für Pausenzeiten und

b) neben der CalendarId vor allem die CalenderRuleId, um unser Update-Objekt eindeutig identifizieren zu können.

WorkingHour_CreateNewCalendarUm a) zu erfüllen, legt man daher zunächst einen Clone des aktuell gültigen Kalenders (Pattern) an. Dies erfordert das Auslesen und Identifizieren von Pausenzeiten, sowie Arbeitsfreien Zeiten.

Würde man diesen Schritt nicht unternehmen und etwa eine Pause direkt updaten (z.B. die Dauer aktualisieren), so stellt man fest, dass man dies nicht nur für den aktuellen Tag getätigt hat, sondern für alle bestehenden Arbeitszeitenkalender des Users.

Hat man hingegen einen Clone nur für diesen Tag angelegt, so äußert sich dies in der GUI durch ein leicht verändertes Icon des Arbeitszeitenkalenders. Er deutet auf einen Pattern (gültig nur für diesen Tag) hin. Auch hier ist jedoch die Reihenfolge wichtig:

Erst wird ein neuer Arbeitszeitenkalender mit allen gefundenen Pausen angelegt. Dies geschieht z.B. über den Code:

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

_localeId = currentUserSettings.LocaleId;
_timeZoneCode = currentUserSettings.TimeZoneCode;

// Get the user id
Guid userid = ((WhoAmIResponse)service.Execute(new WhoAmIRequest())).UserId;

// Get the calendar id of the user

Entity systemUserEntity = slos.Retrieve(„systemuser“, userid, 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“];

// Create a new inner calendar
Entity newInnerCalendar = new Entity(„calendar“);
newInnerCalendar.Attributes[„businessunitid“] = new EntityReference(„businessunit“, ((Microsoft.Xrm.Sdk.EntityReference)(userCalendarEntity[„businessunitid“])).Id);
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[„duration“] = 1440;
calendarRule.Attributes[„extentcode“] = 1;
calendarRule.Attributes[„pattern“] = „FREQ=DAILY;COUNT=1“;
calendarRule.Attributes[„rank“] = 0;
calendarRule.Attributes[„timezonecode“] = _timeZoneCode;
calendarRule.Attributes[„innercalendarid“] = new EntityReference(„calendar“, innerCalendarId);

// starting at 12:00
calendarRule.Attributes[„starttime“] = new DateTime(2013, 7, 28,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
slos.Update(userCalendarEntity);

// Daily Work Rule
Entity calendarRule1 = new Entity(„calendarrule“);

// set duration
calendarRule1.Attributes[„duration“] = 600;
calendarRule1.Attributes[„effort“] = 1.0;
calendarRule1.Attributes[„issimple“] = true;

// offset 120 i.e. 2 hours from start time (12:00)
calendarRule1.Attributes[„offset“] = 420;
calendarRule1.Attributes[„rank“] = 0;
calendarRule1.Attributes[„subcode“] = 1;
calendarRule1.Attributes[„timecode“] = 0;
calendarRule1.Attributes[„timezonecode“] = -1;
calendarRule1.Attributes[„calendarid“] = new EntityReference(„calendar“, innerCalendarId);

// Daily Break Rule
Entity calendarRuleBreak = new Entity(„calendarrule“);

// duration of 1 hour
calendarRuleBreak.Attributes[„duration“] = 60;
calendarRuleBreak.Attributes[„issimple“] = true;

// offset 120 i.e. 2 hours from start time (12:00)
calendarRuleBreak.Attributes[„offset“] = 720;
calendarRuleBreak.Attributes[„rank“] = 0;
calendarRuleBreak.Attributes[„subcode“] = 4;
calendarRuleBreak.Attributes[„timecode“] = 2;
calendarRuleBreak.Attributes[„timezonecode“] = -1;
calendarRuleBreak.Attributes[„calendarid“] = new EntityReference(„calendar“, innerCalendarId);
EntityCollection innerCalendarRules = new EntityCollection();
innerCalendarRules.EntityName = „calendarrule“;
innerCalendarRules.Entities.Add(calendarRule1);
innerCalendarRules.Entities.Add(calendarRuleBreak);
newInnerCalendar.Attributes[„calendarrules“] = innerCalendarRules;
newInnerCalendar.Attributes[„calendarid“] = innerCalendarId;
slos.Update(newInnerCalendar);

Zu diesem Code will ich eigentlich nur zwei Anmerkungen machen: Zum Einen ermittle ich über die currentUserSettings zunächst die Zeitzone des Users und lege diese nicht pauschal mit 110 an. Zum Anderen ist klar, dass ich die Daily Break Rule in Anzahl der gefundenen Pausen kopieren muss und die festen Werte von Dauer und Offset-Angaben natürlich dynamisch setzen muss.

Will man die erstellten Regeln und Kalender hinterher einem Update unterziehen, so könnte man sich die CalendarId und CalendarRuleIds in eine Hilfs-Entität speichern.

Der Einfachheit halber gebe ich diese anschließend einfach mal mit

Console.WriteLine(„CalendarId:“+innerCalendarId.ToString());

Console.WriteLine(„CalendarBreakRuleId:“+calendarRuleBreak.Id.ToString());

aus.

WorkingHour_InnerCalendarId_NoBreakIdWir entdecken jedoch ein Problem. Während wir die neu erzeugte CalendarId erhalten, können wir die CalendarRuleId der erzeugten Pause nicht direkt aus dem o.g. Code gewinnen. Zur Laufzeit ist die GUID noch leer.

Abhilfe schafft hier die Abfrage des erzeugten Kalenders nach dem letzten slos.Update – Befehl. Wir ergänzen hier unseren Code um folgende Zeilen:

QueryExpression query = new QueryExpression(„calendar“);
query.ColumnSet = new ColumnSet(true);
ConditionExpression condition = new ConditionExpression();
condition.AttributeName = „calendarid“;
condition.Operator = ConditionOperator.Equal;
condition.Values.Add(innerCalendarId.ToString());
query.Criteria.Conditions.Add(condition);

EntityCollection calendars = slos.RetrieveMultiple(query);
EntityCollection calendarrule = calendars[0].GetAttributeValue<EntityCollection>(„calendarrules“);

var RecordId = calendars[0].GetAttributeValue<EntityCollection>(„calendarrules“)[1][„calendarruleid“].ToString().ToUpper();

Console.WriteLine(„CalendarId:“+innerCalendarId.ToString());
Console.WriteLine(„CalendarBreakRuleId:“+calendarRuleBreak.Id.ToString());
Console.WriteLine(„CalendarBreakRuleAfterRetrieveId:“+RecordId.ToString());

In meiner zweiten EntityCollection habe ich alle Kalenderregeln und weiß aus meiner vorherigen Anlage, dass in [0] die Grundregel (Arbeitszeit) und in [1] meine Pause definiert wurde. In RecordId sollte ich somit die Id meiner zuvor angelegten Pause haben.

WorkingHour_InnerCalendarIdUnd siehe da – ich habe die gewünschte CalendarRuleId, die ich zur Identifikation benötige.

Zum Vergleich als Ausgabe der kompletten Kalenderabfrage:

<b:ArrayOfTimeInfo>

<b:TimeInfo>

<b:ActivityStatusCode>-1</b:ActivityStatusCode>

<b:CalendarId>44efe353-ccf6-e211-97f4-000c299a9455</b:CalendarId>

<b:DisplayText />

<b:Effort>0</b:Effort>

<b:End>2013-07-28T11:00:00Z</b:End>

<b:IsActivity>false</b:IsActivity>

<b:SourceId>47efe353-ccf6-e211-97f4-000c299a9455</b:SourceId>

<b:SourceTypeCode>4004</b:SourceTypeCode>

<b:Start>2013-07-28T10:00:00Z</b:Start>

<b:SubCode>Break</b:SubCode>

<b:TimeCode>Unavailable</b:TimeCode>

</b:TimeInfo>

<b:TimeInfo>

<b:ActivityStatusCode>-1</b:ActivityStatusCode>

<b:CalendarId>44efe353-ccf6-e211-97f4-000c299a9455</b:CalendarId>

<b:DisplayText />

<b:Effort>1</b:Effort>

<b:End>2013-07-28T15:00:00Z</b:End>

<b:IsActivity>false</b:IsActivity>

<b:SourceId>46efe353-ccf6-e211-97f4-000c299a9455</b:SourceId>

<b:SourceTypeCode>4004</b:SourceTypeCode>

<b:Start>2013-07-28T05:00:00Z</b:Start>

<b:SubCode>Schedulable</b:SubCode>

<b:TimeCode>Available</b:TimeCode>

</b:TimeInfo>

</b:ArrayOfTimeInfo>

Dieses Beispiel kann ich auch auf Arbeitsfreie Zeiten hin anwenden und habe in meiner Hilfsentität nunmehr die Objekt-IDs. Im Zusammenspiel zwischen der Hilfsentität, einer Abfrage der Attribute und der zuletzt vorgestellten Abfrage des Kalenders, kann ich aus der letzten EntityCollection die jeweiligen Objekte eindeutig identifizieren.

Zurück also zur Ausgangsfrage: Wie aktualisiere ich diese Kalenderregel im Anschluss?

Wie schon erwähnt wird eine direkte Aktualisierung durch das SDK nicht unterstützt. Aber dank der zuvor dargestellten Identifikation unserer Objekte können wir nunmehr mit:

QueryExpression query = new QueryExpression(„calendar“);
query.ColumnSet = new ColumnSet(true);
ConditionExpression condition = new ConditionExpression();
condition.AttributeName = „calendarid“;
condition.Operator = ConditionOperator.Equal;
condition.Values.Add(„44efe353-ccf6-e211-97f4-000c299a9455“);  // Id bekommen wir aus QueryMultipleSchedulesResponse
query.Criteria.Conditions.Add(condition);
EntityCollection calendars = slos.RetrieveMultiple(query);
EntityCollection calendarrule = calendars[0].GetAttributeValue<EntityCollection>(„calendarrules“);

calendars[0].GetAttributeValue<EntityCollection>(„calendarrules“)[1][„duration“] = „600“;

slos.Update(calendars[0]);

eine Aktualisierung der Dauer der Pause erwirken. Hierzu haben wir erst den kompletten Kalender abgefragt und aus der EntityCollection der calendarrule setzen wir nunmehr für unser [1]-Objekt (Pause) eine neue Dauer. Anschließend können wir mit dem slos.Update Befehl den kompletten Kalender wieder aktualisieren.

Fazit: Auch Kalenderregeln lassen sich aktualisieren, sofern man die Tricks und Kniffe des Arbeitszeitenkalenders sich zu Gunsten gemacht hat. Und für Euch kompakt noch einmal die “goldenen Regeln” zusammengefasst:

1.) Arbeitsfreie Zeiten bestehen aus einem unabhängigen Kalender + einer Kalenderregel mit der Arbeitsfreien Zeit

2.) Der Arbeitszeitenkalender des Tages ist nicht zwingend ein Tagesarbeitszeitenkalender

3.) Vor einer Aktualisierung immer sicherstellen, dass man einen Tagesarbeitszeitenkalender hat – wenn nicht, erst den Pattern auslesen und einen neuen Arbeitszeitenkalender erstellen. Dabei gilt:

            a) erst den Arbeitszeitenkalender erstellen,

            b) anschließend die Arbeitsfreien Zeiten auf Basis des neu erstellten Kalenders erzeugen

4.) An die CalendarId kommen wir mit einfachen Mitteln heran. Um jedoch die CalendarRuleIds zu ermitteln, muss man den erstellten Kalender Abfragen und die erzeugten IDs ermitteln. Es hilft, sich diese in einer Hilfsentität abzulegen.

5.) Um eine Kalenderregel zu aktualisieren müssen wir immer den kompletten Kalender aktualisieren. Bei einer Arbeitsfreien Zeit ist dies sehr einfach, da immer im Objekt [0] die Definition der Arbeitsfreien Zeit erfolgt. Wir können hier also immer direkt z.B. eine neue Dauer setzen. Bei Arbeitszeiten inkl. Pausen müssen wir uns unser Objekt zuerst ermitteln.

Wenn Ihr diese Regeln in Euren Projekten anwendet, stehen Euch zahlreiche Möglichkeiten rund um den Arbeits-zeitenkalender im CRM zur Verfügung.

Ich wünsche Euch viel Spass bei der Umsetzung Eurer Ziele.

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