Entity Parameters
Overview
The Entity Parameters module provides a flexible key-value configuration and feature-flag system. It allows business-level and entity-level (e.g., location) parameter overrides by linking entries from a Parameter Catalog to specific entities.
Architecture
entity-parameters/
├── entity-parameters.module.ts # NestJS module (DI wiring)
├── domain/
│ └── entity-parameters-repository.domain.ts # Port interface + DI token + parseBooleanParameterValue
├── application/
│ └── entity-parameters.service.ts # Use cases (CRUD + getBooleanForBusiness)
├── infrastructure/
│ ├── entity-parameters.repository.ts # Kysely DB adapter
│ └── feature-parameter.guard.ts # LoyaltyEnabledGuard, StoreCreditEnabledGuard
└── interfaces/
├── entity-parameters.controller.ts # REST endpoints (/entity-parameters)
├── dtos/
│ ├── create-entity-parameter.dto.ts
│ └── update-entity-parameter.dto.ts
└── query/
└── paginate-entity-parameters.query.ts
Dependency flow: Controller -> Service -> Repository (via domain port)
Domain Concepts
Parameter Catalog
A catalog of available parameters (e.g., LOYALTY_ENABLED, BARCODE_SCAN_INPUT_ENABLED). Each entry defines:
code— unique parameter keydataType— expected type (boolean,string,number)entityType— scope level (business,location)defaultValue/allowedValues— constraints
Entity Parameter
A concrete assignment of a catalog parameter to a business entity:
parameterCatalogId— links to the catalog entryparameterValue— JSON object, convention:{ "value": <actual_value> }businessId— owning businessentityType+entityId— target entity (nullentityId= business-wide default)
Priority Resolution
When resolving a parameter value for a business:
- Specific entity match (entityId = businessId) takes priority
- Business-wide default (entityId = null) is the fallback
- null if no parameter is set
Feature Guards
The module exports two NestJS guards for feature-gating:
| Guard | Parameter Code | Purpose |
|---|---|---|
LoyaltyEnabledGuard | LOYALTY_ENABLED | Gates loyalty program endpoints |
StoreCreditEnabledGuard | STORE_CREDIT_ENABLED | Gates store credit endpoints |
Both guards resolve businessId from: Firebase claims -> request body -> query params -> locationId DB lookup.
Available Parameter Codes
| Code | Type | Entity Type | Description |
|---|---|---|---|
SERVICE_BOOKING_ENABLED | boolean | business | Enable service booking |
SERVICE_BOOKING_REQUIRED | boolean | business | Require service booking in documents |
ASSIGN_CUSTOMER_FROM_BOOKING_PLATFORM | boolean | business | Auto-assign booking customer |
CASH_REGISTER_DISCREPANCY_APPROVAL_THRESHOLD | number | business | Approval threshold amount |
CASH_REGISTER_CASH_OUT_APPROVAL_THRESHOLD | number | business | Cash-out threshold amount |
REQUIRE_CASH_REGISTER_SESSION_FOR_ADD_DOCUMENT | boolean | business | Require cash session for docs |
LOYALTY_ENABLED | boolean | business | Enable loyalty program |
STORE_CREDIT_ENABLED | boolean | business | Enable store credit wallet |
BARCODE_SCAN_INPUT_ENABLED | boolean | location | Enable barcode scan input |
DISCOUNT_ROUNDING_MODE | enum | business | How stacked discount totals are rounded (half_up | up | down). Default: half_up. See Discount rounding policy. |
DISCOUNT_ROUNDING_PRECISION | enum | business | Decimal precision for rounded discount totals (cents | whole). Default: cents (2 decimal places). whole rounds to the nearest whole currency unit (0 decimal places). See Discount rounding policy. |
Frontend i18n (PWA)
The Entity Parameter form shows human-readable labels in the parameter dropdown (#parameterCatalogId). Labels resolve in this order:
system.parameterCatalog.codes.<CODE>inapps/frontend-pwa/src/i18n/locales/en.jsonandes.json- The catalog row
namefrom the API (custom parameters or missing translations) - The raw
codeas a last resort
When you add a new parameter_catalog row via migration, add matching entries under system.parameterCatalog.codes in both locale files. Parameters created in the Parameter Catalog UI only need i18n keys if you want translated labels beyond the name field stored in the database.
API Endpoints
All endpoints require Bearer token authentication.
| Method | Path | Description |
|---|---|---|
POST | /entity-parameters | Create entity parameter |
GET | /entity-parameters | List with pagination/search |
GET | /entity-parameters/:id | Get by ID |
PATCH | /entity-parameters/:id | Update (partial) |
DELETE | /entity-parameters/:id | Delete |
Create Entity Parameter
POST /entity-parameters
{
"parameterCatalogId": "<uuid>",
"parameterValue": { "value": true },
"businessId": "<uuid>",
"isActive": true,
"createdBy": "<uuid>",
"entityType": "business",
"entityId": "<uuid or omit for business-wide>"
}
List Entity Parameters
GET /entity-parameters?businessId=<uuid>&page=1&size=20&search=LOYALTY
Update Entity Parameter
PATCH /entity-parameters/:id
{
"parameterValue": { "value": false },
"updatedBy": "<uuid>"
}
Design Decisions
-
Parameter values are JSON objects (
{ "value": ... }) rather than raw primitives — this allows future extension with metadata (e.g.,{ "value": true, "overriddenAt": "..." }). -
parseBooleanParameterValuelives in the domain layer — it encodes business rules about how to interpret parameter values and is framework-agnostic. -
Repository is injected via DI token (
ENTITY_PARAMETERS_REPOSITORYSymbol) — the service depends on the domain port interface, not the concrete Kysely implementation. -
Feature guards use shared
resolveBusinessIdhelper — centralizes the multi-source businessId resolution logic.