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
CashierShiftandDrawerCount. 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
CashierShiftanchors cash-handling activity to a named employee-register-store-time tuple.terminal_idis a plain string matchingpos_gateway.TerminalSession.terminal_id, kept as a string rather than a foreign key to avoid coupling the two apps.shift_idis generated on save asSHIFT-<YYYYMMDD>-<8-char hex>. See Models.DrawerCountrecords a physical cash count against a shift. Closing counts enforce a blind-count workflow:expected_amountstays null untilsubmitted_amountis received. An optionalwitnessed_by/witnessed_atpair 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.shiftis a nullable foreign key intoCashierShift; the cannabis-verticalCashReconciliationReportGeneratorreadsCashierShiftandDrawerCountand joins them againstpayments.TransactionPaymentto 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.mjsupdated with new domain mapping:cash_management→cash-managementgenerate-navigation.mjsupdated with the Cash Management section entry- All Pages index updated with the Cash Management section
docs/overview/DOCUMENTATION_TREE.mdanddocs/overview/README.mdrefreshed to reflect the current 17-domain file inventory, includingcore/admin.md,core/exceptions.md,compliance/validators.md, the fullprocurement/page set, and the newcash_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/, andPOST /auth/register/resend-otp/, backed by a registration service holding pending state with a one-hour window andreg_-prefixed tokens. A newly created user receives a deterministicsanity_idofuser_{pk}. See Views and Services. WebSessioncarriescart_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
AllowAnywith empty authentication classes, bypassing the global defaults by design. See Middleware. - Session-key storage migrated to a single
web_session_idkey; 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.StatusgainsAwaiting Compliance Verification(between Partially Received and Received), andPurchaseOrdergains acompliance_verificationJSONField: a vertical-agnostic slot holdingverifiedAt,verifiedBy, andpackageIds. See Models.- All three models (
Supplier,PurchaseOrder,PurchaseOrderLineItem) are Sanity-synced. API authentication uses theTokenheader scheme. See Overview. - Receiving a purchase order increments
stock_on_handandstock_lifetime_receivedautomatically. The daily reorder-level check alerts only on variants withcustom_inventory_managementenabled. See Signals and Tasks. ProcurementAnalyticsService.get_supplier_lead_time_performance()computes per-supplier on-time rates and lead-time variance againstSupplier.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 apos_sessionJWT passed as a?token=query parameter; the session middleware never rejects the handshake, the consumer'sconnect()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 supportsopenonly;close,count, anddepositreturn 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.mjsupdated with new domain mapping:procurement→procurementgenerate-navigation.mjsupdated 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:
StoreandTaxJurisdictionare Sanity-owned with bidirectional sync;Siteis Django-owned with no sync path in either direction (the site seam is dormant);Store.slugis Django-derived and never synced;ComplianceCheckrecords 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
regulatedComplianceblock 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 oldsettingspayloads, a scrub that keeps moved keys out ofsettings, and an always-emitted outbound block. See Serializers. - Dashboard home KPI semantics corrected:
revenue_todayandtransactions_todayuse the UTC calendar date (no per-store timezone is activated on this path), and inventory alerts readstock_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, andSanityIntegratedModelwith 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 throughsafe_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.mjsupdated with new file mapping:admin.md→admingenerate-navigation.mjsupdated 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
PromoCoderecords and codeless auto-applyPromotionrecords, evaluated against every cart. Both use the samediscount_effect/applies_toJSON 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
getQuantityunits in eachbuyQuantity + getQuantitygroup are discounted bygetDiscountPercentage. The free-shipping calculator returns the transaction's actualfulfillment.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, andapply_default_promo_to_cart_candidate()for the validated apply-to-cart path, which returns anApplyResultand never auto-applies. See Services. - URL grounding correction: the domain's own
urls.pyis 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/andrecord-conversion/(atomicF()-expression counters), and the analytics actionsGET /campaigns/analytics/summary/andGET /promo-codes/analytics/performance/. See Views. PromotionAnalyticsServicedocumentation expands to all four methods, addingget_campaign_lift(campaign-window revenue against an equal-length prior control period) andget_auto_promotion_performance(grouped bypromotion_snapshot_id, so history survives promotion deletion). See Analytics Service.- No promotional deletion propagates outbound to Sanity:
PromotionalCampaign,PromoCode, andABTestcarry delete handlers that are deliberately disabled (log only), andPromotionhas 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:
StripeGatewayis live;SquareGateway,PayPalGateway(Zettle), andCustomBankGatewayimplement all gateway methods and activate when their environment variables are configured (SQUARE_*,PAYPAL_ZETTLE_*+PAYPAL_WEBHOOK_ID,BANK_GATEWAY_*). Without credentials they raiseGatewayConnectionError, notNotImplementedError.MOCK_PAYMENT_PROCESSORS=Trueswitches all four gateways, Stripe included, to deterministic mock responses (StripeGatewaygatesget_connection_token,create_payment_session,process_refund,charge_card_not_present, andtest_connectionon 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-Algoenforced toSHA256withRSA, the signing certificate validated and fetched fromPayPal-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 byIdempotencyRecord, with anauthorize_before_idempotency()hook that runs ownership checks before any cached-response short-circuit, per-web-session key scoping,409 IDEMPOTENCY_KEY_BUSYfor concurrent same-key requests,409 IDEMPOTENCY_KEY_BODY_MISMATCHfor same-key different-body, and cached terminal responses for replays. Applied byPaymentViewSet,RefundViewSet,TerminalCreatePaymentView, andStripeConfirmPaymentView. 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 theCheckoutSessionbehind the payment. See Views.- New Django-owned
TransactionPaymentfields documented:operator(FK to the POS cashier who took the payment) andregister_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 tosucceededalso schedules a re-sync of the parentSaleTransactionto 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.Statusnow documents all 8 states, includingpayment_requires_actionfor transactions holding on 3D Secure / Strong Customer Authentication. A newsource_systemfield (pos,web_commerce,dashboard,api) records which channel created the transaction. See Models.transaction_typeis documented as not a field onSaleTransaction: it lives on the active vertical's transaction compliance profile, reached viatransaction.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 throughTaxClassificationService, proportional discount allocation to the taxable subtotal, and vertical excise added on top throughget_active_vertical_provider().additional_excise().tax_breakdownis 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 aCheckoutGateBlockedtoTransactionServiceError) 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
StoreCreditViewSet or/store-credits/endpoint exists anywhere (store credit is created only throughReturnService), and the transactions app's ownPaymentMethodViewSetis not registered anywhere, since/payment-methods/is bound to the payments app's different class of the same name. See Views. CashDrawerViewSetis 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.Tasksexpands 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 viatransaction.on_commit.- Field-name correction:
Analytics Pre-AggregationdocumentedDailyInventorySnapshotfields against the pre-renamequantity_on_hand; corrected tostock_on_hand(the products domain'squantity_*→stock_*rename). Pickupendpoint paths corrected from/pickup/...to/transactions/pickup/...;PickupViewSetis 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_sessionFK ownership rather than trustingsession['cart_id']byte-equality. All 5 cart endpoints check that the cart isACTIVEand owned by the resolved session; checkout endpoints load theCheckoutSessiononly 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-Tokenheader, SHA-256 hashed againstWebSession.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. CheckoutInitiateViewno longer acceptsweb_session_idin the request body; identity comes only from the resolved session.CheckoutCompleteViewenforces 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 otherACTIVEcart on the same web session, closing the divergence that previously caused legitimate cart requests to 403 after a store switch. See Services. CartValidationMiddlewarenow resolves identity through the authoritativeWebSessionrather than a session-stored id, and claims (rather than orphans) a legacy cart whoseweb_sessionFK was never backfilled, via a race-safe conditional update. See Middleware.- Field-name corrections throughout the domain's pages:
InventoryLevelreservation fields arestock_on_hand/stock_reserved(the products domain'squantity_*→stock_*rename), not the previously documentedquantity_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 Backends —
CustomTokenAuthentication, 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_datais documented as the single source of truth for role assignment:sync_roles_to_model_records()creates, updates, or deletes theEmployee/Customer/Manager/Influencer/Viprows 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 emptystore_assignmentsarray 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 differentsanity_idis linked to that user instead of creating a duplicate, recorded toSyncAuditLogwithstatus='quarantined'. See Services.UserViewSetis documented as declaring nopermission_classesof 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 ofUserViewSet. See Views.- New models added to the domain's documentation that had no prior page coverage:
UserProductCollection(wishlists, curations, staff picks),SavedAddress(checkout addresses), andLoyaltyLedger(the immutable audit trail behindCustomer.loyalty_points). See Models.
Navigation & Tooling
- Side navigation updated with a new entry: Backends under Users.
convert-docs.mjsupdated with a new file mapping:backends.md→backends.generate-navigation.mjsupdated with a sub-page title mapping:backends→Backends.overview/page.mdxAll 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
complianceapp holds the abstract base models, theRegulatedCustomerOnboardingServiceorchestrator, theTaxClassificationServicerate-filter engine, the KYC adapter seam, and the vertical-provider seam. Everything cannabis-specific lives incompliance/cannabis/. See Overview. - New abstract base
RegulatedStoreProfileBasecarries the store-level compliance policy shared across regulated verticals:require_age_gate,required_credential_types,secondary_review_credential_types, andpermits.StoreComplianceProfilesubclasses it and adds the cannabis-specificstate_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 atstore.regulatedCompliance.permits. See Validators. - Agnostic callers reach the active vertical through
get_active_vertical_provider()incompliance/services/vertical_provider.py. The cannabis providerCannabisComplianceProvidersupplies 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 onproducts.InventoryLevel. An install without the cannabis app carries none of these, and the agnostic models hold no cannabis import. TaxClassificationServiceis now the agnostic rate engine (get_applicable_rates,calculate_base_rate,build_tax_breakdownover rate components only). Cannabis transaction-type classification and the Ohio vape excise moved toCannabisTaxService; the vape excise is applied at the model layer through the provider'sadditional_exciseslot. 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
- Integrations Validators — The outbound reference-shape validator and the schema-to-data parity classifier.
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 oncreate_document,create_or_replace_document,push_update,create_if_not_exists_and_set, andbatch_create_or_replace, and records any finding as a non-failingSyncAuditLogrow withstatus='shape_warning'. See Validators. - Reference generation reads only. Domain generators return an existing
sanity_idor a deterministicderive_synthetic_sanity_id(instance, sanity_type), and no longer writesanity_idto 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 asanity_idor 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.statusaddsquarantined(an inbound user document held for identity reconciliation) andshape_warning(an outbound reference-shape finding). Both are non-terminal and distinct fromfailed;quarantinedrows areinbound,shape_warningrows areoutbound. See Models.- Schema-to-data parity classification locks each Sanity schema field on a Django-mirrored document type to one of
Populated,PopulatedViaContainedSeam,IntentionallyAbsent, orGenuineGap; a guard fails when a new schema field carries no classification. Thestoretype is classified end to end, withsettings.pickupEnabled,settings.returnToInventory, andsettings.payAtStoreEnabledtracked asGenuineGap(the outbound transform rebuildssettingsfrom scalar fields and omits them). See Validators. SanityDataPreserver.ensure_reference_existsresolves a referenced record's domain handler throughget_domain_handler_for_modeland syncs the record when it has nosanity_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, andstock_lifetime_receivedonInventoryLevelandProductVariant(Sanity payloads and the Elasticsearch index carry the matching camelCase namesstockOnHand/stockInitial). A computedstock_availableproperty returnsmax(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 theemployeeormanagerrole, 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, andCollectionViewSetare read-only; Sanity Studio is the authoring surface. Mutation runs through@actionendpoints gated toIsEmployeeOrManager(sync_variants,sync_from_sanity).InventoryLevelViewSetremains the one writable viewset, sinceInventoryLevelis 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 canonicalProductVariantrecords and theirInventoryLevelrows rather than theProduct.variantsJSONField, 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, orlow_stockactive). 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 ownAppConfig.ready()(cannabis registerscannabis_profile), so the agnosticInventoryLevelmodel 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-scopedInventoryLevelrecords; par-level quantities materialize intoInventoryLevelrows 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_locationentries carrystore.sanity_id(the Sanity document ID) as_ref, not the store's Django UUID;InventoryLevel.save()raisesValueErrorif the target store has nosanity_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 onGET /inventory-levels/?store_id=does not extend to this action. See Examples.GET /search/accepts abrandparameter and echoes it in the responsefilters, but the search service applies no brand filter to the Elasticsearch query. Filter by brand throughGET /search/smart-filter/(brand[]) instead. See Search.PUT /product-variants/{sku}/is routed by a strayupdate()override on the otherwise read-onlyProductVariantViewSet. The call fails with a server error and performs no mutation; it remains gated byIsAuthenticated. See Views.
Version 0.85.0 (2026-04-20)
New Documentation Pages
- Compliance Adapters —
KYCVendorAdapterbase contract,VerificationResultdataclass, 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 Utilities —
credential_utilsandpermit_utilslookup 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_reconciliationadded toComplianceReport.ReportTypechoices. SeePOST /compliance/cannabis/reports/. - IRS 8300 advisory flag: cumulative daily cash receipts per store are summed across shifts;
irs_8300_flag=Truewhen the running total reaches$10,000. Threshold isIRS_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_totalis pre-wired and will populate whencashless_atmis added as aTransactionPayment.payment_method_typechoice 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 passEmployeeComplianceService.check_can_process_sale(active DCC agent permit + cleared background check). - New endpoint:
GET /compliance/onboarding/pending-credentials/?store_id=<id>lists pending credentials bysubmittedAtStoreId; 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) andsecondary_review_credential_types(types that always enterpending_employee_reviewon POS capture). - Stored
verificationDatais filtered toverifiedDocumentId,verifiedBiometricId,reviewNotes— the subset that matches the Sanitycredentialschema, so bidirectional sync stays consistent. - Pending status set:
pending_verification,pending_employee_review,pending_vendor_response. - New cannabis credential validators:
government_id_verification(requiresdocumentType+dateOfBirth) andmedical_marijuana_patient_card(requirespatientId, normalisesprogramNameandcaregiverAllowed). 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 toRegulatedCustomerOnboardingServicewithchannel='storefront'; every credential is written aspending_verification. Returns201on success,400on validation failure,409on duplicate email. KYCVendorAdapterabstract base incompliance/services/kyc/base.py. Two methods:initiate_verification(user, credential_key)returns a redirect URL;handle_webhook(payload)returns aVerificationResult. Tri-state outcome:approved|rejected|unresolved. See Adapters.PersonaAdapterconcrete implementation. Inquiry-based; the credential_keyis passed as Persona'sreference-idand 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}"withPERSONA_WEBHOOK_SECRET; 300-second replay tolerance (_PERSONA_SIG_TOLERANCE_SECONDS); parses rotation-set signature headers (multiple space-separatedt=...,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 writesapprovedBy,approvedAt,rejectedBy, orrejectedAt.
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:
/overviewcompliance section expanded from 5 to 9 rows. - No changes to
convert-docs.mjsFILE_MAPPINGorgenerate-navigation.mjsSUB_PAGE_TITLES— both already includeadapters,webhooks,validators, andutilities.
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
StoreandProductVariantto profile models.StoreComplianceProfileis auto-created per store.ProductVariantCannabisProfilecarries limit category, unit weight, menu type, and vape tax flag. See API Changes below. - Phase 4 — limit enforcement:
PurchaseLimitEnginewired 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:
AgeVerificationServicerecordsCannabisAgeVerificationEventon each attempt.POST /compliance/age-verification/andGET /compliance/age-verification/status/are live. - Phase 4.75 — credential refactor: 14 flat credential fields removed from
EmployeeandCustomer. All credential data lives inuser.credentialsJSONField.credential_utils.get_active_credential()provides the read API. - Phase 5 — tax classification:
TaxClassificationServicewired intorecalculate_totals(). FiltersTaxJurisdiction.tax_rates_databyappliesTomatching transaction type. Vape excise scoped to vape-item subtotal only. - Phase 5.5 — model verticalization:
transaction_typemoved fromSaleTransactiontoSaleTransactionCannabisProfile— snapshotted at checkout viatransaction.compliance_profile.MetrcTagmodel added tracking the full RFID tag lifecycle (UNVERIFIED→VERIFIED→ACTIVE→SOLD/RETURNED/DESTROYED/DISCREPANCY). - Phase 6 — Metrc state tracking:
MetrcClient— Ohio Metrc API client with dual-key Basic Auth. Implementspull_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 fromAWAITING_COMPLIANCE_VERIFICATIONtoRECEIVED. - Phase 7 — recall and destruction:
CannabisRecallEvent,CannabisProductRecall, andCannabisDestructionEventmodels.CannabisRecallServiceimplements activate, record_destruction, and resolve.MetrcClient.report_destruction()added. Signal createsCannabisDestructionEventautomatically onReturnLineItemwithdisposition=DESTRUCTION.InventoryLevel.is_quarantinedadded as a cross-vertical compliance hold field. - Phase 8A — compliance reporting:
ComplianceReportmodel (migrationcompliance/0004) with generator registry andgenerate_compliance_reportCelery task.ComplianceReportServicewith 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.mjsDOMAIN_MAPPING updated with'compliance': 'compliance'.generate-navigation.mjsDOMAINS updated withcomplianceunder 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 2 —
compliance/cannabissub-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 byCartViewto enrich the cart response before checkout.GET /api/promotions/active/returns all active promotions for a store. Acceptsstore_idquery param.is_appliedflag on promotions —Truewhen the discount is active in the cart, exposed inCartItem.applied_promotions.recalculate_totals()is now the sole writer oforderLevelDiscount— 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.
PaymentSummaryin the checkout flow shows promotion savings as a line item.- Cart types gain
applied_promotions: ActivePromotion[]onCartItemandfree_shipping_active: boolonCart.
POS
- Promotion banners and discounted prices display in the cart panel.
ActivePromotiontype andpromotionUtilshelpers added to the POS TypeScript layer.
Stores
TaxJurisdictiongainsis_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
PaymentOrchestrationServicedispatches 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. Acceptspayment_intent_id, verifies the PaymentIntent via Stripe, and advances the transaction toCOMPLETED.payment_intent.succeededwebhook advances stalledPAYMENT_REQUIRES_ACTIONorders automatically.- Transactions that remain in
PAYMENT_REQUIRES_ACTIONfor more than one hour are voided by a Celery Beat task. PaymentProcessorandPaymentMethodregistered 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 callsPOST /api/payments/confirm/on resolution. - Checkout draft state persists to
localStoragevianext-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
CheckoutSessiongainsfulfillment_dataJSONField — stores cart line data for 3DS recovery flows where the checkout session must be resumed after the Stripe action completes.
Admin
PortableTextRendererrenders Sanity portable text JSON as formatted HTML in Django admin.body_displayon products is read-only.SanityVariantFormsetWidgetprovides a card UI for editing product variants inline, with attribute picker and Sanity_keypreservation.
Version 0.76.0 (2026-03-21)
New Features
Infrastructure
- Nginx serves a
Content-Security-Policyheader on all five frontend subdomains:dashboard.djanity.com,pos.djanity.com,docs.djanity.com,shop.djanity.com, andcms.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 permitsunsafe-evaland all Sanity Studio origins includingwss://*.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 updatedrequirements/production.txt. - A database backup script (
scripts/backup_db.sh) runspg_dumpagainst the primary container and rotates files older than 14 days. Configurable viaBACKUP_DIR,RETENTION_DAYS,DB_CONTAINER,DB_NAME, andDB_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 forDatabaseBackend,CacheBackend, andDefaultFileStorageHealthCheck. Pass?format=jsonfor a machine-readable response. HTTP 200 when all checks pass; HTTP 500 if any check fails. See Core.
Payments
elavonis now a validprocessor_typeonPaymentProcessorandWebhookEvent. Records withprocessor_type='elavon'route to the Custom gateway, which readsBANK_GATEWAY_*credentials. See Payment Models.
Storefront
- Multi-store picker appears on storefront when more than one store is available. The store marked
is_defaultis pre-selected on initial load. This behaviour applies both to the inline selector and withinStoreSelectionDialog. StoreSelectionDialogopens regardless of whether a server-side store ID is present, letting users switch store context from any page.- Inactive stores are excluded from the
StoreSelectionDialogpicker. ErrorBoundarywraps 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
AlertDialogfor confirmation and Sonner toast for error feedback. Previously these usedwindow.confirmandwindow.alert, which Electron 28 withcontextIsolation: truesilently returnsfalsefrom — 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
CustomerAnalyticsServicegainsget_cohort_retention(),get_repeat_purchase_rate(), andget_rfm_scores()(quintile 1–5 viaPERCENT_RANK()).ProcurementAnalyticsServicegainsget_supplier_lead_time_variance().PromotionAnalyticsServicegainsget_campaign_lift().- New
GET /api/analytics/growth/auto-promotions/endpoint — acceptsstore_id,start_date,end_date. get_inventory_trends()now reads fromDailyInventorySnapshot; historical ranges route to the replica DB.
Procurement
PurchaseOrderLineItemgainsquantity_received(nullable) — units received per line, enabling partial-receipt reconciliation. Migrationprocurement/0X.
Promotions
- New
Promotionmodel for codeless automatic discounts evaluated at checkout. Non-stackable promotions compete on highest value; stackable ones combine. Applied promotions snapshot toSaleTransactionAppliedPromotion.SaleTransactiongainsappliedPromotionDiscount. Migrationspromotions/0006–0009,transactions/0025. process_expired_campaignsCelery Beat task runs daily at 08:00 UTC — computes campaign lift and marks expired campaigns inactive.
Stores
purge_test_storesmanagement command removes test store records and their Sanity documents. Accepts--dry-run.
Studio
sitedocument type replacescompanyInfofor company-level configuration. ExistingcompanyInfodocuments should be migrated.
Transactions
SaleTransactiongainstax_breakdown— nullable JSONField snapshotting per-jurisdiction tax at completion (jurisdiction,taxable_amount,rate,tax_collected).export_tax_remittancemanagement command exports CSV. Migrationtransactions/0023.SaleLineItemgainscostAtSale— nullable DecimalField recordingInventoryLevel.current_costat checkout for margin analytics. Migrationtransactions/0024.
API Changes
Integrations
InventoryLevelchanges now propagate to Sanity via post-save signal — previously only inbound (Sanity→Django) sync was active.SaleTransaction,SaleLineItem, andShipmentremoved from Sanity sync scope and Studio desk structure.- Sync write-backs now use
queryset.update()instead ofinstance.save(), preventing post-save signals from triggering recursive sync. - User and transaction sync handlers now call
create_or_replace()instead ofpush_update(), fixing silent failures when no Sanity document existed yet.
Transactions
- Proportional refund calculation now includes
appliedPromotionDiscountalongsideorderLevelDiscount.
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
shippingInfoSavedguard — 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
LoyaltyLedgerentry 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 Services —
ActiveOperationsService(live operational queues: pending pickups, in-transit shipments, pending transactions, open returns) andInfrastructureService(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.mjsupdated with new domain mapping:analytics→analyticsgenerate-navigation.mjsupdated 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
DailyRevenueSummaryandDailyInventorySnapshot. Covers Celery rollup schedules, query routing between replica and live DB, the_pre_save_statussignal guard, backfill management command, and composite indexes added toFulfillment,PickupSchedule, andShipment. - 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 bytransactions.rollup_daily_revenue; refreshed every 15 min intraday bytransactions.refresh_today_revenue_summaryDailyInventorySnapshot— populated nightly at 00:05 byproducts.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_statussnapshot onSaleTransactionprevents double-countingorder_countanditems_soldon status re-saves - Promotions — deadlock prevention —
PromotionalCampaignlock acquisition order sorted to prevent deadlocks under concurrent transaction completion - Promotions — A/B test conversion —
ABTest.record_conversion()now wired to transaction completion signal; conversions were previously not recorded - Promotions — campaign metrics —
PromotionalCampaign.performance_metricsupdated on transaction completion; revenue attribution was previously not tracked - Users — UserProductCollection — M2M signal reverse accessor fixed;
statisticsauto-refreshes when items are added or removed - Users — Customer store credit —
Customer.store_creditnow recalculated automatically when aStoreCreditrecord is saved
Navigation & Tooling
- Side navigation updated with 2 new entries: Dashboard Home under Stores, Analytics Pre-Aggregation under Transactions
convert-docs.mjsupdated with 2 new file mappings:dashboard_home.md→dashboard-home,analytics_pre_aggregation.md→analytics-pre-aggregationgenerate-navigation.mjsupdated 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_responsedecorator 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 Resolver —
SerializerReferenceResolverwrapper for DRF serializers to resolve Sanity document references. Documentsresolve_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
_refreferences 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 Broadcasting —
WebSocketBroadcasterutility 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
PosGatewayConfigapp startup withGatewayInitializationService, WebSocket URL routing for real-time terminal connections, REST API URL routing, and deployment considerations
Products
- Smart Filter Service —
SmartFilterServicefor Elasticsearch-based product filtering. Documentsexecute_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, andPOST /products/search/API endpoint with request/response examples
Transactions
- Pickup System —
PickupServiceandPickupViewSetfor 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
- Middleware —
CartValidationMiddlewaredocumentation. 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(), andcleanup_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_inventoryfield 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.mjsupdated 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.mdgenerate-navigation.mjsupdated with sub-page title mappings for all new documentation pages
Version 0.50.1 (2026-02-01)
New Features
-
Storefront Component Library Overhaul
ThemeSelectorcomponent: Light, Dark, System theme options with localStorage persistence (user_theme_preference), dropdown with icons, click-outside close, memoized client component integrated intoHeader.tsxuseThemePreferencehook: Manages theme state, detects OS preference viaprefers-color-scheme, syncs across tabsProductQuickViewcomponent: Full product quick view modal for storefrontProductCardmajor expansion: Variant selection, color swatches, image hover, quick view triggerProductSectionrefactored: Grid layout with responsive breakpoints, sort controls, empty statesMobileNavigationcomponent: Full mobile nav drawer with animated transitionsContainer,NavLinkcommon components for consistent layout patternsGeneralIcons,PaymentIcons,SocialIconsicon librariesuseVariantSelectionhook: Variant state management with attribute-based selection logicuseAttributesQuery,useStoresQuerydata-fetching hooksFiltersSkeleton,ProductCardSkeletonloading 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.cssrework for theme variable supportPortableTextutility 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
ProductDetailClientcomponent: 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 URLsimageUtils: Image URL transformation and fallback utilitiesrobots.ts,sitemap.ts: SEO metadata generation for product pagesnext.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
ErrorBoundaryReact class component with custom fallback UI and reset OrderSummaryand product component updates for SSR compatibilityuseProductDetailhook refactored for client/server data handoff
-
SerializerReferenceResolver (15 files, +895 lines)
SerializerReferenceResolverclass (integrations/sync/services/serializer_reference_resolver.py, 134 lines): Wrapper layer for DRF serializers to resolve Sanity referencesresolve_fk_reference(): Single reference resolution with optionalrequiredflagresolve_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_sanitymanagement command (467 lines): Bulk import utility for bootstrapping Django from Sanityproduct_sync.pyexpanded: Enhanced product sync handler with reference resolution (+84 lines)store_reference_logic.pyexpanded: Store-specific reference resolution patterns (+75 lines)sync_context.py: Sync context additions for resolver statestores/serializers.py: Store serializer updated to use resolver- Sanity Studio schema updates:
salesTransaction.js,productVariant.jsfield 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
!userand valid email regex — hidden for authenticated users, appears only after valid email entry - Shipping flow:
create_accountfield in react-hook-form/zod checkout schema, sent withcheckoutFormData - Pickup flow:
pickupCreateAccountstate, sent ascreate_account: pickupCreateAccountin pickup payload - Database migration:
0014_add_create_account_to_fulfillment.pyaddscreate_accountfield to fulfillment model transactions/models.py:create_accountBooleanField on fulfillmentfulfillment_service.py: Passes opt-in flag through fulfillment creationpayments/services.py: Enhanced payment processing with account creation handling (+95 lines)checkout_service.py: Extracts and forwardscreate_accountfrom checkout form datawebhook_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_keyper promo code instance - Rapid save detection with 2-second window prevents duplicate processing
- Signal-driven invalidation:
invalidate_campaign_cache()onPromotionalCampaignsave/delete,invalidate_promocode_cache()onPromoCodesave/delete - Uses
invalidate_cache_pattern()fromcore/utils/cache.pyfor 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
- Cache-based sync loop prevention:
-
DRF-Extensions Caching and Invalidation (11 files, +174 lines)
drf-extensionsadded torequirements/base.txtmake_cache_key_func()incore/utils/cache.py: Generates consistent cache keys for DRFcache_responsedecorator, format{prefix}:{path}:{query_params_sorted}cache_result()decorator expanded: Configurable timeout (default 300s), key prefix,vary_onparameters, MD5 hashing for long keysinvalidate_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.pyprefetch and cache decorator additions (+43 lines) web_commerce/services/email_service.py: Email service additionstasks/views.py: Task viewset cache integration
-
Transaction Query Optimization
transactions/serializers.py: Reverse relationship (obj.returns.all()) replaces explicitReturn.objects.filter(originalSaleId=obj)querytransactions/views.py:Prefetchobject withselect_related('employeeId', 'processedBy')andprefetch_related('return_line_items', 'refunds')for returns- Eliminates N+1 queries when fetching transactions with associated returns
Bug Fixes
-
Inventory Field Rename:
initialInventoryin Sanity product queriesgatsby-node.js,products.js,product-template.js: Sanity GROQ queries updated frominventorytoinitialInventoryuseProducts.js: Fallback handling — checksinitialInventoryfirst, falls back toinventoryfor 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 handlingpayment_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-receiptendpoint added to allowed paths inweb_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:
.editorconfigand.prettierrcadded, whitespace normalization across 285 files (dashboard, POS, storefront, studio, web, web_docs) - Development Roadmap:
docs/comprehensive_development_roadmap.mdexpanded (+248/-116 lines) with updated app and model counts - Documentation Tooling:
convert-docs.mjs: Added file mappings fortasks.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 domainsgenerate-navigation.mjs: Added sub-page title mappings for all new pagesgenerated-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 acceptscreate_accountboolean field for guest account opt-in- Product variant availability checks use
initialInventoryfield (replacesinventory) - 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_responsedecorator POST /transactions/{id}/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
ReceiptEmailServicefor 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_attimestamp 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_PROVIDERenvironment 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_emailfield 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 readysend_order_confirmation_email- Send order confirmation (pickup/shipping)
API Endpoints
New and enhanced endpoints:
GET /pickup/{id}/for-modification/- Get pickup for cart modificationPOST /transactions/{id}/send-receipt/- Send receipt email
Version 0.24.0 (2025-12-28)
New Features
-
Pickup Order Management: Comprehensive pickup scheduling and lifecycle
PickupViewSetwith 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
PaymentTimingenum:immediateorat_pickuppayment_timingfield 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
upcfield (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
PickupServiceclassgenerate_available_time_slots()- Store-hours aware slot generationcreate_pickup_schedule()- Fulfillment-linked schedulingmark_ready_for_pickup()- Staff preparation trackingcomplete_pickup()- Inventory commit and transaction completioncancel_pickup_order()- Customer-facing cancellation with refund
-
Transaction Cancellation: Customer-facing order cancellation
cancelledAt,cancellationReason,cancelledByfields- Distinct from staff void operations
- Automatic inventory release for pending orders
-
Checkout Service: Enhanced orchestration
pickup_scheduled_timesupport 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 slotsPOST /pickup/schedule/- Create pickup schedulePOST /pickup/{id}/mark-ready/- Mark order readyPOST /pickup/{id}/complete/- Complete pickupPOST /pickup/{id}/cancel/- Cancel pickup orderGET /pickup/store/{store_id}/pending/- Store pending pickupsGET /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-Operationheader 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 awarenessmark_deleted_from_sanity()for webhook-triggered deletionsmark_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_revisionandlast_synced_django_revisionfields 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
- Added
-
Sync Health Admin Interface: Visual sync monitoring in Django Admin
SyncHealthAdminMixinfor list view sync indicatorsSyncHealthFilterfor filtering by sync lag status- Color-coded health indicators (green/orange/red/dark red)
SyncRevisionFieldsetfor admin detail view
-
Bundled Sync System: Efficient Sanity synchronization
BundleRegistryfor centralized bundle configurationBundledSyncMixinfor 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 OTPVerifyRegistrationView- Complete registration, create Customer roleResendOTPView- Resend OTP during registration- Automatic guest cart merge on registration
-
User Profile Editing: Account management features
UpdateUserView- Update name and emailInitiateEmailChangeView- Email change with OTP verification- Support for
first_name,last_name,loyalty_pointsfields
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_storenested field - Proper availability calculation (
quantity_on_hand - quantity_reserved) - Auto-population of
store_locationandstore_specific_productin InventoryLevel model
- Store-specific inventory levels with
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_availablecalculation 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
inventoryfield toinitial_inventoryfor 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.