25. April 2013 · von Steffen Nörtershäuser

Erklärt: Automatische Berechung von Mehrwertsteuern in CRM 2011

Wie bereits in unserem vorherigen Artikel über die automatische Berechnung von Mehrwertsteuern angekündigt, werde ich an dieser Stelle die technische Umsetzung der dort aufgezeigten beschriebenen Lösung beleuchten.

Mögliche Ansätze
Bei der Berechnung der Mehrwertsteuer gibt es aus technischer Sicht in CRM zwei Ansätze einer Implementierung: Als Javascript oder in Form eines Plugin’s. Bei dieser Gegenüberstellung haben sich folgende Nachteile bei der Umsetzung als Javascript herauskristallisiert:

  • Grundsätzlich wird Javascript-Funktionalität nur ausgeführt, wenn die Daten durch einen Nutzer in der CRM Oberfläche eingepflegt werden. Wird jedoch beispielsweise ein Vorgangsakten-Produkt aus einem Plugin heraus angelegt oder durch einen Datenimport, würde die Mehrwertsteuer nicht berechnet werden.
  • Setzt man die Berechnung im OnSave-Event des Formulares oder bei der Auswahl eines Produktes um, sind die Daten von vorhandenen Produkten noch nicht geladen und die Felder Betrag etc. nicht berechnet. Man kann somit nicht Gebrauch von der CRM-internen Preisberechnung machen und muss alle notwendigen Daten selbst laden und die Preise berechnen.
  • Setzt man die Berechnung im OnLoad-Event des Formulares um, sind die Daten befüllt, nachdem der Nutzer gespeichert hat. Trägt man nun Angaben zur Steuer ein, muss der Nutzer erneut auf Speichern drücken, um diese Berechnung persistent in der Datenbank zu sichern.

Setzt man die Funktionalität in einem CRM Plugin als Post-Operation um, fallen all diese Nachteile weg. Aus diesem Grund haben wir uns für diesen Weg entschieden.

Plugin Funktionalität
Wie oben bereits erwähnt wurde das Plugin als Post-Operation umgesetzt. Zu diesem Zeitpunkt ist der Datensatz bereits mit den berechneten Preiswerten in der Datenbank abgespeichert und diese Daten können somit für die Berechnung der Mehrwertsteuer herangezogen werden. Anzumerken ist hier jedoch, daß nicht ein Pre- oder Post-Image genutzt werden kann, sondern die Daten über die Webservices abgefragt werden müssen, da die Images noch die alten Daten beinhalten. Das Plugin ist für die Update-Nachricht registriert. Diese wird durch die interne CRM-Preisberechnung auch bei dem Erzeugen eines Verkaufschancenprodukts, Auftragsprodukts etc. aufgerufen.

Der Ablauf des Plugins sieht somit folgendermaßen aus:

  1. Abfragen der Entitätsdaten
  2. Prüfen ob das Feld „Steuer nicht berechnen“ auf true gesetzt ist, wenn ja abbrechen
  3. Prüfen ob der Kunde der Verkaufschance, des Angebots, des Auftrags oder der Rechnung ein Auslandskunde ist, wenn ja abbrechen
  4. Wenn ein vorhandenes Produkt zugeordnet ist, Mehrwertsteuersatz abfragen (Default oder zugeordneter) und prüfen, ob dieser für die aktuelle Entität gültig ist. Wenn er nicht gültig ist abbrechen.
  5. Wenn kein vorhandes Produkt zugeordnet ist, Mehrwertsteuersatz am Verkaufschancenprodukt (oder Auftragsprodukt, etc.) abfragen. Wenn kein Mehrwertsteuersatz zugeordnet ist, abbrechen.
  6. An dieser Stelle sind die notwendigen Beträge und der Mehrwertsteuersatz bekannt und der Mehrwertsteuerbetrag kann berechnet werden und in der Entität gespeichert werden.

Der Code ist an den meisten Stellen trivial, da er primär aus Webservice-Anfragen zum Abfragen von Daten besteht. Ich habe hier allerdings mit Late Bound Klassen gearbeitet, um auf einfache Weise die gleiche Funktionalität für Verkaufschancenprodukte, Auftragsprodukte etc. arbeiten zu können. Da die wichtigen Felder wie Betrag, Steuer oder Rabatt auf diesen Entitäten überall gleich benannt sind, ist dies sehr einfach und verhindert redundanten Programmcode. So können beispielsweise die Daten für jede der vier Entitäten durch folgenden Aufruf abgefragt werden:

//Get current Values of entity
ColumnSet currentValueColumns = new ColumnSet(PRODUCTID_FIELD_NAME, BASEAMOUNT_FIELD_NAME, MANUALDISCOUNTAMOUNT_FIELD_NAME, NOTAX_FIELD_NAME, VATCONFIGID_FIELD_NAME, parentEntityFieldName);
Entity currentValue = localContext.OrganizationService.Retrieve(localContext.PluginExecutionContext.PrimaryEntityName, localContext.PluginExecutionContext.PrimaryEntityId, currentValueColumns);
if (currentValue == null)
{
    return;
}

Alles weitere ist das Anwenden der im vorherigen Artikel beschriebenen Formeln und Bedingungen.

JavaScript
Neben der zentralen Plugin-Funktionalität wurden auch mehrere, kleinere Hilfsfunktionalitäten in JavaScript implementiert, um den Nutzern ein bequemes Arbeiten zu ermöglichen. Zum einen wird das Lookup-Feld Mehrwertsteuer ausgeblendet, wenn ein vorhandenes Produkt ausgewählt wird. Wählt der Nutzer ein Produkt manuell eingeben aus, wird dieses Lookup-Feld eingeblendet und mit der gültigen Standard-Mehrwertsteuer vorbelegt. Des Weiteren gibt es eine Funktion, welche dem Nutzer einen Hinweis anzeigt, dass der Kunde der Verkaufschance ein ausländischer Kunde ist. Hierdurch wird sichergestellt, dass der Benutzer direkt versteht, warum an dieser Stelle die Mehrwertsteuer nicht berechnet wird. Dieser Hinweis sieht beispielsweise so aus:
Mwst_FirmaAuslaender

Diese Funktionalitäten sind auf drei Javascript Funktionen aufgeteilt. Auf diese Weise muss ein Nutzer der Bibliothek nur die für ihn notwendigen Funktionen einbinden. In meinen Augen ist die Funktionalität des Nutzerhinweises hier am spannensten, weshalb ich hier das Codesnippet zeigen möchte:

/** brief Displays a notifaction if the customer of the parent entity is a foreigner
  */
function VATPluginSetForeignerNotification() {
    //Validate data
    if (typeof XrmServiceToolkit == "undefined" || Xrm.Page.getControl(VATPLUGIN_NO_TAX_FIELD) == null || Xrm.Page.getControl(VATPLUGIN_VAT_CONFIG_ID_FIELD) == null) {
        return;
    } 

    //Get parentcustomer Id
    var parentEntityFieldName = "";
    var parentEntity = "";
    var entityName = Xrm.Page.data.entity.getEntityName();
    switch (entityName) {
        case "quotedetail":
            parentEntityFieldName = "quoteid";
            parentEntity = "quote";
            break;
        case "opportunityproduct":
            parentEntityFieldName = "opportunityid";
            parentEntity = "opportunity";
            break;
        case "salesorderdetail":
            parentEntityFieldName = "salesorderid";
            parentEntity = "salesorder";
            break;
        case "invoicedetail":
            parentEntityFieldName = "invoiceid";
            parentEntity = "invoice";
            break;
    }

    //Retrieve entity, parententity and customer
    var cols = [ VATPLUGIN_CUSTOMERID_FIELD ];
    var retrievedParentEntity = XrmServiceToolkit.Soap.Retrieve(parentEntity, Xrm.Page.getAttribute(parentEntityFieldName).getValue(), cols);
    if (retrievedParentEntity == null || retrievedParentEntity.attributes[VATPLUGIN_CUSTOMERID_FIELD] == null) {
        return;
    }

    cols = [ VATPLUGIN_BOOKINGGROUP_FIELD ];
    var retrievedCustomerEntity = XrmServiceToolkit.Soap.Retrieve(retrievedParentEntity.attributes[VATPLUGIN_CUSTOMERID_FIELD].logicalName, retrievedParentEntity.attributes[VATPLUGIN_CUSTOMERID_FIELD].id, cols);
    if(retrievedCustomerEntity == null || retrievedCustomerEntity.attributes[VATPLUGIN_BOOKINGGROUP_FIELD] == null) {
        return;
    }

    //Check if customer is a foreigner add notification for user that tax will not be calculated
    if (retrievedCustomerEntity.attributes[VATPLUGIN_BOOKINGGROUP_FIELD].value != VATPLUGIN_BOOKINGGROUP_NATIVE) {
        var notificationsArea = document.getElementById('crmNotifications');
        notificationsArea.AddNotification("VATPlugin_Info_Foreigner", 3, "Mwst.", "Übergeordneter Kunde ist kein Inländer. Steuer wird nicht berechnet.");

        Xrm.Page.getControl(VATPLUGIN_NO_TAX_FIELD).setDisabled(true);
        Xrm.Page.getControl(VATPLUGIN_VAT_CONFIG_ID_FIELD).setDisabled(true);
    }
}

Zu Beginn wird hier der Name der Entität, welche dem Produkt übergeordnet ist, ermittelt (Verkaufschance über dem Verkaufschancenprodukt, etc.). Anschließend kann darüber die Verkaufschance abgefragt werden und hierrüber der Kunde. Wie in der Lösungsbeschreibung gezeigt ist dieses am Kunden abgelegt, ob es sich um einen inländischen Kunden handelt oder nicht. Ist der Kunde kein Inländer wird nun die „notificationArea“ abgefragt. Dieses Element mit der Id „crmNotifications“ ist ein besonderes HTML-Objekt, an dem die Benachrichtigungen an den Usern hängen. Über die Funktion AddNotification kann man nun eine neue Benachrichtigung hinzufügen. Die 3 ist dabei der Schweregrad der Benachrichtigung. in diesem Fall wird eine Info angezeigt. Eine Zwei ist hier gleichbedeutend mit einer Warnung. Eine Eins zeigt einen kritischen Fehler an. Anschließend werden hier noch die die Steuerelemente, welche mit der Mehrwertsteuer zu tun haben deaktiviert.

Mapping der neuen Felder
Ein weiterer Punkt, der bedacht werden muss, ist die Vererbung, der neu hinzugefügten Felder von Verkaufschancenprodukten zu Angebotsprodukten etc. Hierfür kann das CRM-interne Mapping genutzt werden. Dieses muss lediglich um die neuen Felder erweitert werden. Wie man diese Erweiterung durchführt ist in folgendem englischem Blogartikel schön beschrieben: Mapping MSCRM fields from Opportunity Product to Quote Product

Fazit
Durch diese gut konfigurierbare Lösung kann man auf einfache Weise CRM um die Funktionalität, automatisiert die Mehrwertsteuer zu berechnen, erweitern.

Die hier vorgestellte Lösung haben wir ebenfalls auf Codeplex hochgeladen.



Diesen Blogeintrag bewerten:

3 Stimmen mit durchschnittlich 4/5 Punkten

Haben Sie Fragen zu diesem Artikel oder brauchen Sie Unterstützung?

Nehmen Sie mit uns Kontakt auf!

Wir unterstützen Sie gerne bei Ihren Vorhaben!


Schreibe einen Kommentar

Kontakt.
Lassen Sie sich von uns beraten
Wir freuen uns über Ihr Interesse an unseren Leistungen. Hinterlassen Sie
uns Ihren Namen, Ihre Telefonnummer und E-Mail Adresse – wir melden
uns schnellstmöglich bei Ihnen.
Kontakt aufnehmen