Tax Management
Within the Price Systems there is a Tax Management component to deal with all sorts of taxes. So it is possible to calculate and print out taxes as needed.
The Tax Management is quite transparent and should not affect the system very deeply. As a result - if not necessary (e.g. because you have a B2B system, etc.) - you don't need to deal with taxes in E-Commerce Framework at all. Also, if you have your very own tax implementation, this should be perfectly possible and you should need to not deal with the Tax Management in the E-Commerce Framework at all. Just use all the default values and every thing should be fine.
Components of Tax Management
If you need to deal with taxes following components are important to know of:
Tax Configuration with OnlineShopTaxClass
The configuration of taxes is done with OnlineShopTaxClass
Pimcore objects within in the Pimcore Backend UI. The actual
tax calculation is always based on the configuration of such a tax class. So, tax configurations for different countries
and product groups can be established. The tax calculation is always based on one tax class, a combination of tax classes
is not possible.
The configuration concludes a TaxEntryCombinationMode (combine
and one after another
) and one or more tax entries
with a name and a tax rate in percent.
The selection of the correct tax class (for a certain country and a certain product group) is done by the Price System (see next section).
Tax Class Selection with Price Systems
The Price System decides for each product and environment which tax class to take for tax calculation. Therefore following two methods need to be implemented:
getTaxClassForProduct
: Should return tax class for given product. The default implementation returns a generic tax class based on the Website Settings or - if not set - an empty tax class.getTaxClassForPriceModification
: See later for more information.
The logic behind the tax class selection can be of any complexity. It can be based on a simple Website Setting (for one tax class fits all), or based on a complex matrix with product group, sending country, delivering country and several other aspects.
Tax Calculation with TaxCalculationService
The actual tax calculation is done in the TaxCalculationService::updateTaxes(PriceInterface $price, $calculationMode = self::CALCULATION_FROM_NET)
method. It updates taxes in given price object by using its tax entries, tax combination mode and net or gross amount
based on the given $calculationMode
.
The calculation is done by the framework in two places:
- By Price Systems when creating the
PriceInfo
objects - By
PriceInterface
objects whensetAmount
,setGrossAmount
orsetNetAmount
is called and$recalc
is set to true.
The PriceInterface
object
Every price in the E-Commerce Framework comes down to an PriceInterface
object. Therefore also all calculated taxes and all
necessary information for calculating taxes is stored in PriceInterface
objects. Following methods are important:
getTaxEntries()
,setTaxEntries($taxEntries)
: Sets and gets tax entries for price. Each price can have one or more tax entries based on the tax laws. Each of these tax entries contain percent rate and calculated amount. The order of the tax entries is important.getTaxEntryCombinationMode()
,setTaxEntryCombinationMode($taxEntryCombinationMode)
: Sets and gets the tax entry combination mode which defines how tax calculation is done based on the tax entries:TaxEntry::CALCULATION_MODE_COMBINE
: Sum up all tax rates and calculate tax amount afterwards.TaxEntry::CALCULATION_MODE_ONE_AFTER_ANOTHER
: For each tax rate calculate tax amount, add it to total price and then calculate tax amount for next tax rate based on new total sum.TaxEntry::CALCULATION_MODE_FIXED
: Amounts and percent rates are fixed and cannot be (re)calculated based on information within thePriceInterface
object. This mode is needed forsubTotal
andgrandTotal
inCartPriceCalculator
.
getGrossAmount()
,setGrossAmount($grossAmount, $recalc = false)
: Sets and gets gross amount of price. If$recalc
is set totrue
, corresponding net price is calculated based on tax entries and tax entry combination mode.getNetAmount()
,setNetAmount($netAmount, $recalc = false)
: Sets and gets net amount of price. If$recalc
is set totrue
, corresponding gross price is calculated based on tax entries and tax entry combination mode.getAmount()
,setAmount($amount, $priceMode = self::PRICE_MODE_GROSS, $recalc = false)
: Gets the gross amount by default and sets the gross or net amount based on the params.
Special Aspects
Tax Calculation in Pricing Rules
E-Commerce Framework Pricing Rules always modify the gross price of the product and recalculate the net price based on the tax entries of the product price. So specified discount amounts on product level need to be configured as gross discounts.
Since cart discounts are implemented as CartPriceModificators
the next point is important for them. It might be necessary
to extend the default implementation of the cart discounts for the correct selection of the tax class for discounts. The
default implementation uses the getTaxClassForPriceModification
method of the Price System called default
for tax class
selection.
Tax Calculation in CartPriceModificators
The CartPriceModificators
themselves can decide if the modification should be done as net or gross amount since the
interface ModificatedPriceInterface
extends PriceInterface
and therefore all tax related information as explained above.
The method getTaxClassForPriceModification
in Price Systems can be used to delegate the tax class selection to Price
Systems.
Putting the Pieces Together
For setting up the tax management, following steps are necessary.
1) Defining Tax Classes
Create and configure all the OnlineShopTaxClass
objects within Pimcore.
2) Configuring Price System
Setup the correct price systems and implement their methods getTaxClassForProduct
and getTaxClassForPriceModification
.
It might also be necessary to have a look at the CartPriceModificator
for cart discounts and make sure that it selects the
correct tax class.
3) Printing Taxes
To print taxes into the frontend use following samples:
Product Detail Page
{% set price = product.OSPrise %}
<strong>{{ price }}</strong>
<div class="tax">
<p><strong>{{ 'shop.detail.included_tax'|trans }}'</strong></p>
<ul>
{% for entry in price.taxEntries %}
<li>{{ entry.entry.name }}: {{ entry.percent }}% ({{ price.currency.toCurrency(entry.amount) }})</li>
{% endfor %}
</ul>
</div>
Cart
{% set grandTotal = cart.priceCalculator.grandTotal %}
{% if grandTotal.taxEntries %}
<tr>
<td>{{ 'cart.taxes'|trans }}</td>
<td>
{% for taxEntry in grandTotal.taxEntries %}
{{ taxEntry.entry.name }}: {{ taxEntry.percent }}% ({{ grandTotal.currency.toCurrency(taxEntry.amount) }})<br/>
{% endfor %}
</td>
</tr>
{% endif %}
Order Confirmation Mail
{% if order.taxInfo %}
<tr>
<td>{{ 'cart.taxes'|trans }}</td>
<td>
{% for taxEntry in order.taxInfo %}
{{ taxEntry[0] }}: {{ taxEntry[1] }} ({{ currency.toCurrency(taxEntry[2])) }})<br/>
{% endfor %}
</td>
</tr>
{% endif %}
4) Taxes in OnlineShopOrders
Make sure that all tax related attributes are available in OnlineShopOrder
and OnlineShopOrderItem
. Then the
E-Commerce Framework populates all needed values automatically and therefore stores the tax information in the
order objects.