Skip to main content

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_listPriceListImportHandler
    • price_list_scopePriceListScopeImportHandler
    • price_list_itemPriceListItemImportHandler

3. Template Schemas

Updated apps/backend/src/data-import/application/template-schema.service.ts:

  • Added price_list schema (9 columns)
  • Added price_list_scope schema (5 columns)
  • Added price_list_item schema (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_TYPES array

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_item to 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

ColumnRequiredTypeNotes
namestringUnique per business
currencystringCurrency code (GTQ, USD, etc.)
kindenumretail, restaurant, both (default: both)
channelenumpos, online, dine_in, takeaway, delivery, kiosk, wholesale (default: pos)
priorityinteger0-1000 (default: 100)
validFromISO dateOptional validity start
validToISO dateOptional validity end (must be after validFrom)
isActivebooleanDefault: true
notestextOptional notes

price_list_scope

ColumnRequiredTypeNotes
priceListNamestringPrice list name (must exist)
locationNamestringLocation name (null = global scope)
channelenumpos, online, dine_in, takeaway, delivery, kiosk, wholesale
isDefaultbooleanOnly one default per business + location + channel (default: false)
isActivebooleanDefault: true

price_list_item

ColumnRequiredTypeNotes
priceListNamestringPrice list name (must exist)
itemTypeenumproduct, service, modifier
itemIdentifierstringSKU (product), name (service/modifier)
unitPricedecimalPrice in currency units (converted to minor units)
minQtydecimalMinimum quantity for tier pricing (default: 1)
priorityintegerPriority for overlapping rules (default: 0)
validFromISO dateOptional validity start
validToISO dateOptional validity end (must be after validFrom)
isActivebooleanDefault: true

Example Import Files

price_list.xlsx

namecurrencykindchannelpriorityvalidFromvalidToisActivenotes
Weekend SpecialUSDrestaurantdine_in2002026-01-012026-12-31TRUEWeekend pricing
Retail StandardGTQretailpos100TRUEStandard retail prices

price_list_scope.xlsx

priceListNamelocationNamechannelisDefaultisActive
Weekend SpecialStore 1dine_inTRUETRUE
Weekend SpecialonlineFALSETRUE
Retail StandardposTRUETRUE

price_list_item.xlsx

priceListNameitemTypeitemIdentifierunitPriceminQtypriorityvalidFromvalidToisActive
Weekend SpecialproductSKU12319.9910TRUE
Weekend SpecialproductSKU12317.99100TRUE
Weekend SpecialserviceHaircut25.0010TRUE
Retail StandardmodifierExtra Cheese2.5010TRUE

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 template
  • GET /data-import/templates/price_list_scope - Download template
  • GET /data-import/templates/price_list_item - Download template
  • POST /data-import/upload - Upload file (importType: price_list | price_list_scope | price_list_item)
  • GET /data-import/jobs - List jobs
  • GET /data-import/jobs/:id - Get job status
  • GET /data-import/jobs/:id/errors - Get error rows

Notes

  1. Money Conversion: The price_list_item handler converts decimal prices to minor units based on the currency's minorUnit field (e.g., 19.99 USD with minorUnit=2 → 1999 cents). Both unit_price_minor (integer, authoritative) and unit_price (decimal, display) are stored.

  2. Unique Default Constraint: The price_list_scope handler enforces that only one scope can be marked as default per business + location + channel. When setting a new default, it automatically unsets the existing default.

  3. Upsert Semantics: All handlers use upsert logic (update if exists, create if not) to make re-imports safe and idempotent.

  4. Import Order: Users should import in this order:

    1. Price Lists (requires currencies)
    2. Price List Scopes (requires price lists, optionally locations)
    3. Price List Items (requires price lists and products/services/modifiers)
  5. Global vs Location Scopes: When locationName is empty/null in price_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

  1. Manual Testing: Follow the testing checklist above
  2. Database Migration: Ensure the pricing tables migration (2026-02-27t04:29:09.000z-pricing-tables.mjs) is applied to all environments
  3. Deployment: Deploy backend and frontend changes together
  4. User Training: Update user documentation with pricing import examples
  5. 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