Compliance - Models

Location: api/nextango/apps/compliance/models.py, api/nextango/apps/compliance/cannabis/models.py Last Updated: 2026-07-06

Overview

The compliance app separates models into two layers. Abstract base classes in compliance/models.py define the structural contract for each compliance concept. Cannabis-specific concrete classes in compliance/cannabis/models.py extend these bases with vertical-specific fields and db_table declarations.


Abstract Base Classes

These classes are never instantiated directly. They establish the field contract that concrete subclasses must honour.

PurchaseLimitRecordBase

Records that a customer purchased a regulated quantity within a compliance window.

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
customerFK → users.Customeron_delete=PROTECT, db_index=True
transactionFK → transactions.SaleTransactionon_delete=PROTECT
storeFK → stores.Storeon_delete=PROTECT, db_index=True
period_typeCharField(25)db_index=True; subclass defines TextChoices
period_startDateTimeFielddb_index=True; store-local midnight (adult-use) or first of month (medical)
is_voidedBooleanFielddefault=False, db_index=True; set on return
voided_atDateTimeFieldnullable
voided_by_returnFK → transactions.Returnon_delete=SET_NULL, nullable

IdentityVerificationEventBase

Audit record for an identity or credential check at point of sale.

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
customerFK → users.Customerdb_index=True
storeFK → stores.Store
verified_byFK → users.EmployeeStaff member who performed the check
verification_typeCharField(30)Subclass defines choices
resultCharField(30)db_index=True; subclass defines choices
transactionFK → transactions.SaleTransactionnullable; links event to a specific sale
notesTextField

BatchTestResultBase

Certificate of Analysis or equivalent batch test record.

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
lab_nameCharField(255)
test_dateDateField
test_typeCharField(30)Subclass defines choices
is_passingBooleanFielddefault=True
is_currentBooleanFielddb_index=True; only one current COA per batch
report_urlURLFieldnullable
confirmed_byFK → users.Employeenullable
confirmed_atDateTimeFieldnullable
sourceCharField(20)Subclass defines choices

StateTrackSyncRecordBase

Log entry for every state-tracking regulatory API call.

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
operationCharField(30)db_index=True; subclass defines TextChoices
statusCharField(10)db_index=True; subclass defines TextChoices
reference_typeCharField(50)Identifies the model type being reported
reference_idCharField(100)db_index=True; PK of the referenced record
request_payloadJSONFieldRaw API request body
response_payloadJSONFieldRaw API response
error_messageTextFieldPopulated on failure
performed_byFK → users.Employeenullable

RegulatedBatchProfileBase

Batch provenance fields with completed cross-vertical evidence matrices.

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
inventory_levelOneToOneField → products.InventoryLevelAccessor name is %(class)s; concrete classes override with a friendly name
lot_numberCharField(100)nullable; 21 CFR 211.130(c) (pharma), TTB 27 CFR 5.66 (alcohol)
is_quarantinedBooleanFielddefault=False, db_index=True; set by recall service on regulatory hold; distinct from InventoryLevel.is_active

RegulatedStoreProfileBase

Vertical-agnostic store compliance policy. Holds the store-level compliance fields shared across regulated verticals; a concrete subclass declares the Store OneToOne and adds vertical-specific store fields. These four fields are the Django side of the regulatedCompliance sync seam (store.regulatedCompliance.* in Sanity).

Base class: TimestampedModel | Abstract: yes

FieldTypeNotes
require_age_gateBooleanFielddefault=True; POS requires age verification before checkout
required_credential_typesJSONFielddefault=list; credential-type strings a customer must present to complete a purchase at this store. Vertical-agnostic; read by POS, storefront, and the onboarding backend
secondary_review_credential_typesJSONFielddefault=list; credential types that always enter pending_employee_review on POS capture instead of going straight to active
permitsJSONFielddefault=list; array of permit objects synced from Sanity store.regulatedCompliance.permits
compliance_managerFK → users.Employeeon_delete=SET_NULL, nullable; a concrete subclass may override the reverse accessor

RegulatedSaleLineSnapshotBase

OneToOneField anchor for point-of-sale compliance snapshots.

Base class: TimestampedModel | Abstract: yes

Each concrete subclass provides its own related_name and adds vertical-specific content fields. Snapshots are immutable after creation.

RegulatedReturnLineSnapshotBase

OneToOneField anchor for return-line compliance snapshots.

Base class: TimestampedModel | Abstract: yes

RegulatedTransactionProfileBase

OneToOneField anchor for per-transaction compliance profiles.

Base class: TimestampedModel | Abstract: yes


Cannabis Concrete Models

CannabisPurchaseLimitRecord

db_table: cannabis_purchase_limit_record Base class: PurchaseLimitRecordBase

FieldTypeNotes
customerFK → users.Customerrelated_name='cannabis_purchase_limit_records'
transactionFK → transactions.SaleTransactionrelated_name='cannabis_purchase_limit_records'
storeFK → stores.Storerelated_name='cannabis_purchase_limit_records'
period_typeCharField(25)Choices: adult_use_daily, medical_monthly
product_categoryCharField(20)Choices: flower, concentrate, edible, other
category_amountDecimalField(12,4)Units: flower=oz, concentrate=g, edible=mg THC
thc_mg_totalDecimalField(12,4)Total THC milligrams consumed in this transaction

Index: cplr_limit_lookup_idx on (customer, store, period_type, period_start, product_category, is_voided).

CannabisAgeVerificationEvent

db_table: cannabis_age_verification_event Base class: IdentityVerificationEventBase

FieldTypeNotes
customerFK → users.Customerrelated_name='cannabis_age_verification_events'
storeFK → stores.Store
verified_byFK → users.EmployeeBudtender who performed the check
transactionFK → transactions.SaleTransactionnullable
verification_typeCharField(20)Choices: government_id, passport, military_id, medical_card
resultCharField(25)Choices: approved, rejected, rejected_expired, rejected_invalid
id_expiry_dateDateFieldnullable; expiry on the presented document
customer_date_of_birthDateFieldnullable; captured for audit on first verification

CannabisLabResult

db_table: cannabis_lab_result Base class: BatchTestResultBase

FieldTypeNotes
inventory_levelFK → products.InventoryLevelrelated_name='cannabis_lab_results', db_index=True
test_typeCharField(30)Choices: full_panel, potency, residual_solvents, microbials, heavy_metals, pesticides
sourceCharField(20)Choices: sanity_seed, manual_entry, state_track_pull
cannabinoid_dataJSONFieldArray of compound objects with compound name, valuePct, and valueMgPerG
terpene_dataJSONFieldArray of terpene objects with name and valuePct
confirmed_byFK → users.Employeenullable

Constraint: cannabis_lab_result_one_current_per_batch, at most one is_current=True per inventory_level.

StoreComplianceProfile

The cannabis vertical's store compliance profile.

db_table: store_compliance_profile Base class: RegulatedStoreProfileBase Accessor: store.compliance_profile

require_age_gate, required_credential_types, secondary_review_credential_types, and permits are inherited from RegulatedStoreProfileBase (see the field table above). The permits array mirrors Sanity store.regulatedCompliance.permits; each entry has _key, permitType, permitNumber, status, issuedDate, expiryDate, payload, documentUrl, notes. The concrete class adds the store relation, a cannabis-specific field, and a friendlier reverse accessor for the manager.

FieldTypeNotes
storeOneToOneField → stores.Storerelated_name='compliance_profile'
compliance_managerFK → users.Employeenullable; overrides the base field to pin related_name='managed_compliance_stores'
state_tracking_license_numberCharField(100)Cannabis-specific; state-issued license for Metrc or equivalent

ProductVariantCannabisProfile

Denormalized cannabis compliance flat columns. The authoritative source is ProductVariant.cannabis_compliance (JSONField). This profile is never written directly; it is populated automatically, synchronously, whenever the parent ProductVariant is saved.

db_table: product_variant_cannabis_profile Base class: TimestampedModel Accessor: variant.cannabis_profile

FieldTypeNotes
variantOneToOneField → products.ProductVariantrelated_name='cannabis_profile'
limit_categoryCharField(20)Choices: flower, concentrate, edible, other; db_index=True
unit_weight_mgDecimalField(10,4)nullable; net weight of one unit in milligrams
menu_typeCharField(15)Choices: adult_use, medical, both
vape_tax_applicableBooleanFielddefault=False; triggers Ohio vape excise if True

InventoryLevelCannabisProfile

Cannabis-specific batch compliance data for an inventory level. Populated on stock receipt from supplier manifest or Metrc package pull.

db_table: cannabis_inventory_level_profile Base class: RegulatedBatchProfileBase Accessor: inventory_level.cannabis_profile

FieldTypeNotes
inventory_levelOneToOneField → products.InventoryLevelrelated_name='cannabis_profile'
lot_numberCharField(100)Inherited from base; nullable
is_quarantinedBooleanFieldInherited from base; set by recall service
batch_numberCharField(100)nullable
packaged_dateDateFieldnullable
expiry_dateDateFieldnullable
coa_urlURLFieldnullable
state_tracking_package_idCharField(30)nullable, db_index=True; Metrc package UID
harvest_dateDateFieldnullable
actual_thc_pctDecimalField(5,2)nullable; batch-tested THC %; overrides label claim for limit calculations
total_thc_mgDecimalField(10,4)nullable; unit_weight_mg × (actual_thc_pct / 100); used by PurchaseLimitEngine

SaleTransactionCannabisProfile

Point-in-time compliance classification for a transaction. Written once at checkout Gate 1; never recomputed after the sale closes.

db_table: cannabis_saletransaction_profile Base class: TimestampedModel Accessor: transaction.compliance_profile

FieldTypeNotes
transactionOneToOneField → transactions.SaleTransactionrelated_name='compliance_profile'
transaction_typeCharField(20)Choices: adult_use, medical; db_index=True; derived by TaxClassificationService.classify_transaction_type()

transaction_type is never accepted from the client payload. The serializer exposes it read-only via SerializerMethodField.

SaleLineItemCannabisSnapshot

Cannabis compliance data captured at point of sale. Immutable after creation.

db_table: cannabis_sale_line_item_snapshot Base class: RegulatedSaleLineSnapshotBase Accessor: line_item.cannabis_snapshot

FieldTypeNotes
sale_line_itemOneToOneField → transactions.SaleLineItemrelated_name='cannabis_snapshot'
state_tracking_package_id_snapshotCharField(30)Metrc package UID at time of sale
thc_mg_snapshotDecimalField(10,4)nullable; THC mg per unit at time of sale; used for limit reversal on return

Only created for cannabis line items. Non-cannabis line items have no snapshot row.

ReturnLineItemCannabisSnapshot

Cannabis return snapshot. Created automatically when a cannabis ReturnLineItem is saved.

db_table: cannabis_return_line_item_snapshot Base class: RegulatedReturnLineSnapshotBase Accessor: return_line_item.cannabis_snapshot

FieldTypeNotes
return_line_itemOneToOneField → transactions.ReturnLineItemrelated_name='cannabis_snapshot'
state_tracking_package_tagCharField(30)nullable; copied from the originating SaleLineItemCannabisSnapshot

MetrcTag

Tracks individual state-tracking package tags pulled into the system. One row per unique package tag.

db_table: cannabis_metrc_tag Base class: TimestampedModel

FieldTypeNotes
tagCharField(30)unique=True, db_index=True; Metrc format: 24-char tag
statusCharField(15)Choices: unverified, verified, active, sold, returned, destroyed, discrepancy; db_index=True
inventory_levelFK → products.InventoryLevelnullable; related_name='state_track_tags'; set when linked to a stock batch
purchase_orderFK → procurement.PurchaseOrdernullable; related_name='state_track_tags'
pulled_atDateTimeFieldnullable; when the tag was fetched from the state track API
last_sync_atDateTimeFieldnullable
state_track_dataJSONFieldRaw package data from the API

StateTrackSyncRecord

Log of every state-tracking API call (stub or production).

db_table: cannabis_state_track_sync_record Base class: StateTrackSyncRecordBase

FieldTypeNotes
tracking_systemCharField(30)Choices: metrc; db_index=True
operationCharField(30)Choices: package_pull, sale_report, transfer_manifest, adjustment, destruction, return_report
statusCharField(10)Choices: stub, success, failed, retry
performed_byFK → users.Employeenullable
state_tracking_package_idCharField(30)Package tag involved in this operation

CannabisRecallEvent

A DCC or manufacturer-issued cannabis product recall.

db_table: cannabis_recall_event Base class: TimestampedModel

FieldTypeNotes
titleCharField(255)
recall_typeCharField(25)Choices: mandatory, voluntary, market_withdrawal
statusCharField(20)Choices: pending, active, resolved, cancelled; db_index=True
issuing_authorityCharField(255)
reasonTextField
recall_dateDateField
resolution_dateDateFieldnullable
affected_lot_numbersJSONFieldLot numbers matched against InventoryLevelCannabisProfile.lot_number
affected_package_idsJSONFieldMetrc package IDs matched against InventoryLevelCannabisProfile.state_tracking_package_id
affected_variantsM2M → products.ProductVariantThrough: CannabisProductRecall
affected_inventory_levelsM2M → products.InventoryLevel
managed_byFK → users.Employeenullable
dcc_notice_urlURLFieldnullable
internal_notesTextField

CannabisProductRecall

Through model linking CannabisRecallEvent to ProductVariant.

db_table: cannabis_product_recall Base class: TimestampedModel

FieldTypeNotes
recall_eventFK → CannabisRecallEvent
product_variantFK → products.ProductVariantrelated_name='cannabis_product_recalls'
units_affectedPositiveIntegerField
units_disposedPositiveIntegerField
disposal_notesTextField

CannabisDestructionEvent

Immutable audit record for every cannabis destruction. Immutable after creation; admin has has_delete_permission=False.

db_table: cannabis_destruction_event Base class: TimestampedModel

FieldTypeNotes
return_line_itemFK → transactions.ReturnLineItemnullable; set when destruction was triggered by a return
recall_eventFK → CannabisRecallEventnullable; set when triggered by a recall
inventory_levelFK → products.InventoryLevelon_delete=PROTECT
state_tracking_package_id_snapshotCharField(30)Metrc tag at time of destruction; snapshot, not a live reference
quantity_destroyedPositiveIntegerField
reasonCharField(25)Choices: customer_return, recall, expired, quality_failure, compliance_order, damaged
destruction_dateDateField
witnessed_byFK → users.Employeenullable; signal-created events have no employee context
state_track_reported_atDateTimeFieldnullable; set when reported to state track
notesTextField

Async Report Jobs

ComplianceReport

Status record for an async compliance report generation job. Created by ComplianceReportService.request_report(); populated by the generate_compliance_report Celery task, which sets output_file and transitions status to ready.

db_table: compliance_report Base class: TimestampedModel

FieldTypeNotes
report_typeCharField(30)db_index=True; choices: daily_sales, purchase_limit_audit, age_verification_audit, recall_destruction_audit, cash_reconciliation
storeFK → stores.Storenullable, on_delete=PROTECT, related_name='compliance_reports'; null=True means all stores the requester has access to
date_fromDateFieldReport range start (inclusive)
date_toDateFieldReport range end (inclusive)
statusCharField(15)db_index=True; choices: pending, generating, ready, failed; default pending
requested_byFK → users.Employeenullable, on_delete=PROTECT, related_name='requested_compliance_reports'
generated_atDateTimeFieldnullable; set when the task writes output_file
output_fileFileFieldnullable; upload_to='compliance_reports/'
error_messageTextFieldPopulated when status='failed'

Ordering: -created_at.

Access control is applied at request time in ComplianceReportService, store-scoped reports verify that the requester's employment record covers the store, chain-wide reports (store=None) are restricted to roles with cross-store visibility.

Was this page helpful?