Ok I figured this one out and posting in case I forget in future, but also to assist with anyone else who was looking for a solution to this problem :)
So I basically resolved this using a custom totals collector, as follows:
Step 1
Create app/code/Vendor/Module/etc/sales.xml
file and reference a new collector class as follows for example:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd">
<section name="quote">
<group name="totals">
<item name="tax_subtotal" instance="Vendor\Module\Model\Sales\Total\Quote\Subtotal" sort_order="600"/>
</group>
</section>
</config>
Step 2
Create your custom class PHP file app/code/Vendor/Module/Model/Sales/Total/Quote/Subtotal.php
with something like the following:
<?php
namespace Vendor\Module\Model\Sales\Total\Quote;
use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory;
use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory;
use Magento\Tax\Helper\Data as TaxHelper;
use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory;
use Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory;
use Magento\Tax\Model\Sales\Total\Quote\Tax;
use Magento\Tax\Model\Config;
use Magento\Tax\Api\TaxCalculationInterface;
use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory;
use Magento\Framework\Serialize\Serializer\Json;
class Subtotal extends Tax
{
public function __construct(
Config $taxConfig,
TaxCalculationInterface $taxCalculationService,
QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory,
QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory,
CustomerAddressFactory $customerAddressFactory,
CustomerAddressRegionFactory $customerAddressRegionFactory,
TaxHelper $taxData,
Json $serializer
) {
parent::__construct(
$taxConfig,
$taxCalculationService,
$quoteDetailsDataObjectFactory,
$quoteDetailsItemDataObjectFactory,
$taxClassKeyDataObjectFactory,
$customerAddressFactory,
$customerAddressRegionFactory,
$taxData,
$serializer
);
}
/**
* Calculate tax on product items. The result will be used to determine shipping
* and discount later.
*
* @param \Magento\Quote\Model\Quote $quote
* @param ShippingAssignmentInterface $shippingAssignment
* @param Address\Total $total
* @return $this
*/
public function collect(
\Magento\Quote\Model\Quote $quote,
\Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
\Magento\Quote\Model\Quote\Address\Total $total
) {
// return if no items in quote
if (!$quote->getItems()) {
return $this;
}
// set some default values
$subtotal = 0;
$tax = 0;
$quoteId = $quote->getId();
foreach ($quote->getItems() as $quoteItem) {
$productId = $quoteItem->getProductId();
// if current item requires VAT relief
// update row totals for item (remove VAT)
$this->setRowExclTax($quoteItem);
// add to cart subtotal
$subtotal += $quoteItem->getBaseRowTotalInclTax();
// add to cart tax
$tax += $quoteItem->getBaseTaxAmount();
}
// Update the cart totals for subtotal, total Inc/Ex tax etc
$this->updateCartTotals($total, $subtotal, $tax);
return $this;
}
// Sets the item row calculations and removes Tax
private function setRowExclTax($quoteItem)
{
// calculate the subtotal for each item row
$rowTotal = ($quoteItem->getPrice() * $quoteItem->getQty());
// Calculate item row totals
$quoteItem->setPrice($quoteItem->getPrice());
$quoteItem->setBasePrice($quoteItem->getBasePrice());
$quoteItem->setPriceInclTax($quoteItem->getPrice());
$quoteItem->setBasePriceInclTax($quoteItem->getBasePrice());
$quoteItem->setRowTotal($rowTotal);
$quoteItem->setBaseRowTotal($rowTotal);
$quoteItem->setRowTotalInclTax($rowTotal);
$quoteItem->setBaseRowTotalInclTax($rowTotal);
// Remove Tax
$quoteItem->setTaxAmount(0);
$quoteItem->setBaseTaxAmount(0);
$quoteItem->setTaxPercent(0);
}
// updates totals such as subtotal, price Incl/Excl Tax etc.
private function updateCartTotals($total, $subtotal, $tax)
{
$total->setTotalAmount('subtotal', $subtotal);
$total->setBaseTotalAmount('subtotal', $subtotal);
$total->setSubtotalInclTax($subtotal);
$total->setBaseSubtotalInclTax($subtotal);
// Remove Tax
$total->setTotalAmount('tax', $tax);
$total->setBaseTotalAmount('tax', $tax);
// Set the grand total Inc Tax total
$total->setGrandTotal($subtotal);
}
}
And that was pretty much it :)
everything now works as planned when removing VAT from specific items. Hopefully this will help someone else when they need it.
You can see