Skip to content

Multi-Tenancy & Access Control ​

MEGSTAT POS is built from the ground up to support multi-store chains, franchises, and multi-tenant software-as-a-service deployments.

Tenancy Isolation Model ​

Isolation is enforced at the database level using a shared database with discriminator columns (tenantId). Every core entity carries a non-nullable, indexed tenantId:

  • Tenants & Branches: Tenant, Branch, Terminal
  • Identity & Access: User, Role, TenantUser
  • Catalog & Pricing: Product, Category, PriceList, Promotion
  • Customers & Suppliers: Customer, Supplier, CustomerGroup
  • Operations: Register, Shift, Sale, Purchase, StockMovement, LedgerEntry
prisma
model Product {
  id        String   @id @default(cuid())
  tenantId  String
  sku       String
  name      String
  // ...
  tenant    Tenant   @relation(fields: [tenantId], references: [id])

  @@unique([tenantId, sku])
  @@index([tenantId])
}

Golden Rule of Tenancy ​

The Golden Rule

tenantId is ALWAYS extracted from the verified session, NEVER trusted from request body or query parameters.

In backend/src/middleware/tenant.ts:

  1. The authentication middleware verifies the incoming JWT or session cookie and sets c.set("user", user) and c.set("tenantId", tenantId).
  2. All database queries throughout route handlers and services inject where: { tenantId } derived from the context.
  3. If an endpoint explicitly accepts :tenantId in the URL (for multi-tenant cross-store administrative actions), the handler must use requireTargetTenantPermission or requireTenantParam to verify that the calling user has authorized access to that specific tenant.

Role-Based Access Control (RBAC) ​

MEGSTAT POS implements fine-grained RBAC with role inheritance and override capabilities:

Predefined Roles ​

  • Owner: Full system authority, store billing, tenant settings, and rollover control.
  • Store Manager: Shift authorization, cashier overrides, voiding transactions, price adjustments, and stock auditing.
  • Cashier / POS Operator: Billing, cash drawer movements, hold/recall sales, customer creation.
  • Inventory Clerk: Purchase receiving, warehouse transfers, stock adjustments, and stocktaking counts.

Permissions Matrix ​

Permissions are represented as scoped strings:

  • pos:checkout, pos:refund, pos:hold, pos:discount
  • inventory:view, inventory:adjust, inventory:transfer, inventory:audit
  • reports:view, reports:export, reports:financials
  • settings:tax, settings:users, settings:hardware

Routes enforce permissions declaratively:

ts
app.post("/sales", requirePermission("pos:checkout"), async (c) => {
  // Handler logic
});

Manager Override Engine ​

Counter cashiers frequently encounter situations requiring elevated approval (e.g., overriding a maximum line discount, voiding an already-tendered line item, or opening a drawer outside of a sale).

MEGSTAT POS provides a lightweight manager override mechanism:

  1. Cashier UI triggers ManagerOverrideModal.
  2. Manager enters their 4-digit PIN or credentials.
  3. Endpoint POST /api/auth/manager-override verifies manager permissions.
  4. Generates an ephemeral single-use override token logged directly to AuditLog.
  5. The cashier's action completes under the audited manager approval.

MEGSTAT POS — Built for Retail Stores & Multi-Branch Businesses