Stores Domain - Views & API Endpoints Documentation

Source: api/nextango/apps/stores/views.py and api/nextango/apps/stores/viewsets/tax_jurisdiction_viewset.py Last Updated: 2026-07-09

Overview

The Stores endpoints are served by root-router registrations in the root URLconf (api/nextango/urls.py:60-63):

RouteViewSet
/sites/SiteViewSet
/stores/StoreViewSet
/tax-jurisdictions/TaxJurisdictionViewSet
/compliance-checks/ComplianceCheckViewSet

The app-level router in stores/urls.py is not included in the root URLconf, so the paths above are the only live Stores routes. Zone and planogram endpoints are served by the zones app at /zones/ and /planograms/ (root router, nextango/urls.py:66-72), not by the stores-namespace viewsets.

None of these ViewSets declares permission_classes, so all resolve to the platform default: IsActiveUser in production, AllowAny when DEBUG is on (settings/base.py:251-253).

SiteViewSet

Purpose: CRUD for the Django-owned Site model; the storefront's branding/config lookup by domain.

Route: /sites/

Source: views.py:18-36

  • Queryset is filtered to is_active=True and ordered default-first (-is_default, name), so inactive sites are not served.
  • Filterable via ?domain=, ?is_active=, ?is_default= (filterset_fields).
  • Used by the Next.js storefront's site resolution at SSR time, e.g. GET /sites/?domain=downtown.myshop.com, and by internal management.

Standard ModelViewSet actions apply (list, retrieve by pk, create, update, partial update, destroy). Setting is_default: true on one site auto-clears it on the others (serializer invariant).

StoreViewSet

Purpose: Store location CRUD for dashboard administration, POS terminal store lookup, and the storefront store selector.

Route: /stores/

Source: views.py:39-151

Queryset and lookup

  • List queryset: select_related('tax_jurisdiction'), filtered to is_active=True, ordered -is_default, name. Inactive stores never appear in the list.
  • lookup_field = 'store_code', with multi-lookup in get_object() (views.py:73-106): a UUID lookup value is tried first as the Django id, then as sanity_id; a non-UUID value resolves by store_code. Single-object resolution uses the unfiltered queryset, so retrieving an inactive-but-existing store returns the object (with is_active: false) rather than 404.

List (cached)

GET /stores/

Cached for 600 seconds per request path and query params; invalidated on any Store save or delete (see Signals). Returns the paginated StoreSerializer representation.

Retrieve (summary shape)

GET /stores/{store_code}/

Source: views.py:112-126

Overridden: returns StoreService.get_store_summary() instead of the serializer representation, so the retrieve response is the summary shape (identity fields plus employee_count, active_zones_count, total_zones_count, recent_compliance_checks_count), not the full Store object. The summary is cached 300 seconds per store. 404 with {"error": "Store not found"} when the store disappears between lookup and summary.

See Services for the exact response fields.

Dashboard home

GET /stores/{store_code}/dashboard_home/

Source: views.py:128-151

Custom detail action returning today's KPIs, top products, low-stock alerts, and recent transactions for the store. Delegates to DashboardService.get_dashboard_home() (60-second cache). Returns 500 with {"error": "Failed to load dashboard data"} on aggregation failure. Full response schema: Dashboard Home.

Create / Update / Delete

POST /stores/
PUT /stores/{store_code}/
PATCH /stores/{store_code}/
DELETE /stores/{store_code}/

Standard ModelViewSet behavior through StoreSerializer: partial updates only touch fields present in the payload; setting is_default: true clears the flag on other stores; slug is server-derived and rejected as input. Deletion can fail on FK constraints from dependent records and does not remove the Sanity document (outbound deletion is disabled; see Signals).

TaxJurisdictionViewSet

Purpose: CRUD for tax jurisdictions.

Route: /tax-jurisdictions/

Source: viewsets/tax_jurisdiction_viewset.py:9-28

  • lookup_field = 'sanity_id': detail routes address jurisdictions by their Sanity document ID, e.g. GET /tax-jurisdictions/taxJurisdiction-franklin-oh/.
  • list() is overridden to serialize the full queryset directly and returns a bare array, bypassing the platform's page-number pagination.
  • Creating or updating with tax_rates_data triggers the model's automatic total_rate recalculation.
  • DELETE fails while any store references the jurisdiction (PROTECT).
GET    /tax-jurisdictions/
POST   /tax-jurisdictions/
GET    /tax-jurisdictions/{sanity_id}/
PUT    /tax-jurisdictions/{sanity_id}/
PATCH  /tax-jurisdictions/{sanity_id}/
DELETE /tax-jurisdictions/{sanity_id}/

ComplianceCheckViewSet

Purpose: CRUD for compliance audit records.

Route: /compliance-checks/

Source: views.py:175-195

  • Queryset prefetches zone and employee (select_related).
  • lookup_field = 'check_id': detail routes use the business identifier, e.g. GET /compliance-checks/CHK-2026-001/.
  • timestamp is read-only; a created check syncs outbound to Sanity asynchronously (see Signals).
GET    /compliance-checks/
POST   /compliance-checks/
GET    /compliance-checks/{check_id}/
PUT    /compliance-checks/{check_id}/
PATCH  /compliance-checks/{check_id}/
DELETE /compliance-checks/{check_id}/

Stores-namespace Zone and Planogram ViewSets

views.py:153-217 defines lightweight ZoneViewSet and PlanogramViewSet classes registered only on the app-level router in stores/urls.py. Because that router is not included in the root URLconf, these viewsets are not reachable; the live /zones/ and /planograms/ routes are the zones app's full-featured viewsets. Documented here only to explain why the classes exist in this module.

Was this page helpful?