Cash Management - Shift and Drawer Reconciliation

Domain: Cash Management Location: api/nextango/apps/cash_management/ Last Updated: 2026-07-09

Overview

The Cash Management app tracks physical cash handling at the register: who worked which terminal during which time window, and the physical cash counts taken during that window. It exists to support blind-count reconciliation and cash-handling audit requirements without depending on any specific regulatory vertical.

The app has no views, serializers, services, signals, or Celery tasks. It exposes two plain Django models and their admin registrations. No URL routes reference cash_management anywhere in the project (nextango/urls.py has no mount, root-router registration, or import for this app), so there is no HTTP API surface to document. Other domains read and write these models directly through the ORM.

FileDescription
models.mdCashierShift, DrawerCount

Key Concepts

Shift as the anchor record

CashierShift records a time window during which one employee operates one register at one store. Every drawer count and every cash drawer event elsewhere in the platform ties back to a shift, either directly (DrawerCount.shift) or by nullable foreign key (transactions.CashDrawerEvent.shift). terminal_id is stored as a plain string rather than a foreign key to pos_gateway.TerminalSession, so this app does not import from or depend on pos_gateway.

Blind count workflow

DrawerCount records a physical cash count tied to a shift. For a closing count, expected_amount is left null until after submitted_amount is received: the counting employee submits what they physically counted before the system-expected total is computed or shown to them. discrepancy is the difference between the two once both are known.

Two-person verification

DrawerCount carries an optional witnessed_by employee and witnessed_at timestamp, supporting a second-person sign-off on a count. Both fields are nullable; the model does not require a witness.

Ownership and Sync

CashierShift and DrawerCount are plain django.db.models.Model subclasses. Neither inherits SanityIntegratedModel, neither carries a sanity_id field, and neither app has an outbound sync adapter. These are operational records born from register activity, not content authored in Sanity Studio, so Django owns them outright and no sync boundary applies.

Admin

cash_management/admin.py registers both models with the Django admin. CashierShiftAdmin lists shift_id, store, employee, terminal_id, status, started_at, and ended_at, filters by status and store, and searches shift_id, terminal_id, and employee__email. shift_id, created_at, and updated_at are read-only. DrawerCountAdmin lists shift, count_type, submitted_by, submitted_amount, expected_amount, discrepancy, and submitted_at, filters by count_type, and keeps submitted_at read-only.

  • Transactions: CashDrawerEvent.shift is a nullable foreign key to CashierShift, and CashDrawerEvent.shiftId stores the shift's string identifier for display
  • Payments: cash reconciliation reporting aggregates TransactionPayment records against each shift's store and time window
  • Compliance: the cannabis-vertical CashReconciliationReportGenerator reads CashierShift and DrawerCount to build a per-shift reconciliation report, one row per shift, joined against TransactionPayment totals

Was this page helpful?