taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 6afae331c2f37f9f20539e05ee620ee278368db9
parent b1409afcc496a973bd39b444301497d1bf0024c7
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 25 Dec 2025 16:59:22 +0100

expand DD78 based on inspiration from ERPnext

Diffstat:
Mdesign-documents/078-taxes.rst | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 206 insertions(+), 37 deletions(-)

diff --git a/design-documents/078-taxes.rst b/design-documents/078-taxes.rst @@ -50,54 +50,223 @@ Requirements Proposed Solution ================= -* Add a table with (0) tax serial number, (1) instance ID, (2) year, - (3) tax name, (4) tax rate, (5) tax description, (6) I18n description, - (7) rounding mode (up, down, nearest) and (8) rounding unit (amount). - to track known tax rules. Unique should be "instance+year+name". +We borrow ideas from ERPnext. + +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_group`` table with: + + (0) product group serial number + (1) instance ID (foreign key) + (2) product group name (unique with instance ID) + +* Expand ``inventory_products`` table with: + + (1) product group serial number (foreign key), allow NULL for default + + + +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_pot`` table with: + + (0) money pot serial number + (1) instance ID (foreign key) + (2) money pot name (unique with instance ID) + (3) money pot total amount + +* Add a trigger to update statistics whenever the money pot + total amount is incremented. + + + +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_class`` table with: + + (0) tax class serial number + (1) instance ID (foreign key) + (2) tax class name (unique with instance ID) + (3) tax rate (percentage), for taxes charged per value + (4) 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) + (5) tax description + (6) i18n description + (7) calculation mode (net total, cummulative) + (8) rounding mode (up, down, nearest) + (9) rounding unit (amount) + (10) 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. -* Add a new array of applicable taxes to each product, referencing the - tax serial numbers. -* Add a global taxes object to the order/contract terms which - maps "year||tax name" to the - rates, descriptions, and internationalized descriptions. -* In addition to the "taxes" array on each product, also have a - "taxes" array on each order, giving the total taxes actually - applied for the specific order. -* When creating orders, compute taxes from the product array if - possible, but allow client to override the entire array. -* When overriding, generally allow the client to simply only - specify which taxes to apply while doing the calculations in - the backend. But do allow a form where the client also does - the calculations. The latter should be discouraged as it is - likely more error-prone. -* When generating the contract terms, append the tax details + +* 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. As usual, allow the client to define additional - custom tax classes in the order. -* Add new configuration sections "[taxes-$YEAR-$ID]" that specify common tax + 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 table with these values. Add a + 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). -* Expand the statistics to include total taxes applied by year and name - for the various time intervals for paid orders. -* Expand the statistics to add up the various brut amounts of products - (or orders, given a per-order override) for which a tax was applied. - Note that a tax counts as applied if it is listed in the "taxes" array, - even if the tax rate is zero (for example, to ensure donations are also - totaled up). If a given order or product has multiple taxes associated, - the respective total is added to all of those tax classes. If an order - consists of taxed products, the taxes for the order are added per - product. If the taxes are overriden only for an entire order, the - total of the entire order is added to the respective statistics and the - individual products and their taxes are ignored (and not reproduced in - the contract). + +* When an order is paid, make sure to add the tax totals to each + of the money pots. + +* For tipping, specifying a "tax" ``tip-$STAFF`` with a custom amount can + thus be easily assigned to the tip money pot of ``$STAFF``. + +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 + + (0) tax rule serial number + (1) instance ID (foreign key) + (2) tax rule name (unique with instance ID), ``__fallback__`` is reserved + for the fallback rule + (3) tax class serial number (foreign key) + (4) filter: array of product group serial IDs, NULL for all, [] for none + (5) filter: array of product ID serial IDs, NULL for all, [] for none + (6) 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. + + + +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 + + (0) tax regime serial number + (1) instance ID (foreign key) + (2) tax regime name (unique with instance ID), ``__default__`` is reserved + for the default regime + (3) array 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. Test Plan