Changelog

Platform version history and release notes. This documentation tracks infrastructure-level additions, API changes, schema changes, and behavior changes across all platform versions.


Version 0.95.0 (2026-07-09 - Latest)

New Documentation Pages

  • Cash Management — the cash management domain publishes for the first time: an overview and a models page covering CashierShift and DrawerCount. The domain has no HTTP API, no serializers, no services, no signals, and no tasks; the pages document the two models and their admin registrations.

New Features

Cash Management

  • CashierShift anchors cash-handling activity to a named employee-register-store-time tuple. terminal_id is a plain string matching pos_gateway.TerminalSession.terminal_id, kept as a string rather than a foreign key to avoid coupling the two apps. shift_id is generated on save as SHIFT-<YYYYMMDD>-<8-char hex>. See Models.
  • DrawerCount records a physical cash count against a shift. Closing counts enforce a blind-count workflow: expected_amount stays null until submitted_amount is received. An optional witnessed_by / witnessed_at pair supports two-person verification. See Models.
  • Both models are plain Django models with no Sanity sync: they are operational records born from register activity, not content authored in Studio. See Overview.
  • transactions.CashDrawerEvent.shift is a nullable foreign key into CashierShift; the cannabis-vertical CashReconciliationReportGenerator reads CashierShift and DrawerCount and joins them against payments.TransactionPayment to build per-shift reconciliation rows. See Overview.

Navigation & Tooling

  • Side navigation updated with the Cash Management domain: Cash Management and its Models sub-page under Business Domains
  • convert-docs.mjs updated with new domain mapping: cash_managementcash-management
  • generate-navigation.mjs updated with the Cash Management section entry
  • All Pages index updated with the Cash Management section
  • docs/overview/DOCUMENTATION_TREE.md and docs/overview/README.md refreshed to reflect the current 17-domain file inventory, including core/admin.md, core/exceptions.md, compliance/validators.md, the full procurement/ page set, and the new cash_management/ domain

Version 0.94.0 (2026-07-09)

New Documentation Pages

  • Procurement — the procurement domain publishes for the first time: eight pages covering suppliers, purchase orders, receiving-driven inventory updates, reorder monitoring, and procurement analytics. The source pages existed but the domain was never wired into the documentation pipeline.

New Features

Web Auth

The Web Auth domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Middleware, Authentication, and Examples.

  • The domain mounts at /auth/ with no /api/ prefix and now includes a three-step OTP-based passwordless customer-registration flow: POST /auth/register/initiate/, POST /auth/register/verify/, and POST /auth/register/resend-otp/, backed by a registration service holding pending state with a one-hour window and reg_-prefixed tokens. A newly created user receives a deterministic sanity_id of user_{pk}. See Views and Services.
  • WebSession carries cart_token_hash (SHA-256, unique, indexed): a cookie-independent credential for guest-cart continuity. See Models.
  • IP blocking is documented precisely: a flood of 30 requests per second earns a one-hour block; repeated authentication failures earn a ten-minute block. Every web_auth view declares AllowAny with empty authentication classes, bypassing the global defaults by design. See Middleware.
  • Session-key storage migrated to a single web_session_id key; legacy keys are cleared when a new session is created. PIN login is not part of this domain: it lives in the users app and is consumed by the POS Gateway. See Services.

Procurement

  • PurchaseOrder.Status gains Awaiting Compliance Verification (between Partially Received and Received), and PurchaseOrder gains a compliance_verification JSONField: a vertical-agnostic slot holding verifiedAt, verifiedBy, and packageIds. See Models.
  • All three models (Supplier, PurchaseOrder, PurchaseOrderLineItem) are Sanity-synced. API authentication uses the Token header scheme. See Overview.
  • Receiving a purchase order increments stock_on_hand and stock_lifetime_received automatically. The daily reorder-level check alerts only on variants with custom_inventory_management enabled. See Signals and Tasks.
  • ProcurementAnalyticsService.get_supplier_lead_time_performance() computes per-supplier on-time rates and lead-time variance against Supplier.lead_time_days; it is implemented and tested but not yet reachable from any endpoint. See Analytics Service.

POS Gateway

The POS Gateway domain documentation is re-grounded across Overview, Models, Views, Consumers, Broadcast, Authentication, Infrastructure, Signals, and Examples.

  • The gateway mounts at /pos/ and exposes two WebSocket routes: /ws/pos-gateway/terminal/{terminal_id}/ and /ws/pos-gateway/manager/{store_id}/. WebSocket auth is a pos_session JWT passed as a ?token= query parameter; the session middleware never rejects the handshake, the consumer's connect() decides. See Consumers and Authentication.
  • PIN verification is fully delegated to the users domain; the gateway's authentication module is its sole production caller. POS Bearer tokens are honored on any default-auth endpoint platform-wide, not only under /pos/. See Authentication.
  • Broadcasting is driven by the transactions domain's signal handlers calling the broadcaster; gateway services never broadcast directly. Every broadcast targets the store_{store_id} group. See Broadcast and Signals.
  • The payment endpoint returns 202 Accepted: inventory commit and Sanity sync complete after the response. Cash drawer supports open only; close, count, and deposit return 501. Dedicated inventory lookup/reservation endpoints were removed; the POS frontend calls the products API directly. See Views.

Navigation & Tooling

  • Side navigation updated with the Procurement domain: Procurement and its seven sub-pages under Business Domains
  • convert-docs.mjs updated with new domain mapping: procurementprocurement
  • generate-navigation.mjs updated with the Procurement section entry
  • All Pages index updated with the Procurement section

Version 0.93.0 (2026-07-09)

New Documentation Pages

  • Admin Infrastructure — the shared editorial-admin layer for Sanity-synced models: widget fields, form mixins, sync-health display, and the deletion-guard contract.

New Features

Stores

The Stores domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Signals, Dashboard Home, and Examples. Previously documented endpoints and service methods that do not exist were removed.

  • Ownership model documented precisely: Store and TaxJurisdiction are Sanity-owned with bidirectional sync; Site is Django-owned with no sync path in either direction (the site seam is dormant); Store.slug is Django-derived and never synced; ComplianceCheck records are Django-created with outbound-only async sync. See Models.
  • The live endpoint surface is root-router only: /sites/, /stores/, /tax-jurisdictions/, and /compliance-checks/. The stores app-level router is not mounted; its Zone and Planogram viewsets are unreachable. See Views.
  • Store retrieve accepts three lookup shapes (store_code, Django UUID, sanity_id) and returns a summary shape distinct from the list serializer; the store list response is cached for 600 seconds and invalidated on any store change. See Views.
  • The regulatedCompliance block is documented end-to-end on the store side: a top-level document block backed by the active vertical's compliance profile, a dual-read legacy bridge for old settings payloads, a scrub that keeps moved keys out of settings, and an always-emitted outbound block. See Serializers.
  • Dashboard home KPI semantics corrected: revenue_today and transactions_today use the UTC calendar date (no per-store timezone is activated on this path), and inventory alerts read stock_on_hand. See Dashboard Home.

Core

The Core domain documentation is re-grounded across Overview, Models, Fields, Validators, Utilities, Caching, Exceptions, and Examples, and gains the new Admin Infrastructure page.

  • The model spine is documented exactly: BaseModel (UUID primary key), TimestampedModel, and SanityIntegratedModel with its revision-tracking semantics (insert starts at 1; saves increment atomically; webhook-sourced and metadata-only saves do not bump revision). See Models.
  • The deletion-semantics contract is documented in full: the Sanity-source-of-truth classifier, the fail-closed dependency check, and the origin-aware safe_delete() dispatch matrix (blocked, soft_deleted, hard_deleted, noop). Django-owned models never take deletion orders from Sanity. See Models.
  • The new Admin Infrastructure page documents the five Sanity widget fields and their stored shapes, SanityObjectFormMixin (overlay, pass-through, ISO coercion, hide-don't-disable), sync-health admin display, and the deletion-guard mixin that removes delete permission for Sanity-owned models and routes Django-owned deletions through safe_delete(). See Admin Infrastructure.
  • Financial validator bounds corrected: refund and cash-drawer amounts have an effective floor of 0.01 (a passed 0.00 floor is replaced by the minimum transaction amount). See Validators.
  • Cache invalidation is documented as SCAN-based (non-blocking, batched), with cross-domain invalidation receivers listed per domain. See Caching.

API Changes

Stores — fictional routes removed from documentation

Before: Pages documented /stores/by_code/ and other store routes, plus StoreService methods, that do not exist in code. After: Documentation lists only the live root-router routes and real service methods. Impact: Callers relying on documented-but-nonexistent paths were never hitting real routes; use /stores/ with the multi-lookup retrieve. See Views.

Navigation & Tooling

  • Side navigation updated with 1 new entry: Admin Infrastructure under Core
  • convert-docs.mjs updated with new file mapping: admin.mdadmin
  • generate-navigation.mjs updated with sub-page title mapping: admin → Admin

Version 0.92.0 (2026-07-08)

New Features

Promotions

The Promotions domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Signals, Examples, and Analytics Service. The prior pages documented camelCase model fields (campaignName, discountEffect) that predate the snake_case field layer, and described the BOGO and free-shipping calculators as placeholders; both descriptions no longer match code.

  • The domain has two discount mechanisms sharing one vocabulary: customer-entered PromoCode records and codeless auto-apply Promotion records, evaluated against every cart. Both use the same discount_effect / applies_to JSON shapes and both flow through a shared clamp engine that bounds the combined discount so no unit's stacked discount can exceed its own price, and the transaction total can never exceed the subtotal. Stackable discounts (allow_combination=True) apply together against a shared per-unit headroom pool; non-stackable discounts compete for a single winning slot chosen by post-clamp contribution. See Services.
  • The BOGO calculator computes from real transaction line items: units are expanded per-quantity, sorted by price descending, and the last getQuantity units in each buyQuantity + getQuantity group are discounted by getDiscountPercentage. The free-shipping calculator returns the transaction's actual fulfillment.total_shipping. Neither is a placeholder. See Services.
  • New models documented that had no prior page coverage: Promotion (auto-apply, inventory-gated at checkout), SaleTransactionAppliedPromotion (immutable per-transaction snapshot preserving discount facts after a promotion is edited or deleted), and three retry-safe ledgers backing usage counters (PromoUsageLedger, CampaignConversion, ABTestConversionLedger), each with a unique constraint per transaction. See Models.
  • The two-tier basePromoCode hierarchy is documented: a product variant resolves its effective default promo code (variant override, then product fallback) via PromotionService.resolve_promo_code_for_display() for suggestion UI, and apply_default_promo_to_cart_candidate() for the validated apply-to-cart path, which returns an ApplyResult and never auto-applies. See Services.
  • URL grounding correction: the domain's own urls.py is never included in the root URLconf; the live routes come from the root router at top level. /campaigns/, /promo-codes/, /ab-tests/, and /promotions/ carry no /promotions/ or /api/ prefix. The previously documented /promotional-campaigns/ route does not exist. See Views.
  • Previously undocumented endpoints added: GET /promotions/active/ (public, POS/storefront auto-apply preview), POST /ab-tests/{id}/record-impression/ and record-conversion/ (atomic F()-expression counters), and the analytics actions GET /campaigns/analytics/summary/ and GET /promo-codes/analytics/performance/. See Views.
  • PromotionAnalyticsService documentation expands to all four methods, adding get_campaign_lift (campaign-window revenue against an equal-length prior control period) and get_auto_promotion_performance (grouped by promotion_snapshot_id, so history survives promotion deletion). See Analytics Service.
  • No promotional deletion propagates outbound to Sanity: PromotionalCampaign, PromoCode, and ABTest carry delete handlers that are deliberately disabled (log only), and Promotion has no delete handler at all. See Signals.

Payments

The Payments domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Signals, Webhooks, and Examples. The prior pages placed every endpoint under a fictional /api/payments/ prefix and described the Square, PayPal, and Custom Bank gateways as stubs; all four gateways are complete implementations.

  • Gateway status corrected: StripeGateway is live; SquareGateway, PayPalGateway (Zettle), and CustomBankGateway implement all gateway methods and activate when their environment variables are configured (SQUARE_*, PAYPAL_ZETTLE_* + PAYPAL_WEBHOOK_ID, BANK_GATEWAY_*). Without credentials they raise GatewayConnectionError, not NotImplementedError. MOCK_PAYMENT_PROCESSORS=True switches all four gateways, Stripe included, to deterministic mock responses (StripeGateway gates get_connection_token, create_payment_session, process_refund, charge_card_not_present, and test_connection on the setting). See Services.
  • PayPal (Zettle) is device-initiated: create_payment_session() returns a UUID reference rather than a client secret, and the terminal itself confirms the charge. PayPalGateway.verify_webhook() performs full RSA-SHA256 certificate-based verification per the PayPal v1 scheme: five required PayPal v1 headers, PayPal-Auth-Algo enforced to SHA256withRSA, the signing certificate validated and fetched from PayPal-Cert-Url, and the signature verified over {transmission_id}|{transmission_time}|{webhook_id}|{crc32(body)}; a parse-only passthrough exists only in mock mode. See Services and Webhooks.
  • IdempotencyMixin (mixins/idempotency.py) is documented for the first time: DRF-view-level dedup backed by IdempotencyRecord, with an authorize_before_idempotency() hook that runs ownership checks before any cached-response short-circuit, per-web-session key scoping, 409 IDEMPOTENCY_KEY_BUSY for concurrent same-key requests, 409 IDEMPOTENCY_KEY_BODY_MISMATCH for same-key different-body, and cached terminal responses for replays. Applied by PaymentViewSet, RefundViewSet, TerminalCreatePaymentView, and StripeConfirmPaymentView. See Services.
  • StripeConfirmPaymentView (POST /payments/confirm/) is documented for the first time: the post-3DS-challenge verification endpoint. It re-fetches the PaymentIntent from Stripe rather than trusting the client, completes the transaction and commits reserved inventory on success, and voids the transaction and releases inventory on failure. Ownership is enforced server-side by matching the caller's web session to the CheckoutSession behind the payment. See Views.
  • New Django-owned TransactionPayment fields documented: operator (FK to the POS cashier who took the payment) and register_id (the terminal that took it). Both are excluded from Sanity sync and from the standard API serializer. See Models.
  • Signals documentation corrected: six handlers exist, including two with real behavior that were previously undocumented. enforce_refund_cumulative_integrity (pre-save) blocks any refund that would push cumulative pending-plus-succeeded refunds past the payment amount, on every save path including admin and scripts. enqueue_awaiting_webhook_reconciliation (post-save) drains processor webhooks that arrived before the payment row existed. A refund transitioning to succeeded also schedules a re-sync of the parent SaleTransaction to Sanity; the prior claim that the domain had no Sanity integration is refuted. See Signals.

API Changes

Payments — legacy direct-Stripe POS endpoints retired, not deprecated

Before: POST /payments/stripe/connection-token/ and POST /payments/stripe/create-payment-intent/ were documented as deprecated but still available. After: Both views were removed from the codebase; the routes no longer exist. The processor-agnostic terminal endpoints are the only path. Impact: POS clients must call POST /payments/terminal/connection-token/ and POST /payments/terminal/create-payment/ with a processor_type in the body. See Views.

Payments — root mount documented, /api/ prefix removed

Before: Every payments endpoint was documented under /api/payments/.... After: No /api/ prefix exists. The app mounts at /payments/ (giving /payments/payments/, /payments/processors/, /payments/methods/, /payments/refunds/, and the terminal/webhook/confirm paths); the root router additionally serves /payment-processors/, /payment-methods/, and /refunds/. The root router's flat payments registration is split: its list/create route at /payments/ is shadowed by the app mount's API root, but its detail route at /payments/<pk>/ matches no app-router pattern and falls through to the root router, so payment detail is reachable at both /payments/<pk>/ and /payments/payments/<pk>/. Impact: Callers using /api/payments/... paths were never hitting real routes; use the paths above. See Views.

Promotions — endpoint paths corrected to root level

Before: Campaign endpoints were documented at /promotional-campaigns/, and promo-code apply/generate/by-campaign actions were documented that do not exist. After: Live routes are /campaigns/, /promo-codes/ (lookup by code string), /ab-tests/, and /promotions/, all at root level. The documented-but-nonexistent actions are removed. Impact: Callers must use the root-level paths; /promo-codes/validate/ remains the public checkout validation endpoint and does not increment usage. See Views.

Navigation & Tooling

  • No new pages, mappings, or navigation entries added; all 8 promotions sub-pages and all 8 payments sub-pages were corrected in place.

Version 0.91.0 (2026-07-08)

New Features

Transactions

The Transactions domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Tasks, Signals, Pickup, and Analytics Pre-Aggregation. The pass is surgical: existing pages were corrected in place rather than rewritten wholesale, focused on the drift the compliance-verticalization and payment-authentication work introduced since the last touch.

  • SaleTransaction.Status now documents all 8 states, including payment_requires_action for transactions holding on 3D Secure / Strong Customer Authentication. A new source_system field (pos, web_commerce, dashboard, api) records which channel created the transaction. See Models.
  • transaction_type is documented as not a field on SaleTransaction: it lives on the active vertical's transaction compliance profile, reached via transaction.compliance_profile.transaction_type, and the serializer exposes it only for backward compatibility through a computed field. See Models and Serializers.
  • recalculate_totals() is documented with its full tax pipeline: base rate resolution through TaxClassificationService, proportional discount allocation to the taxable subtotal, and vertical excise added on top through get_active_vertical_provider().additional_excise(). tax_breakdown is rebuilt on every call rather than only at completion. See Models.
  • TransactionService.complete_transaction() is documented with its pre-completion compliance gate (run_pre_completion_gates(), mapping a CheckoutGateBlocked to TransactionServiceError) and its post-completion vertical consumption recording, both run inside the same atomic block as the status transition. See Services.
  • URL grounding correction: all endpoint examples across Views had a fictional /api/ prefix that does not exist anywhere in the root URLconf; removed. The page now documents that /transactions/ is mounted ahead of the root router, so app-local routes (fulfillments, shipments, pickup, and the analytics actions) live under that prefix while several ViewSets (returns, sale-transactions, sale-line-items, cash-drawer) are additionally reachable at the router's top-level path.
  • Two routing gaps are now flagged explicitly: no StoreCredit ViewSet or /store-credits/ endpoint exists anywhere (store credit is created only through ReturnService), and the transactions app's own PaymentMethodViewSet is not registered anywhere, since /payment-methods/ is bound to the payments app's different class of the same name. See Views.
  • CashDrawerViewSet is documented with its full authorization model: staff-only (IsEmployeeOrManager), with POS-terminal callers bound to their authenticated store/terminal and web-session staff callers gated by store assignment. See Views.
  • Tasks expands from 5 documented tasks to a summary of all 18 registered Celery tasks in the module, including the previously undocumented stale-3DS-payment cleanup pair and the post-completion deferral group (loyalty points, promo usage, campaign conversion, inventory analytics, A/B test conversions) dispatched via transaction.on_commit.
  • Field-name correction: Analytics Pre-Aggregation documented DailyInventorySnapshot fields against the pre-rename quantity_on_hand; corrected to stock_on_hand (the products domain's quantity_*stock_* rename).
  • Pickup endpoint paths corrected from /pickup/... to /transactions/pickup/...; PickupViewSet is registered only under the app-local router, not the root URLconf.

Navigation & Tooling

  • No new pages, mappings, or navigation entries added; all 9 existing transactions sub-pages were surgically corrected in place.

Version 0.90.0 (2026-07-08)

New Features

Web Commerce

The Web Commerce domain documentation is re-grounded against current code across Overview, Models, Views, Services, Serializers, Middleware, Signals, and Tasks. The cart and checkout access model changed materially since the prior pass: ownership-based authorization replaced session-value byte-equality, and a cart-session token backs identity in cookie-hostile contexts.

  • Cart and checkout endpoints authorize by web_session FK ownership rather than trusting session['cart_id'] byte-equality. All 5 cart endpoints check that the cart is ACTIVE and owned by the resolved session; checkout endpoints load the CheckoutSession only when it is owned by the resolved session, and unowned or missing sessions receive one generic response so existence and ownership are never disclosed. See Views.
  • A new dual-auth identity resolver, SessionService.resolve_authorized_web_session(), tries a cart-session token (X-Cart-Session-Token header, SHA-256 hashed against WebSession.cart_token_hash) before falling back to the session cookie. The token is minted only at session establishment (guest session create, store change) for guest sessions, never on a cart operation, and is invalidated on login-merge. See Services.
  • CheckoutInitiateView no longer accepts web_session_id in the request body; identity comes only from the resolved session. CheckoutCompleteView enforces checkout ownership before its idempotency-key lookup, so an unowned replay cannot receive another session's cached success. See Views.
  • A single-active-cart invariant is now enforced at every cart-creation point: SessionService.enforce_single_active_cart() abandons any other ACTIVE cart on the same web session, closing the divergence that previously caused legitimate cart requests to 403 after a store switch. See Services.
  • CartValidationMiddleware now resolves identity through the authoritative WebSession rather than a session-stored id, and claims (rather than orphans) a legacy cart whose web_session FK was never backfilled, via a race-safe conditional update. See Middleware.
  • Field-name corrections throughout the domain's pages: InventoryLevel reservation fields are stock_on_hand/stock_reserved (the products domain's quantity_*stock_* rename), not the previously documented quantity_on_hand/quantity_reserved.

Navigation & Tooling

  • No new pages, mappings, or navigation entries added; all 7 existing web-commerce sub-pages were re-authored in place.

Version 0.89.0 (2026-07-08)

New Documentation Pages

  • Users Models — User, Employee, Customer, Manager, Influencer, Vip, UserProductCollection, SavedAddress, and LoyaltyLedger, rewritten to the current models-page standard (field tables, no ORM internals).
  • Users BackendsCustomTokenAuthentication, newly wired into the conversion pipeline and site navigation; the page existed on disk but was never converted or linked before this release.

New Features

Users

The Users domain documentation is re-grounded against current code across Overview, Models, Permissions, Authentication, Views, Services, Serializers, Signals, and Examples. The prior pages predated several model fields and endpoints and, in places, described endpoints and fields that do not exist in code (a plain store foreign key and pin field on Employee, a service.authenticate_user() method). All fixed in place.

  • User.user_roles_data is documented as the single source of truth for role assignment: sync_roles_to_model_records() creates, updates, or deletes the Employee/Customer/Manager/Influencer/Vip rows to match it on every save. See Models.
  • store_ref_matches() is documented as the shared store-reference comparison used across permissions, PIN login, and store-scoped queries: a stored reference matches a store by either its Sanity ID or its Django UUID. A manager row with an empty store_assignments array is documented as unrestricted store access rather than no access. See Permissions and Authentication.
  • UserService.sync_user_from_sanity() is documented with its identity-disposition step: an inbound Sanity user document with no email is quarantined unless every role on it is non-customer (employee, manager, influencer, VIP); a document whose email matches an existing user under a different sanity_id is linked to that user instead of creating a duplicate, recorded to SyncAuditLog with status='quarantined'. See Services.
  • UserViewSet is documented as declaring no permission_classes of its own — it inherits the project's default permission classes rather than a domain-specific gate. The role-specific ViewSets (EmployeeViewSet, CustomerViewSet, ManagerViewSet, InfluencerViewSet, VipViewSet) are documented as deprecated in favor of UserViewSet. See Views.
  • New models added to the domain's documentation that had no prior page coverage: UserProductCollection (wishlists, curations, staff picks), SavedAddress (checkout addresses), and LoyaltyLedger (the immutable audit trail behind Customer.loyalty_points). See Models.

Navigation & Tooling

  • Side navigation updated with a new entry: Backends under Users.
  • convert-docs.mjs updated with a new file mapping: backends.mdbackends.
  • generate-navigation.mjs updated with a sub-page title mapping: backendsBackends.
  • overview/page.mdx All Pages index updated with the Backends row under Users.

Version 0.88.0 (2026-07-06)

New Features

Compliance

The Compliance domain documentation is re-grounded against current code across Overview, Models, Views, Services, and Signals. The domain splits into an industry-agnostic core and a vertical container; cannabis is documented as one vertical plugged into agnostic slots, and the same slots hold a food-safety or pharmaceutical vertical without rewriting the core.

  • The agnostic compliance app holds the abstract base models, the RegulatedCustomerOnboardingService orchestrator, the TaxClassificationService rate-filter engine, the KYC adapter seam, and the vertical-provider seam. Everything cannabis-specific lives in compliance/cannabis/. See Overview.
  • New abstract base RegulatedStoreProfileBase carries the store-level compliance policy shared across regulated verticals: require_age_gate, required_credential_types, secondary_review_credential_types, and permits. StoreComplianceProfile subclasses it and adds the cannabis-specific state_tracking_license_number. See Models.
  • A store emits those four keys to Sanity at top-level regulatedCompliance (requireAgeGate, requiredCredentialTypes, secondaryReviewCredentialTypes, permits), sourced from the store's compliance profile. Permit payloads are authored at store.regulatedCompliance.permits. See Validators.
  • Agnostic callers reach the active vertical through get_active_vertical_provider() in compliance/services/vertical_provider.py. The cannabis provider CannabisComplianceProvider supplies store credential policy, credential validators, employee authorization, the pre-completion checkout gates, purchase-limit recording, and the vertical's additional tax excise. See Services.
  • The cannabis vertical registers itself from ComplianceCannabisConfig.ready(): it connects its signals, registers its five report generators, and registers its batch profile as a hard-delete blocker on products.InventoryLevel. An install without the cannabis app carries none of these, and the agnostic models hold no cannabis import.
  • TaxClassificationService is now the agnostic rate engine (get_applicable_rates, calculate_base_rate, build_tax_breakdown over rate components only). Cannabis transaction-type classification and the Ohio vape excise moved to CannabisTaxService; the vape excise is applied at the model layer through the provider's additional_excise slot. See Services.

API Changes

Compliance — customer-facing gate endpoints relocated, contract unchanged

Before: PurchaseLimitStatusView, AgeVerificationView, and AgeVerificationStatusView were defined in the agnostic compliance/views.py. After: These view classes are defined in the cannabis container (compliance/cannabis/views.py) and stay registered on the agnostic compliance/urls.py. The public paths /compliance/purchase-limits/status/, /compliance/age-verification/, and /compliance/age-verification/status/ are unchanged. Impact: No caller change. The same paths, request shapes, and responses hold. See Views.

Compliance — age-gate flag read from the store compliance profile

Before: AgeVerificationService.enforce_age_gate read a store.require_age_gate flag. After: It reads store.compliance_profile.require_age_gate; a store with no compliance profile is treated as gate-off. Impact: The age gate fires from the store's compliance profile. See Services.


Version 0.87.0 (2026-07-06)

New Documentation Pages

New Features

Sync

The integrations sync and reference surface is documented against current code across Models, Validators, Reference Logic, and the domain overview: outbound reference-shape validation, read-only reference generation, dual-shape resolution, and the audit dispositions.

  • The outbound reference-shape validator inspects every document the sync client sends to Sanity. validate_outbound_refs(document, sanity_type, full_doc=True) runs on create_document, create_or_replace_document, push_update, create_if_not_exists_and_set, and batch_create_or_replace, and records any finding as a non-failing SyncAuditLog row with status='shape_warning'. See Validators.
  • Reference generation reads only. Domain generators return an existing sanity_id or a deterministic derive_synthetic_sanity_id(instance, sanity_type), and no longer write sanity_id to the database on the reference-build path. The object's own self-sync is the single writer, and the synthetic prefix matches the self-sync document type so the ids converge. See Reference Logic.
  • Reference resolvers resolve dual-shape. resolve_dual_shape(model_class, reference_id) accepts a sanity_id or a legacy Django-UUID primary key; the product, transaction, and user resolvers try it first and keep their business-field lookups (slug, name, sku, store_code, transaction identifiers) as a fallback, so a reference a generator emits round-trips back to its instance. See Reference Logic.
  • SyncAuditLog.status adds quarantined (an inbound user document held for identity reconciliation) and shape_warning (an outbound reference-shape finding). Both are non-terminal and distinct from failed; quarantined rows are inbound, shape_warning rows are outbound. See Models.
  • Schema-to-data parity classification locks each Sanity schema field on a Django-mirrored document type to one of Populated, PopulatedViaContainedSeam, IntentionallyAbsent, or GenuineGap; a guard fails when a new schema field carries no classification. The store type is classified end to end, with settings.pickupEnabled, settings.returnToInventory, and settings.payAtStoreEnabled tracked as GenuineGap (the outbound transform rebuilds settings from scalar fields and omits them). See Validators.
  • SanityDataPreserver.ensure_reference_exists resolves a referenced record's domain handler through get_domain_handler_for_model and syncs the record when it has no sanity_id, returning a faithful {_type: 'reference', _ref}. A referenced-but-unsynced record now produces a real reference instead of being silently dropped.

API Changes

Sync — outbound documents carrying a malformed reference are blocked

Before: No outbound reference-shape assertion existed. A malformed reference (a typeless ref, a reference array member with no _key, a raw model object, a bare string in a reference array, or a schema-required reference absent from a full document) was written to Sanity silently. After: The Sanity client raises OutboundRefShapeError before the network write when a hard reference-shape finding is present and SANITY_REF_SHAPE_ENFORCE is on (the default). Advisory findings (a strong reference in an array, an all-digit _key) log a shape_warning row but never block. Set SANITY_REF_SHAPE_ENFORCE=False to fall back to warn-only. Impact: Sync code that emits a malformed reference now fails fast at the write with a typed error instead of persisting a broken document. See Validators.

Sync — payment reference prefix aligned

Before: Payment references were generated as payment_<pk>, while the payment self-sync wrote its document as transactionPayment_<pk>, so a payment reference could not resolve to the document Sanity held. After: Payment reference generation uses derive_synthetic_sanity_id(instance, 'transactionPayment'), matching the self-sync document type. A payment that already has a sanity_id keeps it. Impact: Payment references round-trip to the correct document. See Reference Logic.

Navigation & Tooling

  • Side navigation updated with one new entry: Validators under Integrations.
  • All Pages index updated with Validators.

Version 0.86.0 (2026-07-06)

New Features

Products

The Products domain documentation is fully re-grounded against current code: the catalog and inventory API, Elasticsearch search, and the smart filter service.

  • Inventory quantities use stock_on_hand, stock_reserved, stock_initial, and stock_lifetime_received on InventoryLevel and ProductVariant (Sanity payloads and the Elasticsearch index carry the matching camelCase names stockOnHand / stockInitial). A computed stock_available property returns max(0, stock_on_hand - stock_reserved). See Models.
  • Raw per-store quantities (stock_on_hand, stock_reserved, stock_available, store_inventory, total_inventory) reach only callers with the employee or manager role, or POS-session principals. Every other caller, including the storefront service principal, customers, and anonymous search requests, receives binary availability (is_in_stock, is_available) plus price. See Views.
  • ProductViewSet, ProductVariantViewSet, AttributeViewSet, BrandViewSet, ProductTypeViewSet, CategoryViewSet, and CollectionViewSet are read-only; Sanity Studio is the authoring surface. Mutation runs through @action endpoints gated to IsEmployeeOrManager (sync_variants, sync_from_sanity). InventoryLevelViewSet remains the one writable viewset, since InventoryLevel is Django-owned. See Views.
  • The Elasticsearch product index now builds its nested variants[] entries, and every aggregate field derived from them (total_inventory, has_inventory, variant_names, variant_skus, price_range, featured_variants_count, on_sale_variants_count), from the canonical ProductVariant records and their InventoryLevel rows rather than the Product.variants JSONField, so Django-authored variants index correctly and per-store availability tracks live inventory. Field names are unchanged. See Search.
  • Smart filter (GET /search/smart-filter/) attribute filters now match attribute identity together with value, not value alone, so the same option value under two different attribute definitions no longer cross-contaminates results; an attribute name that fails to resolve to a known attribute matches nothing rather than falling back to a value-only match. See Smart Filters.
  • Smart filter manifest routing serves the pre-generated per-store manifest only for unfiltered browsing (no search text, no brand, category, productType, collection, attributes, on_sale, or low_stock active). Any of those filters, or a search query, routes to real-time Elasticsearch aggregations so counts reflect the active filter set. See Smart Filters.
  • InventoryLevel.is_quarantined() and its hard-delete blockers are driven by a vertical registry: a vertical's compliance app registers its batch-profile relation from its own AppConfig.ready() (cannabis registers cannabis_profile), so the agnostic InventoryLevel model carries no vertical-specific import and an install without a given vertical's compliance app contributes no blocker for it. See Models.
  • Zone-level par-level inventory (ParLevelItem, Zones) coexists with store-scoped InventoryLevel records; par-level quantities materialize into InventoryLevel rows rather than replacing them.

API Changes

Products: inventory field rename

Before: InventoryLevel carried quantity_on_hand, quantity_reserved, current_quantity, and inventory_when_created; ProductVariant carried initial_inventory. Sanity payloads and the search index used the matching camelCase names (quantityOnHand, inventoryWhenCreated, initialInventory). After: Migration products/0032 renames them: quantity_on_hand to stock_on_hand, quantity_reserved to stock_reserved, current_quantity to stock_lifetime_received, and both inventory_when_created (InventoryLevel) and initial_inventory (ProductVariant) to stock_initial. Sanity payloads and the Elasticsearch index use stockOnHand, stockReserved, stockInitial. Impact: Any integration reading or writing the old field names must switch to the stock_* names. See Models.

Products: known endpoint behavior

The following endpoint behaviors are now documented as-is rather than left to be discovered:

  • InventoryLevel.store_location entries carry store.sanity_id (the Sanity document ID) as _ref, not the store's Django UUID; InventoryLevel.save() raises ValueError if the target store has no sanity_id. See Serializers.
  • GET /inventory-levels/by_store/?store_id= accepts only the store's Django UUID. The dual Django-UUID-or-Sanity-ID shape supported on GET /inventory-levels/?store_id= does not extend to this action. See Examples.
  • GET /search/ accepts a brand parameter and echoes it in the response filters, but the search service applies no brand filter to the Elasticsearch query. Filter by brand through GET /search/smart-filter/ (brand[]) instead. See Search.
  • PUT /product-variants/{sku}/ is routed by a stray update() override on the otherwise read-only ProductVariantViewSet. The call fails with a server error and performs no mutation; it remains gated by IsAuthenticated. See Views.

Version 0.85.0 (2026-04-20)

New Documentation Pages

  • Compliance AdaptersKYCVendorAdapter base contract, VerificationResult dataclass, and the Persona adapter.
  • Compliance Webhooks — Persona HMAC-signed webhook flow: signature verification, replay protection, state-gated credential transitions.
  • Compliance Validators — Permit validator registries (generic and cannabis) with a shared dispatcher, plus cannabis credential validators.
  • Compliance Utilitiescredential_utils and permit_utils lookup helpers for the two JSONField slots the domain reads from.

New Features

Compliance — Phase 8B (Cash Reconciliation Reporting)

The compliance report registry now includes cash_reconciliation. CashReconciliationReportGenerator emits one row per CashierShift, aggregates TransactionPayment totals scoped to the shift's store and started_at → ended_at window, and surfaces the blind-count total and discrepancy from the close-of-shift DrawerCount.

  • Report type: cash_reconciliation added to ComplianceReport.ReportType choices. See POST /compliance/cannabis/reports/.
  • IRS 8300 advisory flag: cumulative daily cash receipts per store are summed across shifts; irs_8300_flag=True when the running total reaches $10,000. Threshold is IRS_8300_THRESHOLD = Decimal('10000.00').
  • CSV columns (in order): shift_date, shift_id, store_id, terminal_id, employee_id, opening_float, closing_float_system, blind_count_total, count_discrepancy, cash_sales_total, card_sales_total, cashless_atm_sales_total, other_sales_total, total_expected, irs_8300_flag, irs_8300_cumulative_total.
  • cashless_atm_sales_total is pre-wired and will populate when cashless_atm is added as a TransactionPayment.payment_method_type choice in a future phase.

Compliance — Phase 9A (Regulated Customer Onboarding, POS)

RegulatedCustomerOnboardingService atomically creates a user with credentials and enforces store-level policy. Channel-aware initial status assignment decides whether a submitted credential is immediately active or routes through manager secondary review.

  • New endpoint: POST /compliance/onboarding/regulated-customer/ (IsAuthenticated + IsEmployeeOrManager). Accepts {user_data, credentials, channel}; returns the created user and credentials.
  • New endpoints: POST /compliance/onboarding/credentials/{key}/approve/ and /reject/ for pending-credential review. Both require the acting employee to pass EmployeeComplianceService.check_can_process_sale (active DCC agent permit + cleared background check).
  • New endpoint: GET /compliance/onboarding/pending-credentials/?store_id=<id> lists pending credentials by submittedAtStoreId; the requester must have an employee or manager role with a store assignment covering that store.
  • Store policy fields on StoreComplianceProfile: required_credential_types (credential types a customer must present) and secondary_review_credential_types (types that always enter pending_employee_review on POS capture).
  • Stored verificationData is filtered to verifiedDocumentId, verifiedBiometricId, reviewNotes — the subset that matches the Sanity credential schema, so bidirectional sync stays consistent.
  • Pending status set: pending_verification, pending_employee_review, pending_vendor_response.
  • New cannabis credential validators: government_id_verification (requires documentType + dateOfBirth) and medical_marijuana_patient_card (requires patientId, normalises programName and caregiverAllowed). See Validators.

Compliance — Phase 9B (Storefront Self-Registration + KYC Vendor Seam)

Storefront customers can now self-enrol, and KYC vendor integration is wired through a swappable adapter seam.

  • New endpoint: POST /compliance/onboarding/storefront-customer/ (AllowAny). Delegates to RegulatedCustomerOnboardingService with channel='storefront'; every credential is written as pending_verification. Returns 201 on success, 400 on validation failure, 409 on duplicate email.
  • KYCVendorAdapter abstract base in compliance/services/kyc/base.py. Two methods: initiate_verification(user, credential_key) returns a redirect URL; handle_webhook(payload) returns a VerificationResult. Tri-state outcome: approved | rejected | unresolved. See Adapters.
  • PersonaAdapter concrete implementation. Inquiry-based; the credential _key is passed as Persona's reference-id and echoed back on every webhook. Status mapping: approved/completed → approved; failed/declined/expired → rejected; everything else → unresolved.
  • New endpoint: POST /compliance/kyc/persona/webhook/ (AllowAny, HMAC-authorised). HMAC-SHA256 over "{ts}.{raw_body}" with PERSONA_WEBHOOK_SECRET; 300-second replay tolerance (_PERSONA_SIG_TOLERANCE_SECONDS); parses rotation-set signature headers (multiple space-separated t=...,v1=... sets).
  • Webhook state gate: credential must be in {pending_verification, pending_vendor_response} to be mutated — this preserves audit fields written by the human-review workflow when a late vendor webhook arrives.
  • Audit-field isolation: the webhook only ever writes verificationData.{vendor, vendorStatus, vendorReferenceId}. It never writes approvedBy, approvedAt, rejectedBy, or rejectedAt.

Compliance — Store Permit Authoring

StoreComplianceProfile.permits mirrors store.settings.permits from Sanity. Seven permit types validate via a composed dispatcher: five generic (certificate_of_operation, local_permit, tax_vendor_license, excise_tax_registration, insurance) and two cannabis-specific (cannabis_retail_license, metrc_registration). See Validators and Utilities.

API Changes

Compliance — ComplianceReport.ReportType extended

Before: Four report types (daily_sales, purchase_limit_audit, age_verification_audit, recall_destruction_audit). After: Five report types — cash_reconciliation added. Impact: Callers listing known report types must include the new value. See Services.

Compliance — StoreComplianceProfile fields added

Before: store, require_age_gate, compliance_manager, state_tracking_license_number. After: Adds required_credential_types (JSONField, default []), secondary_review_credential_types (JSONField, default []), permits (JSONField, default []). Impact: Profile reads now return the three additional fields. RegulatedCustomerOnboardingService requires a profile to exist on the target store — onboarding raises OnboardingError if it is missing. See Models.

Compliance — credential verificationData shape locked

Before: Approve/reject writes could store any key under verificationData. After: RegulatedCustomerOnboardingService.approve_pending_credential filters to verifiedDocumentId, verifiedBiometricId, reviewNotes. The webhook view writes only vendor, vendorStatus, vendorReferenceId. Impact: Custom keys submitted at approval time are silently dropped — the stored object matches the Sanity schema and bidirectional sync stays correct. See Services.

Navigation & Tooling

  • Side navigation updated with 4 new entries under Compliance in Business Domains: Adapters, Webhooks, Validators, Utilities.
  • All Pages index: /overview compliance section expanded from 5 to 9 rows.
  • No changes to convert-docs.mjs FILE_MAPPING or generate-navigation.mjs SUB_PAGE_TITLES — both already include adapters, webhooks, validators, and utilities.

Version 0.80.0 (2026-04-14)

New Documentation Pages

  • Compliance Overview — Domain overview, abstract model hierarchy, and architecture summary.
  • Compliance Models — 8 vertical-agnostic abstract base models and all cannabis concrete implementations.
  • Compliance Services — TaxClassificationService, AgeVerificationService, PurchaseLimitEngine, MetrcClient.
  • Compliance Signals — Profile sync, return snapshots, Metrc tag sync, and destruction event signals.
  • Compliance Views — Purchase limit status, age verification, and Metrc PO verification endpoints.

New Features

Compliance — Phases 3–8A

Business logic wired, state tracking live, and compliance reporting operational.

  • Phase 3 — flat field removal: Compliance configuration moved from flat fields on Store and ProductVariant to profile models. StoreComplianceProfile is auto-created per store. ProductVariantCannabisProfile carries limit category, unit weight, menu type, and vape tax flag. See API Changes below.
  • Phase 4 — limit enforcement: PurchaseLimitEngine wired to checkout. Adult-use daily limits (flower: 2.5 oz, concentrate: 15 g, edible: 15,000 mg THC) enforced before payment. void_purchase() runs on return processing. GET /compliance/purchase-limits/status/ exposes current consumption.
  • Phase 4 — age gate: AgeVerificationService records CannabisAgeVerificationEvent on each attempt. POST /compliance/age-verification/ and GET /compliance/age-verification/status/ are live.
  • Phase 4.75 — credential refactor: 14 flat credential fields removed from Employee and Customer. All credential data lives in user.credentials JSONField. credential_utils.get_active_credential() provides the read API.
  • Phase 5 — tax classification: TaxClassificationService wired into recalculate_totals(). Filters TaxJurisdiction.tax_rates_data by appliesTo matching transaction type. Vape excise scoped to vape-item subtotal only.
  • Phase 5.5 — model verticalization: transaction_type moved from SaleTransaction to SaleTransactionCannabisProfile — snapshotted at checkout via transaction.compliance_profile. MetrcTag model added tracking the full RFID tag lifecycle (UNVERIFIEDVERIFIEDACTIVESOLD / RETURNED / DESTROYED / DISCREPANCY).
  • Phase 6 — Metrc state tracking: MetrcClient — Ohio Metrc API client with dual-key Basic Auth. Implements pull_package(), report_sale(), report_return(), create_transfer_manifest(). pull_package() wired to PO receipt via signal. POST /compliance/cannabis/purchase-orders/{pk}/metrc-verify/ transitions POs from AWAITING_COMPLIANCE_VERIFICATION to RECEIVED.
  • Phase 7 — recall and destruction: CannabisRecallEvent, CannabisProductRecall, and CannabisDestructionEvent models. CannabisRecallService implements activate, record_destruction, and resolve. MetrcClient.report_destruction() added. Signal creates CannabisDestructionEvent automatically on ReturnLineItem with disposition=DESTRUCTION. InventoryLevel.is_quarantined added as a cross-vertical compliance hold field.
  • Phase 8A — compliance reporting: ComplianceReport model (migration compliance/0004) with generator registry and generate_compliance_report Celery task. ComplianceReportService with store-access authorization. REST API supports list, create, poll, and download. Four CSV generators: DailySalesReportGenerator, PurchaseLimitAuditReportGenerator, AgeVerificationAuditReportGenerator, RecallDestructionAuditReportGenerator.

API Changes

Compliance — Phase 3 breaking removal

Before: Store had flat compliance fields including require_age_gate. ProductVariant had limit_category, unit_weight_mg, menu_type, vape_tax_applicable directly on the model. After: Age gate and license config is on store.compliance_profile (StoreComplianceProfile). Variant cannabis data is on variant.cannabis_profile (ProductVariantCannabisProfile). Impact: Direct reads of flat compliance fields will fail. Use store.compliance_profile.require_age_gate and variant.cannabis_profile.limit_category.

Users — Phase 4.75 breaking removal

Before: Employee and Customer had 14 flat credential fields (drivers_license_number, id_expiry_date, medical_card_number, and others). After: All credential data is in user.credentials JSONField. Impact: Direct attribute reads on any removed credential field raise AttributeError. Use credential_utils.get_active_credential(user, credential_type).

Transactions — transaction_type accessor change

Before: transaction.transaction_type read directly from SaleTransaction. After: transaction.compliance_profile.transaction_type — snapshotted to SaleTransactionCannabisProfile at checkout. Impact: Direct reads of transaction.transaction_type no longer exist on SaleTransaction. Read from transaction.compliance_profile.

Transactions — tax_breakdown now always populated

Before: SaleTransaction.tax_breakdown was populated manually or left null. After: Populated by TaxClassificationService on every recalculate_totals() call. The amount key is present on every entry. Impact: Callers that expected null in non-exempt jurisdictions now receive a populated list.

Navigation & Tooling

  • Side navigation updated with 5 new entries under Compliance in Business Domains: Overview, Models, Services, Signals, Views.
  • convert-docs.mjs DOMAIN_MAPPING updated with 'compliance': 'compliance'.
  • generate-navigation.mjs DOMAINS updated with compliance under Business Domains.

Version 0.79.0 (2026-04-09)

New Features

Compliance (foundation)

The compliance Django app scaffold is introduced with two layers: vertical-agnostic abstract base models in compliance/ and cannabis-specific concrete implementations in compliance/cannabis/.

  • Phase 1A — structural corrections to existing models to support the compliance domain.
  • Phase 1B — additive compliance fields and Sanity schema additions.
  • Phase 2compliance/cannabis sub-app scaffold with all abstract base models and concrete cannabis model definitions. See Compliance Models.

Abstract base models: PurchaseLimitRecordBase, IdentityVerificationEventBase, BatchTestResultBase, StateTrackSyncRecordBase, RegulatedBatchProfileBase, RegulatedSaleLineSnapshotBase, RegulatedReturnLineSnapshotBase, RegulatedTransactionProfileBase.

Cannabis concrete models: CannabisPurchaseLimitRecord, CannabisAgeVerificationEvent, CannabisLabResult, StateTrackSyncRecord, StoreComplianceProfile, ProductVariantCannabisProfile, InventoryLevelCannabisProfile, SaleLineItemCannabisSnapshot, ReturnLineItemCannabisSnapshot, SaleTransactionCannabisProfile.


Version 0.78.0 (2026-03-27)

New Features

Promotions

  • PromotionService.preview_from_cart_items() evaluates all active promotions against cart items and returns projected discount amounts. Called by CartView to enrich the cart response before checkout.
  • GET /api/promotions/active/ returns all active promotions for a store. Accepts store_id query param.
  • is_applied flag on promotions — True when the discount is active in the cart, exposed in CartItem.applied_promotions.
  • recalculate_totals() is now the sole writer of orderLevelDiscount — client-side discount values are no longer trusted.

Storefront

  • Promotion banners render in the cart with per-item discount tags and effective post-discount row totals.
  • PaymentSummary in the checkout flow shows promotion savings as a line item.
  • Cart types gain applied_promotions: ActivePromotion[] on CartItem and free_shipping_active: bool on Cart.

POS

  • Promotion banners and discounted prices display in the cart panel.
  • ActivePromotion type and promotionUtils helpers added to the POS TypeScript layer.

Stores

  • TaxJurisdiction gains is_exempt (BooleanField, default=False). When True, tax calculation is skipped for transactions at stores in this jurisdiction.

Version 0.77.0 (2026-03-22)

New Features

Payments

  • PaymentOrchestrationService dispatches checkout payments through the configured gateway — Stripe or a custom bank gateway — with Celery retry logic for transient failures.
  • POST /api/payments/confirm/ — post-3DS verification endpoint. Accepts payment_intent_id, verifies the PaymentIntent via Stripe, and advances the transaction to COMPLETED.
  • payment_intent.succeeded webhook advances stalled PAYMENT_REQUIRES_ACTION orders automatically.
  • Transactions that remain in PAYMENT_REQUIRES_ACTION for more than one hour are voided by a Celery Beat task.
  • PaymentProcessor and PaymentMethod registered in Django admin with masked config display.

Storefront

  • Checkout form uses Stripe CardElement. 3DS challenges are handled inline — the storefront shows the Stripe action UI then calls POST /api/payments/confirm/ on resolution.
  • Checkout draft state persists to localStorage via next-pwa — a browser refresh does not lose cart contents.

POS

  • Terminal readers are discovered and connected automatically — no manual pairing required.
  • Terminal offline mode enabled — the reader queues transactions locally when network connectivity is lost.

Web Commerce

  • CheckoutSession gains fulfillment_data JSONField — stores cart line data for 3DS recovery flows where the checkout session must be resumed after the Stripe action completes.

Admin

  • PortableTextRenderer renders Sanity portable text JSON as formatted HTML in Django admin. body_display on products is read-only.
  • SanityVariantFormsetWidget provides a card UI for editing product variants inline, with attribute picker and Sanity _key preservation.

Version 0.76.0 (2026-03-21)

New Features

Infrastructure

  • Nginx serves a Content-Security-Policy header on all five frontend subdomains: dashboard.djanity.com, pos.djanity.com, docs.djanity.com, shop.djanity.com, and cms.djanity.com. Each policy is scoped to the actual origins the application contacts — POS includes Stripe Terminal and WebSocket connections; shop includes the Sanity CDN; cms permits unsafe-eval and all Sanity Studio origins including wss://*.api.sanity.io.
  • Flower Celery monitor is running as a production Swarm service, accessible at https://api.djanity.com/flower/. Protected by HTTP Basic Auth. Redis credentials are read from Docker secrets and URL-encoded at container startup.
  • Production file logging now outputs structured JSON via python-json-logger. Console output remains human-readable. Requires image rebuild after deploying with the updated requirements/production.txt.
  • A database backup script (scripts/backup_db.sh) runs pg_dump against the primary container and rotates files older than 14 days. Configurable via BACKUP_DIR, RETENTION_DAYS, DB_CONTAINER, DB_NAME, and DB_USER.

API Changes

Django — CSP report-only mode (behaviour change)

Before: Only the enforced Content-Security-Policy was active on Django-served pages (admin, auth). After: Content-Security-Policy-Report-Only is now active alongside it, observing violations for a stricter policy that removes unsafe-inline from script-src and style-src. No requests are blocked. Impact: Browsers emit CSP violation reports to the console on Django admin and auth pages. No action required from API consumers.


Version 0.75.0 (2026-03-18)

New Features

Core

  • GET /health/ is now live in all environments. Returns status for DatabaseBackend, CacheBackend, and DefaultFileStorageHealthCheck. Pass ?format=json for a machine-readable response. HTTP 200 when all checks pass; HTTP 500 if any check fails. See Core.

Payments

  • elavon is now a valid processor_type on PaymentProcessor and WebhookEvent. Records with processor_type='elavon' route to the Custom gateway, which reads BANK_GATEWAY_* credentials. See Payment Models.

Storefront

  • Multi-store picker appears on storefront when more than one store is available. The store marked is_default is pre-selected on initial load. This behaviour applies both to the inline selector and within StoreSelectionDialog.
  • StoreSelectionDialog opens regardless of whether a server-side store ID is present, letting users switch store context from any page.
  • Inactive stores are excluded from the StoreSelectionDialog picker.
  • ErrorBoundary wraps all page content in the root layout, preventing unhandled React errors from producing a blank page.

API Changes

Storefront — store filter priority (behaviour change)

Before: useSmartFiltersQuery used sessionStoreId when both serverStoreId and sessionStoreId were set, causing server-rendered pages to filter inventory using the wrong store. After: serverStoreId takes priority over sessionStoreId when both are present. Impact: Storefront pages rendered with a server-resolved store ID consistently filter products to that store.


Version 0.74.0 (2026-03-15)

New Features

POS

  • Cancel transaction, void transaction, cancel pickup, cancel return, and cart-clear operations now use AlertDialog for confirmation and Sonner toast for error feedback. Previously these used window.confirm and window.alert, which Electron 28 with contextIsolation: true silently returns false from — blocking all destructive POS operations in the packaged app.
  • TypeScript type definitions corrected across the POS: TransactionStatus, SaleTransaction, PromoValidationResult, cart item shapes, and React Query cache configuration now match runtime values.

Version 0.73.0 (2026-03-14)

New Features

Analytics

Procurement

Promotions

  • New Promotion model for codeless automatic discounts evaluated at checkout. Non-stackable promotions compete on highest value; stackable ones combine. Applied promotions snapshot to SaleTransactionAppliedPromotion. SaleTransaction gains appliedPromotionDiscount. Migrations promotions/00060009, transactions/0025.
  • process_expired_campaigns Celery Beat task runs daily at 08:00 UTC — computes campaign lift and marks expired campaigns inactive.

Stores

  • purge_test_stores management command removes test store records and their Sanity documents. Accepts --dry-run.

Studio

  • site document type replaces companyInfo for company-level configuration. Existing companyInfo documents should be migrated.

Transactions

  • SaleTransaction gains tax_breakdown — nullable JSONField snapshotting per-jurisdiction tax at completion (jurisdiction, taxable_amount, rate, tax_collected). export_tax_remittance management command exports CSV. Migration transactions/0023.
  • SaleLineItem gains costAtSale — nullable DecimalField recording InventoryLevel.current_cost at checkout for margin analytics. Migration transactions/0024.

API Changes

Integrations

  • InventoryLevel changes now propagate to Sanity via post-save signal — previously only inbound (Sanity→Django) sync was active.
  • SaleTransaction, SaleLineItem, and Shipment removed from Sanity sync scope and Studio desk structure.
  • Sync write-backs now use queryset.update() instead of instance.save(), preventing post-save signals from triggering recursive sync.
  • User and transaction sync handlers now call create_or_replace() instead of push_update(), fixing silent failures when no Sanity document existed yet.

Transactions

Web Auth

  • clear_auth_spam_block() now called automatically on successful login or OTP verification, removing the need for manual cache expiry.

Bug Fixes

Storefront

  • Shipping checkout Continue to Payment was permanently disabled due to an incorrect shippingInfoSaved guard — removed; Zod validation gates submission.
  • Logged-in user email now pre-populates on the shipping fulfillment step, not just pickup.
  • Logout button now shows a confirmation dialog before clearing the session.
  • Checkout <select> elements now respect dark mode.

Transactions

  • Voiding a LoyaltyLedger entry with zero points no longer raises an exception; treated as a no-op.

Version 0.70.1 (2026-03-06)

New Documentation Pages

  • Analytics Overview — Architecture, response envelope format, KPI value shape, comparison windows, and the full table of domain services delegated to by analytics views.
  • Analytics Views — Reference for all 12 analytics endpoints across Commerce, Operations, People, Growth, and System groups. Covers query parameters, response shapes, and per-field descriptions.
  • Analytics ServicesActiveOperationsService (live operational queues: pending pickups, in-transit shipments, pending transactions, open returns) and InfrastructureService (sync pipeline stats, daily throughput, DB/Redis/Celery health checks).

New Features

Analytics Domain

A new analytics app provides composed dashboard endpoints that aggregate data from multiple business domains into a single {meta, data} envelope per page. Each response includes the active period, a comparison window, and a source field (live or pre_aggregated). Twelve endpoints span four groups:

  • Commerce: overview, sales, products, hourly-traffic
  • Operations: inventory, fulfillment, procurement
  • People: customers
  • Growth: promotions
  • System: infrastructure (sync pipeline health + DB/Redis/Celery status)

All numeric KPIs include a delta_pct comparison against the prior period or prior year. See Analytics Views for the full endpoint reference.

Store default selection

Store.is_default designates the primary store loaded first in the dashboard store switcher. Setting a store as default via StoreSerializer.update() auto-clears the flag from all other stores. See Store Models.

SaleTransaction — expiry field and partially-paid status

SaleTransaction.expires_at records the expiry datetime for layaway and pay-at-pickup orders. CartService or the POS sets this on creation; counter-sale transactions leave it null. The active-orders queue now surfaces partially_paid transactions alongside pending ones — PARTIALLY_PAID is a new status covering orders where an initial deposit has been collected but the balance is outstanding. See Transaction Models.

Infrastructure stats — configurable range

GET /api/analytics/system/infrastructure/ now accepts ?days=N (7–365, default 30). Values outside that range are clamped. Previously the window was fixed at 30 days. See Analytics Views.

API Changes

Infrastructure stats — outbound sync now tracked (behaviour change)

Before: sync stats in the infrastructure endpoint reported inbound Sanity→Django traffic only (WebhookEvent counts). After: sync reports both directions: inbound (WebhookEvent), outbound (SyncAuditLog direction='outbound'), and a combined total. The daily throughput chart now includes an outbound column.

Impact: Clients that read data.sync should handle the new inbound, outbound, combined keys. The old flat total_received/processed/failed keys are now inside data.webhook.stats. See Analytics Services.

Return.total_return_value — calculation changed (behaviour change)

Before: total_return_value (formerly get_total_return_value()) summed ReturnLineItem.returnPrice — the original item value requested for return. After: total_return_value is a property that sums Refund.amount (succeeded refunds) plus StoreCreditTransaction.amount. This reflects actual money returned to the customer rather than the requested item value.

Impact: The value can differ from the old result if partial refunds were issued or store credit was substituted. See Transaction Models.

Navigation & Tooling

  • Side navigation updated with 3 new entries: Analytics Overview, Analytics Views, Analytics Services under Advanced Operations
  • convert-docs.mjs updated with new domain mapping: analyticsanalytics
  • generate-navigation.mjs updated with new domain entry for Analytics under Advanced Operations

Version 0.70.0 (2026-03-05)

New Documentation Pages

  • Analytics Pre-Aggregation — Architecture and field reference for DailyRevenueSummary and DailyInventorySnapshot. Covers Celery rollup schedules, query routing between replica and live DB, the _pre_save_status signal guard, backfill management command, and composite indexes added to Fulfillment, PickupSchedule, and Shipment.
  • Dashboard Home — Reference for GET /stores/{id}/dashboard_home/. Documents the KPI fields (revenue_today, transactions_today, avg_transaction_value, active_promotions), top_products_today, low_stock_alerts, recent_transactions, and the 60-second Redis cache behaviour.

New Features

Analytics Pre-Aggregation

Two daily summary tables now back all historical analytics queries, eliminating full-table scans:

  • DailyRevenueSummary — populated nightly at 00:05 by transactions.rollup_daily_revenue; refreshed every 15 min intraday by transactions.refresh_today_revenue_summary
  • DailyInventorySnapshot — populated nightly at 00:05 by products.rollup_daily_inventory_snapshot
  • Historical date ranges route to the replica DB; today's window falls back to live ORM aggregation
  • Backfill: python manage.py backfill_daily_revenue --start YYYY-MM-DD --end YYYY-MM-DD [--dry-run]

Payment Gateway Implementations

Square, PayPal (Zettle), and Custom Bank gateways have full production API logic. All five methods implemented per gateway: get_connection_token, create_payment_session, verify_webhook, process_refund, test_connection. See Payments Overview for credential requirements and MOCK_PAYMENT_PROCESSORS configuration.

Delivery Schedules API

/tasks/delivery-schedules/ endpoints are now live. DeliveryScheduleViewSet was previously implemented but not URL-registered. See Tasks.

API Changes

Tax calculation — per-line-item exemption (behaviour change)

SaleTransaction.recalculate_totals() now taxes only non-exempt line items. SaleLineItem.isTaxExempt (BooleanField) controls exemption per item. A DEFAULT_TAX_RATE_FALLBACK applies when the store has no configured tax jurisdiction. Existing transaction totals are unaffected; new transactions with exempt items will produce lower taxAmount values. See Transaction Models.

User signals — role payload fields now propagated

Employee and Manager records created via Sanity webhook now correctly receive employeeId, pinCode, hourlyRate, and commissionRate from the payload. Previously these were ignored and records were created with defaults. Existing records are not backfilled. See User Signals.

Bug Fixes

  • Analytics signals_pre_save_status snapshot on SaleTransaction prevents double-counting order_count and items_sold on status re-saves
  • Promotions — deadlock preventionPromotionalCampaign lock acquisition order sorted to prevent deadlocks under concurrent transaction completion
  • Promotions — A/B test conversionABTest.record_conversion() now wired to transaction completion signal; conversions were previously not recorded
  • Promotions — campaign metricsPromotionalCampaign.performance_metrics updated on transaction completion; revenue attribution was previously not tracked
  • Users — UserProductCollection — M2M signal reverse accessor fixed; statistics auto-refreshes when items are added or removed
  • Users — Customer store creditCustomer.store_credit now recalculated automatically when a StoreCredit record is saved

Navigation & Tooling

  • Side navigation updated with 2 new entries: Dashboard Home under Stores, Analytics Pre-Aggregation under Transactions
  • convert-docs.mjs updated with 2 new file mappings: dashboard_home.mddashboard-home, analytics_pre_aggregation.mdanalytics-pre-aggregation
  • generate-navigation.mjs updated with sub-page title mappings for both new pages

Version 0.6.3 (2026-02-20)

New Documentation Section: Sanity Studio

Added a complete /sanity documentation section covering CMS content editing workflows for all major content types. Each page provides step-by-step creation guides, naming conventions, field descriptions, and best practices.

  • Sanity Overview — Content types index, webhook sync flow diagram, camelCase→snake_case field transformation reference
  • Products — Product creation workflow with naming conventions, organization hierarchy (Product Types, Brands, Collections, Tags), and variant relationship model
  • Product Variants — Variant creation with attributes, SKU assignment, pricing overrides, and inventory tracking setup
  • Product Attributes — Attribute type definitions (Size, Color, Material, Custom), values management, and one-per-type validation rule
  • Inventory Levels — Per-store stock management with quantity tracking (on-hand, reserved, current), costing methods (Standard, Average, FIFO), reorder points, max stock levels, and sale pricing configuration
  • Planograms — Visual merchandising blueprints covering zones, featured product placement, store assignments, versioning, and campaign tie-ins
  • Stores — Store creation with address, operating hours (weekly schedule), timezone, currency, tax jurisdiction, service options, and staff assignments
  • Users — User management with five role types: Customer (loyalty, wishlists), Employee (commissions, POS access), Manager (overrides, reports), Influencer (social promotion, commission tracking), VIP (exclusive access, special discounts). Covers multi-role support and per-role configuration
  • Promo Codes — Discount code configuration covering five discount types (Percentage, Fixed Amount, Free Shipping, BOGO, Buy X Get Y Discounted), application scope (Entire Order, Specific Products, Collections, Shipping Only), date ranges, usage limits, minimum order amounts, and store restrictions
  • Promotional Campaigns — Campaign coordination covering five campaign types (Seasonal, Flash, Clearance, Influencer, Loyalty), product targeting, promo code linking, A/B test integration, markdown schedules, and performance tracking metrics
  • A/B Tests — Split testing documentation with test types (Pricing, Promotion, Messaging, Product Placement), variant configuration, traffic allocation, test period management, and per-variant results tracking (conversion rate, revenue, statistical confidence)

New API Documentation Pages

Added 13 new pages documenting core infrastructure, integration services, and domain-specific systems:

Core Infrastructure

  • Caching Infrastructure — Redis-backed DRF response caching architecture. Documents @cache_response decorator usage, make_cache_key_func() for consistent key generation, invalidate_cache_pattern() for prefix-based Redis key invalidation, signal-based cache invalidation patterns, and domain-specific timeout configuration (Brands/Categories at 1hr, Products at 5min, Promotions at 10min, Stores at 5min, Zones at 1hr)

Integrations

  • Serializer Reference ResolverSerializerReferenceResolver wrapper for DRF serializers to resolve Sanity document references. Documents resolve_fk_reference() for single references, resolve_fk_array() for many-to-many patterns, auto-fetch from Sanity API when referenced documents exist in Sanity but not yet in Django, circular reference detection with depth limiting (max 5 levels), and timeout protection (15 seconds)
  • Sync Context — Thread-local loop prevention for bidirectional Sanity sync. Documents context managers, sync state tracking, and integration patterns with sync handlers to prevent infinite sync loops during webhook processing
  • Sync Services Reference Logic — Domain-specific reference resolution patterns. Documents common utilities shared across product, store, and transaction sync handlers for resolving Sanity _ref references to Django foreign keys
  • Integration Tasks — Celery task documentation covering webhook processing pipeline, dependency fetching for out-of-order webhook arrivals, cross-domain resolution for references spanning multiple Django apps, signal management for sync dispatch, and product-specific indexing tasks

POS Gateway

  • WebSocket BroadcastingWebSocketBroadcaster utility class bridging domain services with Django Channels. Documents 6 broadcasting methods (transaction creation/updates/completion, returns, inventory changes, system notifications), channel group management, consumer integration patterns, ~10-20ms latency characteristics, and testing patterns with mock channel layers
  • Infrastructure — POS Gateway application configuration. Documents PosGatewayConfig app startup with GatewayInitializationService, WebSocket URL routing for real-time terminal connections, REST API URL routing, and deployment considerations

Products

  • Smart Filter ServiceSmartFilterService for Elasticsearch-based product filtering. Documents execute_smart_search() pipeline, dynamic availability calculation (showing accurate counts per filter option and disabling zero-result options), smart aggregations for Brands/Categories/Collections/Attributes, store-specific inventory filtering, text search with prefix matching for short queries and fuzzy matching for longer queries, and POST /products/search/ API endpoint with request/response examples

Transactions

  • Pickup SystemPickupService and PickupViewSet for pickup order lifecycle. Documents 8 API endpoints (available-slots, schedule, mark-ready, complete, cancel, for-modification, store/{id}/pending, store/{id}/today), status lifecycle (PENDING→READY→PICKED_UP and PENDING→EXPIRED), pay-at-pickup with partial payment tracking, inventory reservation/commitment/release flow, store-specific time slot generation based on operating hours, and email notifications for order-ready status

Web Commerce

  • MiddlewareCartValidationMiddleware documentation. Covers cart session validation against database records, orphaned cart detection and cleanup, FK relationship verification, and active status enforcement
  • Serializers — Web commerce serializer reference. Documents read-only serializers (CartLineItemSerializer, CartSerializer, CheckoutSessionSerializer, ReservationContextSerializer) and write-only serializers for checkout flow orchestration with validation
  • Signal Handlers — Checkout session and web session lifecycle event handlers. Documents automatic reservation release on session expiry/deletion, handle_checkout_session_status_change(), and cleanup_checkout_reservations() signal handlers
  • Celery Tasks — Four periodic cleanup tasks: cleanup_expired_checkout_sessions (every 5 min), cleanup_abandoned_carts (daily, 24hr threshold), cleanup_stale_guest_sessions (weekly, 30-day threshold), cleanup_old_reservation_contexts (monthly, 90-day threshold)

Documentation Updates

Consistency Pass — Existing documentation updated across all domains for improved readability, enhanced API endpoint descriptions, and consistent formatting:

  • Integrations: Adapters, serializers, signals, sync context, reference logic, tasks, views, and domain overview
  • Payments: Overview, models, serializers, services, signals, views, and webhooks
  • POS Gateway: Overview, authentication, infrastructure, models, and services
  • Products: Overview, examples, models (updated initial_inventory field documentation), signals, and views
  • Promotions: Overview, services (added Redis caching layer documentation), and signals
  • Stores: Overview, signals, and views
  • Transactions: Overview, serializers, signals, and views
  • Users: Overview, models, serializers, services, and signals
  • Tasks: Overview
  • Web Auth: Overview and serializers
  • Web Commerce: Overview
  • Zones: Overview, services, and signals

Caching Documentation Added to Views — Products, Promotions, Stores, and Zones view pages updated with @cache_response decorator configurations, cache key prefixes, and timeout values.

Schema Field Descriptions — 60+ Sanity Studio schema objects enhanced with detailed field-level descriptions covering payments, products, transactions, stores, roles, promotions, and purchase orders.

Navigation & Tooling

  • Side navigation updated with entries for all 24 new pages across 6 domains (Sanity, Core, Integrations, POS Gateway, Products, Transactions, Web Commerce)
  • convert-docs.mjs updated with 9 new file mappings for automated markdown→MDX conversion: tasks.md, serializer_reference_resolver.md, smart_filter_service.md, sync_context.md, sync_services_reference_logic.md, broadcast.md, infrastructure.md, pickup.md, caching.md
  • generate-navigation.mjs updated with sub-page title mappings for all new documentation pages

Version 0.50.1 (2026-02-01)

New Features

  • Storefront Component Library Overhaul

    • ThemeSelector component: Light, Dark, System theme options with localStorage persistence (user_theme_preference), dropdown with icons, click-outside close, memoized client component integrated into Header.tsx
    • useThemePreference hook: Manages theme state, detects OS preference via prefers-color-scheme, syncs across tabs
    • ProductQuickView component: Full product quick view modal for storefront
    • ProductCard major expansion: Variant selection, color swatches, image hover, quick view trigger
    • ProductSection refactored: Grid layout with responsive breakpoints, sort controls, empty states
    • MobileNavigation component: Full mobile nav drawer with animated transitions
    • Container, NavLink common components for consistent layout patterns
    • GeneralIcons, PaymentIcons, SocialIcons icon libraries
    • useVariantSelection hook: Variant state management with attribute-based selection logic
    • useAttributesQuery, useStoresQuery data-fetching hooks
    • FiltersSkeleton, ProductCardSkeleton loading state components
    • API client refactor in storefront/web/src/lib/api/client.ts
    • Product types definition (src/types/product.ts)
    • Product detail page with loading skeleton (products/[slug]/loading.tsx, products/[slug]/page.tsx)
    • globals.css rework for theme variable support
    • PortableText utility for Sanity rich text rendering
    • Smart filter service update for new component integration
    • Web commerce views update for storefront API compatibility
  • Server-Side Rendering for Products

    • ProductDetailClient component: Client-side interactivity layer for SSR product pages
    • Server-side API client (src/lib/api/server.ts): Direct Django API calls from Next.js server components
    • sanityImageLoader: Next.js image loader for Sanity CDN URLs
    • imageUtils: Image URL transformation and fallback utilities
    • robots.ts, sitemap.ts: SEO metadata generation for product pages
    • next.config.ts: Image domain configuration for Sanity CDN
    • Error boundary components: global-error.tsx (top-level), products/[slug]/error.tsx (product-specific), products/[slug]/not-found.tsx (custom 404)
    • Reusable ErrorBoundary React class component with custom fallback UI and reset
    • OrderSummary and product component updates for SSR compatibility
    • useProductDetail hook refactored for client/server data handoff
  • SerializerReferenceResolver (15 files, +895 lines)

    • SerializerReferenceResolver class (integrations/sync/services/serializer_reference_resolver.py, 134 lines): Wrapper layer for DRF serializers to resolve Sanity references
    • resolve_fk_reference(): Single reference resolution with optional required flag
    • resolve_fk_array(): Array reference resolution for many-to-many patterns
    • Auto-fetch from Sanity API when referenced document exists in Sanity but not yet in Django
    • Circular reference detection with depth limiting (max 5 levels), timeout protection (15 seconds), singleton pattern
    • import_from_sanity management command (467 lines): Bulk import utility for bootstrapping Django from Sanity
    • product_sync.py expanded: Enhanced product sync handler with reference resolution (+84 lines)
    • store_reference_logic.py expanded: Store-specific reference resolution patterns (+75 lines)
    • sync_context.py: Sync context additions for resolver state
    • stores/serializers.py: Store serializer updated to use resolver
    • Sanity Studio schema updates: salesTransaction.js, productVariant.js field additions
  • Account Creation Opt-In (8 files, +186 lines)

    • Checkbox on both shipping and pickup checkout flows for guests to create an account
    • Visibility gated on !user and valid email regex — hidden for authenticated users, appears only after valid email entry
    • Shipping flow: create_account field in react-hook-form/zod checkout schema, sent with checkoutFormData
    • Pickup flow: pickupCreateAccount state, sent as create_account: pickupCreateAccount in pickup payload
    • Database migration: 0014_add_create_account_to_fulfillment.py adds create_account field to fulfillment model
    • transactions/models.py: create_account BooleanField on fulfillment
    • fulfillment_service.py: Passes opt-in flag through fulfillment creation
    • payments/services.py: Enhanced payment processing with account creation handling (+95 lines)
    • checkout_service.py: Extracts and forwards create_account from checkout form data
    • webhook_processing.py: Updated to handle account creation during webhook-triggered flows
    • Label: "Create an account for faster checkout next time" with verification email description

Caching & Performance

  • Redis Caching for Promotions (12 files, +2,769 lines of documentation)

    • Cache-based sync loop prevention: webhook_processing_key, django_sync_key, rapid_save_key per promo code instance
    • Rapid save detection with 2-second window prevents duplicate processing
    • Signal-driven invalidation: invalidate_campaign_cache() on PromotionalCampaign save/delete, invalidate_promocode_cache() on PromoCode save/delete
    • Uses invalidate_cache_pattern() from core/utils/cache.py for prefix-based Redis KEYS matching
    • New documentation files created: core/caching.md, integrations/serializer_reference_resolver.md, products/smart_filter_service.md, transactions/pickup.md, web_commerce/middleware.md, web_commerce/serializers.md, web_commerce/signals.md, web_commerce/tasks.md
    • Additions to existing docs: products/views.md, promotions/views.md, stores/views.md, zones/views.md
  • DRF-Extensions Caching and Invalidation (11 files, +174 lines)

    • drf-extensions added to requirements/base.txt
    • make_cache_key_func() in core/utils/cache.py: Generates consistent cache keys for DRF cache_response decorator, format &#123;prefix&#125;:&#123;path&#125;:&#123;query_params_sorted&#125;
    • cache_result() decorator expanded: Configurable timeout (default 300s), key prefix, vary_on parameters, MD5 hashing for long keys
    • invalidate_cache_pattern(): Pattern-based Redis key invalidation with prefix matching and count logging
    • View-level caching applied to: products/views.py (+40 lines), promotions/views.py (+23 lines), stores/views.py (+13 lines), zones/views.py (+19 lines)
    • Signal-based invalidation wired in: products/signals.py, promotions/signals.py, stores/signals.py, zones/signals.py
    • settings/base.py: Cache backend configuration update
  • Cache Invalidation for Reference Data (12 files, +176 lines)

    • Signal-based invalidation across products, stores, zones on model save/delete
    • products/signals.py: Brand, Category, Collection, ProductType invalidation (+41 lines)
    • stores/signals.py: Store cache invalidation on save/delete (+17 lines)
    • zones/signals.py: Zone cache invalidation on save/delete (+17 lines)
    • zones/serializers.py: Serializer updates for cached data patterns
    • Transaction viewset: sale_transaction_viewset.py prefetch and cache decorator additions (+43 lines)
    • web_commerce/services/email_service.py: Email service additions
    • tasks/views.py: Task viewset cache integration
  • Transaction Query Optimization

    • transactions/serializers.py: Reverse relationship (obj.returns.all()) replaces explicit Return.objects.filter(originalSaleId=obj) query
    • transactions/views.py: Prefetch object with select_related('employeeId', 'processedBy') and prefetch_related('return_line_items', 'refunds') for returns
    • Eliminates N+1 queries when fetching transactions with associated returns

Bug Fixes

  • Inventory Field Rename: initialInventory in Sanity product queries

    • gatsby-node.js, products.js, product-template.js: Sanity GROQ queries updated from inventory to initialInventory
    • useProducts.js: Fallback handling — checks initialInventory first, falls back to inventory for legacy data
    • Color swatches now included in variant attributes even when no hex value is provided (previously skipped)
  • Account Creation Checkbox Visibility: Enhanced display logic for guest checkout

    • Checkbox now correctly hidden for authenticated users on both shipping and pickup flows
    • Regex validation ensures checkbox only appears after syntactically valid email entry
  • Sync Infrastructure Fixes (6 files, +249 lines)

    • integrations/sync/base.py: Sync base class updates for improved error handling
    • payment_sync.py: Payment sync handler refactored (+115 lines changed)
    • transaction_sync.py: Transaction sync handler expanded with return sync support (+151 lines)
    • Removed stale SESSION_HANDOFF.md (472 lines deleted)
  • Auth Middleware: send-receipt endpoint added to allowed paths in web_auth/middleware.py

    • Dockerfile.prod updated for production compatibility
    • sale_transaction_viewset.py: Receipt endpoint permission handling improved

Infrastructure

  • Font Assets: TTNormsPro (Black weight, woff/woff2) and Gotham (Book weight, woff/woff2) added to platform/assets/fonts/
  • Codebase Formatting: .editorconfig and .prettierrc added, whitespace normalization across 285 files (dashboard, POS, storefront, studio, web, web_docs)
  • Development Roadmap: docs/comprehensive_development_roadmap.md expanded (+248/-116 lines) with updated app and model counts
  • Documentation Tooling:
    • convert-docs.mjs: Added file mappings for tasks.md, serializer_reference_resolver.md, smart_filter_service.md, sync_context.md, sync_services_reference_logic.md, broadcast.md, infrastructure.md, pickup.md, caching.md — zero conversion skips across all domains
    • generate-navigation.mjs: Added sub-page title mappings for all new pages
    • generated-navigation.ts: Regenerated with new sidenav entries across 6 domains — Web Commerce (middleware, serializers, signals, tasks), Integrations (reference resolver, sync context, reference logic, tasks), Products (smart filters), Transactions (pickup), POS Gateway (broadcast, infrastructure), Core (caching)

API Endpoints

  • POST /checkout/complete/: Now accepts create_account boolean field for guest account opt-in
  • Product variant availability checks use initialInventory field (replaces inventory)
  • Promotional campaign and promo code endpoints benefit from Redis response caching with signal-driven invalidation
  • Product, store, zone, and task list endpoints now use DRF-extensions cache_response decorator
  • POST /transactions/&#123;id&#125;/send-receipt/: Auth middleware updated to allow this endpoint through

Documentation

New documentation files (12 files, +2,769 lines):

  • docs/overview/core/caching.md — Cache utilities, decorators, invalidation patterns (408 lines)
  • docs/overview/integrations/serializer_reference_resolver.md — Reference resolver architecture and usage (352 lines)
  • docs/overview/products/smart_filter_service.md — Elasticsearch smart filtering with dynamic availability (479 lines)
  • docs/overview/transactions/pickup.md — Pickup lifecycle, scheduling, and management (509 lines)
  • docs/overview/web_commerce/middleware.md — Session and CSRF middleware (192 lines)
  • docs/overview/web_commerce/serializers.md — Request/response serialization (341 lines)
  • docs/overview/web_commerce/signals.md — Event handlers (174 lines)
  • docs/overview/web_commerce/tasks.md — Celery background tasks (230 lines)
  • Additions to existing: products/views.md, promotions/views.md, stores/views.md, zones/views.md

Version 0.38.0 (2025-12-31)

New Features

  • Receipt Email Service: Email receipts for POS and storefront transactions

    • ReceiptEmailService for sending branded HTML/text receipts
    • Supports both Resend and Hostinger SMTP providers
    • Includes store branding, line items, payment details
    • Discount and tax breakdown in receipt
    • Cash payment details (amount tendered, change due)
  • Order Ready Notifications: Async customer notifications for pickup orders

    • send_order_ready_notification() Celery task
    • Triggered when pickup is marked ready
    • Payment status aware (shows balance due or paid in full)
    • Store timezone-aware pickup time display
    • customer_notified_at timestamp tracking
  • Pickup Modification API: Load pickup orders for modification

    • GET /pickup/{id}/for-modification/ endpoint
    • Returns cart-compatible format with line items
    • Includes payment balance and verification info
    • Supports PENDING and READY status pickups
  • Enhanced Email Infrastructure: Dual provider support

    • ORDER_EMAIL_PROVIDER environment variable (resend/hostinger)
    • Hostinger SMTP configuration for production
    • Consistent email formatting across all notification types
    • Dev mode logging for email debugging

Improvements

  • Pickup Date Range Filter: Filter pickups by date range

    • Added date filtering to pending pickups endpoint
    • READY status included in end-of-day pickup processing
  • Transaction Email Response: Customer email in transaction response

    • customer_email field added to SaleTransaction serializer
  • Promo Code Support: Enhanced promo code handling in pickups

    • Promo code fields preserved in pickup modification

Async Tasks

New Celery tasks for email notifications:

  • send_order_ready_email - Notify customer when order is ready
  • send_order_confirmation_email - Send order confirmation (pickup/shipping)

API Endpoints

New and enhanced endpoints:

  • GET /pickup/{id}/for-modification/ - Get pickup for cart modification
  • POST /transactions/{id}/send-receipt/ - Send receipt email

Version 0.24.0 (2025-12-28)

New Features

  • Pickup Order Management: Comprehensive pickup scheduling and lifecycle

    • PickupViewSet with full CRUD operations for pickup schedules
    • Available time slots generation based on store operating hours
    • Pickup lifecycle: Pending → Ready → Picked Up / Expired / Cancelled
    • Store-specific pending pickups dashboard endpoint
    • Today's pickups grouped by status with summary counts
    • Pickup cancellation with inventory release
  • Pay-at-Pickup Option: Deferred payment for pickup orders

    • PaymentTiming enum: immediate or at_pickup
    • payment_timing field on SaleTransaction model
    • Inventory validation during checkout (pre-payment)
    • Partial payment tracking for pickup orders
    • Payment collection at pickup completion
  • Promo Code Checkout Validation: Read-only promo code validation

    • POST /promo-codes/validate/ endpoint for storefront checkout
    • Validates code without incrementing usage count
    • Returns discount type, amount, and formatted message
    • Store-specific validation support
    • Subtotal-aware discount calculations
  • UPC Barcode Support: ProductVariant barcode scanning

    • upc field (14-character) on ProductVariant model
    • Database index for fast POS barcode lookups
    • Search and filter support in admin and API
    • Synced from Sanity CMS product variants
  • Cart Image URLs: Enhanced cart display

    • Product image URLs included in cart line items
    • Stepper UI components for quantity adjustment
    • Improved checkout flow with visual product display

Improvements

  • Pickup Service Layer: New PickupService class

    • generate_available_time_slots() - Store-hours aware slot generation
    • create_pickup_schedule() - Fulfillment-linked scheduling
    • mark_ready_for_pickup() - Staff preparation tracking
    • complete_pickup() - Inventory commit and transaction completion
    • cancel_pickup_order() - Customer-facing cancellation with refund
  • Transaction Cancellation: Customer-facing order cancellation

    • cancelledAt, cancellationReason, cancelledBy fields
    • Distinct from staff void operations
    • Automatic inventory release for pending orders
  • Checkout Service: Enhanced orchestration

    • pickup_scheduled_time support in checkout initiation
    • Inventory validation before payment
    • Promo code application during checkout

API Endpoints

New pickup management endpoints:

  • GET /pickup/available-slots/?store_id= - Get available time slots
  • POST /pickup/schedule/ - Create pickup schedule
  • POST /pickup/{id}/mark-ready/ - Mark order ready
  • POST /pickup/{id}/complete/ - Complete pickup
  • POST /pickup/{id}/cancel/ - Cancel pickup order
  • GET /pickup/store/{store_id}/pending/ - Store pending pickups
  • GET /pickup/store/{store_id}/today/ - Today's pickups by status

Version 0.23.0 (2025-12-21)

New Features

  • Delete Webhook Handling: Robust Sanity CMS delete event processing

    • Sanity-Operation header detection for reliable delete identification
    • Domain-specific delete handlers in sync adapters
    • Transaction-aware soft-delete with soft_delete_if_needed() method
    • Cascade delete for Product → ProductVariant relationships
    • InventoryLevel deactivation before marking deleted
    • User sync status checks to prevent re-syncing deleted users
  • Pickup Order Expiration: End-of-day pickup management

    • mark_expired() method for unclaimed pickup orders
    • Automatic inventory release for expired pickups
    • Store-specific return-to-inventory behavior
    • Timezone-aware expiration based on store closing hours
  • SoftDeleteMixin Enhancements: Extended soft-delete capabilities

    • soft_delete_if_needed() for transaction history awareness
    • mark_deleted_from_sanity() for webhook-triggered deletions
    • mark_expired() for pickup order lifecycle management

Improvements

  • Documentation Updates: Comprehensive December 2025 refresh
    • Updated all "Last Updated" dates to 2025-12-21
    • Removed line count references from documentation tables
    • Added delete webhook handling documentation to Integrations
    • Enhanced SoftDeleteMixin documentation in Core models

Documentation

  • Updated integrations/adapters.md with delete handling patterns
  • Updated integrations/README.md with delete webhook features
  • Updated integrations/tasks.md with delete event flow diagrams
  • Updated transactions/README.md with pickup expiration management
  • Updated core/models.md with SoftDeleteMixin enhancements
  • Regenerated all MDX files for web documentation

Version 0.11.0 (2025-12-10)

New Features

  • Revision-Based Sync Tracking: Deterministic sync decisions for Sanity CMS

    • Added django_revision and last_synced_django_revision fields to SanityIntegratedModel
    • Automatic revision bumping on meaningful field changes
    • needs_sync_to_sanity() method for deterministic sync decisions
    • Composite database index for efficient out-of-sync queries
  • Sync Health Admin Interface: Visual sync monitoring in Django Admin

    • SyncHealthAdminMixin for list view sync indicators
    • SyncHealthFilter for filtering by sync lag status
    • Color-coded health indicators (green/orange/red/dark red)
    • SyncRevisionFieldset for admin detail view
  • Bundled Sync System: Efficient Sanity synchronization

    • BundleRegistry for centralized bundle configuration
    • BundledSyncMixin for batched entity sync
    • Transaction bundling with line items and payments
    • 80-90% reduction in Sanity API calls
    • Fallback to individual sync on validation errors
  • Customer Registration Flow: Full storefront registration

    • InitiateRegistrationView - Start registration with OTP
    • VerifyRegistrationView - Complete registration, create Customer role
    • ResendOTPView - Resend OTP during registration
    • Automatic guest cart merge on registration
  • User Profile Editing: Account management features

    • UpdateUserView - Update name and email
    • InitiateEmailChangeView - Email change with OTP verification
    • Support for first_name, last_name, loyalty_points fields

Improvements

  • SanityIntegratedModel: Enhanced with revision tracking methods
  • Web Auth: Added RegistrationService for registration logic
  • Storefront: Enhanced session context with user profile fields

Documentation

  • Updated Core models.md with revision tracking documentation
  • Updated Integrations README with bundled sync architecture
  • Updated Web Auth views.md with registration and profile endpoints
  • Updated DOCUMENTATION_TREE.md with December 2025 changes

Version 0.0.7 (2025-12-04)

New Features

  • Smart Filters: Added Elasticsearch-based smart filtering for products

    • Store-specific inventory filtering
    • Multi-field fuzzy search with autocomplete
    • Dynamic aggregations for brands, categories, product types, and collections
    • Attribute-based filtering with color swatches
    • Improved cache strategy with TanStack Query (5-minute stale time)
  • Inventory Management: Enhanced inventory tracking

    • Store-specific inventory levels with inventory_by_store nested field
    • Proper availability calculation (quantity_on_hand - quantity_reserved)
    • Auto-population of store_location and store_specific_product in InventoryLevel model

Improvements

  • Search Functionality:

    • Prefix matching for 2-3 character queries
    • Fuzzy matching for longer queries with typo tolerance
    • Intelligent variant filtering (shows all variants for product matches, specific variants for variant matches)
  • Type Safety: Updated TypeScript types for ProductVariant interface

    • Changed price fields from string to number
    • Made product_short_description optional
  • Performance:

    • Removed verbose logging in production
    • Optimized React Query caching strategies
    • Added Suspense boundaries for Next.js static generation

Bug Fixes

  • Fixed out-of-stock items appearing in search results
  • Fixed is_available calculation to account for reserved inventory
  • Fixed nested field search issues in Elasticsearch
  • Fixed Next.js build errors with useSearchParams()

Documentation

  • Added internal use disclaimer to documentation header
  • Mobile-friendly disclaimer popover

Version 0.0.6 (2025-11-30)

New Features

  • Fulfillment & Shipment: Implemented fulfillment and shipment models
  • Digital Inventory: Added support for digital product inventory
  • Circuit Breaker: Enhanced Sanity sync with circuit breaker pattern

Improvements

  • Renamed inventory field to initial_inventory for clarity
  • Enhanced user account and order history features
  • Improved storefront registration flow

Version 0.0.5 (2025-11-25)

New Features

  • Initial product sync implementation
  • Store management features
  • Basic inventory tracking

Breaking Changes

  • None

Future Releases

Planned Features

  • Post-filter aggregations for smart disable logic
  • Advanced inventory reservation system
  • Real-time inventory updates via WebSockets
  • Enhanced search relevance scoring

Version Numbering

Djanity follows semantic versioning (MAJOR.MINOR.PATCH):

  • MAJOR: Breaking API changes
  • MINOR: New features, backwards compatible
  • PATCH: Bug fixes, backwards compatible

Currently in pre-release (0.x.x) - API may change without notice.

Was this page helpful?