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.
Documentation Quick Links
Core Documentation
| File | Description |
|---|---|
| models.md | Product, ProductVariant, InventoryLevel, taxonomy, and snapshot models |
| serializers.md | Product API serializers and computed inventory fields |
| views.md | Catalog, variant, inventory, and search endpoints |
| services.md | Product business logic and query expression helpers |
| analytics_service.md | Product and inventory analytics aggregations |
| smart_filter_service.md | Faceted smart filtering and store manifests |
| search.md | Elasticsearch document, indexing, and search API |
| signals.md | Variant cascade, inventory seeding, and reindex signals |
| tasks.md | Celery tasks for manifests, snapshots, and reconciliation |
| examples.md | Usage examples and patterns |
Key Features
Product Catalog
- Dual-structure variants:
Product.variantsJSONField mirrors the Sanity shape;ProductVariantrecords 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
upcfield on variants for POS scanning, alongside the uniquesku - Read-only API: catalog viewsets accept
GETonly; mutation happens through@actionendpoints gated to employees and managers
Inventory Tracking
- Store-scoped inventory: one
InventoryLevelrecord 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 computedstock_available(max(0, stock_on_hand - stock_reserved)) - Movement audit trail: every mutation emits an append-only
InventoryMovementrow - Low stock and reorder filters: list filters and a
low_stock_alertsaction surface items at or below their reorder point - Daily snapshots:
DailyInventorySnapshotrollups feed inventory trend analytics - Zone par levels: zone-level inventory and par levels live in the Zones domain and materialize into store
InventoryLevelrecords
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
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/products/ | GET | Authenticated | List products (lookup by slug) |
/products/{slug}/ | GET | Authenticated | Retrieve a product |
/products/{slug}/variants/ | GET | Authenticated | Variants from the JSONField |
/products/{slug}/variant_records/ | GET | Staff or POS session | Variant model records, unredacted |
/products/{slug}/sync_variants/ | POST | Employee/manager | Sync JSONField variants to model records |
/products/sync_from_sanity/ | POST | Employee/manager | Pull products from Sanity |
/products/dashboard_summary/ | GET | Employee/manager | Catalog dashboard summary |
/products/analytics/top-products/, .../by-category/, .../by-brand/ | GET | Employee/manager | Product analytics |
Variants
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/product-variants/ | GET | Authenticated | List variants (lookup by sku) |
/product-variants/{sku}/ | GET | Authenticated | Retrieve a variant |
/product-variants/{sku}/inventory_status/ | GET | Authenticated | Inventory status for one variant |
/product-variants/by_sku_list/ | GET | Staff or POS session | Bulk 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
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/inventory-levels/ | GET, POST, PUT, PATCH, DELETE | Authenticated; writes employee/manager | Store inventory CRUD |
/inventory-levels/summary/ | GET | Authenticated | Inventory statistics across stores |
/inventory-levels/by_store/, /inventory-levels/by_product/ | GET | Authenticated | Inventory grouped by store or product |
/inventory-levels/low_stock_alerts/ | GET | Authenticated | Items at or below reorder point |
/inventory-levels/{id}/adjust_quantity/, .../reserve_quantity/, .../release_reservation/ | POST | Employee/manager | Locked, audited stock mutations |
/inventory-levels/analytics/health/, .../turnover/, .../dead-stock/, .../movements/ | GET | Employee/manager | Inventory analytics |
Search
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/search/ | GET | AllowAny | Elasticsearch full-text search |
/search/smart-filter/ | GET | AllowAny | Faceted filtering with aggregations |
Taxonomy
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/brands/, /categories/, /product-types/, /collections/ | GET | Authenticated | Read-only taxonomy (GET, HEAD, OPTIONS only) |
/categories/{slug}/product_types/ | GET | Authenticated | Product types within a category |
/attributes/ | GET | Authenticated | Attribute taxonomy, plus options and for_filters actions |
/attributes/sync_from_sanity/ | POST | Employee/manager | Pull 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_initialseeds 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
Related Domains
- Zones - Zone-based inventory with par levels
- Stores - Store locations for inventory
- Transactions - Product sales and returns
- Promotions - Product discounts and campaigns
- Integrations - Sanity CMS product sync
Key Architectural Decisions
- Sanity-authored catalog, read-only API: Studio is the authoring surface, so catalog viewsets expose no REST CRUD
- Dual-structure variants: JSONField for Sanity compatibility, relational records for queries and FKs
- Django-owned inventory:
InventoryLevelis the one writable surface, with locked mutations and a movement audit trail - Elasticsearch for search: public, redaction-aware search instead of PostgreSQL full-text search
- 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 syncAttributeService: filter attributes and Sanity syncProductSearchService: Elasticsearch query executionProductAnalyticsServiceandInventoryAnalyticsService: dashboard aggregationsSmartFilterService: faceted search and store manifest generation
Next Steps
- Start with models.md: understand the data structure
- Review views.md: the access model and API endpoints
- Then search.md: learn search integration
- Check signals.md: see variant synchronization
- Review serializers.md: understand API data flow