Architecture
DG SmartPOS is a monorepo of four applications sharing one Laravel API. Each application is its own independent git repository (separate history, separate branches), living side by side on disk:
POS/├── dg_smart_pos_api/ # Laravel 12, central + per-tenant API├── dg_smart_pos_admin/ # Vue 3 + Vuetify 3, central SaaS admin panel├── dg_smart_pos_frontend/ # Vue 3 + Vuetify 3, POS terminal SPA + Electron desktop app└── dg_smart_pos_storefront/ # Astro + Preact, customer-facing online storefrontSee Applications for what each one does. This page covers the thing that shapes almost every other decision in the codebase: multitenancy.
Multitenancy
Section titled “Multitenancy”DG SmartPOS is multi-tenant using Stancl Tenancy (stancl/tenancy ^3.9). One Laravel codebase serves every client; each client (tenant) gets an isolated database.
Central vs. tenant databases
Section titled “Central vs. tenant databases”| Database | Holds |
|---|---|
Central DB (pos_central) |
tenants, domains, packages, features, tenant_features, tenant_contacts, subscription history, eTIMS/Tuma configs, admin notifications |
Tenant DB (one per tenant, myclient_{tenant_id}_db) |
users, stores, store_stocks, customers, sales, inventory, roles/permissions, receipts, everything store-operational |
A tenant is identified by App\Models\Tenant. Creating one triggers a queued pipeline: CreateDatabase → MigrateDatabase (runs database/migrations/tenant/*) → SeedTenantDatabase (roles, permissions, units) → OnboardTenant (first store, admin user).
How a request knows which tenant it’s for
Section titled “How a request knows which tenant it’s for”Every tenant-scoped API call must carry the header X-Tenant-ID: {uuid}. There is no domain-based routing for this in practice. Identification is request-header based (InitializeTenancyByRequestData, configured in AppServiceProvider::boot()).
routes/api.php: central routes, no tenant header needed (tenant resolution itself, Tuma callbacks, everything underroutes/admin.php).routes/tenant.php: tenant routes, requires the header; middleware stack isapi,InitializeTenancyByRequestData,CheckSubscriptionStatus.
The two POS-facing apps set this header automatically once a tenant is resolved:
- Frontend (POS terminal): user enters a client code or the app resolves tenant by domain, then the Axios interceptor in
src/axios/index.jsattachesX-Tenant-IDto every request. Tenant ID is cached inlocalStorage/Electron store underx-tenant-id. - Admin: never sends the header. It only ever talks to central resources (managing tenants, not operating as one).
- Storefront: resolves its website/tenant server-side per request (Astro SSR), independent of the frontend’s client-side flow.
Subscriptions and features
Section titled “Subscriptions and features”Tenants have a package and optional tenant_features overrides. CheckSubscriptionStatus middleware rejects tenant requests with codes like SUBSCRIPTION_CANCELLED or ACCOUNT_SUSPENDED (403), which the frontend’s subscription store turns into a suspension UI. Feature gating in code uses tenant()->hasFeature(...) or FeatureService.
Authentication
Section titled “Authentication”Laravel Sanctum (auth:sanctum) for token-based auth, shared shape across Admin and POS. Inside a tenant, roles/permissions use Spatie Laravel Permission, seeded per tenant by SeedTenantDatabase.
Business type: retail vs. restaurant
Section titled “Business type: retail vs. restaurant”Every store has a business_type column (retail | restaurant, default retail). Restaurant mode is not a separate app or module. It’s a configuration:
accompanying_stock_enabledturns on automatically; barcode/SKU/serial settings turn off at store creation.- Certain sidebar items hide (Warehouses, Stock Transfers, GRNs, Quotations).
- Frontend exposes this via
systemConfigStore.isRestaurantMode.
This pattern (a business type as a configuration of capabilities rather than a fork in the codebase) is also the direction the whole platform is being restructured toward. See Roadmap.
Where to go next
Section titled “Where to go next”- Local setup: get all four apps running.
- Conventions: the hard rules, git workflow, database safety, the em-dash thing.
- Applications: per-app detail.
- Modules: per-module business logic inside the API.