Saltar al contenido principal

Business Tax Config

Overview

The business-tax-config module manages the association between a Business and its configured TaxDefinition records. A single business may have multiple active tax configurations (e.g., one general VAT and one withholding tax).

Default tax configurations are automatically seeded when a new business is created, based on the business's country.


Domain Concepts

ConceptDescription
BusinessTaxConfigA join record linking a business to a tax_definition. Carries isActive and audit fields.
TaxDefinitionA globally-defined tax (e.g. "IVA Guatemala 12%"). Managed by the tax-definition module.
Default seedingOn business.create event, one tax config is auto-created based on countryId.

Country → Default Tax mapping

CountryTax Definition
Guatemala (GT)IVA GT
El Salvador (SV)IVA SV
Honduras (HN)IVA HN
Mexico (MX)IVA MX
United States (US)IVA US
(any other)IVA GT (fallback)

Architecture

This module follows Hexagonal Architecture:

domain/
business-tax-config-repository.domain.ts ← IBusinessTaxConfigRepository port
application/
business-tax-config.service.ts ← Use cases + OnCreateBusinessEvent handler
infrastructure/
business-tax-config.repository.ts ← Kysely adapter implementing the port
interfaces/
business-tax-config.controller.ts ← HTTP routes (thin controller)
dtos/
create-business-tax-config.dto.ts
update-business-tax-config.dto.ts
query/
paginate-business-tax-config.query.ts

Dependency direction: Controller → Service → Repository interface ← Repository implementation


API Endpoints

All endpoints require Bearer token authentication. The controller applies RolesGuard with PermissionResource(PolicyResource.BusinessTaxConfig) at class level, enforcing RBAC for all routes.

MethodPathDescription
POST/business-tax-configCreate a new tax config
GET/business-tax-configList active configs (paginated)
GET/business-tax-config/:idGet config by ID
PATCH/business-tax-config/:idUpdate a config
DELETE/business-tax-config/:idDelete a config

All read/delete endpoints require a businessId query parameter for multi-tenant scoping.

GET /business-tax-config

Query parameters:

ParameterTypeRequiredDescription
businessIdUUIDNoFilter by business
searchstringNoSearch across tax definition name and code
pagenumberNoPage number (default 1)
sizenumberNoPage size (default 20)

Only records where isActive = true are returned.

POST /business-tax-config

{
"businessId": "uuid",
"taxDefinitionId": "uuid",
"createdBy": "uuid",
"isActive": true
}

PATCH /business-tax-config/:id

{
"businessId": "uuid",
"taxDefinitionId": "uuid",
"updatedBy": "uuid",
"isActive": false
}

All fields except businessId and updatedBy are optional.


Event Integration

The service listens to the business.create event emitted by the businesses module:

@OnEvent(OnCreateBusinessEvent.eventName, { async: true })
private async handleBusinessCreateEvent({ business }) { ... }

This auto-seeds the appropriate default tax configuration without any HTTP call.


Bruno API Collection

Located at api-client/flowpos/collections/business-tax-config/. Request files follow the naming convention <verb> business tax config[s].yml. Uses environment variables:

VariableDescription
{{BASE_URL}}API base URL (e.g. http://localhost:4000)
{{ID_TOKEN}}Firebase Bearer token
{{businessId}}Target business UUID
{{taxDefinitionId}}Tax definition UUID
{{dbUserId}}Authenticated user's DB UUID
{{businessTaxConfigId}}Auto-set after POST by the after-response script

Design Decisions

  • isActive filter in base query — both the count and results queries share the same base query so pagination counts are always consistent with the returned data.
  • No transaction wrapper in controller — create and update are single-operation statements and are inherently atomic; explicit transaction wrappers at the HTTP layer added complexity without benefit.
  • Country→tax mapping as a constant — the DEFAULT_TAX_BY_COUNTRY map in the service is a pure data structure, easy to extend for new countries without touching control flow.
  • delete returns SelectableBusinessTaxConfig — returning only the deleted record's flat fields is sufficient; loading full relations after deletion was a type-safety lie (the mixin was never applied).
  • Domain port is framework-agnosticIBusinessTaxConfigRepository does not import from the interfaces layer; sortable key types are expressed as keyof SelectableBusinessTaxConfig to keep the dependency direction inward.
  • RBAC enforced at controller level@UseGuards(RolesGuard) + @PermissionResource(PolicyResource.BusinessTaxConfig) applied to the class, consistent with other modules (e.g. products).