Skip to content

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

  1. Alle producten kregen 21% BTW (zou 9% vs 21% moeten zijn)
  2. Cart en checkout toonden prijzen inclusief BTW
  3. BTW werd niet gesplitst op facturen (alleen "BTW" totaal)
  4. 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.total key
  • Order total template gebruikt adjustment.amount key
  • 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:

use Drupal\commerce_price\Price;
$totals['subtotal'] = new Price($subtotal_number, $currency_code);


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

  1. /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.services.yml
  2. Tax rate resolver correct geregistreerd
  3. Invoice number subscriber toegevoegd

  4. /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/vg_commerce.module

  5. Views hooks voor cart price display
  6. Bestaande invoice presave hook (ongewijzigd)

  7. /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/TaxRateResolver.php

  8. Bestaande implementatie (ongewijzigd maar nu actief)

  9. /var/www/sites/dev.voedingsgeneeskunde/web/modules/custom/vg_commerce/src/EventSubscriber/InvoiceNumberSubscriber.php

  10. Nieuw: invoice nummering op basis van order nummer

  11. /var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/vg25.theme

  12. Preprocess hooks voor BTW split en subtotaal herberekening
  13. Helper functie vg25_recalculate_totals()

  14. /var/www/sites/dev.voedingsgeneeskunde/web/themes/custom/vg25/templates/commerce-checkout/commerce-checkout-order-summary.html.twig

  15. 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.