Tax Definitions
Overview
The tax-definitions module manages a global catalog of tax definitions (IVA, ISR, withholding taxes, etc.) scoped by country. These are reference records that businesses link to via the business-tax-config module.
Tax definitions are not business-specific — they are shared across all tenants. A tax definition describes a tax rate, type, and country, which businesses can then adopt into their own tax configuration.
Domain Concepts
| Concept | Description |
|---|---|
TaxDefinition | A globally-defined tax record (e.g. "IVA Guatemala 12%"). Contains name, code, rate, type, and country. |
isInclusive | Whether the tax is already included in prices (tax-inclusive pricing) or added on top. |
assignByDefaultTo | Entity type this tax is automatically assigned to (e.g. product, service). |
countryId | ISO country code (e.g. gt, mx, us) used to scope taxes by country. |
businessTaxConfig | A separate module that links businesses to tax definitions. This module provides query endpoints that join with businessTaxConfig for convenience. |
Architecture
This module follows Hexagonal Architecture with a repository injection token:
tax-definitions/
tax-definitions.module.ts <- NestJS module (DI config)
domain/
tax-definitions-repository.domain.ts <- ITaxDefinitionsRepository port + TAX_DEFINITIONS_REPOSITORY token
application/
tax-definitions.service.ts <- Use cases (depends on port interface)
infrastructure/
tax-definitions.repository.ts <- Kysely adapter implementing the port
interfaces/
tax-definitions.controller.ts <- HTTP routes with Swagger documentation
dtos/
create-tax-definition.dto.ts <- POST request body
update-tax-definition.dto.ts <- PATCH request body (partial)
tax-definition-response.dto.ts <- Swagger response schemas
query/
paginate-tax-definitions.query.ts <- GET query parameters
Dependency direction: Controller -> Service -> Repository interface <- Repository implementation
The service is injected with the TAX_DEFINITIONS_REPOSITORY symbol token, depending only on the ITaxDefinitionsRepository interface — never on the concrete Kysely implementation.
API Endpoints
All endpoints require Bearer token authentication (global AuthGuard).
| Method | Path | Description |
|---|---|---|
POST | /tax-definitions | Create a new tax definition |
GET | /tax-definitions | List tax definitions (paginated, filterable) |
GET | /tax-definitions/:id | Get a tax definition by ID |
PATCH | /tax-definitions/:id | Partially update a tax definition |
DELETE | /tax-definitions/:id | Delete a tax definition (204 No Content) |
GET | /tax-definitions/with-relation/:businessId | Get tax definitions linked to a business |
GET | /tax-definitions/unlinked-or-global/:businessId | Get all taxes for a country with link status |
GET /tax-definitions
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
countryId | string | No | Filter by country code (e.g. gt) |
search | string | No | Search across name, code, type, and countryId |
page | number | No | Page number (default 1) |
size | number | No | Page size (default 10; 0 = no limit) |
orderBy | string | No | Sort field: name, code, or type |
order | string | No | Sort direction: asc or desc |
POST /tax-definitions
{
"name": "IVA Guatemala",
"code": "IVA-GT",
"rate": 12,
"type": "percentage",
"isInclusive": true,
"countryId": "gt"
}
PATCH /tax-definitions/:id
All fields are optional:
{
"rate": 7
}
GET /tax-definitions/with-relation/:businessId
Returns only tax definitions that are actively linked to the given business (INNER JOIN on businessTaxConfig where isActive = true). Includes businessTaxConfigId in the response.
GET /tax-definitions/unlinked-or-global/:businessId?countryId=gt
Returns all active tax definitions for the given country, with a businessTaxConfigId field indicating whether each is linked to the business (btcOrder: 1) or unlinked (btcOrder: 2, businessTaxConfigId: null).
Bruno API Collection
Located at api-client/flowpos/collections/tax-definitions/. Uses environment variables:
| Variable | Description |
|---|---|
{{BASE_URL}} | API base URL (e.g. http://localhost:4000) |
{{ID_TOKEN}} | Firebase Bearer token |
{{businessId}} | Target business UUID |
{{taxDefinitionId}} | Auto-set after POST by the after-response script |
Design Decisions
- No RBAC guards — Tax definitions are global reference data, not business-scoped. The global
AuthGuardensures only authenticated users can access them, but no role or CASL checks are applied. Business-level access control is handled by thebusiness-tax-configmodule. - Business relation queries in this module — The
with-relationandunlinked-or-globalendpoints join withbusinessTaxConfigfor convenience, avoiding a separate API call + client-side merge. This is a read-only concern and doesn't violate module boundaries. btcOrdersort field — Returned in business relation queries to allow the UI to display linked taxes before unlinked ones without client-side sorting.- No transactions for reads — Read queries don't use transactions to avoid tying up connection pool resources unnecessarily.
PartialTypefrom@nestjs/swagger— The update DTO uses Swagger'sPartialType(not@nestjs/mapped-types) so all optional fields are discoverable in Swagger UI.