BTW Berekening & Prijsweergave Fix¶
Datum: 2025-12-28 Status: Voltooid Doel: Correcte BTW-berekening (9% vs 21%), prijsweergave exclusief BTW, en BTW-split op facturen
Context¶
De webshop moest prijzen exclusief BTW tonen in cart en checkout, met correcte berekening van 9% BTW voor publicaties en 21% BTW voor tickets/merchandise. Daarnaast moest de BTW gesplitst worden weergegeven op facturen en in cart/checkout totalen.
Problemen¶
- Alle producten kregen 21% BTW (zou 9% vs 21% moeten zijn)
- Cart en checkout toonden prijzen inclusief BTW
- BTW werd niet gesplitst op facturen (alleen "BTW" totaal)
- Subtotaal in cart/checkout berekende met tax-inclusive prijzen
Oorzaak¶
De custom TaxRateResolver was niet correct geregistreerd als service. De service tag was verkeerd (commerce_tax.rate_resolver in plaats van commerce_tax.tax_rate_resolver), en de constructor dependency injection ontbrak.
Oplossing¶
1. Tax Rate Resolver Service Registratie¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.services.yml
Correcte service registratie:
services:
vg_commerce.tax_rate_resolver:
class: Drupal\vg_commerce\TaxRateResolver
arguments: ['@entity_type.manager']
tags:
- { name: commerce_tax.tax_rate_resolver, priority: 100 }
Kritiek detail: De tag moet commerce_tax.tax_rate_resolver zijn (met punt tussen commerce_tax en tax_rate_resolver), niet commerce_tax.rate_resolver.
2. Tax Rate Resolver Implementatie¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/TaxRateResolver.php
Maps product variation types naar BTW percentages: - 9% (reduced): abonnementen, los_nummer, uitgave - 21% (standard): tickets, merchandise, vgbc_ticket
De resolver heeft priority 100 (hoger dan default -100), dus wordt eerst aangeroepen.
3. Cart Prijsweergave Exclusief BTW¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.module
Twee hooks voor cart view:
- vg_commerce_views_pre_render() - Berekent base prices en slaat op in view object
- vg_commerce_preprocess_views_view_field() - Vervangt field output met base price
Kritieke fix: Data opslaan op $view->vg_base_prices in plaats van als dynamic property op field object (voorkomt PHP 8.2 deprecation warning).
4. Checkout Prijsweergave Exclusief BTW¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/templates/commerce-checkout/commerce-checkout-order-summary.html.twig
Template override die base prices toont:
{% set base_price = order_item.getPurchasedEntity.getPrice %}
{% set quantity = order_item.getQuantity %}
{% set total_base = base_price.multiply(quantity) %}
{{ total_base|commerce_price_format }}
5. BTW Split en Subtotaal Herberekening¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/vg25.theme
Preprocess hooks voor alle totalen weergaves:
- vg25_preprocess_commerce_invoice() - Voor factuur PDF
- vg25_preprocess_commerce_checkout_order_summary() - Voor checkout
- vg25_preprocess_commerce_order_total_summary() - Voor cart totalen
Helper functie vg25_recalculate_totals() doet:
1. Herberekent subtotaal van base prices (excl. BTW)
2. Verzamelt tax adjustments van order items
3. Groepeert per percentage (9% en 21%)
4. Voegt gesplitste BTW toe aan adjustments array
5. Herberekent totaal: subtotaal + shipping + BTW
Belangrijk: Tax adjustments zitten op order item niveau, niet op order niveau. Daarom moeten we door alle order items itereren.
6. Invoice Item Prijzen¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.module
Bestaande hook vg_commerce_commerce_invoice_presave():
function vg_commerce_commerce_invoice_presave(Drupal\Core\Entity\EntityInterface $invoice) {
foreach ($invoice->getItems() as $invoice_item) {
$order_item = $invoice_item->getOrderItem();
$purchased_entity = $order_item->getPurchasedEntity();
if ($purchased_entity && $purchased_entity->hasField('price')) {
$base_price = $purchased_entity->getPrice();
$invoice_item->setUnitPrice($base_price);
}
}
}
Dit zorgt ervoor dat invoice entities zelf aangepaste unit prices hebben, waardoor Commerce automatisch correct subtotaal/totaal berekent.
7. Invoice Nummer Op Basis van Order Nummer¶
Bestand: /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/EventSubscriber/InvoiceNumberSubscriber.php
Event subscriber op InvoiceEvents::INVOICE_PRESAVE:
public function onInvoicePresave(InvoiceEvent $event) {
$invoice = $event->getInvoice();
if ($invoice->getInvoiceNumber()) {
return;
}
$orders = $invoice->getOrders();
if (!empty($orders)) {
$order = reset($orders);
$order_number = $order->getOrderNumber();
$year = date('Y');
$invoice_number = sprintf('VG-%s-%03d', $year, $order_number);
$invoice->setInvoiceNumber($invoice_number);
}
}
Format: VG-2025-054 voor order nummer 54.
Technische Details¶
Template Keys Verschil¶
- Invoice template gebruikt
adjustment.totalkey - Order total template gebruikt
adjustment.amountkey - Oplossing: beide keys meegeven in adjustment array
Subtotaal Berekening¶
Commerce's getSubtotalPrice() gebruikt unit prices die al tax bevatten. Daarom moeten we subtotaal herberekenen van base prices:
$subtotal_number = '0';
foreach ($order->getItems() as $order_item) {
$base_price = $order_item->getPurchasedEntity()->getPrice();
$quantity = $order_item->getQuantity();
$item_total = $base_price->multiply($quantity);
$subtotal_number = bcadd($subtotal_number, $item_total->getNumber(), 6);
}
Price Object Manipulation¶
Gebruik altijd bcadd(), bcmul() etc. voor nauwkeurige berekeningen met prices. Maak nieuwe Price objecten:
Resultaat¶
Voor Orders >= 56¶
Orders vanaf nummer 56 (aangemaakt na de fix) hebben: - Correcte BTW: 9% voor publicaties, 21% voor tickets/merchandise - Cart: item prijzen excl. BTW, subtotaal excl. BTW, gesplitste BTW (9% + 21%), totaal incl. BTW - Checkout: zelfde als cart - Factuur PDF: eenheidsprijzen excl. BTW, gesplitste BTW, correct totaal
Oude Orders (51-55)¶
Orders van voor de fix hebben nog steeds 21% BTW op alles. Dit is te verwachten omdat tax adjustments al waren berekend en opgeslagen.
Rekenvoorbeeld Order 56¶
- Los nummer: EUR 9.13 excl. BTW
- Ticket: EUR 49.95 excl. BTW
- Subtotaal: EUR 59.08
- BTW 9%: EUR 0.82 (op los nummer)
- BTW 21%: EUR 10.49 (op ticket)
- Verzending: EUR 4.10
- Totaal: EUR 74.49
Bestanden Gewijzigd¶
/var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.services.yml- Tax rate resolver correct geregistreerd
-
Invoice number subscriber toegevoegd
-
/var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.module - Views hooks voor cart price display
-
Bestaande invoice presave hook (ongewijzigd)
-
/var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/TaxRateResolver.php -
Bestaande implementatie (ongewijzigd maar nu actief)
-
/var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/EventSubscriber/InvoiceNumberSubscriber.php -
Nieuw: invoice nummering op basis van order nummer
-
/var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/vg25.theme - Preprocess hooks voor BTW split en subtotaal herberekening
-
Helper functie
vg25_recalculate_totals() -
/var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/templates/commerce-checkout/commerce-checkout-order-summary.html.twig - Nieuw: template override voor checkout prijzen
Open Issues¶
Geen¶
Alle oorspronkelijke problemen zijn opgelost.
Changelog¶
2025-12-28 14:00: Sessie start - identificatie van problemen 2025-12-28 15:00: Tax resolver service registratie gefixed 2025-12-28 15:30: Cart price display hooks geimplementeerd 2025-12-28 16:00: BTW split logic toegevoegd 2025-12-28 16:30: Subtotaal herberekening geimplementeerd 2025-12-28 16:50: Sessie afgerond - alle functionaliteit werkend
Document eigenaar: Warp Agent + Robin Laatste update: 2025-12-28 Versie: 1.0
Voor AI: Dit document beschrijft de volledige oplossing voor BTW berekening en prijsweergave. Alle orders vanaf nummer 56 hebben correcte BTW percentages en prijsweergave. Oude orders (51-55) blijven 21% BTW tonen omdat ze voor de fix zijn aangemaakt.