Results for tag "crm"

Fehler bei Plugin-Registrierung – The Web Service plug-in failed

Hallo Leute,

nach längerer Abwesenheit mal wieder was neues zum Thema CRM.
Wir mussten einen unserer CRM-Server neu aufsetzen und hatten nach der Registrierung der Plugins schlimme Probleme.

Fehlermeldung (im Eventviewer) war die folgende:

The Web Service plug-in failed in OrganizationId EntityName: sdk message; Stage: 30; MessageName: RetrieveMultiple

Unsere Recherche hat uns schließlich zum Blog von KAUSTUBH GHANEKAR geführt.

Er schreibt:

I registered following dll files from Server/CRMWeb/bin into the Global Assembly Cache of CRM server.

Microsoft.Xrm.Sdk.dll
Microsoft.Crm.Extensibility.dll

Und tatsächlich, das hat das Problem behoben.

Ich hoffe ich konnte euch ein paar Minuten eurer Zeit sparen 🙂

Beste Grüße
Christian

CRM 2011 – RibbonsDiffXml – Verwenden von DisplayRules (ValueRule)

Hi Leute,

heute möchte ich euch ein einfaches Beispiel liefern, wie sich (mittels CRM-Standard) div. Ribbon-Buttons an Hand div. Regeln ein bzw. ausblenden lassen.
Dafür ist es, wie (fast) immer bei Änderungen an Ribbon-Buttons notwendig, dass Ihr zunächst das Menüband in eure Lösung aufnehmt und die Lösung downloaded und extrahiert. Wie das genau geht, habe ich schon mehrfach beschrieben, u.a. hier.

Dieses Beispiel geht davon aus, dass Ihr bereits einen eigenen Ribbon-Button hinzugefügt habt. Wie das geht, beschreibe ich ebenfalls hier.

Öffnet nun die „Customizations.xml“ und sucht die Entität die Ihr verändern wollt und öffnet dort die Node „RibbonDiffXml“. Euer Custom-Ribbon-Button hat das Attribute „Command“, wo auf eine „CommandDefinition“ verwiesen wird. Sucht dieses Element. Es sollte in etwa wie folgt aussehen:

[sourcecode language=“xml“]
<CommandDefinition Id="Sample.SubGrid.xyz_auftrag.Button1">
<EnableRules />
<DisplayRules />
<Actions>
<JavaScriptFunction Library="$webresource:xyz_cs_auftrag" FunctionName="Storno"></JavaScriptFunction>
</Actions>
</CommandDefinition>
[/sourcecode]

Fügt nun den DisplayRules eine Regel hinzu, das geht wie folgt:

[sourcecode language=“xml“]
<DisplayRules>
<DisplayRule Id="ShowStorno" />
</DisplayRules>
[/sourcecode]

Etwas weiter unten im RibbonDiffXml müssen wir die Rule, auf die wir eben verwiesen haben („ShowStorno“) auch noch definieren. Sucht dafür den Node „RuleDefinitions“ und fügt eine DisplayRule hinzu. Das könnte nun wie folgt aussehen:

[sourcecode language=“xml“]
<RuleDefinitions>
<DisplayRules>
<DisplayRule Id="ShowStorno">
[ …. ]
</DisplayRule>
</DisplayRules>
<EnableRules />
</RuleDefinitions>
[/sourcecode]

Eine DisplayRule kann nun mehrere Arten von Conditions hinterlegt bekommen. Lt. SDK sind das die folgenden:

[sourcecode language=“xml“]
<CrmClientTypeRule />
<CrmOfflineAccessStateRule />
<CrmOutlookClientTypeRule />
<CrmOutlookClientVersionRule />
<EntityPrivilegeRule />
<EntityPropertyRule />
<EntityRule />
<FormEntityContextRule />
<FormStateRule />
<MiscellaneousPrivilegeRule />
<OrganizationSettingRule />
<OrRule />
<OutlookRenderTypeRule />
<OutlookVersionRule />
<PageRule />
<ReferencingAttributeRequiredRule />
<RelationshipTypeRule />
<SkuRule />
<ValueRule />
[/sourcecode]

Ich denke die meisten von uns werden die Notwendigkeit haben, an Hand eines bestimmten Attributs im Formular, einen Button ein oder ausblenden zu müssen. Deswegen gehe ich in diesem Beispiel nur auf die ValueRule ein.

Diese wiederum gestaltet sich in der Regel sehr einfach:

[sourcecode language=“xml“]
<ValueRule Field="xyz_type" Value="908940000"></ValueRule>
[/sourcecode]

Solltet Ihr an anderen Rules interessiert sein, postet einfach einen Kommentar und ich helfe euch weiter. Oder seht selbst in der SDK nach.

Verpackt nun wieder sämtliche Dateien in ein ZIP-Archiv und importiert dieses in eure CRM-Instanz.

Cheers,
Christian

CRM 2011 – N:M Relation mit selber Entity – entityA n : m entityA – Retrieve Items

Mal wieder etwas spannendes aus der CRM Welt. Ich habe hier schon mal über Relationen gebloggt (siehe: Relationen). Nun kam mir etwas spannendes unter.

Und zwar musste eine N:M Relation abgebildet werden, wobei es sich auf beiden Seiten um die selbe Entität handelt. Also eine Adresse kann N andere Adressen referenziert haben und umgekehrt.

Betrachtet man das in nem Classdiagram, sieht’s wie folgt aus:

Man sollte denken, dass die Relation nun zwei ID’s hat, welche beide „entityiaid“ heissen. Das ist aber nicht der Fall. CRM hängt dem zweiten Schlüssel ein „one“ an. Das ist irrsinnig wichtig zu beachten, da es sonst zu kryptischen Fehlern bei FetchXml oder QueryExpressions kommt.

Cheers,
Christian

CRM 2011 – Verkaufschance abschließen – „gewonnen“/“verloren“

Nachfolgend findet Ihr zwei nützliche Funktionen, mit welchen Ihr programmatisch eine Verkaufschance abschließen (als ‚gewonnen‘ schließen bzw. als ‚verloren‘ schließen) könnt:

[sourcecode language=“csharp“]

/// <summary>
/// Marks as lost.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entityId">The entity id.</param>
/// <returns></returns>
public Result MarkAsLost(IOrganization organization, string entityId)
{
try
{
LoseOpportunityRequest req = new LoseOpportunityRequest();
Entity opportunityClose = new Entity("opportunityclose");
opportunityClose.Attributes.Add("opportunityid", new EntityReference("opportunity", new Guid(entityId)));
opportunityClose.Attributes.Add("subject", "opportunity lost");
req.OpportunityClose = opportunityClose;

OptionSetValue o = new OptionSetValue();
o.Value = 4;
req.Status = o;
LoseOpportunityResponse resp = (LoseOpportunityResponse) Helper.OrgService(organization).Execute(req);

return Helper.CreateResult("ok", Result.StatusCodes.Ok, null);
}
catch (Exception ex)
{
return Helper.CreateResult(ex.Message, Result.StatusCodes.Error, null);
}
}

/// <summary>
/// Marks as won.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entityId">The entity id.</param>
/// <returns></returns>
public Result MarkAsWon(IOrganization organization, string entityId)
{
try
{
WinOpportunityRequest req = new WinOpportunityRequest();
Entity opportunityClose = new Entity("opportunityclose");
opportunityClose.Attributes.Add("opportunityid", new EntityReference("opportunity", new Guid(entityId)));
opportunityClose.Attributes.Add("subject", "opportunity won");
req.OpportunityClose = opportunityClose;

//The Status parameter corresponds to Status Reason in the Microsoft CRM application. If you pass -1 for this parameter, the platform sets the status to the appropriate value for the Microsoft CRM application.
req.Status = new OptionSetValue(-1);

WinOpportunityResponse res = (WinOpportunityResponse)Helper.OrgService(organization).Execute(req);
return Helper.CreateResult("ok", Result.StatusCodes.Ok, null);
}
catch (Exception ex)
{
return Helper.CreateResult(ex.Message, Result.StatusCodes.Error, null);
}
}

[/sourcecode]

Happy coding!

CRM 2011 – Custom View – Filter auf N:M Relation

Folgendes Beispiel:

Ihr habt in eurem Projekt folgende Entitätshierarchie:

Adresse ( n:m ) Gebäude ( n: 1 ) Standort

Am Formular des Standorts soll der Benutzer nun zunächst in einem Lookup das entsprechende Gebäude auswählen können (kein Filter). Nach Auswahl des Gebäudes sollen nur noch jene Adressen ausgewählt werden können, welche eine Verknüpfung mit dem ausgewählten Gebäude haben. Vom Prinizp eine Funktionsweise wie bei einer dependend Dropdownlist.

Normalerweise greift man jetzt einfach zu einem „FetchXML“ und baut sich das Konstrukt entsprechend auf. Aber hier beginnts zu haken: Beim Einrichten des Filters merkt man schnell, dass sich eine N:M-Relation nicht, wie z.B. eine 1:1/1:n-Relation, an einem Fremdschlüssel „aufhängt“, es fehlt also das Filter-Attribut. Jetzt wird man im Internet relativ schnell fündig, dass man in solchen Fällen einfach eine „Linked-Entity“ in das Fetch-Xml einbauen kann/muss. Problem hierbei: die addCustomView-Methode erwartet eine Entität, keine Relation.

Die Lösung sieht nun wie folgt aus:

Zunächst baut man das Fetch-XML wie gewohnt auf und lässt es mittels „Linked Entity“ auf die Relation verweisen. Innerhalb der Linked Entity, welche ja eig. keine Entität sondern eine Relation ist, hat man Zugriff auf die beiden Primary-Key-Felder der Relation. In meinem Fall „Adressid“ und „Gebaeudeid“. Dort lässt sich dann der Filter entsprechend einrichten.

Das fertige Fetch-XML sieht nun in meinem Fall wie folgt aus:

[sourcecode language=“xml“]
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
<entity name="<strong>new_address</strong>">
<attribute name="new_name" />
<attribute name="new_line1" />
<attribute name="new_city" />
<attribute name="new_stateorprovince" />
<link-entity name="<strong>relationname</strong>" from="primarykey" to="primarykey" >
<all-attributes/>
<filter type="and">
<condition attribute="primarykey" operator="eq" value="{1BF2077D-B6F0-E011-8481-005056A20041}" />
</filter>
</link-entity>
<order attribute="createdon" decending="true" />
</entity>
</fetch>
[/sourcecode]

Happy programming!
Chris

CRM 2011 – Ribbons via Javascript deaktivieren (disable Ribbons with javascript)

Jeder CRM Developer wird wohl schon vor folgendem Problem gestanden sein: Einen Ribbon Button deaktivieren, weil dieser im aktuellen Kontext keinen Sinn macht. Andere Buttons verwirren den Kunden, wiederrum andere sollen überhaupt verschwinden.

In einem meiner Projekte hatten wir nun die Situation, dass wir in einem bestimmten Prozess-Stati alle Ribbon Buttons von ca. 15-20 Entitäten sperren sollten. Inklusive der Original-Ribbon-Buttons (Datei, Entität, Hinzufügen, Listentools, etc.). Microsoft stellt dafür keinen vernünftigen Weg zur Verfügung, schlägt nur die Customizations.xml vor, was für ca. 400 Buttons ein enormer Zeitaufwand wäre, abgesehen von der extremen fehleranfälligkeit.

Ich habe daher, trotz aller Warnungn von div. CRM-Spezialisten („dieser Weg wird von MS nicht supportet“, etc.), einen Weg mittels Javascript gefunden, mittels welchem sich die Ribbon Buttons deaktivieren oder ganz ausblenden lassen, seht selbst:

[sourcecode language=“javascript“]

function HideRibbonButtons () {

var dateiButton = parent.document.getElementById("jewelcontainer"); // versteckt den "Datei"-Ribbon. Diesen zu deaktivieren habe ich nicht getestet!
dateiButton.style.display = ’none‘;

var ribbon = parent.document.getElementById("Mscrm.Ribbon");
var container = ribbon.childNodes[2].childNodes[0]; // container = Ein Tab, oberhalb der dargestellten Ribbons.

for (var i = 0; i < container.childNodes.length; i++) {
var group = container.childNodes[i];

if (group.id.indexOf(‚Isv‘) > 0) { // selbst hinzugefügte tabs enthalten bei mir den Hinsweis "Isv" und werden explizit ausgenommen.
// do nothing…
}
else {
DisableAllChilds(group);
}
}
}

// iteriert über alle child elements, bis das element mit der Rolle "button" gefunden wurde.
// An dieser Stelle kann dann die Deactivate oder Hide Funktionalität aufgerufen werden
function DisableAllChilds(element) {

for (var i = 0; i < element.childNodes.length; i++) {
var subElement = element.childNodes[i];

if (subElement.childNodes.length > 0)
DisableAllChilds(subElement);
}

if (element.role == "button") {

DeactivateRibbonButton(element);

}

}

// nachdem sich ein button nicht so ohne weiteres deaktivieren lässt (der dahinterliegende Event lässt sich nicht ausschalten),
// habe ich das element einfach geklont und das original-Element durch das erzeugte Element ersetzt.
function DeactivateRibbonButton(element) {
var link = document.createElement("a");
link.role = "button";
link.className = "ms-cui-ctl-large ms-cui-disabled";

try {
link.appendChild(element.childNodes[0]); // image/buttion
link.appendChild(element.childNodes[0]); // label
}
catch (exception) {
// do nothing…
}

element.parentNode.replaceChild(link, element);
}

// einen Button ausblenden ist hingegen recht easy und sollte keine negativen Auswirkungen haben.
function HideRibbonButton(element) {
element.style.display = ’none‘;
}

[/sourcecode]

Wichtig ist es noch zu wissen, dass das CRM nur den ersten Tab voll lädt, bedeutet, die Ribbons für alle weiteren Tabs werden erst bei erstmaligem Aufruf nachgeladen. Daher ist es notwendig, die Funktion „HideRibbonButtons()“ mehrmals aufzurufen, ich mache dieses mittels einem „setInterval“. Einen eleganteren Weg habe ich leider nicht finden können.

Abschließend nochmals explizit der Hinweis: Dies ist ein von Microsoft nicht unterstützter Weg! Bitte überlegt euch daher gut, ob ihr diesen gehen wollt!

Viel Spaß beim Nachmachen!

CRM 2011 – Custom View für Subgrid

In einem anderen Blog beschreibe ich, wie man einen Custom View zu einem Lookup hinzufügen kann. Heute habe ich die gleiche Funktionalität für ein Subgrid benötigt. Nachdem ich nicht gleich eine Lösung dafür gefunden habe, möchte ich euch meine nicht vorenthalten bzw. die Unterschiede zeigen:

[sourcecode language=“javascript“]
//Add your new view to the Lookup’s set of availabe views and make it the default view
$("#" + lookupFieldId).attr("disableViewPicker", "0");
Xrm.Page.getControl(lookupFieldId).addCustomView(viewId, entityName, viewDisplayName, fetchXml, layoutXml, viewIsDefault);
$("#" + lookupFieldId).attr("disableViewPicker", "1");
[/sourcecode]
So funktionierts für ein Lookup…

[sourcecode language=“javascript“]
var grd = document.getElementById(lookupFieldId);
if (grd.readyState != "complete") {
setTimeout("SetLookupForm(…params…);", 500);
}

grd.control.setParameter("fetchXml", fetchXml);
grd.control.refresh();
[/sourcecode]
… und so für ein Subgrid.

Achtet besonders auf die Abfrage auf „readystate“. Wenn Ihr die Funktionalität im OnLoad-Event verwendet, muss das Grid noch nicht geladen sein!! In diesem Fall rufe ich die Funktion einfach erneut auf…

Viel Spaß beim Nachmachen!

CRM 2011 – Nummerngenerierung (Laufnummer erzeugen)

Leider ein sehr leidiges Thema in der CRM-Entwicklung. Microsoft arbeitet mit GUIDs als Primärschlüssel, was einerseits natürlich sinnvoll ist, andererseits keinen „Schlüssel“ bzw. „Autowert“ zur Verfügung stellt, mit welchem man arbeiten kann. CRM bietet stattdessen die sog. „Automatische Nummerierung“ an. Leider hat diese Funktionalität einen riesen Haken: Die Funktionalität steht nur für die Entitäten Verträge, Anfragen, Angebote, Aufträge, Artikel, Rechnungen und Kampagnen zur Verfügung.

Bei einem größeren Projekt, z.B. wenn man komplett eigene Geschäftsprozesse, ist das natürlich ein wahnsinn. Keine Ahnung was Microsoft sich da hat einfallen lassen. Vermutlich kommt hier das übliche Argument zum tragen: „Für 95% der Kunden reicht’s aus. Wir wollen unseren Implementierungspartnern nicht die komplette Arbeit abnehmen.“. Naja, ich war noch nie ein großer Fan dieser Strategie…

Um das Problem zu lösen, habe ich eine eigene Entität erstellt, welche ich „Laufnummer“ genannt habe. Diese Entität hat keine Ansichten/Formulare, sie besitzt lediglich die Felder „neu_laufnummerid“ und „neu_nummer“.

Nachdem ich in meinen CRM-Projekten stark auf Webservices setze, habe ich eine Klasse zum Erzeugen der Laufnummer (nach Schema JJMMTT001) entwickelt, welche diese Nummer für den aktuellen Tag (in der Entität) sucht und ggf. hochzählt. Falls die Laufnummer (für diesen Tag) noch nicht generiert wurde, wird sie natürlich neu erzeugt:

[sourcecode language=“csharp“]
public class Laufnummer
{
public const string EntityName = "neu_laufnummer";
public const string EntityPrimaryKey = "neu_laufnummerid";

/// <summary>
/// Finds the highest verkaufsprojekt nummer.
/// </summary>
/// <param name="organization">The organization.</param>
/// <returns></returns>
public static string GetNew(contracts.IOrganization organization)
{
string laufNummer;
string suffix = StringHelper.FillLeftZero(1, 3);

QueryExpression query = new QueryExpression();
query.ColumnSet.AddColumns("neu_laufnummerid", "neu_nummer");
query.EntityName = EntityName;
query.AddOrder("neu_nummer", OrderType.Descending);
query.PageInfo = new PagingInfo();
query.PageInfo.Count = 1;
query.PageInfo.PageNumber = 1;

EntityCollection results = Helper.OrgService(organization).RetrieveMultiple(query);

if (results.Entities.Count > 0)
{
string lastNumber = results[0].Attributes["wit_nummer"].ToString();
if (lastNumber.Length < 9)
{
throw new InvalidProgramException("Die letzte Laufnummer war weniger als 9 Zeichen lang!");
}

string lastDayPattern = lastNumber.Substring(0, 6);
int lastCount = Convert.ToInt32(lastNumber.Substring(6, 3));
if (lastDayPattern == GetCurrentDayPattern())
{
lastCount++;
laufNummer = lastDayPattern + utils.StringHelper.FillLeftZero(lastCount, 3);
}
else
{
laufNummer = GetCurrentDayPattern() + suffix;
}

}
else
{
laufNummer = GetCurrentDayPattern() + suffix;
}

var entity = new Entity(EntityName);
entity.Attributes["neu_nummer"] = laufNummer;
EntityHelper.CreateEntity(organization, entity);

return laufNummer;
}

/// <summary>
/// Gets the current day pattern.
/// </summary>
/// <returns></returns>
public static string GetCurrentDayPattern()
{
return utils.StringHelper.FillLeftZero(Convert.ToInt32(DateTime.Now.Year.ToString().Substring(2, 2)), 2) +
utils.StringHelper.FillLeftZero(DateTime.Now.Month, 2) +
utils.StringHelper.FillLeftZero(DateTime.Now.Day, 2);
}
[/sourcecode]

Dieses Webservice kann man nun z.B. im OnSave-Event der Entität oder durch einen sonstigen Event triggern.
Viel Spaß beim Anwenden.

Cheers,
Chris