Pricing Module Data Import — Implementation Summary
Date: 2026-02-27
Status: ✅ Complete
Overview
Added bulk import support for the pricing module tables: price_list, price_list_scope, and price_list_item. This allows users to import pricing data from Excel or CSV files through the Data Import Center.
What Was Implemented
Backend (NestJS)
1. Import Handlers
Three new handlers following the ImportTypeHandler interface:
PriceListImportHandler (apps/backend/src/data-import/application/handlers/price-list-import.handler.ts)
- Validates: name (required), currency (required), kind, channel, priority, date range
- Resolves: currency by code/name (must be linked to business)
- Upserts by:
businessId + name - Defaults: kind=both, channel=pos, priority=100, isActive=true
PriceListScopeImportHandler (apps/backend/src/data-import/application/handlers/price-list-scope-import.handler.ts)
- Validates: priceListName (required), channel (required)
- Resolves: price list by name, location by name (optional, null = global)
- Upserts by:
businessId + priceListId + locationId + channel - Handles unique default constraint: only one default scope per business + location + channel
- Defaults: isDefault=false, isActive=true
PriceListItemImportHandler (apps/backend/src/data-import/application/handlers/price-list-item-import.handler.ts)
- Validates: priceListName (required), itemType (required), itemIdentifier (required), unitPrice (required)
- Resolves:
- Price list by name
- Product by SKU (itemType=product)
- Service by name (itemType=service)
- Modifier by name (itemType=modifier)
- Converts decimal price to minor units (e.g., 19.99 USD → 1999 cents)
- Upserts by:
priceListId + itemType + productId/serviceId/modifierId + minQty + validFrom + validTo - Defaults: minQty=1, priority=0, isActive=true
2. Module Registration
Updated apps/backend/src/data-import/data-import.module.ts:
- Added handler imports
- Added handlers to providers array
- Registered handlers in
ImportTypeRegistry:price_list→PriceListImportHandlerprice_list_scope→PriceListScopeImportHandlerprice_list_item→PriceListItemImportHandler
3. Template Schemas
Updated apps/backend/src/data-import/application/template-schema.service.ts:
- Added
price_listschema (9 columns) - Added
price_list_scopeschema (5 columns) - Added
price_list_itemschema (9 columns)
4. Unit Tests
Three test suites with comprehensive coverage:
price-list-import.handler.spec.ts(validation, create, update, currency resolution)price-list-scope-import.handler.spec.ts(validation, create, update, default constraint)price-list-item-import.handler.spec.ts(validation, create, update, multi-type resolution, price conversion)
Frontend (React PWA)
1. Import Type Schemas
Updated apps/frontend-pwa/src/components/forms/data-import/import-type-schemas.ts:
- Added labels: "Price Lists", "Price List Scopes", "Price List Items"
- Added schemas with headers and required fields
2. Dashboard Cards
Updated apps/frontend-pwa/src/components/forms/data-import/ImportCenterDashboardPage.tsx:
- Added icons:
DollarSign,MapPinned,Receipt - Added three cards to
IMPORT_TYPESarray
3. Menu Entries
Updated config/firebase/remote-config/pwaMenu.json:
- Added "Price Lists" menu item (order 13)
- Added "Price List Scopes" menu item (order 14)
- Added "Price List Items" menu item (order 15)
4. i18n Translations
Updated apps/frontend-pwa/src/i18n/locales/en.json:
priceLists: "Price Lists"priceListScopes: "Price List Scopes"priceListItems: "Price List Items"
Updated apps/frontend-pwa/src/i18n/locales/es.json:
priceLists: "Listas de precios"priceListScopes: "Alcances de lista de precios"priceListItems: "Artículos de lista de precios"
Documentation
1. User Guide
Updated docs/data-import/user-guide.md:
- Added pricing to bulk-load capabilities
- Added three new rows to "Import Types and Required Fields" table
- Updated references section
2. API Documentation
Updated docs/data-import/data-import-postman-curls.md:
- Added
price_list,price_list_scope,price_list_itemto importType enum - Added three new rows to "Import type schemas" table
Import Dependencies
Price List
Prerequisites:
- Currency (must be linked to business)
Import order: Can be imported anytime after currencies are set up
Price List Scope
Prerequisites:
- Price List (must exist in same business)
- Location (optional, for location-specific scopes)
Import order: After price lists
Price List Item
Prerequisites:
- Price List (must exist in same business)
- Product (for itemType=product)
- Service (for itemType=service)
- Product Modifier (for itemType=modifier)
Import order: After price lists and products/services/modifiers
Column Reference
price_list
| Column | Required | Type | Notes |
|---|---|---|---|
| name | ✓ | string | Unique per business |
| currency | ✓ | string | Currency code (GTQ, USD, etc.) |
| kind | enum | retail, restaurant, both (default: both) | |
| channel | enum | pos, online, dine_in, takeaway, delivery, kiosk, wholesale (default: pos) | |
| priority | integer | 0-1000 (default: 100) | |
| validFrom | ISO date | Optional validity start | |
| validTo | ISO date | Optional validity end (must be after validFrom) | |
| isActive | boolean | Default: true | |
| notes | text | Optional notes |
price_list_scope
| Column | Required | Type | Notes |
|---|---|---|---|
| priceListName | ✓ | string | Price list name (must exist) |
| locationName | string | Location name (null = global scope) | |
| channel | ✓ | enum | pos, online, dine_in, takeaway, delivery, kiosk, wholesale |
| isDefault | boolean | Only one default per business + location + channel (default: false) | |
| isActive | boolean | Default: true |
price_list_item
| Column | Required | Type | Notes |
|---|---|---|---|
| priceListName | ✓ | string | Price list name (must exist) |
| itemType | ✓ | enum | product, service, modifier |
| itemIdentifier | ✓ | string | SKU (product), name (service/modifier) |
| unitPrice | ✓ | decimal | Price in currency units (converted to minor units) |
| minQty | decimal | Minimum quantity for tier pricing (default: 1) | |
| priority | integer | Priority for overlapping rules (default: 0) | |
| validFrom | ISO date | Optional validity start | |
| validTo | ISO date | Optional validity end (must be after validFrom) | |
| isActive | boolean | Default: true |
Example Import Files
price_list.xlsx
| name | currency | kind | channel | priority | validFrom | validTo | isActive | notes |
|---|---|---|---|---|---|---|---|---|
| Weekend Special | USD | restaurant | dine_in | 200 | 2026-01-01 | 2026-12-31 | TRUE | Weekend pricing |
| Retail Standard | GTQ | retail | pos | 100 | TRUE | Standard retail prices |
price_list_scope.xlsx
| priceListName | locationName | channel | isDefault | isActive |
|---|---|---|---|---|
| Weekend Special | Store 1 | dine_in | TRUE | TRUE |
| Weekend Special | online | FALSE | TRUE | |
| Retail Standard | pos | TRUE | TRUE |
price_list_item.xlsx
| priceListName | itemType | itemIdentifier | unitPrice | minQty | priority | validFrom | validTo | isActive |
|---|---|---|---|---|---|---|---|---|
| Weekend Special | product | SKU123 | 19.99 | 1 | 0 | TRUE | ||
| Weekend Special | product | SKU123 | 17.99 | 10 | 0 | TRUE | ||
| Weekend Special | service | Haircut | 25.00 | 1 | 0 | TRUE | ||
| Retail Standard | modifier | Extra Cheese | 2.50 | 1 | 0 | TRUE |
Testing Checklist
- Backend handlers created
- Handlers registered in module
- Template schemas added
- Frontend schemas added
- Dashboard cards added
- Menu entries added
- i18n translations added
- Documentation updated
- Unit tests written
- Linting passed
Manual Testing (TODO)
- Download template for each import type
- Upload valid file → verify success
- Upload file with missing required field → verify error
- Upload file with invalid enum → verify error
- Upload file with invalid currency → verify error
- Upload file with invalid price list reference → verify error
- Upload file with invalid product/service/modifier → verify error
- Re-upload same file → verify upsert (no duplicates)
- Verify price conversion to minor units
- Verify default scope constraint (only one default per business + location + channel)
- Verify frontend wizard shows all target fields
- Verify import history shows pricing imports
API Endpoints
All pricing import types use the standard Data Import API:
GET /data-import/templates/price_list- Download templateGET /data-import/templates/price_list_scope- Download templateGET /data-import/templates/price_list_item- Download templatePOST /data-import/upload- Upload file (importType: price_list | price_list_scope | price_list_item)GET /data-import/jobs- List jobsGET /data-import/jobs/:id- Get job statusGET /data-import/jobs/:id/errors- Get error rows
Notes
-
Money Conversion: The
price_list_itemhandler converts decimal prices to minor units based on the currency'sminorUnitfield (e.g., 19.99 USD with minorUnit=2 → 1999 cents). Bothunit_price_minor(integer, authoritative) andunit_price(decimal, display) are stored. -
Unique Default Constraint: The
price_list_scopehandler enforces that only one scope can be marked as default perbusiness + location + channel. When setting a new default, it automatically unsets the existing default. -
Upsert Semantics: All handlers use upsert logic (update if exists, create if not) to make re-imports safe and idempotent.
-
Import Order: Users should import in this order:
- Price Lists (requires currencies)
- Price List Scopes (requires price lists, optionally locations)
- Price List Items (requires price lists and products/services/modifiers)
-
Global vs Location Scopes: When
locationNameis empty/null inprice_list_scope, the scope applies globally (all locations). Otherwise, it applies only to the specified location.
Files Changed
Backend
apps/backend/src/data-import/application/handlers/price-list-import.handler.ts(new)apps/backend/src/data-import/application/handlers/price-list-scope-import.handler.ts(new)apps/backend/src/data-import/application/handlers/price-list-item-import.handler.ts(new)apps/backend/src/data-import/application/handlers/__tests__/price-list-import.handler.spec.ts(new)apps/backend/src/data-import/application/handlers/__tests__/price-list-scope-import.handler.spec.ts(new)apps/backend/src/data-import/application/handlers/__tests__/price-list-item-import.handler.spec.ts(new)apps/backend/src/data-import/data-import.module.ts(modified)apps/backend/src/data-import/application/template-schema.service.ts(modified)
Frontend
apps/frontend-pwa/src/components/forms/data-import/import-type-schemas.ts(modified)apps/frontend-pwa/src/components/forms/data-import/ImportCenterDashboardPage.tsx(modified)apps/frontend-pwa/src/i18n/locales/en.json(modified)apps/frontend-pwa/src/i18n/locales/es.json(modified)config/firebase/remote-config/pwaMenu.json(modified)
Documentation
docs/data-import/user-guide.md(modified)docs/data-import/data-import-postman-curls.md(modified)docs/data-import/pricing-import-implementation.md(new)
Next Steps
- Manual Testing: Follow the testing checklist above
- Database Migration: Ensure the pricing tables migration (
2026-02-27t04:29:09.000z-pricing-tables.mjs) is applied to all environments - Deployment: Deploy backend and frontend changes together
- User Training: Update user documentation with pricing import examples
- Monitoring: Watch for import errors in Sentry after deployment
Support
For issues or questions:
- Backend: Check handler logs in
apps/backend/src/data-import/ - Frontend: Check Import Center at
/imports - API: Test with Postman using curls in
docs/data-import/data-import-postman-curls.md