Türchen 07: Fixierung von Bruttopreisen - Gleiche Preise für alle Kunden

Ein typischer deutscher Online-Shop beliefert hauptsächlich Kunden innerhalb Deutschlands. Kunden aus dem umliegenden Ausland bilden einen geringen Anteil. Die Steuereinstellungen sind mit German Setup schnell eingerichtet: die Liste der Steuersätze und -Zonen wird mit 25 Ländern gefüllt und für alle Länder die deutsche Mehrwertsteuer in Höhe von 19% bzw. 7% eingestellt. Somit erwerben alle Kunden, auch die aus dem Ausland, die Ware zu Bruttopreisen inklusive der deutschen Mehrwertsteuer, und alle sind glücklich.

Spannend wird es erst, wenn der Nettoumsatz der ins Ausland verkauften Ware eine landesspezifische Lieferschwelle überschreitet. In diesem Fall hat der Händler sich im Bestimmungsland umsatzsteuerlich zu registrieren und mit der Steuer dieses Landes abzurechnen.

Fallbeispiel: Ein deutscher Händler verkauft u. a. nach Dänemark, wobei sein Jahresumsatz mit dänischen Kunden die Lieferschwelle überschreitet. Die Merwertsteuer in Dänemark beträgt für alle Waren 25%.

In diesem Fallbeispiel bekommt ein dänischer Kunde einen höheren Preis berechnet. Kostet ein Produkt 100,- EUR inkl. 19% Merwertsteuer, muss der dänische Kunde für das gleiche Produkt 105,- EUR inkl. 25% MwSt. bezahlen (100,- EUR / 1,19 * 1,25 = 105,- EUR). Das Fatale dabei ist dass ein neuer Kunde den höheren Preis erst im letzten Checkout-Schritt angezeigt bekommt, da erst durch die Eingabe seiner Lieferadresse sein Bestimmungsland und damit der höhere Steuersatz feststanden.

Ein registrierter Kunde, der eine Adresse in Dänemark als Standardlieferadresse in seinem Benutzerkonto gewählt hat, bekommt im eingeloggten Zustand die höheren Preise schon im Katalog angezeigt. Die vom Händler in grafischen Bannern beworbene Preise stimmen nicht mehr. Auch hat er den unrunden Neuner-Preis nicht mehr unter Kontrolle (der Kunde kommt z. B. 10,40 EUR statt 9,90 EUR angezeigt).

Aus den genannten Gründen möchte der Händler allen Kunden zum gleichen Endkundenpreis verkaufen. Dabei geht er gerne den Kompromiss ein zu einem niedrigeren Nettopreis zu verkaufen. Er möchte also zu einem festen Brutto- und zu einem variablen Nettopreis verkaufen. Magento rechnet aber mit festen Nettopreisen!

Berechnung des Bruttopreises in Magento

Für die Berechnung des Kaufpreises wird die passende Steuerregel herangezogen (Verkäufe > Steuer > Steuerregeln verwalten), die von den drei Variablen abhängt:

  • Kundengruppe - Kunden > Kundengruppen / Verkäufe > Steuer > Kundensteuerklassen
  • Bestimmungsland - Verkäufe > Steuer > Steuerzonen und -Sätze verwalten
  • Produktsteuerklasse - Verkäufe > Steuer > Artikelsteuerklassen

Die Kundengruppe wird dabei über die Kundensteuerklasse verknüpft. Die Steuerregel bestimmt den Steuersatz, der auf den Nettoproduktpreis aufgeschlagen wird.

In der Voreinstellung (System > Konfiguration > Verkäufe: Steuer > Berechnung: Katalog Preise) werden Katalogpreise im Backend als Bruttopreise angegeben. Die darin enthaltene Mehrwertsteuer bestimmt das in System > Konfiguration > Verkäufe: Versandeinstellungen > Herkunft eingestellte Herkunftsland.

Die im Katalog und Warenkorb angezeigten Bruttopreise für Artikel und Versandkosten hängen von dem Steuersatz des Bestimmungslands ab. Da das Bestimmungsland nicht von Anfang an bekannt ist, wird es in drei Stufen festgelegt, angefangen mit der niedrigsten Priorität:

  1. Standardbestimmungsland - wenn ein neuer Besucher den Shop betritt
  2. Standardadresse - wenn ein Kunde sich eingeloggt hat
  3. Im Checkout gewählte oder eingegebene Adresse - wenn der Kunde ein Checkout-Versuch unternommen hat, bei dem die endgültige Lieferadresse festgelegt wurde

Das Standardbestimmungsland (1) ist einstellbar in System > Konfiguration > Verkäufe: Steuer > Standard Ursprung für Steuerberechnung (Hinweis: die deutsche Übersetzung des Gruppentitels ist mißverständlich, da Verwechslungsgefahr mit Herkunft in Versandeinstellungen).

In der Voreinstellung (System > Konfiguration > Verkäufe: Steuer > Berechnung: Steuerberechnung basiert auf) legt die Lieferadresse das Bestimmungsland fest.

Die Standardlieferadresse (2) ist erst bekannt, wenn der Kunde sich eingeloggt hat. Wird beim Checkout (3) eine andere Adresse aus dem Adressbuch gewählt oder eine Adresse eingegeben, legt sie das aktuelle Bestimmungsland fest, nach dem der Kunde auf die Schaltfläche Weiter im Checkout-Schritt geklickt hat. Um die neuberechnete Bruttopreise zu sehen, reicht es in den Warenkorb zurückzukehren ohne die Bestellung abzuschließen.

Das folgende Diagramm zeigt die Abhängigkeiten und den Rechenweg von Magento zur Bestimmung des Bruttopreises, der dem Kunden im Warenkorb angezeigt wird:

Zur besseren Veranschaulichung ist unter jedem Block ist ein Beispielwert angegeben.

Fixierung des Bruttopreises

Bei meiner Recherche (Suchanfrage: magento fixed gross price) fand ich mehrere unbeantwortete Forenfragen und sogar ein ausgeschriebenes Projekt zu diesem Thema. Mehrere Entwickler, die ich angefragt habe, haben das Projekt als sehr aufwändig eingeschätzt, da die gesamte Steuerrechenlogik von Magento überarbeitet werden müsse. Doch genau dieser Ansatz war mir von Anfang an unsympatisch, weil er das Problem nicht bei der Ursache anpackt.

Nach Einarbeitung in die Rechenlogik von Magento, die ich oben beschrieben habe, fand ich eine Lösung die genau das tut: sie fixiert den Bruttopreis im Ursprung seiner Berechnung durch eine einfache Operation: Setze Herkunftsland gleich dem Bestimmungsland.

class Xonu_FGP_Model_Calculation extends Mage_Tax_Model_Calculation
{
    public function getRateOriginRequest($store = null)
    {
        $request = new Varien_Object();

	$quote = $session->getQuote();
	$request = $this->getRateRequest(
		$quote->getShippingAddress(),
		$quote->getBillingAddress(),
		$quote->getCustomerTaxClassId(),
		$store
	);

        return $request;
    }
}

Doch Magento wäre nicht Magento wenn es alles so einfach wäre: wechselt man den StoreView und wechselt dabei auch die Währung (und nur dann) gerät Magento in eine Endlosschleife!

Im Backtracing wurde klar, dass beim Wechsel des StoreView und Währung der Warenkorb nicht beim ersten Aufruf von getRateOriginRequest() zur Verfügung steht. Die Existenz des Warenkorbs wird deshalb mit $session->hasQuote() geprüft.

Und so sieht der Code der Extension Fixed Gross Price aus, die nun auf Magento Connect zur Verfügung steht:

class Xonu_FGP_Model_Calculation extends Mage_Tax_Model_Calculation
{
	public function getRateOriginRequest($store = null)
	{
		// set origin to destination

		$request = new Varien_Object();

		$session = Mage::getSingleton('checkout/session');
		if($session->hasQuote()) // getQuote() would lead to infinite loop here when switching currency
		{
			// use quote destination if quote exists

			$quote = $session->getQuote();
			$request = $this->getRateRequest(
				$quote->getShippingAddress(),
				$quote->getBillingAddress(),
				$quote->getCustomerTaxClassId(),
				$store
			);

			return $request;
		}
		else // quote is not available when switching currency
		{
			return $this->getDefaultDestination();
		}
	}

	private function getDefaultDestination($store = null)
	{
		$address = new Varien_Object();
		$request = new Varien_Object();

		$address
			->setCountryId(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_COUNTRY, $store))
			->setRegionId(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_REGION, $store))
			->setPostcode(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_POSTCODE, $store));

		$customer = $this->getCustomer();
		$customerTaxClass = $customer->getTaxClassId();

        $request
            ->setCountryId($address->getCountryId())
            ->setRegionId($address->getRegionId())
            ->setPostcode($address->getPostcode())
            ->setStore($store)
            ->setCustomerClassId($customerTaxClass);

		return $request;
	}
}



Ein Beitrag von Pawel Kazakow
Pawel's avatar

Pawel Kazakow ist Magento Certified Developer Plus und CompTIA Certified Technical Trainer und der Inhaber von xonu EEC, einem Anbieter von E-Commerce-Lösungen mit Magento. Der Shop-Betreiber bekommt ein Full-Service-Paket: Vom individuellen Shop-Design, Einrichtung und Integration des Magento-Systems, Anpassung und Magento-Entwicklung, Performance Optimierung, Produkt-Import, SEO/SEM, Mitarbeiter-Training, Shop-Wartung und Unterstützung beim Marketing.

Alle Beiträge von Pawel

Kommentare
Robert am

Hallo an alle! Kann es sein, dass die Lösung zwar die Preise im Warenkorb/Checkout fixt, aber nicht auf den Produktseiten? Wenn der User als Standardadresse eine Ausladsadresse angibt, wo es eine andere MwSt gibt (z.B. Österreich mit 20%), dann werden die Preise in Liste und Detailseite noch mit der falschen Berechnung angegeben.

Patrick am

Gibt es die Extension auch für Magento 1.8.1 ? Hänge irgendwie genau zwischen der 1.9er die die Funktion bereits im backend integriert hat und der 1.7er für die die Extension noch funktioniert.

Oder kann der Code auch so in die 1.8er integriert werden?

Bin dankbar für Antworten.

SG-SR am

@Johannes Bauchinger Wir haben das gleiche Problem festgestellt, aber bisher NUR bei "Produkten mit Optionen".

Nach unserenTests und Recherchen wird im entsprechenden Template der Preis noch korrekt ausgeben (und damit auch vorher korrekt berechnet). Beim Laden der Seite läuft jedoch dann ein Javascript drüber und manipuliert den Preis auf der Produktdetailseite, so dass dieser eben nicht mehr stimmt. :-(

@Pawel Kazakow Ich vermute mal, dass der Javascript-Code irgendwie anders berechnet und die Extension da nicht greift? Lässt sich das fixen?

Mike Bauer am

Danke erstmal für diese Extension, funktioniert sehr gut! Allerdings wäre es für uns ganz toll wenn wir bestimmte Länder aus dieser Berechnung ausschließen können. Beispielsweise sollen Kunden aus der Schweiz tatsächlich den Nettopreis zahlen. Kunden aus anderen Ländern in den keine Mwst. erhoben wird, müssen hingegen den eigentlich Bruttopreis als Nettopreis entrichten. Lässt sich das irgendwie lösen?

Reinhard Lamprecht am

Was muss ich tun um für Länder ohne MwSt. den Nettopreis anzuzeigen? Schweizer sollten z.B. tatsächlich netto einkaufen und nicht auch den Bruttopreis angezeigt bekommen. Ich bekomm das irgendwie nicht hin.

Michael Leiss am

Ich habe ein Problem im Modul gefunden. Es wird versucht, die Adresse des Kunden zu holen, wenn dieser existiert. Dabei verlässt man sich darauf, dass der Kunde eine Adresse hat. Es kann aber einen registrierten Kunden geben, der keine Adresse hat. Dann wird Steuersatz mit 0% zurückgegeben.

Ich habe quick and dirty einen Fix erstellt, aber absolut ungeprüft! Nur um das Verhalten zu korrigieren und ohne den Code verinnerlicht zu haben. Geht bestimmt besser, aber es zeigt das Problem.


 //START FIX     
142                 if(!$address->getData('country_id')){
143                 $address
144                                         ->setCountryId(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_COUNTRY, $store))
145                                         ->setRegionId(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_REGION, $store))
146                                         ->setPostcode(Mage::getStoreConfig(Mage_Tax_Model_Config::CONFIG_XML_PATH_DEFAULT_POSTCODE, $store));
147                 }
148                 //END FIX
Pawel Kazakow am

Vielen Dank für die Verbesserungsvorschläge von Matthias und Benjamin. Es gibt nun ein Update auf Magento Connect. Die neue Version ist kompatibel mit Magento 1.5 und funktioniert auch mit Bestellungen, die im Backend erstellt werden.

Zum Artikel: http://magento.xonu.de/magento-extensions/empfehlungen/fixed-gross-price-update/

Benjamin Wunderlich am

@Martin, @Pawel: Das Modul in der aktuellen Version läuft erst ab Magento 1.6, da dort erst in Mage_Checkout_Model_Session die Methode hasQuote implementiert wurde. $_quote ist eine Klassenvariable und läuft deshalb nicht über die magische Methode __call().

Eine Lösung habe ich persönlich noch nicht für verschiedene Währungen. Wenn man nur eine Währung im Shop hat, dann kann man hasQuote() durch getQuote() ersetzen. Dann läuft es auch in 1.4 und 1.5.

Pawel Kazakow am

Wenn FGP geladen ist, sollten die Bruttopreise sofort fixiert sein. Deaktivierung über einen Backend-Schalter ist nicht vorgesehen (d.h. die ganze Extension müsste deaktiviert werden). SBE ist von FGP unabhängig.

Martin am

Hallo Pawel, die Lösung hört sich sehr interessant an, leider bekomme ich sie aber nicht zum laufen.

Ich bekomme z.b. für Griechenland immernoch 105€ und nicht 100€ angezeigt, gerade so als ob die extension garnicht installiert wäre: die Module Xonu_FGP und Xonu_SBE laufen jedoch. hast du irgendwo noch einen EIN/AUS Schalter versteckt, den ich nicht finde?

Magento 1.5.0.1

Gruß, Martin

CS am

Die Lösung funktioniert leider nur wenn man im Katalog Bruttopreise verwendet :-(

Interessant wäre eine beeinflussung des eingegebenen Nettopreises. d.h. eventuell überladen von getPrice() ?

Johannes Bauchinger am

Ps: die Lösung fehlt mir leider noch...

Johannes Bauchinger am

Also ich glaube ich habe den Fehler gefunden...

Einstellung im Backend: Steuerberechnung basiert auf Rechnungsadresse ...Bruttopreis bleibt erhalten ...Steuerberechnung im Warenkorb fehlerhaft

Einstellung im Backend: Steuerberechnung basiert auf Versandadresse ...Bruttopreis ändert sich, wenn ich im Warenkorb das Land ändere ...Steuerberechnung im Warenkorb OK

Johannes Bauchinger am

Hallo,

hab ein Problem festgestellt: Ein nicht eingeloggter Kunde gibt einen Artikel in den Warenkorb. Ursprungsland ist Österreich (20%), im Warenkorb ändert der Kunde die Lieferadresse das Land auf Deutschland (19%). Soweit so gut, Bruttopreis im Warenkorb stimmt noch. Geht der Kunde allerdings nochmal zurück zum Artikel, so ändert sich der Bruttopreis plötzlich (Preis/1,19*1,2).? Ich hab mir nun auch den Warenkorb genauer angeschaut... Die einzelnen Positionen "Zwischensumme", "Versandkosten" und "Inkl. MwSt" stimmen noch, die Gesamtsumme weicht aber von den Werten ab und ergibt sich irgendwie anders, hab noch nicht herausgefunden wie... Hier meine Werte: Bruttopreis 389,9, Versandkosten 19,99 Gesamtpreis korrekt 409,89, Gesamtpreis ausgegeben: 413,08

Danke für eure Hilfe!

Fixed Gross Price Extension: Gleiche Bruttopreise für alle Kunden | Magento Training und Beratung (Magento Beratung/Consulting und Training/Schulungen) am

[...] Die Berechnung des Bruttopreises in Magento und das Funktionsprinzip der Fixed Gross Price Extension... Dieser Beitrag wurde unter Empfehlungen veröffentlicht. Setze ein Lesezeichen auf den Permalink. ← SSL-Sicherheitswarnung im Magento-Warenkorb: Informationen werden über eine unverschlüsselte Verbindung gesendet [...]

Pawel Kazakow am

Hallo Johannes,

die Extension wurde heute auf Connect freigegeben. Den Code von Matthias ist allerdings noch nicht eingebaut.

Johannes Bauchinger am

Hallo,

finde ich sehr interessant - weis man schon, wann die Extension rauskommt, bzw. kann man schon eine Vorabversion erhalten?

Pawel Kazakow am

Hi Matthias, vielen Dank für den Verbesserungsvorschlag. Den werde ich in die Extension übernehmen. (Auf Connect wurde sie leider immer noch nicht freigeschaltet.)

Matthias Zeis am

Leider ist die Anpassung noch wirkungslos, wenn man Bestellungen im Backend über "Sales" > "Orders" > "Create New Order" anlegt. Der Grund ist, dass im Backend nicht "checkout/session" verwendet wird, sondern "adminhtml/session_quote". Du kannst die Session zum Beispiel so holen:

public function getSession()
    {
        if (Mage::app()->getStore()->isAdmin() || Mage::getDesign()->getArea() == 'adminhtml') {
            return Mage::getSingleton('adminhtml/session_quote');
        }

        return Mage::getSingleton('checkout/session');
    }

Dann holst du dir die Session in getRateOriginRequest():

public function getRateOriginRequest($store = null)
    {
        // set origin to destination

        $request = new Varien_Object();

        $session = $this->getSession();
        if($session->hasQuote()) // getQuote() would lead to infinite loop here when switching currency
        {
           /* usw. */
        }
    }

Zuletzt musst du noch die Methode hasQuote() in adminhtml/session_quote hinzufügen. Dann klappt es hoffentlich auch im Backend.

PS: die Code-Snippets waren in der Vorschau abgeschnitten. Falls das im Kommentar auch so ist: einfach den HTML-Quelltext der Seite ansehen. ;-)

Vinai Kopp am

Sehr schöne Lösung für ein nerviges Problem - vielen Dank!

Dein Kommentar