18.78. DD 78: Taxes#
18.78.1. Summary#
We’ve received various requests for the merchant backend to provide transaction reports for accountants. While not always stated explicitly, we believe this is largely also related to tax reporting. This document explains the plan for how we intend to deal with periodic reporting.
18.78.2. Motivation#
Making tax-reporting easy for merchants will lower the barrier for them to adopt GNU Taler. Furthermore, customers will often require receipts which state the specific tax amounts that were paid, so including tax information in digital receipts (and thus the Taler contracts) is important. The current situation where taxes are specified per-product is not great, as a merchant would have to update all products if a tax-percentage were to change, and also has to re-calculate the tax every time a product price changes.
18.78.3. Requirements#
Deal with the complexity of tax calculations across different jurisdictions. Sure, we may not cover all cases globally, but we should at least cover the most common scenarios. This likely includes different ways how taxes should be computed in terms of rounding, and computing taxes given prices in gross or net amounts.
Make it as easy as possible for the merchant to manage taxes. If possible, have some backend-wide defaults so that merchants do not have to enter common taxes that apply across the currency domain.
Ensure customers get correct tax receipts as part of their contracts.
Incorporate the tax data when informing the merchant about their transaction history, including showing which taxes applied to which purchases so that merchants can easily account for applicable taxes.
Treat donations and gifts as a special-cases as again special tax rules likely apply.
A single product may have multiple applicable taxes (say VAT and luxury tax).
A single order may contain multiple products (some with known tax rules and others without) and thus a mix of applicable taxes.
A merchant may need to override or adapt the tax details for each order, for example because reverse VAT rules may shift the responsibility to pay taxes to the buyer.
18.78.4. Proposed Solution#
We borrow ideas from ERPnext.
18.78.4.1. Phase 1: Products groups#
This is inspired by ERPnext’s “item groups”.
A product group allows multiple products to be treated in the same way for accounting and tax purposes, avoiding the need to configure each product individually.
There can only be one group per product, all products that are not explicitly
in a group should be in some __default__ group that always exists. The
product groups will be used for sales statistics (revenue per group) and will
be the basis for associating taxes with products in the next phase(s).
Unlike categories, product group membership is mutually exclusive and not used for the point-of-sale app display. Re-using categories would confuse a feature for taxes/accounting with a feature for user-interfaces of sales people.
Add a
product_grouptable with:product group serial number
instance ID (foreign key)
product group name (unique with instance ID)
product group description
Expand
inventory_productstable with:product group serial number (foreign key), allow NULL for default
price_is_net boolean flag to indicate if the given price is the net price and all taxes should be added on top of it, or if it is a fixed retail price and the merchant covers all applicable taxes, at the expense of profits if taxes vary (for example, to preserve the price structure even if taxes vary between dine-in and take-out).
18.78.4.2. Phase 2: Money pots#
This is inspired by ERPnext’s “accounts”.
A money pot allow users aggregate amounts over time periods for accounting.
Money pots can be for different types of taxes, but also for tips or to separate out different kinds of internal accounts as well as fees paid to the exchange.
When an order is created, the total amount paid by the customer will be split into the various money pots based on rules that can be given per product, product group or taxes, and also later overriden explicitly in the contract terms.
The increments to the various pots will also be shown in the various accounting statistics.
Add a
money_pottable with:money pot serial number
instance ID (foreign key)
money pot name (unique with instance ID)
money pot description
money pot total amount
Code should update statistics whenever the money pot total amount is incremented.
18.78.4.3. Phase 3: Tax Class#
This is inspired by ERPnext’s “item tax templates”.
A tax class specifies a possible tax and how it is to be calculated for a given product or order.
Add a
tax_classtable with:tax class serial number
instance ID (foreign key)
tax class name (unique with instance ID)
tax rate (percentage), for taxes charged per value
charge amount (multiplied with quantity of the product, for example for tourist overnight tax per night, for taxes charged per unit and not per value)
tax description
i18n description
calculation mode (net total, cummulative)
rounding mode (up, down, nearest)
rounding unit (amount)
money pot (where to accumulate taxes paid under this tax class).
to track known tax rules. Unique should be “instance+tax class name”.
When “rounding up” is used, round up from net to gross, but round down from gross to net. Similarly, when “rounding down” is used, round down from net to gross, but round up from gross to net. Finally, “round-to-nearest” implies rounding in the same way for both conversion directions, and rounding up from the exact mid-point between multiples of the rounding unit.
When cummulative calculation is used, the order in which tax classes are applied starts to matter. This will become important when defining tax rules later.
Orders can specify that a particular tax class and amount is to be applied to specific products or to the entire order, but not both. In this case, the backend adds the exact amounts to the contract terms and the respective pot-statistics.
Orders can specify that only a particular tax class is to be applied to a product or the entire order, but without giving the tax amount. In this case, the backend computes the applicable tax and adds the exact amounts to the contract terms and the respective pot-statistics. It is also possible that for some products in the order the frontend calculated the exact amount, while for others the calculation is left to the backend.
When generating the contract terms, append the tax class details of applicable taxes (rates, descriptions) from the database to the contract.
Allow the client to define and use additional custom tax classes per order.
Add new configuration sections “[taxes-$ID]” that specify common tax classes (name and description as string) and rates (floating point) and per-quantity charges (amount) with calculation and rounding mode that should be automatically provided to all instances. When creating a new instance, populate the tax class table with these values. Add a command-line tool to add all configured taxes to all existing instances (for example, to update default taxes for the next year).
When an order is paid, make sure to add the tax totals to each of the money pots.
For tipping, specifying a “tax”
tip-$STAFFwith a custom amount can thus be easily assigned to the tip money pot of$STAFF.
18.78.4.4. Phase 4: Tax Rules#
This is inspired by ERPnext’s “tax rules”.
A tax rule specifies whether a tax class applies to a particular product, group of products, or order.
There should be an optional
__fallback__tax rule that is applied to all orders and products that do not match a specific rule.Except for the
__fallback__tax rule, it is possible that multiple tax rules apply, in which case they all apply at the same time. Only the “fallback” rule only applies if no other rules apply.Add a table “tax_rules” with
tax rule serial number
instance ID (foreign key)
tax rule name (unique with instance ID),
__fallback__is reserved for the fallback ruletax class serial number (foreign key)
filter: array of product group serial IDs, NULL for all, [] for none
filter: array of product ID serial IDs, NULL for all, [] for none
filter: total_only (boolean), true if the tax rule only applies to the final total of the order, and not individual products; if total_only is true, then the product and product group arrays MUST both be empty
In the future, we may want to expand this to add per-order filters, like on the shipping address; however, for now we will limit the filters to make this more implementable.
Orders can specify an array of tax rules (by tax rule name) to apply to each product or the entire order (depending on the filter of the tax rule). In this case, the tax rules are applied in the sequence specified in the array to compute which tax classes to apply to each product or the entire order. Tax rules must not be specified when an order already specifies a tax class for the entire order.
However, it is possible to specify tax classes for some products and then tax rules would still apply for other products or the entire order. But if a product in the order is specified as having an explicit tax class, the order-wide product-specific tax rules will no longer apply to it. Order-wide tax rules on the order total would still be applied.
If a tax rule is specified for an order but it does not apply to the order or a product in it, the rule is simply ignored.
18.78.4.5. Phase 5: Tax Regimes#
This is inspired by ERPnext’s “tax categories”.
A tax regime determines which set of tax rules to apply in which order (with cummulative taxes, the order may matter). The idea here is that for some customers or transactions completely different sets of rules may apply, but still the sets of rules are frequently the same.
There should be a “default” tax regime that is applied to all orders where the client did not specify a tax regime. Basically, not specified implies
__default__. However, if a default tax regime is not configured, this is not an error and simply no taxes are applied.Add a table “tax_regime” with
tax regime serial number
instance ID (foreign key)
tax regime name (unique with instance ID),
__default__is reserved for the default regimearray of applicable tax rules
Orders can specify a tax regime instead of an array of tax rules. In this case, the array of tax rules is simply obtained from the tax regime.
Orders that specify a tax regime must not also specify an array of tax rules.
18.78.5. Test Plan#
Unit tests for the rounding functions.
Shell-script tests for the CRUD API on taxes and automatic import of tax classes from the configuration.
Shell-script tests to check tax calculations in created orders and to test statistics on taxes paid.
Manual tests for SPA.
Manual tests on PDF report generation.
Manual tests on rendering taxes in wallets.
18.78.6. Definition of Done#
Specification updated
Database updated
Merchant backend updated:
CRUD API for tax definitions
INI-based tax class importer
CRUD API update for product management
Order creation update
Statistics update on order paid
Merchant backend SPA updated:
CRUD for tax class definitions
CRUD for associating tax classes with products
Order creation with tax-class override (at least for the entire order, not necessarily per-product)
Statistics page rendering tax statistics
Wallets updated to render taxes (upon request, in detailed view on payment or from order history)
18.78.7. Alternatives#
Communism? Crypto-anarchy?
18.78.8. Drawbacks#
Quite a bit of extra complexity
18.78.9. Discussion / Q&A#
(This should be filled in with results from discussions on mailing lists / personal communication.)