PRD: Tax Group Configuration (Back Office)
| Module | Tax & Invoice | PRD ID | PRD-GRP-001 |
| Status | Ready to dev | FEAT | GRP |
| Epic | BANA-1318 | Plane | BANA-1740 |
| Date | 2026-06-30 | Version | v1.1 |
| Packages | @nx/taxation | URD | GRP |
| Surface | BO · Ops | ||
| Owner | Phát Nguyễn | ||
TL;DR
Lets the Operations team (NEXPANDO & VNPAY) author reusable tax groups from the Back Office: each group declares a tax method (Deduction / Direct) and bundles multiple tax rate lines (tax type, rate %, included in price or not, Sale/Purchase direction, tax bearer, priority) - the tax engine provisions those lines onto products assigned to the group and reconciles them whenever a product changes.
1. Context & Problem
Every product must carry the declared set of taxes (VAT, excise, environmental…), but setting tax product-by-product is error-prone and unmaintainable. A tax group is a reusable template: declare the group's tax rate lines once, then assign the group to products during product setup - the product receives the group's full set of tax lines with no per-tax manual entry.
This increment is the Back-Office tax-group configuration screen: the Operations team authors groups through three blocks - the group list, the group form, and the tax-rate-line block on the edit screen. Provisioning tax onto products and reconciling on product change is background work in the tax engine, not an action on this screen.
2. Goals & Non-Goals
Goals
- Author tax groups: identifier, bilingual name, tax method (Deduction / Direct), status.
- Author tax rate lines within each group: tax type (5 classifications), rate %, included-in-price, priority, direction, tax bearer.
- The tax engine provisions the group's tax lines onto assigned products and reconciles them when a product changes (background).
- Constrain tax groups by the merchant's tax method - a group is assignable only where the method matches (not built yet, see FR-6).
Non-Goals
- The merchant's tax identity and e-invoice profile (Tax identity).
- Issuing invoices or tax declarations (Invoice lifecycle, Tax declaration).
- Per-product manual tax overrides outside the group mechanism.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Tax by template | A product assigned a group receives all of the group's tax lines, with no per-tax manual entry |
| Consistency | Changing or removing a product makes the engine recompute its provisioned tax; no tax from a stale configuration remains |
| Compatibility | 🚧 A tax group is assignable only to merchants with the same tax method (not built - FR-6) |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Operations team (NEXPANDO & VNPAY) | Author tax groups and their rate lines so products are taxed consistently from a template |
Core scenario: the Operations team creates a "Retail VAT" group under the Deduction method; after saving, they reopen the group on the edit screen to add the line "VAT 10%, included in price, Sale direction, borne by the customer"; products assigned the group receive that tax line automatically; when a product changes or is removed, the engine reconciles the provisioned tax.
5. User Stories
- As the Operations team, I want to author a tax group with its tax rate lines, so assigned products receive their full tax set without manual entry.
- As the Operations team, I want each tax line declared as a %, with an included-in-price flag, a Sale/Purchase direction, and a tax bearer, so each tax's obligation is described precisely.
- As the Operations team, I want a priority on each line, so when a product matches several lines the lowest-numbered line applies first.
- As the Operations team, I want a tax group assignable only to merchants with the same tax method, so tax cannot be misconfigured (not built yet).
6. Functional Requirements
Screen operations
| # | Requirement | Status | URD ref |
|---|---|---|---|
| FR-1 | Tax group list - 4 columns, all 4 filterable, with edit / delete actions | ✅ built | URD-GRP-001 |
| FR-2 | Create / edit a tax group - 5-field form with validation and defaults | ✅ built | URD-GRP-001 |
| FR-3 | Tax rate lines of a group - a group holds multiple tax lines, added / edited / deleted via a dialog on the edit screen | ✅ built | URD-GRP-001 |
System behavior (background - not screen operations)
| # | Requirement | Status | URD ref |
|---|---|---|---|
| FR-4 | Provision tax onto products - a product assigned a group receives all of the group's tax lines | ✅ built (engine) | URD-GRP-002 |
| FR-5 | Reconcile on product change - changing or removing a product recomputes its provisioned tax | ✅ built (engine) | URD-GRP-003 |
| FR-6 | Tax method compatibility - a tax group is assignable only to merchants with the same tax method | 🚧 not built | URD-GRP-004 |
FR-1 - Tax group list ✅
- Columns: Name (bilingual) · Description (bilingual) · Tax method · Status.
- Filters: all 4 columns - Name and Description by fuzzy text search; Tax method and Status by value.
- Row actions: Edit (opens the edit screen) · Delete (confirmation dialog naming the group's identifier).
FR-2 - Create / edit a tax group ✅
| Field | Required | Rule |
|---|---|---|
| Identifier | required | free text (no format / length constraint yet) |
| Name | required | bilingual EN/VI, ≤ 200 characters per language |
| Description | optional | bilingual EN/VI, ≤ 400 characters per language |
| Tax method | required | 2 values: Deduction (default) · Direct |
| Status | required | 3 values: Activated (default) · Deactivated · Archived |
- Leaving the screen with unsaved form changes → warning.
- Saving (create and edit alike) returns to the list.
FR-3 - Tax rate lines of a group ✅
A tax group holds multiple tax rate lines; lines are managed on the group's edit screen - after creating a group you must reopen it to add lines.
| Field | Required | Rule |
|---|---|---|
| Tax type | required | pick 1 of 5 classifications: VAT · Excise · Environmental · Luxury · Personal income tax (PIT) - only active classifications are offered |
| Tax rate | required | entered as a %; stored to exactly 4 decimal places; displayed with 2 (e.g. 10.00%) |
| Included in price | toggle | default not included |
| Priority | required | integer ≥ 1 (default 1); when a product matches several lines, the lowest-numbered line applies first |
| Direction | required | Sale (default) · Purchase |
| Tax bearer | required | Customer (default) · Merchant |
- Lines are added / edited via a dialog; deleting a line shows a confirmation naming the line's rate.
- The line table shows: Tax type (colored label per classification) · Rate % · Included in price (Yes/No) · Priority · Direction · Tax bearer.
- Above the table sits a summary line of the group's rates, e.g.
VAT 10.00% incl · Environmental 2.00% excl.
FR-4 - Provision tax onto products (background) ✅
When a product is assigned a tax group (at product create or update), the tax engine provisions all of the group's tax lines onto the product. There is no action for this on the screen - the screen only authors the template.
FR-5 - Reconcile on product change (background) ✅
When a product is changed or removed, the engine recomputes its provisioned tax against the current group - a product never keeps tax from a stale configuration.
FR-6 - Tax method compatibility 🚧
Rule to build: a tax group may only be assigned to merchants with the same tax method - Deduction groups only to merchants filing under deduction, Direct groups only to merchants filing directly. Today the screen has no check at all: any group can be assigned to any merchant, and correctness rests on the Operations team. Enforcement is needed where groups are assigned, plus a warning while authoring.
Acceptance criteria
- Creating a group without identifier, name, tax method, or status → form error, nothing saved; a name longer than 200 characters → error.
- A new group defaults to the Deduction method and Activated status.
- The list filters on all 4 columns; deleting a group always goes through a confirmation naming its identifier.
- Tax lines can only be added once the group is saved (on the edit screen).
- Entering a rate of
10→ the table shows10.00%; the stored value keeps exactly 4 decimal places. - A priority of
0or negative → error (minimum 1). - The tax-type dropdown offers only active classifications, covering all 5: VAT / Excise / Environmental / Luxury / PIT.
- Deleting a tax line always goes through a confirmation naming its rate; the summary line updates accordingly.
- 🚧 (once FR-6 is built) Assigning a Deduction group to a direct-method merchant → blocked with a message.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Consistency | Provisioned tax is reconciled by the engine on product change; no tax from a stale configuration remains |
| Compatibility | 🚧 A tax group is assignable only to merchants with the same tax method (FR-6, not built) |
| Authz | Only Back-Office accounts with tax-configuration permission can operate this screen |
| i18n | Group name and description are entered bilingually (EN/VI); method / status / tax-type labels display bilingually |
8. UX & Flows
How the Operations team authors a tax group:
Screen structure and the background part:
The screen lives in the Back Office "System Configuration" section.
9. Data & Domain
| Concept | Role in this screen |
|---|---|
| Tax group | A reusable template: one tax method + multiple tax rate lines |
| Tax rate line | One line in a group: tax type, %, included in price, priority, direction, tax bearer |
| Tax type | The classification a line belongs to: VAT / Excise / Environmental / Luxury / PIT |
| Tax method | Deduction or Direct - declared on the group; must match the merchant's method (FR-6 🚧) |
| Applied tax | The tax the engine provisions onto a product from its assigned group's lines |
Conceptual only - provisioning and reconciliation run in the tax engine; the data model lives in the developer docs.
10. Dependencies & Assumptions
Depends on
- Tax identity / method (Tax identity) - the source of the merchant's tax method that FR-6 must check against.
- Products - where tax groups are assigned and tax lands (Products).
Assumptions
- The Operations team is authenticated with tax-configuration permission.
- Tax classifications (VAT, Excise…) are declared and active so the tax-type dropdown has data.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Several tax lines matching the same product | Each line carries a priority; the lowest number applies first |
| No tax-method compatibility check yet | 🚧 FR-6 - currently rests on the Operations team operating correctly |
| Mistyped rates (percent vs decimal confusion) | Rates are entered as %, and the table plus the summary line echo xx.xx% immediately for an eyeball check |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 - available in the Back Office |
| Rollout | Tax-configuration users; no feature flag |
| Migration | None - it configures the existing tax engine |
| Launch criteria | A group can be authored with all 5 fields plus tax lines with all 6 fields; the list filters on 4 columns; assigned products receive the group's full line set; product changes are reconciled by the engine |
| Monitoring | Group / tax-line edits; engine reconciliation outcomes |
13. FAQ
What is a tax group? A reusable template: one tax method + multiple tax rate lines (tax type, %, included in price…), assigned to products so tax applies automatically.
Why can't I add tax lines right after creating a group? Lines are managed on the edit screen - saving a group returns to the list; reopen the group to add its lines.
What happens when a product changes? The engine reconciles its provisioned tax against the current group, so no stale tax remains.
Why can't I use a tax group on a merchant? By design a group is only assignable to merchants with the same tax method - this check is not built yet (FR-6), so nothing blocks it today.
References
- URD: Tax -
GRP(Tax Groups) ·TAX(Tax Identity) - Module: Tax & Invoice - URD · Roadmap: Phase 2 - By Month
- Developer: @nx/taxation