Stores - Multi-Location Management

Domain: Stores Location: api/nextango/apps/stores/ Last Updated: 2026-07-09

Overview

The Stores app manages physical store locations: identity and address data, operating hours, tax jurisdiction assignment, manager assignments, store settings (pickup, returns, appointments), URL slugs for the storefront, and per-zone compliance audit records. Store and TaxJurisdiction sync bidirectionally with Sanity CMS; the Site grouping layer is Django-owned and does not sync.

FileDescription
models.mdStore, TaxJurisdiction, Site, SiteResolver, ComplianceCheck
serializers.mdWebhook processing, settings flattening, compliance-profile bridge
views.mdLive endpoints and ViewSets
services.mdStoreService summary and DashboardService aggregation
signals.mdSlug derivation, outbound sync, cache invalidation, inventory backfill
dashboard_home.mdDashboard home endpoint schema
examples.mdUsage examples

Ownership Model

  • Store and TaxJurisdiction are authored in Sanity Studio and mirrored in Django with bidirectional sync.
  • Site is Django-owned. It does not sync to or from Sanity in either direction; the Sanity site document type is dormant. Site is served only through the /sites/ REST endpoint and is parked as a seam for post-MVP tenancy.
  • Store.slug is Django-owned even though Store syncs: it is derived from the store name on save and never authored in or synced to Sanity.
  • ComplianceCheck records are created in Django and synced outbound to Sanity asynchronously.
  • The store-as-url multi-site concept (companyInfo to site to store host routing) was reversed to a minimal footprint; the storefront is effectively single-site today. Do not treat multi-site host routing as live behavior.

API Endpoints

All Stores routes are root-router registrations (nextango/urls.py:60-63); the app-level stores/urls.py router is not mounted.

EndpointMethodsDescription
/stores/GET, POSTList (cached 600s, active stores only) and create
/stores/{store_code}/GET, PUT, PATCH, DELETERetrieve (summary shape), update, delete. Lookup accepts store_code, Django UUID, or Sanity ID
/stores/{store_code}/dashboard_home/GETToday's KPIs, top products, low stock, recent transactions
/sites/GET, POSTDjango-owned site grouping, filterable by ?domain=, ?is_default=
/sites/{id}/GET, PUT, PATCH, DELETEIndividual site operations
/tax-jurisdictions/GET, POSTTax jurisdiction list (unpaginated array) and create
/tax-jurisdictions/{sanity_id}/GET, PUT, PATCH, DELETELookup by Sanity document ID
/compliance-checks/GET, POSTCompliance audit records
/compliance-checks/{check_id}/GET, PUT, PATCH, DELETELookup by business check_id

Zone and planogram endpoints live in the Zones domain at /zones/ and /planograms/.

All endpoints inherit the platform default permission: IsActiveUser in production, AllowAny in DEBUG (settings/base.py:251-253).

Full endpoint documentation: views.md

Architecture Highlights

Tax jurisdiction system

Stores reference a TaxJurisdiction by Sanity ID (PROTECT on delete). The jurisdiction's total_rate is auto-calculated from its nested rate components on save, and Store.tax_rate exposes it to transaction processing. A store with no jurisdiction, or a jurisdiction with no calculated rate, applies 0% tax with a logged warning; a jurisdiction marked is_exempt applies 0% deliberately. Jurisdiction changes cascade a re-sync of every store using them.

Store settings and the compliance move

Operational settings live in the settings JSONField at exact Sanity paths (settings.pickupEnabled, settings.returnToInventory, settings.payAtStoreEnabled). The regulated-retail compliance policy (age gate, credential types, permits) moved off settings to a top-level regulatedCompliance object backed by the store's compliance profile; the serializer dual-reads for legacy content and scrubs the moved keys from settings. See serializers.md.

Store-as-url slug

Every store gets a unique, URL-routable slug derived from its name on save (pre_save signal), so every active store is reachable at /{slug}/ on the storefront. The slug is Django-owned and never synced. See signals.md.

Signal-based automation

Beyond sync, saving a Store invalidates the cached store list, and creating a Store queues an inventory backfill so a new store is stocked with available-everywhere variants instead of appearing empty. See signals.md.

Common Use Cases

  • Store setup: create a store, assign its tax jurisdiction, configure settings and hours in Sanity Studio
  • Multi-location operations: stores across different tax jurisdictions with shared or separate rates
  • Default store: exactly one is_default store loads first in the dashboard; setting a new default auto-clears the old one
  • POS terminal lookup: terminals resolve their store by store_code or Sanity ID at login
  • Compliance verification: record and review per-zone audit checks
  • Dashboard: per-store summary and trading snapshot endpoints
  • Zones: zone organization; live zone/planogram endpoints
  • Users: manager assignments via manager_ids bidirectional sync
  • Transactions: store-scoped transaction processing and tax application
  • Products: store-scoped inventory levels
  • Compliance: the store compliance profile behind regulatedCompliance
  • Integrations: the sync framework and store sync handler

Next Steps

  1. Start with models.md for the data structure and ownership boundaries
  2. Then views.md for the live endpoints
  3. Review serializers.md for webhook processing and the compliance bridge
  4. Check signals.md for automation
  5. See services.md for the summary and dashboard aggregations

Was this page helpful?