Products - Product Catalog & Inventory Management

Domain: Products Location: api/nextango/apps/products/ Last Updated: 2026-07-06

Overview

The Products app manages the product catalog: products, variants, store inventory levels, brands, categories, product types, collections, and attributes. Sanity Studio is the authoring surface for catalog records; Django mirrors them through inbound webhooks and serves them through a read-only API. Inventory is Django-owned and operationally mutable. Elasticsearch backs two public search endpoints, and dual-structure variants (JSONField plus relational records) stay in sync through signals and service methods.

Core Documentation

FileDescription
models.mdProduct, ProductVariant, InventoryLevel, taxonomy, and snapshot models
serializers.mdProduct API serializers and computed inventory fields
views.mdCatalog, variant, inventory, and search endpoints
services.mdProduct business logic and query expression helpers
analytics_service.mdProduct and inventory analytics aggregations
smart_filter_service.mdFaceted smart filtering and store manifests
search.mdElasticsearch document, indexing, and search API
signals.mdVariant cascade, inventory seeding, and reindex signals
tasks.mdCelery tasks for manifests, snapshots, and reconciliation
examples.mdUsage examples and patterns

Key Features

Product Catalog

  • Dual-structure variants: Product.variants JSONField mirrors the Sanity shape; ProductVariant records carry the canonical relational data
  • Automatic variant sync: signals cascade variant changes back into the JSONField and reindex Elasticsearch
  • Taxonomy: brands, categories, product types, collections, and attributes, all authored in Sanity
  • UPC barcode support: optional upc field on variants for POS scanning, alongside the unique sku
  • Read-only API: catalog viewsets accept GET only; mutation happens through @action endpoints gated to employees and managers

Inventory Tracking

  • Store-scoped inventory: one InventoryLevel record per variant per store
  • Stock fields: stock_on_hand (live), stock_reserved (cart/checkout window), stock_initial (immutable seed), stock_lifetime_received (cumulative receipts), and the computed stock_available (max(0, stock_on_hand - stock_reserved))
  • Movement audit trail: every mutation emits an append-only InventoryMovement row
  • Low stock and reorder filters: list filters and a low_stock_alerts action surface items at or below their reorder point
  • Daily snapshots: DailyInventorySnapshot rollups feed inventory trend analytics
  • Zone par levels: zone-level inventory and par levels live in the Zones domain and materialize into store InventoryLevel records

Search & Discovery

  • Elasticsearch integration: full-text product search with fuzzy matching and completion suggestions
  • Smart filtering: faceted aggregations with option availability, manifest-first for browse and real-time for search
  • Real-time indexing: signals coalesce reindex requests per transaction into a single dispatch
  • Public access with redaction: both search endpoints are AllowAny; raw per-store quantities go only to employee/manager users and POS sessions, everyone else gets binary availability

Analytics

  • Product analytics: top products, category performance, brand performance from completed sale line items
  • Inventory analytics: health snapshot, turnover, dead stock, movement summaries, and daily trends

API Endpoints

The catalog read surface is read-only and authenticated. There is no REST CRUD on products, variants, attributes, or taxonomy; InventoryLevelViewSet is the one writable viewset. See Access Model for the full auth and redaction rules.

Product Catalog

EndpointMethodAuthDescription
/products/GETAuthenticatedList products (lookup by slug)
/products/{slug}/GETAuthenticatedRetrieve a product
/products/{slug}/variants/GETAuthenticatedVariants from the JSONField
/products/{slug}/variant_records/GETStaff or POS sessionVariant model records, unredacted
/products/{slug}/sync_variants/POSTEmployee/managerSync JSONField variants to model records
/products/sync_from_sanity/POSTEmployee/managerPull products from Sanity
/products/dashboard_summary/GETEmployee/managerCatalog dashboard summary
/products/analytics/top-products/, .../by-category/, .../by-brand/GETEmployee/managerProduct analytics

Variants

EndpointMethodAuthDescription
/product-variants/GETAuthenticatedList variants (lookup by sku)
/product-variants/{sku}/GETAuthenticatedRetrieve a variant
/product-variants/{sku}/inventory_status/GETAuthenticatedInventory status for one variant
/product-variants/by_sku_list/GETStaff or POS sessionBulk variant lookup by SKU list

A stray update() override binds PUT /product-variants/{sku}/ in routing; the call fails with a server error, performs no mutation, and is still gated by IsAuthenticated.

Inventory

EndpointMethodAuthDescription
/inventory-levels/GET, POST, PUT, PATCH, DELETEAuthenticated; writes employee/managerStore inventory CRUD
/inventory-levels/summary/GETAuthenticatedInventory statistics across stores
/inventory-levels/by_store/, /inventory-levels/by_product/GETAuthenticatedInventory grouped by store or product
/inventory-levels/low_stock_alerts/GETAuthenticatedItems at or below reorder point
/inventory-levels/{id}/adjust_quantity/, .../reserve_quantity/, .../release_reservation/POSTEmployee/managerLocked, audited stock mutations
/inventory-levels/analytics/health/, .../turnover/, .../dead-stock/, .../movements/GETEmployee/managerInventory analytics

Search

EndpointMethodAuthDescription
/search/GETAllowAnyElasticsearch full-text search
/search/smart-filter/GETAllowAnyFaceted filtering with aggregations

Taxonomy

EndpointMethodAuthDescription
/brands/, /categories/, /product-types/, /collections/GETAuthenticatedRead-only taxonomy (GET, HEAD, OPTIONS only)
/categories/{slug}/product_types/GETAuthenticatedProduct types within a category
/attributes/GETAuthenticatedAttribute taxonomy, plus options and for_filters actions
/attributes/sync_from_sanity/POSTEmployee/managerPull attributes from Sanity

Full endpoint documentation: views.md

Architecture Highlights

Dual-Structure Variants

Product.variants (JSONField)  <- Sanity-shaped, derived source of truth
         | sync (signals + sync_variants)
ProductVariant (relational)   <- canonical source of truth, FKs and queries

The JSONField keeps Sanity CMS compatibility and the relational records carry foreign keys, indexes, and per-variant inventory. Signals cascade variant changes back into the JSONField so the two stay aligned.

Read more: models.md, signals.md

Sync Direction

Sanity to Django only. Inbound webhooks create and update Product, ProductVariant, and InventoryLevel mirrors; outbound Django-to-Sanity signals exist in the codebase but are disabled. Sanity Studio authors catalog records, Django owns inventory mutations.

Read more: signals.md

Store Inventory

ProductVariant
    └─ InventoryLevel (one per store)
        stock_on_hand / stock_reserved / stock_initial / stock_lifetime_received
        stock_available = max(0, stock_on_hand - stock_reserved)

A product is purchasable at a store when it has an active InventoryLevel there with stock_on_hand above stock_reserved. Zone-level par levels in the Zones domain materialize into these store records.

Read more: models.md

Elasticsearch Integration

  • Signal-driven indexing with per-transaction reindex coalescing
  • Fuzzy full-text search and completion suggestions
  • Faceted smart filtering with option availability
  • Capability-keyed inventory redaction on both public endpoints

Read more: search.md, smart_filter_service.md

Getting Started

1. Understand the Models

Start with models.md to understand:

  • Product model (name, slug, organization refs, variants JSONField)
  • ProductVariant model (SKU, UPC, pricing, attributes, stock_initial seed)
  • InventoryLevel model (store, stock fields, costing, reorder settings)
  • Brand, Category, ProductType, Collection, Attribute models

2. Review Variant Synchronization

Read signals.md to learn:

  • How variant changes cascade into the Product JSONField and Elasticsearch
  • How stock_initial seeds InventoryLevel records one time at creation
  • How reindex coalescing collapses many changes into one dispatch

3. Explore API Endpoints

Check views.md for:

  • The access model: read-only catalog, gated actions, inventory redaction
  • Variant and inventory lookups
  • The two public search endpoints

4. Understand Search

Review search.md and smart_filter_service.md for:

  • Elasticsearch document structure
  • Search query patterns
  • Faceted filtering and store manifests

Key Architectural Decisions

  1. Sanity-authored catalog, read-only API: Studio is the authoring surface, so catalog viewsets expose no REST CRUD
  2. Dual-structure variants: JSONField for Sanity compatibility, relational records for queries and FKs
  3. Django-owned inventory: InventoryLevel is the one writable surface, with locked mutations and a movement audit trail
  4. Elasticsearch for search: public, redaction-aware search instead of PostgreSQL full-text search
  5. SKU and slug as lookup fields: natural identifiers for variants and products in URLs

Common Use Cases

  • Catalog reads: serve products and variants to dashboard, storefront, and POS
  • Inventory tracking: monitor stock levels per store with live and reserved quantities
  • Low stock monitoring: identify products at or below their reorder point
  • Product search: public fuzzy search and faceted filtering with inventory redaction
  • Variant synchronization: keep JSONField and relational variants aligned
  • Analytics: top products, category and brand performance, inventory health and trends

Services

See services.md, analytics_service.md, and smart_filter_service.md for:

  • ProductService: display queries, dashboard summary, SKU lookups, Sanity sync
  • AttributeService: filter attributes and Sanity sync
  • ProductSearchService: Elasticsearch query execution
  • ProductAnalyticsService and InventoryAnalyticsService: dashboard aggregations
  • SmartFilterService: faceted search and store manifest generation

Next Steps

  1. Start with models.md: understand the data structure
  2. Review views.md: the access model and API endpoints
  3. Then search.md: learn search integration
  4. Check signals.md: see variant synchronization
  5. Review serializers.md: understand API data flow

Was this page helpful?