ekmungai / eloquent-ifrs

Eloquent Double Entry Accounting with a focus on IFRS Compliant Reporting
MIT License
334 stars 68 forks source link

VAT Coupled with Line Items #87

Closed richmondnursery closed 2 years ago

richmondnursery commented 3 years ago

I love your library and began working with it for my project. Wonderful work.

I noticed VAT is coupled and calculated with each Line Item in a rather fixed method. Since tax calculations vary so greatly around the world, would it not be more flexible if VAT (and all taxes) would be calculated and added as separate line item(s) rather than integrated into each line item? This would allow for countries that use stacked taxes (multiple VAT rules applied in sequence or parallel), rules where taxes need to be rounded up (i.e. ceil($vat_amount)), or where tax is applied on the transaction sub-total rather than Line Item to operate.

Often in my operations I have noticed on vendor invoices there are discrepancies in tax amount caused by differing application or rounding methods.

If the calculation of VAT was based on a separate transaction which created separate Line Item(s), then one could create different calculation methods depending on locale rules. This would make the package more accessible around the world.

ekmungai commented 3 years ago

HI @richmondnursery,

I'm afraid I've not encountered a scenario like the one you mentioned in your comments above, perhaps if you could provide a comprehensive example I could have a starting point from which to think about the problem.

Cheers, Edward

richmondnursery commented 3 years ago

Sure.

In British Columbia, Canada, they use GST and PST. Each tax has it's own rate and control account and taxes are remitted Federally and Provincially respectively. So an item worth $100 would have 5% GST applied and 7% PST applied for a total of $112. However, the package as-is would put the entire $12 value into a single control account so calculating remittances is not possible.

I was thinking of something like this could work:

$item = LineItem::create([ ... ]);
$gst = Vat::addTax( $vat->id_gst, $item ); // returns a Line Item with tax information for one tax
$pst = Vat::addTax( $vat->id_hst, $item ); // returns a Line Item with tax information for second tax
$transaction->addLineItem( $item );
$transaction->addLineItem( $gst );
$transaction->addLineItem( $pst );
$transaction->post();

I believe some countries "stack" taxes as well where a second tax is applied to the first so something like $item_total = ( $item_cost * $tax_rate_1 ) * $tax_rate_2;

In that case it could be better to feed LineItem values to the tax calculator rather than LineItem objects:

$item = LineItem::create([ ... ]);
$tax_1 = Vat::calculateTax( $vat->id_1, $item->amount  ); // returns a Line Item
$tax_2 = Vat::calculateTax( $vat->id_2, ( $item->amount + $tax_1->amount ); // tax 2 is calculated on the item plus tax 1

The second scenario would allow one to also sum all Line Items and pass the transaction sub-total to the tax calculator to create a single Line Item for Tax instead of a per-item line for taxes.

Just some ideas.

ekmungai commented 3 years ago

Hi @richmondnursery,

Building upon your ideas, I have just added the Vat::applyMultiple() function that generates a list of line items with VAT amounts based on the rates of the Vat Ids provided. The function assumes parallel application of tax rates by default, but also caters for serial application (stacking). Please go through the code and tests and let me know if the code covers the scenarios as you described them above.

Cheers

richmondnursery commented 3 years ago

Hi @ekmungai,

Functionally, yes it does work perfectly.

But I do notice it creates two scenarios:

  1. With a single VAT applied, each Line Item specifies the vat_it and vat_inclusive in the database. There is no additional record in the line_item database for the actual tax, only the ledger record.
  2. With multiple VAT applied, all items in the line_item database have a vat_id of zero, but the VAT(s) are listed as Line Items in the database.

That seems like an inconsistency in the way tax is recorded in the database. I'm not sure if that's good with you, but I personally think taxes should be applied consistently be it one or multiple taxes.

Maybe all taxes could be applied with a function like Vat:applyMultiple() regardless of being single VAT or multiple. Then maybe vat_id in the database could point to the line_item.id of the item having the tax applied to? In that way, all taxes are recorded the same and it wouldn't be hard to look up what items have taxes or what item is being taxed. Also, by using this method and the narration as you did rather than a fixed vat_id tied to the line item, then if the tax rate of particular VAT changes over time, historic records don't point to the wrong tax rate but instead have the rate listed in the line item narration. Using scopes, you could filter out tax line items or show only tax line items which would make creating tax sums and invoice sub-totals easy.

Just some thoughts.

Great work.

ekmungai commented 3 years ago

Hi @richmondnursery,

I think you're absolutely right, the above changes are more or less a patch to obtain the desired functionality while maintaining backwards compatibility (i.e not affecting the database). I'll think about it some more to see if I can introduce the item tax relation while having minimal impact on the current implementation, or if a new version will be required to effect the changes.

As always, thanks a lot for your feedback and thoughts. Cheers.