diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index 5f6a571..0000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,859 +0,0 @@
-# FiscGuy Architecture & Engineering Documentation
-
-This document provides comprehensive technical documentation for FiscGuy developers and maintainers.
-
-## Table of Contents
-
-1. [Project Overview](#project-overview)
-2. [Architecture](#architecture)
-3. [Data Models](#data-models)
-4. [Service Layer](#service-layer)
-5. [Cryptography & Security](#cryptography--security)
-6. [ZIMRA Integration](#zimra-integration)
-7. [Receipt Processing Pipeline](#receipt-processing-pipeline)
-8. [Fiscal Day Management](#fiscal-day-management)
-9. [Error Handling](#error-handling)
-10. [Database Design](#database-design)
-11. [Development Guidelines](#development-guidelines)
-
-## Project Overview
-
-FiscGuy is a Django-based library for integrating with ZIMRA (Zimbabwe Revenue Authority) fiscal devices. It manages:
-
-- **Device Registration & Management** - Certificate-based authentication with ZIMRA FDMS
-- **Receipt Generation & Submission** - Full receipt lifecycle with cryptographic signing
-- **Fiscal Day Operations** - Opening/closing fiscal days with counter management
-- **Configuration Management** - Device and taxpayer configuration persistence
-- **Tax Management** - Support for multiple tax types and rates
-
-**Technology Stack:**
-- Django 4.2+
-- Django REST Framework
-- Cryptography (RSA, SHA-256, MD5)
-- QRCode Generation
-- ZIMRA FDMS API (HTTPS with certificate-based auth)
-
-## Architecture
-
-FiscGuy follows a **layered architecture**:
-
-```
-┌─────────────────────────────────────────────────────┐
-│ REST API Layer (views.py) │
-│ ReceiptView, OpenDayView, CloseDayView, etc. │
-└──────────────────┬──────────────────────────────────┘
- │
-┌──────────────────┴──────────────────────────────────┐
-│ Service Layer (services/) │
-│ ReceiptService, OpenDayService, ClosingDayService │
-└──────────────────┬──────────────────────────────────┘
- │
-┌──────────────────┴──────────────────────────────────┐
-│ Data Persistence Layer (models.py) │
-│ Device, Receipt, FiscalDay, Configuration, etc. │
-└──────────────────┬──────────────────────────────────┘
- │
-┌──────────────────┴──────────────────────────────────┐
-│ ZIMRA Integration Layer (zimra_*.py) │
-│ ZIMRAClient, ZIMRACrypto, ZIMRAReceiptHandler │
-└──────────────────┬──────────────────────────────────┘
- │
- ↓
- ZIMRA FDMS REST API
-```
-
-### Layer Responsibilities
-
-#### 1. REST API Layer (`views.py`)
-- **ReceiptView** - List/paginate receipts (GET), create & submit receipts (POST)
-- **OpenDayView** - Open fiscal day operations
-- **CloseDayView** - Close fiscal day and calculate counters
-- **StatusView** - Query device status from ZIMRA
-- **ConfigurationView** - Fetch device configuration
-- **TaxView** - List available taxes
-- **BuyerViewset** - CRUD operations for buyers
-
-#### 2. Service Layer (`services/`)
-
-**ReceiptService** - Orchestrates receipt creation and submission:
-- Validates receipt payload via serializers
-- Persists receipt to database
-- Delegates processing to ZIMRAReceiptHandler
-- Returns atomic operation result (all-or-nothing)
-
-**OpenDayService** - Opens fiscal day on ZIMRA:
-- Queries ZIMRA for next day number
-- Creates FiscalDay record
-- Auto-called if no open day exists when submitting receipt
-
-**ClosingDayService** - Closes fiscal day:
-- Queries ZIMRA for fiscal counters
-- Builds counters hashstring per spec
-- Sends closing request to ZIMRA
-- Updates local FiscalDay record
-
-**ConfigurationService** - Synchronizes configuration:
-- Fetches device config from ZIMRA
-- Syncs taxes from ZIMRA
-- Manages tax updates
-
-**StatusService** - Queries ZIMRA status
-**PingService** - Device connectivity check
-**CertsService** - Certificate management
-
-#### 3. Data Persistence Layer (`models.py`)
-
-See [Data Models](#data-models) section below.
-
-#### 4. ZIMRA Integration Layer
-
-**ZIMRAClient** (`zimra_base.py`):
-- HTTP/HTTPS requests to ZIMRA FDMS
-- Certificate-based authentication
-- Request/response handling
-- Timeout management (30s default)
-
-**ZIMRACrypto** (`zimra_crypto.py`):
-- RSA signing with SHA-256
-- SHA-256 hashing for integrity
-- MD5 for verification code generation
-- QR code verification code from signature
-
-**ZIMRAReceiptHandler** (`zimra_receipt_handler.py`):
-- Complete receipt processing pipeline
-- Receipt data building per ZIMRA spec
-- Hash and signature generation
-- QR code creation
-- Fiscal counter updates
-- FDMS submission
-
-## Data Models
-
-### Device
-```python
-Fields:
-- org_name: str (max 255)
-- activation_key: str (max 255)
-- device_id: str (unique, max 100)
-- device_model_name: str (optional)
-- device_serial_number: str (optional)
-- device_model_version: str (optional)
-- production: bool (test/production environment flag)
-- created_at: datetime (auto_now_add)
-
-Relationships:
-- configuration (OneToOne) → Configuration
-- certificate (OneToOne) → Certs
-- fiscal_days (OneToMany) → FiscalDay
-- fiscal_counters (OneToMany) → FiscalCounter
-- receipts (OneToMany) → Receipt
-```
-
-**Purpose:** Represents a physical/logical ZIMRA fiscal device. Multiple devices can be registered (e.g., different POS terminals).
-
-### Configuration
-```python
-Fields:
-- device: OneToOneField → Device
-- tax_payer_name: str (max 255)
-- tin_number: str (max 20)
-- vat_number: str (max 20)
-- address: str (max 255)
-- phone_number: str (max 20)
-- email: EmailField
-- url: URLField (test/production ZIMRA endpoint)
-- tax_inclusive: bool (tax calculation mode)
-- created_at/updated_at: datetime
-
-Relationships:
-- device (OneToOne) → Device
-```
-
-**Purpose:** Stores taxpayer configuration synced from ZIMRA. One config per device.
-
-### Certs
-```python
-Fields:
-- device: OneToOneField → Device
-- csr: TextField (Certificate Signing Request)
-- certificate: TextField (X.509 certificate)
-- certificate_key: TextField (RSA private key)
-- production: bool (test/production cert)
-- created_at/updated_at: datetime
-
-Relationships:
-- device (OneToOne) → Device
-```
-
-**Purpose:** Stores device certificates and private keys for ZIMRA authentication.
-
-### FiscalDay
-```python
-Fields:
-- device: ForeignKey → Device
-- day_no: int (ZIMRA fiscal day number)
-- receipt_counter: int (receipts issued today, default 0)
-- is_open: bool (day open/closed)
-- created_at/updated_at: datetime
-
-Constraints:
-- unique_together: (device, day_no)
-
-Indexes:
-- (device, is_open) - for fast open day queries
-```
-
-**Purpose:** Represents a fiscal day (accounting period). Each device has one open fiscal day at a time.
-
-### FiscalCounter
-```python
-Fields:
-- device: ForeignKey → Device
-- fiscal_day: ForeignKey → FiscalDay
-- fiscal_counter_type: CharField
- - SaleByTax, SaleTaxByTax
- - CreditNoteByTax, CreditNoteTaxByTax
- - DebitNoteByTax, DebitNoteTaxByTax
- - BalanceByMoneyType, Other
-- fiscal_counter_currency: CharField (USD, ZWG)
-- fiscal_counter_tax_percent: Decimal (optional)
-- fiscal_counter_tax_id: int (optional)
-- fiscal_counter_money_type: CharField (Cash, Card, BankTransfer, MobileMoney)
-- fiscal_counter_value: Decimal (accumulated counter value)
-- created_at/updated_at: datetime
-
-Constraints:
-- Indexed: (device, fiscal_day)
-
-Relationships:
-- device (ForeignKey) → Device
-- fiscal_day (ForeignKey) → FiscalDay
-```
-
-**Purpose:** Accumulates receipt values by type, currency, and tax. Updated on receipt submission. Used to close fiscal day.
-
-### Receipt
-```python
-Fields:
-- device: ForeignKey → Device (FIXED: was missing, added in v0.1.6)
-- receipt_number: str (unique, auto-generated as R-{global_number:08d})
-- receipt_type: str
- - fiscalinvoice (normal receipt)
- - creditnote (debit customer)
- - debitnote (credit customer, not mandatory)
-- total_amount: Decimal (12 digits, 2 decimals)
-- currency: str (USD or ZWG)
-- qr_code: ImageField (PNG, uploaded to Zimra_qr_codes/)
-- code: str (verification code, extracted from signature)
-- global_number: int (ZIMRA global receipt number)
-- hash_value: str (SHA-256 hash)
-- signature: TextField (RSA signature, base64)
-- zimra_inv_id: str (ZIMRA internal receipt ID)
-- buyer: ForeignKey → Buyer (optional)
-- payment_terms: str (Cash, Card, BankTransfer, MobileWallet, Coupon, Credit, Other)
-- submitted: bool (whether sent to ZIMRA)
-- is_credit_note: bool
-- credit_note_reason: str (optional)
-- credit_note_reference: str (receipt_number of original receipt)
-- created_at/updated_at: datetime
-
-Constraints:
-- receipt_number: unique
-
-Relationships:
-- device (ForeignKey) → Device
-- buyer (ForeignKey) → Buyer (optional)
-- lines (OneToMany) → ReceiptLine
-```
-
-**Purpose:** Core receipt entity. Stores receipt data, cryptographic material, and ZIMRA metadata.
-
-### ReceiptLine
-```python
-Fields:
-- receipt: ForeignKey → Receipt
-- product: str (max 255)
-- quantity: Decimal (10 digits, 2 decimals)
-- unit_price: Decimal (12 digits, 2 decimals)
-- line_total: Decimal (quantity × unit_price, 12 digits, 2 decimals)
-- tax_amount: Decimal (12 digits, 2 decimals)
-- tax_type: ForeignKey → Taxes (optional)
-- created_at/updated_at: datetime
-
-Constraints:
-- Indexed: (receipt)
-
-Relationships:
-- receipt (ForeignKey) → Receipt
-- tax_type (ForeignKey) → Taxes (optional)
-```
-
-**Purpose:** Line items on a receipt (products/services).
-
-### Taxes
-```python
-Fields:
-- code: str (tax code, max 10)
-- name: str (human-readable tax name, max 100)
-- tax_id: int (ZIMRA tax identifier)
-- percent: Decimal (tax rate, 5 digits, 2 decimals)
-- created_at: datetime
-
-Constraints:
-- Indexed: (tax_id)
-- Ordered by: tax_id
-
-Example rows:
-- Standard Rated 15.5%, tax_id=1, percent=15.50
-- Zero Rated 0%, tax_id=4, percent=0.00
-- Exempt 0%, tax_id=5, percent=0.00
-```
-
-**Purpose:** Tax type definitions. Auto-synced from ZIMRA on configuration init and day opening.
-
-### Buyer
-```python
-Fields:
-- name: str (max 255, registered business name)
-- address: str (max 255, optional)
-- tin_number: str (max 255, unique within buyer records)
-- trade_name: str (max 100, optional, e.g., branch name)
-- email: EmailField (optional)
-- phonenumber: str (max 20, optional)
-- created_at/updated_at: datetime
-
-Constraints:
-- Indexed: (tin_number)
-
-Relationships:
-- receipts (OneToMany) → Receipt
-```
-
-**Purpose:** Customer/buyer information. Optional on receipts (can be null for cash sales). Uses `get_or_create` to avoid duplicates by TIN.
-
-## Service Layer
-
-### ReceiptService
-
-**Location:** `fiscguy/services/receipt_service.py`
-
-**Purpose:** Validates, persists, and submits receipts to ZIMRA.
-
-**Key Method:** `create_and_submit_receipt(data: dict) → tuple[Receipt, dict]`
-
-```python
-Flow:
-1. Adds device ID to request data
-2. Validates via ReceiptCreateSerializer
-3. Persists receipt to DB (with buyer creation/linking)
-4. Fetches fully hydrated receipt (with lines, buyer)
-5. Delegates to ZIMRAReceiptHandler.process_and_submit()
-6. Returns (Receipt, submission_result)
-
-Atomicity:
-- Wrapped in @transaction.atomic
-- If submission fails: entire operation rolled back, receipt NOT saved
-- If submission succeeds: receipt marked as submitted=True
-
-Raises:
-- serializer.ValidationError: invalid payload
-- ReceiptSubmissionError: processing/FDMS submission failed
-```
-
-### OpenDayService
-
-**Location:** `fiscguy/services/open_day_service.py`
-
-**Purpose:** Opens a new fiscal day with ZIMRA and syncs taxes.
-
-**Key Method:** `open_day() → dict`
-
-```python
-Flow:
-1. Queries ZIMRA status for next day_no (lastFiscalDayNo + 1)
-2. Syncs latest taxes from ZIMRA
-3. Creates FiscalDay record (is_open=True)
-4. Returns ZIMRA response
-
-Auto-call:
-- Triggered automatically if no open day exists when submitting first receipt
-- Adds 5-second delay to allow ZIMRA processing
-```
-
-### ClosingDayService
-
-**Location:** `fiscguy/services/closing_day_service.py`
-
-**Purpose:** Closes fiscal day and sends closing hash to ZIMRA.
-
-**Key Method:** `close_day() → dict`
-
-```python
-Flow:
-1. Fetches open FiscalDay
-2. Queries ZIMRA for fiscal counters (SaleByTax, SaleTaxByTax, etc.)
-3. Fetches local receipts for the day
-4. Builds counters per ZIMRA spec (see below)
-5. Creates closing hashstring with counters
-6. Signs hashstring with RSA
-7. Sends closing request to ZIMRA
-8. Marks FiscalDay as is_open=False
-9. Saves fiscal counters to DB
-
-Counter Ordering (per spec 13.3.1):
-- Sorted by (currency ASC, taxID ASC)
-- Zero-value counters EXCLUDED
-- Format: "counter1|counter2|..."
-```
-
-### ConfigurationService
-
-**Location:** `fiscguy/services/configuration_service.py`
-
-**Purpose:** Syncs taxpayer config and taxes from ZIMRA.
-
-**Key Methods:**
-- `get_configuration()` - Fetches config from ZIMRA
-- `sync_taxes()` - Fetches and updates tax records
-- `sync_all()` - Full sync
-
-### StatusService & PingService
-
-- **StatusService** - Queries device status from ZIMRA
-- **PingService** - Tests device connectivity
-
-## Cryptography & Security
-
-### ZIMRACrypto
-
-**Location:** `fiscguy/zimra_crypto.py`
-
-**Algorithms:**
-- **Signing:** RSA-2048 with PKCS#1 v1.5 padding, SHA-256
-- **Hashing:** SHA-256
-- **Verification Code:** MD5 (from signature bytes)
-- **Encoding:** Base64
-
-**Key Methods:**
-
-#### `generate_receipt_hash_and_signature(signature_string: str) → dict`
-```python
-signature_string = "receipt|data|string|built|per|spec"
-hash_value = SHA256(signature_string) # base64 encoded
-signature = RSA_SIGN(signature_string) # base64 encoded
-
-Returns: {"hash": hash_value, "signature": signature}
-```
-
-**Critical:** The signature_string format is specified by ZIMRA spec (see `ZIMRAReceiptHandler._build_receipt_data()`).
-
-#### `sign_data(data: str) → str`
-- Signs arbitrary data with RSA private key
-- Returns base64-encoded signature
-
-#### `get_hash(data: str) → str`
-- SHA-256 hash, base64-encoded
-
-#### `generate_verification_code(base64_signature: str) → str`
-- Extracts 16-character code from signature for QR
-- Used in QR code data
-
-#### `load_private_key() → RSAPrivateKey`
-- Loads from stored certificate PEM
-- Caches result
-
-### Certificate Management
-
-**Location:** `fiscguy/utils/cert_temp_manager.py`
-
-**Purpose:** Manages temporary PEM files for ZIMRA HTTPS authentication.
-
-**Usage:**
-- ZIMRAClient creates temporary PEM file from certificate + key
-- Session uses cert for mutual TLS authentication
-- Cleanup on object destruction
-
-## ZIMRA Integration
-
-### ZIMRAClient
-
-**Location:** `fiscguy/zimra_base.py`
-
-**Purpose:** HTTP client for ZIMRA FDMS API.
-
-**Endpoints:**
-- **Device API** (requires cert): `https://fdmsapi[test].zimra.co.zw/Device/v1/{device_id}/...`
-- **Public API** (no cert): `https://fdmsapi[test].zimra.co.zw/Public/v1/{device_id}/...`
-
-**Environment Detection:**
-- If `Certs.production=True`: uses production URL
-- Else: uses test URL
-
-**Key Methods:**
-- `register_device(payload)` - Register device (public endpoint, no cert)
-- `get_status()` - Query device status (device endpoint)
-- `submit_receipt(payload)` - Submit receipt to ZIMRA
-- `open_fiscal_day(payload)` - Open fiscal day
-- `close_fiscal_day(payload)` - Close fiscal day
-
-**Session Management:**
-- Persistent `requests.Session` with cert authentication
-- Headers include device model name/version
-- Timeout: 30 seconds
-
-### ZIMRA API Payloads
-
-#### Receipt Submission
-
-```json
-{
- "receiptNumber": "R-00000001",
- "receiptType": "F", // F=invoice, C=credit note, D=debit note
- "receiptTotal": 100.00,
- "receiptCurrency": "USD",
- "receiptGlobalNo": 1,
- "receiptDateTime": "2026-04-01T10:30:00Z",
- "receiptDescription": "...",
- "buyerTIN": "1234567890", // optional
- "paymentMethod": "Cash",
- "receiptLineItems": [
- {
- "itemNumber": 1,
- "itemDescription": "Product",
- "itemQuantity": 1.00,
- "itemUnitPrice": 100.00,
- "itemTaxType": "Standard Rated",
- "itemTaxAmount": 15.50
- }
- ],
- "hash": "base64-encoded-sha256",
- "signature": "base64-encoded-rsa-signature"
-}
-```
-
-#### Fiscal Day Close
-
-```json
-{
- "hash": "base64-encoded-hashstring",
- "signature": "base64-encoded-rsa-signature",
- "counters": [
- {
- "counterType": "SaleByTax",
- "counterCurrency": "USD",
- "counterTaxType": "Standard Rated",
- "counterTaxId": 1,
- "counterValue": 1000.00
- },
- ...
- ]
-}
-```
-
-## Receipt Processing Pipeline
-
-### Complete Flow
-
-```
-POST /api/receipts/
- ↓
-ReceiptView.post()
- ↓
-ReceiptService.create_and_submit_receipt()
- ├─ ReceiptCreateSerializer.validate()
- │ ├─ Validate credit note (if applicable)
- │ └─ Validate TIN (if buyer provided)
- ├─ ReceiptCreateSerializer.create()
- │ ├─ Get or create Buyer (from buyer_data)
- │ ├─ Create Receipt (device + lines)
- │ └─ Create ReceiptLine items
- │
- ├─ ZIMRAReceiptHandler.process_and_submit()
- │ ├─ _ensure_fiscal_day_open()
- │ │ ├─ Check if FiscalDay open
- │ │ └─ If not: auto-call OpenDayService.open_day()
- │ │
- │ ├─ _build_receipt_data()
- │ │ └─ Construct signature_string per ZIMRA spec
- │ │
- │ ├─ ZIMRACrypto.generate_receipt_hash_and_signature()
- │ │ ├─ SHA256 hash
- │ │ └─ RSA sign
- │ │
- │ ├─ _generate_qr_code()
- │ │ ├─ Extract verification code from signature
- │ │ ├─ Create QR PNG image
- │ │ └─ Save to Receipt.qr_code
- │ │
- │ ├─ _update_fiscal_counters()
- │ │ └─ Increment FiscalCounter values
- │ │
- │ └─ _submit_to_fdms()
- │ ├─ POST receipt to ZIMRA
- │ ├─ Parse response
- │ └─ Return submission_result
- │
- └─ Update Receipt (hash, signature, global_no, zimra_inv_id, submitted=True)
- └─ Save to DB
-
-Returns: Response(ReceiptSerializer(receipt), 201)
-```
-
-### Atomic Transaction
-
-The entire flow (ReceiptService.create_and_submit_receipt) is wrapped in `@transaction.atomic`:
-
-```python
-@transaction.atomic
-def create_and_submit_receipt(self, data: dict):
- # All or nothing
- # If step N fails → all changes rolled back
-```
-
-### Automatic Fiscal Day Opening
-
-If no open fiscal day exists:
-1. OpenDayService auto-opens one
-2. 5-second delay for ZIMRA processing
-3. Continues with receipt submission
-
-## Fiscal Day Management
-
-### Fiscal Day Lifecycle
-
-```
-State: is_open=False
- ↓ [POST /open-day/]
-State: is_open=True, receipts can be submitted
- ↓ [receipts submitted, counters accumulated]
- ↓ [POST /close-day/]
-State: is_open=False, counters reset
-```
-
-### Fiscal Counter Update
-
-On receipt submission:
-
-```python
-for each line_item in receipt.lines:
- for each tax_type on line:
- counter_type = f"SaleByTax" or "SaleTaxByTax" or "CreditNoteByTax" etc.
- counter = FiscalCounter.objects.filter(
- fiscal_day=fiscal_day,
- fiscal_counter_type=counter_type,
- fiscal_counter_currency=receipt.currency,
- fiscal_counter_tax_id=line.tax_type.tax_id
- )
- counter.fiscal_counter_value += line.amount_with_tax
- counter.save()
-```
-
-**Raw-level DB Lock:**
-To prevent race conditions, counter updates use F() for row-level locking:
-
-```python
-FiscalCounter.objects.filter(...).update(
- fiscal_counter_value=F('fiscal_counter_value') + amount
-)
-```
-
-## Error Handling
-
-### Exception Hierarchy
-
-```
-FiscalisationError (base)
-├── CertNotFoundError
-├── CryptoError
-├── DeviceNotFoundError
-├── ConfigurationError
-├── TaxError
-├── FiscalDayError
-├── ReceiptSubmissionError
-├── StatusError
-├── ZIMRAAPIError
-├── DeviceRegistrationError
-└── ... others
-```
-
-### Receipt Submission Error Handling
-
-**Flow:**
-
-```python
-try:
- receipt, submission_res = ReceiptService(device).create_and_submit_receipt(data)
- return Response(serializer.data, 201)
-except ReceiptSubmissionError as exc:
- # Entire transaction rolled back
- return Response({"error": str(exc)}, 422)
-except Exception:
- return Response({"error": "Unexpected error"}, 500)
-```
-
-**Key:** If ReceiptSubmissionError is raised, @transaction.atomic ensures the receipt is NOT saved.
-
-### Validation Errors
-
-**ReceiptCreateSerializer.validate():**
-- Credit note validation
-- TIN validation (10 digits)
-- Receipt reference validation
-- Amount sign validation (credit notes must be negative)
-
-## Database Design
-
-### Indexes
-
-```python
-Device:
- - device_id (UNIQUE)
-
-FiscalDay:
- - (device, day_no) UNIQUE
- - (device, is_open)
-
-FiscalCounter:
- - (device, fiscal_day)
-
-Receipt:
- - receipt_number (UNIQUE)
- - (device, -created_at)
-
-ReceiptLine:
- - (receipt)
-
-Taxes:
- - tax_id
-
-Buyer:
- - tin_number
-```
-
-### Relationships
-
-```
-Device (1)
- ├─ Configuration (0..1)
- ├─ Certs (0..1)
- ├─ FiscalDay (0..*)
- ├─ FiscalCounter (0..*)
- └─ Receipt (0..*)
- └─ ReceiptLine (1..*)
- └─ Taxes (0..1)
- └─ Buyer (0..1)
-```
-
-## Development Guidelines
-
-### Adding New Features
-
-1. **Model Changes**
- - Update `models.py`
- - Create migration: `python manage.py makemigrations fiscguy`
- - Document in ARCHITECTURE.md
-
-2. **API Endpoints**
- - Create view in `views.py`
- - Add to `urls.py`
- - Create serializer in `serializers.py`
- - Add tests in `tests/`
-
-3. **Business Logic**
- - Implement in `services/`
- - Keep views thin (just HTTP handling)
- - Use serializers for validation
-
-4. **ZIMRA Integration**
- - Extend `ZIMRAClient` for new endpoints
- - Handle API responses in services
- - Add error handling
-
-### Testing
-
-**Run all tests:**
-```bash
-pytest
-```
-
-**Coverage:**
-```bash
-pytest --cov=fiscguy
-```
-
-**Specific test:**
-```bash
-pytest fiscguy/tests/test_receipt_service.py
-```
-
-### Code Quality
-
-**Linting:**
-```bash
-flake8 fiscguy/
-```
-
-**Type checking:**
-```bash
-mypy fiscguy/
-```
-
-**Code formatting:**
-```bash
-black fiscguy/
-```
-
-### Atomic Transactions
-
-**Always wrap** state-changing operations (receipt creation, day opening/closing) in `@transaction.atomic`:
-
-```python
-@transaction.atomic
-def my_state_changing_operation(self):
- # All-or-nothing
- pass
-```
-
-### Logging
-
-Use `loguru` for structured logging:
-
-```python
-from loguru import logger
-
-logger.info(f"Receipt {receipt.id} submitted")
-logger.warning(f"FDMS offline, using provisional number")
-logger.error(f"Failed to sign receipt: {e}")
-logger.exception(f"Unexpected error") # includes traceback
-```
-
-### Private Methods
-
-Prefix with `_` (e.g., `_build_receipt_data()`, `_ensure_fiscal_day_open()`). Public methods (called from views/tests) have no prefix.
-
-### Model Meta Options
-
-- Always define `ordering` (for consistent query results)
-- Use `indexes` for frequently-filtered fields
-- Use `unique_together` for composite unique constraints
-- Document in docstring
-
-### Serializer Best Practices
-
-- Separate read and write serializers (ReceiptSerializer vs ReceiptCreateSerializer)
-- Mark read-only fields: `read_only_fields = [...]`
-- Implement `validate()` for cross-field validation
-- Use `transaction.atomic` in `create()` for complex nested creates
-
-### Configuration Management
-
-- Store ZIMRA environment URLs in `Configuration.url`
-- Certificate environment (test vs production) in `Certs.production`
-- Sync config on device init and day opening
-- Use `get_or_create` to avoid duplicates
-
----
-
-**Last Updated:** April 2026
-**Version:** 0.1.6
-**Maintainers:** Casper Moyo (@cassymyo)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4a9a0fa..b7241f7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,7 +18,7 @@ Be respectful, inclusive, and professional in all interactions.
```bash
# Clone the repository
-git clone https://github.com/cassymyo-spec/zimra.git
+git clone https://github.com/digtaltouchcode/fisc.git
cd zimra /. to change to fiscguy
# Create virtual environment
diff --git a/DOCS_INDEX.md b/DOCS_INDEX.md
deleted file mode 100644
index 3bd5f28..0000000
--- a/DOCS_INDEX.md
+++ /dev/null
@@ -1,184 +0,0 @@
-# FiscGuy Documentation Index
-
-Welcome to FiscGuy documentation! This guide helps you navigate the different documentation files based on your role and needs.
-
-## 📚 Documentation Files Overview
-
-### For New Users & Integration
-
-**Start here if you're:**
-- Installing FiscGuy for the first time
-- Integrating FiscGuy into your Django project
-- Building a client application
-- Looking for API examples
-
-**Read:**
-1. **[USER_GUIDE.md](USER_GUIDE.md)** (15K) - Complete user guide with:
- - Installation steps
- - Quick start (5-minute setup)
- - Full API endpoint reference
- - 4 practical usage examples
- - Concepts & terminology
- - 30+ FAQs and troubleshooting
-
-2. **[README.md](README.md)** (9K) - Project overview with:
- - Feature highlights
- - Installation options
- - Environment switching guide
- - Basic setup instructions
-
----
-
-### For Developers & Maintainers
-
-**Read if you're:**
-- Contributing to FiscGuy
-- Understanding internal architecture
-- Adding new features
-- Debugging issues
-- Designing integrations
-
-**Read:**
-1. **[ARCHITECTURE.md](ARCHITECTURE.md)** (24K) - Technical deep dive covering:
- - Layered architecture (REST → Services → Models → ZIMRA)
- - Complete data model documentation
- - Service layer details
- - Cryptographic operations
- - ZIMRA FDMS integration
- - Receipt processing pipeline
- - Fiscal day management
- - Database design & indexes
- - Development guidelines
-
-2. **[CONTRIBUTING.md](CONTRIBUTING.md)** (6K) - Contribution guidelines with:
- - Code style requirements (Black, isort, flake8)
- - Test requirements
- - PR process
- - Issue reporting
-
----
-
-### For Integration & DevOps
-
-**Read if you're:**
-- Deploying FiscGuy to production
-- Setting up ZIMRA environment
-- Managing certificates
-- Configuring Django settings
-
-**Read:**
-1. **[INSTALL.md](INSTALL.md)** (6K) - Detailed installation guide
-2. **[USER_GUIDE.md](USER_GUIDE.md)** - Quick Start section (Step 1-5)
-3. **[ARCHITECTURE.md](ARCHITECTURE.md)** - Deployment considerations section
-
----
-
-### API Reference
-
-**For API endpoint details, request/response examples, and error codes:**
-
-**Read:**
-1. **[USER_GUIDE.md](USER_GUIDE.md#api-endpoints)** - Quick API reference with curl examples
-2. **[endpoints.md](endpoints.md)** - Detailed API specification
-3. **[ARCHITECTURE.md](ARCHITECTURE.md#zimra-integration)** - ZIMRA payload specifications
-
----
-
-## Quick Navigation
-
-| Need | Document | Section |
-|------|----------|---------|
-| Install FiscGuy | [USER_GUIDE.md](USER_GUIDE.md#installation) | Installation |
-| Setup project | [USER_GUIDE.md](USER_GUIDE.md#quick-start) | Quick Start |
-| API examples | [USER_GUIDE.md](USER_GUIDE.md#usage-examples) | Usage Examples |
-| Troubleshoot | [USER_GUIDE.md](USER_GUIDE.md#troubleshooting) | Troubleshooting |
-| Answer a question | [USER_GUIDE.md](USER_GUIDE.md#faq) | FAQ |
-| Understand architecture | [ARCHITECTURE.md](ARCHITECTURE.md#architecture) | Architecture |
-| Data models | [ARCHITECTURE.md](ARCHITECTURE.md#data-models) | Data Models |
-| Services | [ARCHITECTURE.md](ARCHITECTURE.md#service-layer) | Service Layer |
-| Cryptography | [ARCHITECTURE.md](ARCHITECTURE.md#cryptography--security) | Cryptography |
-| ZIMRA API | [ARCHITECTURE.md](ARCHITECTURE.md#zimra-integration) | ZIMRA Integration |
-| Receipt flow | [ARCHITECTURE.md](ARCHITECTURE.md#receipt-processing-pipeline) | Receipt Pipeline |
-| Dev guidelines | [ARCHITECTURE.md](ARCHITECTURE.md#development-guidelines) | Dev Guidelines |
-| Contribute | [CONTRIBUTING.md](CONTRIBUTING.md) | All |
-
----
-
-## Documentation Philosophy
-
-**FiscGuy documentation is organized by audience:**
-
-1. **[USER_GUIDE.md](USER_GUIDE.md)** - Practical, example-driven, task-focused
- - How do I...?
- - Why does this happen?
- - What does this mean?
-
-2. **[ARCHITECTURE.md](ARCHITECTURE.md)** - Technical, comprehensive, reference-style
- - How does this work?
- - What are the relationships?
- - What are the constraints?
-
-3. **[CONTRIBUTING.md](CONTRIBUTING.md)** - Process-focused, standards-based
- - How do I contribute?
- - What are the standards?
-
-4. **[endpoints.md](endpoints.md)** - Specification-style
- - What are all the endpoints?
- - What are request/response formats?
-
----
-
-## Version Information
-
-- **Current Version:** 0.1.6 (unreleased)
-- **Python:** 3.11, 3.12, 3.13
-- **Django:** 4.2+
-- **DRF:** 3.14+
-- **Last Updated:** April 1, 2026
-
----
-
-## Getting Help
-
-| Question Type | Where to Look |
-|---------------|---------------|
-| "How do I...?" | [USER_GUIDE.md](USER_GUIDE.md) |
-| "Why isn't it working?" | [USER_GUIDE.md#troubleshooting](USER_GUIDE.md#troubleshooting) |
-| "I have a question" | [USER_GUIDE.md#faq](USER_GUIDE.md#faq) |
-| "How does it work?" | [ARCHITECTURE.md](ARCHITECTURE.md) |
-| "I want to contribute" | [CONTRIBUTING.md](CONTRIBUTING.md) |
-| "I need API details" | [endpoints.md](endpoints.md) |
-| "Issues/bugs" | https://github.com/digitaltouchcode/fisc/issues |
-| "Email support" | cassymyo@gmail.com |
-
----
-
-## Documentation Standards
-
-All FiscGuy documentation:
-- ✅ Uses Markdown with proper formatting
-- ✅ Includes table of contents for long documents
-- ✅ Provides practical examples
-- ✅ Follows clear structure (concept → details → examples)
-- ✅ Includes appropriate diagrams/flowcharts
-- ✅ Is kept in sync with code changes
-- ✅ Is reviewed in pull requests
-
----
-
-## Recent Documentation Updates
-
-**Version 0.1.6:**
-- Added ARCHITECTURE.md (comprehensive internal documentation)
-- Added USER_GUIDE.md (comprehensive user documentation)
-- Updated CHANGELOG.md with device field fix
-- Added device field to ReceiptCreateSerializer
-
-See [CHANGELOG.md](CHANGELOG.md) for full version history.
-
----
-
-**Happy coding! 🚀**
-
-For quick help, start with [USER_GUIDE.md](USER_GUIDE.md).
-For technical depth, see [ARCHITECTURE.md](ARCHITECTURE.md).
diff --git a/DOCUMENTATION_SUMMARY.md b/DOCUMENTATION_SUMMARY.md
deleted file mode 100644
index d25e8c9..0000000
--- a/DOCUMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,359 +0,0 @@
-# Documentation Project Summary
-
-## Overview
-
-Comprehensive documentation for FiscGuy has been created to serve both general users and internal engineering teams. This includes **1,768 lines** of new documentation across 3 files.
-
----
-
-## Files Created
-
-### 1. ARCHITECTURE.md (859 lines)
-**Internal Engineering Documentation**
-
-**Purpose:** Technical reference for developers and maintainers
-
-**Contents:**
-- Project overview and technology stack
-- Layered architecture with diagrams and layer responsibilities
-- Complete data model documentation:
- - Device, Configuration, Certs models
- - FiscalDay, FiscalCounter tracking
- - Receipt, ReceiptLine, Taxes, Buyer models
- - Relationships, constraints, and indexes
-- Service layer details:
- - ReceiptService (validation, persistence, submission)
- - OpenDayService (fiscal day opening)
- - ClosingDayService (counter aggregation, day closure)
- - ConfigurationService, StatusService, PingService
-- Cryptography & security:
- - RSA-2048 signing with SHA-256
- - Hash generation and verification codes
- - Certificate management
-- ZIMRA integration:
- - ZIMRAClient HTTP layer
- - API endpoints (device vs. public)
- - Example API payloads (receipt, fiscal day close)
-- Receipt processing pipeline:
- - Step-by-step flow with atomic transactions
- - Automatic fiscal day opening
-- Fiscal day management:
- - Lifecycle and state transitions
- - Counter updates with F() locking
-- Database design:
- - Index strategy
- - Relationship diagram
-- Development guidelines:
- - Feature addition checklist
- - Testing guidelines
- - Code quality standards
- - Atomic transaction patterns
- - Logging best practices
-
-**Best for:** Developers adding features, understanding internals, code reviews
-
----
-
-### 2. USER_GUIDE.md (725 lines)
-**General User & Integration Guide**
-
-**Purpose:** Practical documentation for users and integrators
-
-**Contents:**
-- Feature overview (8 key features)
-- Installation (PyPI, from source, requirements)
-- Quick start guide (5 steps to working system):
- - Add to Django settings
- - Run migrations
- - Include URLs
- - Register device
- - Make first request
-- API endpoints reference:
- - Receipt management (create, list, detail)
- - Fiscal day management (open, close)
- - Device management (status, config, sync)
- - Taxes listing
- - Buyer CRUD
- - Full curl examples for each
-- Usage examples:
- - Simple cash receipt
- - Receipt with buyer details
- - Credit note (refund)
- - Django code integration
-- Concepts & terminology:
- - Fiscal devices
- - Fiscal days
- - Receipt types (invoice, credit note, debit note)
- - Receipt counters
- - Payment methods
- - Tax types
-- Troubleshooting guide:
- - 10+ common issues with solutions
- - ZIMRA offline handling
- - Missing configuration
- - Invalid TIN format
- - Timeout issues
- - Device registration
-- FAQ section:
- - 15+ frequently asked questions
- - Fiscal day automation
- - Multiple devices
- - ZIMRA offline behavior
- - Credit note creation
- - Multi-currency
- - QR code storage
- - Transaction IDs
- - And more...
-
-**Best for:** Users installing FiscGuy, integrating into projects, API consumers, troubleshooting
-
----
-
-### 3. DOCS_INDEX.md (184 lines)
-**Documentation Navigation & Index**
-
-**Purpose:** Guide users to correct documentation
-
-**Contents:**
-- Documentation overview by audience:
- - New users & integration
- - Developers & maintainers
- - Integration & DevOps
- - API reference
-- Quick navigation table
-- Documentation philosophy
-- Getting help guide
-- Version information
-- Recent updates
-
-**Best for:** First-time visitors, finding right documentation, reference
-
----
-
-## Updated Files
-
-### CHANGELOG.md
-Updated to document:
-1. New documentation files (ARCHITECTURE.md, USER_GUIDE.md)
-2. Device field fix in ReceiptCreateSerializer
-3. Summary of documentation content
-
----
-
-## Documentation Statistics
-
-| Metric | Value |
-|--------|-------|
-| Total lines | 1,768 |
-| Files created | 3 |
-| Files updated | 1 (CHANGELOG.md) |
-| Diagrams/flowcharts | 3 |
-| Tables | 10+ |
-| Code examples | 20+ |
-| API endpoint examples | 8 |
-| FAQ entries | 15+ |
-| Troubleshooting entries | 10+ |
-
----
-
-## Documentation Organization
-
-```
-FiscGuy Documentation Structure:
-
-DOCS_INDEX.md (START HERE)
- ├─ For Users → USER_GUIDE.md
- │ ├─ Installation
- │ ├─ Quick Start
- │ ├─ API Reference
- │ ├─ Usage Examples
- │ ├─ Troubleshooting
- │ └─ FAQ
- │
- ├─ For Developers → ARCHITECTURE.md
- │ ├─ Architecture
- │ ├─ Data Models
- │ ├─ Services
- │ ├─ Cryptography
- │ ├─ ZIMRA Integration
- │ ├─ Pipelines
- │ └─ Dev Guidelines
- │
- ├─ For Contributors → CONTRIBUTING.md
- │ ├─ Code Standards
- │ ├─ Testing
- │ └─ PR Process
- │
- └─ For API Details → endpoints.md
- ├─ All endpoints
- ├─ Request/response
- └─ Error codes
-```
-
----
-
-## Key Highlights
-
-### ARCHITECTURE.md Highlights
-- ✅ Complete data model reference (9 models, all relationships documented)
-- ✅ Service layer architecture with method signatures
-- ✅ Cryptographic operations explained (RSA, SHA-256, MD5)
-- ✅ Receipt processing pipeline with flow diagram
-- ✅ Fiscal counter management and ordering (per spec 13.3.1)
-- ✅ Atomic transaction patterns for consistency
-- ✅ Development checklist for new features
-- ✅ 20+ code examples and snippets
-
-### USER_GUIDE.md Highlights
-- ✅ 5-minute quick start guide
-- ✅ 8 complete API endpoint examples with curl
-- ✅ 4 real-world usage examples (cash, buyer, credit note, Django)
-- ✅ Comprehensive troubleshooting (10+ issues with solutions)
-- ✅ 15+ FAQ entries covering common questions
-- ✅ Clear concept explanations for new users
-- ✅ Links to detailed technical docs when needed
-
-### DOCS_INDEX.md Highlights
-- ✅ Audience-based navigation
-- ✅ Quick reference table
-- ✅ Getting help guide
-- ✅ Documentation philosophy
-- ✅ Single source of truth for doc locations
-
----
-
-## Content Quality
-
-**All documentation:**
-- ✅ Uses clear, professional language
-- ✅ Includes practical examples
-- ✅ Follows Markdown best practices
-- ✅ Has proper structure (TOC, sections, subsections)
-- ✅ Contains relevant diagrams/tables
-- ✅ Cross-references related documents
-- ✅ Accurate to codebase (reflects v0.1.6 state)
-- ✅ Formatted for easy reading
-- ✅ Optimized for search and discoverability
-
----
-
-## How Users Should Navigate
-
-### First Time User
-1. Read DOCS_INDEX.md (2 min)
-2. Read USER_GUIDE.md#quick-start (5 min)
-3. Run `python manage.py init_device` (2-3 min)
-4. Try API endpoint example (2 min)
-5. Reference [USER_GUIDE.md](USER_GUIDE.md) as needed
-
-### Integrating into Existing Project
-1. Read DOCS_INDEX.md (2 min)
-2. Read USER_GUIDE.md#installation (3 min)
-3. Read USER_GUIDE.md#api-endpoints (5 min)
-4. Pick usage example matching your needs
-5. Reference endpoints as needed
-
-### Contributing to FiscGuy
-1. Read DOCS_INDEX.md (2 min)
-2. Read CONTRIBUTING.md (5 min)
-3. Read ARCHITECTURE.md (20 min)
-4. Find relevant section and reference
-5. Implement changes following guidelines
-
-### Debugging Issues
-1. Check USER_GUIDE.md#troubleshooting (5 min)
-2. Check USER_GUIDE.md#faq (5 min)
-3. Check ARCHITECTURE.md for internals (10-30 min)
-4. Check GitHub issues
-5. Contact cassymyo@gmail.com
-
----
-
-## Related Existing Documentation
-
-These files were already present and complement the new documentation:
-
-- **README.md** - Project overview (kept as is)
-- **INSTALL.md** - Installation details
-- **CONTRIBUTING.md** - Contribution guidelines
-- **endpoints.md** - Detailed API specification
-- **CHANGELOG.md** - Version history (updated)
-
----
-
-## Coverage Analysis
-
-| Topic | Coverage | Document |
-|-------|----------|----------|
-| Installation | Complete | USER_GUIDE.md, INSTALL.md |
-| API Reference | Complete | endpoints.md, USER_GUIDE.md |
-| Architecture | Complete | ARCHITECTURE.md |
-| Data Models | Complete | ARCHITECTURE.md |
-| Services | Complete | ARCHITECTURE.md |
-| Cryptography | Complete | ARCHITECTURE.md |
-| ZIMRA Integration | Complete | ARCHITECTURE.md |
-| Examples | Complete | USER_GUIDE.md |
-| Troubleshooting | Complete | USER_GUIDE.md |
-| FAQ | Complete | USER_GUIDE.md |
-| Contributing | Complete | CONTRIBUTING.md |
-| Development | Complete | ARCHITECTURE.md |
-
----
-
-## Maintenance & Updates
-
-**Documentation should be updated when:**
-- New models are added (update ARCHITECTURE.md)
-- New API endpoints are created (update endpoints.md, USER_GUIDE.md)
-- Service logic changes (update ARCHITECTURE.md)
-- New features are added (update CHANGELOG.md, relevant docs)
-- Common issues emerge (update USER_GUIDE.md#troubleshooting)
-- FAQ questions are received (update USER_GUIDE.md#faq)
-
-**Review process:**
-- PR author updates documentation
-- Reviewers check accuracy
-- Merge only after doc review passes
-
----
-
-## Success Metrics
-
-✅ **User Onboarding:** 5-minute quick start available
-✅ **Developer Guidance:** Complete architecture reference exists
-✅ **API Clarity:** All endpoints documented with examples
-✅ **Problem Solving:** Troubleshooting covers 10+ scenarios
-✅ **Knowledge Base:** FAQ answers 15+ questions
-✅ **Navigation:** Single index for all documentation
-✅ **Maintenance:** Clear update guidelines
-✅ **Quality:** Professional, well-structured content
-
----
-
-## Files Summary
-
-| File | Lines | Purpose | Audience |
-|------|-------|---------|----------|
-| ARCHITECTURE.md | 859 | Technical reference | Developers |
-| USER_GUIDE.md | 725 | User guide & examples | Users/Integrators |
-| DOCS_INDEX.md | 184 | Navigation & index | Everyone |
-| **Total** | **1,768** | Complete documentation | All |
-
----
-
-## Conclusion
-
-FiscGuy now has **comprehensive, professional documentation** serving all audiences:
-
-- **Users** can quickly get started with clear examples and troubleshooting
-- **Developers** have detailed architecture and implementation reference
-- **Contributors** understand guidelines and patterns
-- **Everyone** can easily find relevant information
-
-The documentation is **maintainable, cross-referenced, and aligned** with current code (v0.1.6).
-
----
-
-**Documentation Created:** April 1, 2026
-**Version:** 0.1.6
-**Status:** Ready for use ✅
diff --git a/INSTALL.md b/INSTALL.md
deleted file mode 100644
index 26a8565..0000000
--- a/INSTALL.md
+++ /dev/null
@@ -1,300 +0,0 @@
-# Fiscguy Package Installation & Setup Guide
-
-## About Fiscguy
-
-Fiscguy is a Python library for integrating ZIMRA fiscal devices with your Django applications. This guide helps you get started quickly.
-
-## Installation
-
-### Via pip (from PyPI)
-
-```bash
-pip install fiscguy
-```
-
-### From Source (Development)
-
-```bash
-git clone https://github.com/cassymyo-spec/zimra.git
-cd zimra
-pip install -e .
-```
-
-### With Development Dependencies
-
-```bash
-pip install -e ".[dev]" # Includes testing, linting, type checking tools
-```
-
-## Quick Setup (5 minutes)
-
-### 1. Add to Django Settings
-
-```python
-# settings.py
-INSTALLED_APPS = [
- # ...
- 'fiscguy',
- 'rest_framework',
-]
-
-# Optional: Configure fiscal operations
-FISCAL_SETTINGS = {
- 'ENVIRONMENT': 'test', # or 'production'
- 'TIMEZONE': 'Africa/Harare',
-}
-```
-
-### 2. Run Migrations
-
-```bash
-python manage.py migrate fiscguy
-```
-
-### 3. Initialize Device
-
-```bash
-python manage.py init_device
-```
-
-This interactive command will:
-- Prompt for device information
-- Generate certificate signing request (CSR)
-- Register device with ZIMRA
-- Fetch configuration and taxes
-
-### 4. Use the Library
-
-```python
-from fiscguy import open_day, submit_receipt, close_day
-
-# Open fiscal day
-open_day()
-
-# Submit a receipt
-receipt = submit_receipt({
- 'receipt_type': 'fiscalinvoice',
- 'currency': 'USD',
- 'total_amount': '100.00',
- 'payment_terms': 'cash',
- 'lines': [
- {
- 'product': 'Service',
- 'quantity': '1',
- 'unit_price': '100.00',
- 'line_total': '100.00',
- 'tax_amount': '15.50',
- 'tax_name': 'standard rated 15.5%',
- }
- ],
- 'buyer': 1, # Buyer ID
-})
-
-# Close fiscal day
-close_day()
-```
-
-## API Functions
-
-### Six Core Functions
-
-1. **`open_day()`** - Open a fiscal day
-2. **`close_day()`** - Close the current fiscal day
-3. **`submit_receipt(receipt_data)`** - Submit a receipt
-4. **`get_status()`** - Get device status
-5. **`get_configuration()`** - Get device configuration
-6. **`get_taxes()`** - Get available tax types
-
-## REST Endpoints (if using Django views)
-
-Fiscguy also provides REST API endpoints:
-
-```
-GET /fiscguy/open-day/ - Open fiscal day
-GET /fiscguy/close-day/ - Close fiscal day
-GET /fiscguy/get-status/ - Get status
-POST /fiscguy/receipts/ - Submit receipt
-GET /fiscguy/receipts/{id}/ - Get receipt
-GET /fiscguy/configuration/ - Get configuration
-GET /fiscguy/taxes/ - Get taxes
-```
-
-## Database Models
-
-Fiscguy includes these Django models:
-
-- **Device** - Fiscal device info
-- **FiscalDay** - Fiscal day records
-- **Receipt** - Receipt records
-- **ReceiptLine** - Receipt line items
-- **Taxes** - Tax types
-- **Configuration** - Device configuration
-- **Certs** - Device certificates
-- **Buyer** - Customer info
-- **FiscalCounter** - Receipt counters
-
-Access them:
-
-```python
-from fiscguy.models import Device, Receipt, Taxes
-
-device = Device.objects.first()
-receipts = Receipt.objects.all()
-taxes = Taxes.objects.all()
-```
-
-## Configuration
-
-### Environment Variables
-
-## Testing
-
-### Run Unit Tests
-
-```bash
-# All tests
-pytest
-
-# Specific test
-pytest fiscguy/tests/test_api.py::OpenDayTest
-
-# With coverage
-pytest --cov=fiscguy --cov-report=html
-```
-
-### Mock External Services
-
-Tests automatically mock ZIMRA API calls and crypto operations, so they run fast without network access.
-
-## Error Handling
-
-All API functions raise exceptions on error:
-
-```python
-from rest_framework.exceptions import ValidationError
-from fiscguy import submit_receipt
-
-try:
- receipt = submit_receipt(data)
-except ValidationError as e:
- print(f"Validation error: {e.detail}")
-except RuntimeError as e:
- print(f"Runtime error: {e}")
-```
-
-## Troubleshooting
-
-### "No Device found"
-
-```
-RuntimeError: No Device found. Please run init_device management command.
-```
-
-**Solution:** Run device initialization:
-```bash
-python manage.py init_device
-```
-
-### "Tax with name 'X' not found"
-
-```
-ValidationError: Tax with name 'X' not found
-```
-
-**Solution:** Check available taxes and use exact name:
-```python
-from fiscguy.models import Taxes
-taxes = Taxes.objects.all()
-for tax in taxes:
- print(f"{tax.name} - {tax.percent}%")
-```
-
-### Certificate Errors
-
-```
-MalformedFraming: Unable to load PEM file
-```
-
-**Solution:** Re-register device:
-```bash
-python manage.py init_device
-```
-
-### "No open fiscal day"
-
-```
-RuntimeError: No open fiscal day
-```
-
-**Solution:** Open a fiscal day first:
-```python
-from fiscguy import open_day
-open_day()
-```
-
-## Development
-
-### Setting Up Development Environment
-
-```bash
-# Clone repo
-git clone https://github.com/cassymyo-spec/zimra.git
-cd zimra
-
-# Create virtual environment
-python -m venv venv
-source venv/bin/activate
-
-# Install in editable mode with dev tools
-pip install -e ".[dev]"
-
-# Install pre-commit hooks (optional)
-pre-commit install
-```
-
-### Code Quality Checks
-
-```bash
-# Format code
-black fiscguy
-isort fiscguy
-
-# Lint
-flake8 fiscguy
-pylint fiscguy
-
-# Type checking
-mypy fiscguy
-
-# Tests
-pytest
-```
-
-## Documentation
-
-- **README.md** - Project overview and quick start
-- **CONTRIBUTING.md** - Contributing guidelines
-- **CHANGELOG.md** - Version history
-- Inline docstrings - Function documentation
-- `fiscguy/api.py` - Public API module
-
-## Next Steps
-
-1. Install fiscguy
-2. Run migrations
-3. Initialize device
-4. Submit your first receipt!
-5. Read [API Reference](README.md#api-reference)
-6. Check [Contributing Guide](CONTRIBUTING.md)
-
-## Support
-
-- Email: cassymyo@gmail.com
-- Report Issues: [GitHub Issues](https://github.com/cassymyo-spec/zimra/issues)
-- Discussions: [GitHub Discussions](https://github.com/cassymyo-spec/zimra/discussions)
-
-## License
-
-MIT License - See [LICENSE](LICENSE)
-
----
diff --git a/LICENSE b/LICENSE
index fba3e68..dd2af41 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 Casper Moyo
+Copyright (c) 2026 Fiscguy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 3eb417d..72d6e7e 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-# FiscGuy
-
+# FiscGuy
+
[](https://github.com/digitaltouchcode/fisc/actions/workflows/tests.yml?query=branch%3Arelease)
[](https://pypi.org/project/fiscguy/)
[](https://pepy.tech/project/fiscguy)
@@ -9,495 +9,410 @@
[](https://github.com/psf/black)
[](LICENSE)
----
-
**The Modern Python Library for ZIMRA Fiscal Device Integration**
-Production-ready library for integrating with ZIMRA (Zimbabwe Revenue Authority) fiscal devices. Built with Django and Django REST Framework, FiscGuy provides a simple, Pythonic API for managing fiscal operations with enterprise-grade security and reliability.
+FiscGuy gives Django applications a simple, Pythonic interface for every fiscal operation required by the Zimbabwe Revenue Authority — receipt submission, fiscal day management, certificate handling, and more. Built on Django REST Framework, it drops into any Django project in minutes.
-[Documentation](https://github.com/digitaltouchcode/fisc#documentation) • [API Reference](#api-endpoints) • [Contributing](#contributing)
-
-
+[Installation](#installation) • [Quick Start](#quick-start) • [API Reference](#api-reference) • [REST Endpoints](#rest-endpoints) • [Docs](#documentation) • [Contributing](#contributing)
---
-## ✨ Features
-
-🔐 **Secure Device Integration** — Certificate-based mutual TLS authentication with ZIMRA FDMS
-
-📝 **Receipt Management** — Create, sign, and submit receipts with automatic validation and cryptographic signing
-
-🗓️ **Fiscal Day Operations** — Automatic fiscal day management with intelligent counter tracking and state management
-
-⚙️ **Device Configuration** — Sync taxpayer information and tax rates directly from ZIMRA
+
-💳 **Credit & Debit Notes** — Issue refunds and adjustments per ZIMRA specifications
+## Features
-💱 **Multi-Currency Support** — Handle USD and ZWG transactions seamlessly
+- **Six core API functions** — `open_day`, `close_day`, `submit_receipt`, `get_status`, `get_configuration`, `get_taxes`
+- **Full fiscal day lifecycle** — open, manage counters, close with ZIMRA-compliant hash and signature
+- **Receipt types** — Fiscal Invoice, Credit Note, Debit Note with correct counter tracking
+- **Certificate management** — CSR generation, device registration, certificate renewal via `init_device`
+- **Multi-currency** — USD and ZWG support with per-currency counter tracking
+- **Multiple payment methods** — Cash, Card, Mobile Wallet, Bank Transfer, Coupon, Credit, Other
+- **Buyer management** — optional buyer TIN and registration data on receipts
+- **Cursor pagination** — efficient receipt listing for large datasets
+- **Typed exceptions** — every error condition has its own exception class
+- **90%+ test coverage** — mocked ZIMRA and crypto, fast CI
-📊 **QR Code Generation** — Auto-generate verification codes for receipt validation
+---
-✅ **Fully Tested** — 90%+ code coverage with 22+ comprehensive test cases
+## Requirements
-🚀 **Production Ready** — Battle-tested in live ZIMRA deployments
+- Python 3.11, 3.12, or 3.13
+- Django 4.2+
+- Django REST Framework 3.14+
-## 🚀 Installation
+---
-### PyPI
+## Installation
```bash
pip install fiscguy
```
-### From Source
+### From source
```bash
git clone https://github.com/digitaltouchcode/fisc.git
cd fisc
-pip install -e .
+pip install -e ".[dev]"
```
-### Requirements
-
-- Python 3.11+ (tested on 3.11, 3.12, 3.13)
-- Django 4.2+
-- Django REST Framework 3.14+
-
---
-## ⚡ 5-Minute Quick Start
+## Quick Start
-### 1️⃣ Add to Django Settings
+### 1. Add to Django settings
```python
# settings.py
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'rest_framework',
- 'fiscguy', # ← Add this
+ # ...
+ "fiscguy",
+ "rest_framework",
]
```
-### 2️⃣ Run Migrations
+### 2. Run migrations
```bash
-python manage.py migrate fiscguy
+python manage.py migrate
```
-### 3️⃣ Register Your Fiscal Device
+### 3. Initialise your device
```bash
python manage.py init_device
```
-This interactive command will guide you through:
-- Device information entry
-- Certificate generation & registration with ZIMRA
-- Configuration and tax synchronization
-
-> ⚠️ **Note:** Environment switching (test ↔ production) will delete all existing data in that environment and require confirmation with `YES`.
+This interactive command collects your device credentials, generates a CSR, registers the device with ZIMRA, and fetches taxes and configuration automatically.
-### 4️⃣ Include URLs
+### 4. Include URLs
```python
# urls.py
from django.urls import path, include
urlpatterns = [
- path('api/', include('fiscguy.urls')),
+ path("fiscguy/", include("fiscguy.urls")),
]
```
-### 5️⃣ Submit Your First Receipt
+### 5. Submit your first receipt
-```bash
-curl -X POST http://localhost:8000/api/receipts/ \
- -H "Content-Type: application/json" \
- -d '{
+```python
+from fiscguy import open_day, submit_receipt, close_day
+
+open_day()
+
+receipt = submit_receipt({
"receipt_type": "fiscalinvoice",
- "total_amount": "100.00",
"currency": "USD",
- "payment_terms": "cash",
- "lines": [{
- "product": "Test Item",
- "quantity": 1,
- "unit_price": "100.00",
- "line_total": "100.00",
- "tax_name": "standard rated 15.5%"
- }]
- }'
-```
+ "total_amount": "115.00",
+ "payment_terms": "Cash",
+ "lines": [
+ {
+ "product": "Consulting Service",
+ "quantity": "1",
+ "unit_price": "115.00",
+ "line_total": "115.00",
+ "tax_amount": "15.00",
+ "tax_name": "standard rated 15%",
+ }
+ ],
+})
-> 💡 **Pro Tip:** First receipt automatically opens a fiscal day! No need to call `/open_day/` manually.
+close_day()
+```
---
-## 🎯 Key Concepts
-
-### Automatic Fiscal Day Opening
+## API Reference
-When you submit your first receipt without an open fiscal day, FiscGuy **automatically opens a new fiscal day** in the background:
+FiscGuy exposes six top-level functions. Import them directly from `fiscguy`:
-```
-Submit Receipt #1 → Auto-open Fiscal Day → Process Receipt → Automatic 5s ZIMRA delay
-Submit Receipt #2 → Use open Fiscal Day → Process Receipt
-...
-Call close_day() → Close Fiscal Day for the day
+```python
+from fiscguy import open_day, close_day, submit_receipt, get_status, get_configuration, get_taxes
```
-No manual management needed! Just submit receipts and FiscGuy handles the rest.
+### `open_day()`
-### Environment Switching
+Opens a new fiscal day. Syncs the `fiscalDayNo` from FDMS and fetches the latest configuration and taxes.
-When switching between test and production environments:
+```python
+from fiscguy import open_day
+
+result = open_day()
+# {"fiscalDayNo": 42, "fiscalDayOpened": "2026-03-30T08:00:00"}
+```
-| Scenario | Safe? | Action |
-|----------|-------|--------|
-| **Test → Production** | ✅ Yes | Confirm deletion of test data |
-| **Production → Test** | ⚠️ No | Only if you're certain about losing production data |
+**Raises:** `FiscalDayError` if a day is already open or FDMS rejects the request.
---
-## 📚 Usage Examples
+### `submit_receipt(receipt_data)`
-### Example 1: Simple Receipt
+Validates, signs, and submits a receipt to ZIMRA FDMS. Increments fiscal counters. If FDMS is offline, the receipt is saved locally and queued for automatic sync.
```python
-from fiscguy.models import Device
-from rest_framework.test import APIClient
-
-device = Device.objects.first()
-client = APIClient()
-
-response = client.post('/api/receipts/', {
- 'receipt_type': 'fiscalinvoice',
- 'total_amount': '150.00',
- 'currency': 'USD',
- 'payment_terms': 'Cash',
- 'lines': [
+from fiscguy import submit_receipt
+
+receipt = submit_receipt({
+ "receipt_type": "fiscalinvoice", # fiscalinvoice | creditnote | debitnote
+ "currency": "USD", # USD | ZWG
+ "total_amount": "115.00",
+ "payment_terms": "Cash", # Cash | Card | MobileWallet | BankTransfer | Coupon | Credit | Other
+ "lines": [
{
- 'product': 'Bread',
- 'quantity': 2,
- 'unit_price': '50.00',
- 'line_total': '100.00',
- 'tax_name': 'standard rated 15.5%'
+ "product": "Item name",
+ "quantity": "2",
+ "unit_price": "57.50",
+ "line_total": "115.00",
+ "tax_amount": "15.00",
+ "tax_name": "standard rated 15%",
}
- ]
+ ],
+ # Optional
+ "buyer": 1, # Buyer model ID
+ "credit_note_reason": "...", # Required for creditnote
+ "credit_note_reference": "R-...", # Required for creditnote — original receipt number
})
-
-print(response.data['receipt_number']) # R-00000001
-print(response.data['zimra_inv_id']) # ZIM-123456
```
-### Example 2: Credit Note (Refund)
+**Returns:** Serialized receipt data including `receipt_number`, `qr_code`, `hash_value`, and `zimra_inv_id`.
-```python
-response = client.post('/api/receipts/', {
- 'receipt_type': 'creditnote',
- 'credit_note_reference': 'R-00000001', # Original receipt
- 'credit_note_reason': 'customer_return',
- 'total_amount': '-50.00',
- 'currency': 'USD',
- 'payment_terms': 'Cash',
- 'lines': [
- {
- 'product': 'Bread (Returned)',
- 'quantity': 1,
- 'unit_price': '-50.00',
- 'line_total': '-50.00',
- 'tax_name': 'standard rated 15.5%'
- }
- ]
-})
-```
+**Raises:** `ReceiptSubmissionError` on any processing or FDMS failure.
-### Example 3: Receipt with Buyer Information
+---
-```python
-response = client.post('/api/receipts/', {
- 'receipt_type': 'fiscalinvoice',
- 'total_amount': '500.00',
- 'currency': 'USD',
- 'payment_terms': 'BankTransfer',
- 'buyer': {
- 'name': 'Tech Solutions Ltd',
- 'tin_number': '1234567890',
- 'email': 'tech@example.com',
- 'address': '123 Tech Park'
- },
- 'lines': [
- {
- 'product': 'Software License',
- 'quantity': 1,
- 'unit_price': '500.00',
- 'line_total': '500.00',
- 'tax_name': 'standard rated 15.5%'
- }
- ]
-})
-```
+### `close_day()`
----
+Builds the fiscal day closing string, signs it with the device private key, and submits it to ZIMRA. Marks the fiscal day as closed in the database.
-## 📡 API Endpoints
+```python
+from fiscguy import close_day
-| Endpoint | Method | Description |
-|----------|--------|-------------|
-| `/api/receipts/` | `POST` | Create and submit a receipt |
-| `/api/receipts/` | `GET` | List all receipts (paginated) |
-| `/api/receipts/{id}/` | `GET` | Get receipt details |
-| `/api/open-day/` | `POST` | Open a fiscal day |
-| `/api/close-day/` | `POST` | Close the current fiscal day |
-| `/api/status/` | `GET` | Get device and fiscal status |
-| `/api/configuration/` | `GET` | Get device configuration |
-| `/api/taxes/` | `GET` | List available taxes |
-| `/api/buyer/` | `GET` | List all buyers |
-| `/api/buyer/` | `POST` | Create a buyer |
+result = close_day()
+# {"fiscalDayStatus": "FiscalDayClosed", ...}
+```
-For detailed API documentation, see [USER_GUIDE.md](USER_GUIDE.md#api-endpoints) or [endpoints.md](endpoints.md).
+**Raises:** `CloseDayError` if FDMS rejects the request (e.g. `CountersMismatch`, `BadCertificateSignature`).
---
-## 📊 Database Models
+### `get_status()`
-FiscGuy provides comprehensive Django ORM models:
+Fetches the current device and fiscal day status from FDMS.
-- **Device** — Fiscal device information and status
-- **FiscalDay** — Fiscal day records with open/close tracking
-- **FiscalCounter** — Receipt counters aggregated by type and currency
-- **Receipt** — Receipt records with automatic signing and ZIMRA tracking
-- **ReceiptLine** — Line items within receipts
-- **Taxes** — Tax type definitions synced from ZIMRA
-- **Configuration** — Device configuration and taxpayer information
-- **Certs** — Device certificates and cryptographic keys
-- **Buyer** — Buyer/customer information for receipts
+```python
+from fiscguy import get_status
-All models are fully documented in [ARCHITECTURE.md](ARCHITECTURE.md#data-models).
+status = get_status()
+# {"fiscalDayStatus": "FiscalDayOpened", "lastReceiptGlobalNo": 142, ...}
+```
---
-## ⚙️ Architecture
+### `get_configuration()`
-FiscGuy follows a clean layered architecture:
-
-```
-┌─────────────────────────────────────────────┐
-│ REST API Layer (views.py) │
-├─────────────────────────────────────────────┤
-│ Service Layer (services/) │
-├─────────────────────────────────────────────┤
-│ Data Layer (models.py, serializers.py) │
-├─────────────────────────────────────────────┤
-│ ZIMRA Integration (zimra_*.py) │
-└──────────────┬──────────────────────────────┘
- │
- ↓
- ZIMRA FDMS REST API
-```
+Returns the stored taxpayer configuration.
-**Key Design Principles:**
-- 🏗️ **Separation of Concerns** — Clear boundaries between layers
-- 🔒 **Atomic Operations** — Database transactions ensure data consistency
-- 🔐 **Cryptographic Security** — RSA-2048 signing with SHA-256 hashing
-- 📋 **ZIMRA Compliance** — Fully compliant with ZIMRA FDMS specifications
-- ✅ **Comprehensive Testing** — 90%+ code coverage with 22+ test cases
+```python
+from fiscguy import get_configuration
-For complete architecture details, see [ARCHITECTURE.md](ARCHITECTURE.md).
+config = get_configuration()
+# {"tax_payer_name": "ACME Ltd", "tin_number": "...", ...}
+```
---
-## 🧪 Testing
-
-```bash
-# Run all tests
-pytest
+### `get_taxes()`
-# Run with coverage report
-pytest --cov=fiscguy --cov-report=html
+Returns all configured tax types.
-# Run specific test
-pytest fiscguy/tests/test_api.py::SubmitReceiptTest
+```python
+from fiscguy import get_taxes
-# Run with verbose output
-pytest -v
+taxes = get_taxes()
+# [{"tax_id": 1, "name": "Exempt", "percent": "0.00"}, ...]
```
-All tests mock external ZIMRA API calls, so they run fast without network dependencies.
-
---
-## 💻 Development
+## REST Endpoints
-### Setup Development Environment
+When URLs are included, FiscGuy exposes the following endpoints:
-```bash
-# Clone repository
-git clone https://github.com/digitaltouchcode/fisc.git
-cd fisc
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `POST` | `/fiscguy/open-day/` | Open a new fiscal day |
+| `POST` | `/fiscguy/close-day/` | Close the current fiscal day |
+| `GET` | `/fiscguy/get-status/` | Get device and fiscal day status |
+| `POST` | `/fiscguy/get-ping/` | Ping FDMS to report device is online |
+| `GET` | `/fiscguy/receipts/` | List receipts (cursor paginated) |
+| `POST` | `/fiscguy/receipts/` | Submit a new receipt |
+| `GET` | `/fiscguy/receipts/{id}/` | Retrieve a receipt by ID |
+| `GET` | `/fiscguy/configuration/` | Get taxpayer configuration |
+| `POST` | `/fiscguy/sync-config/` | Manually sync configuration from FDMS |
+| `GET` | `/fiscguy/taxes/` | List all tax types |
+| `POST` | `/fiscguy/issue-certificate/` | Renew device certificate |
+| `*` | `/fiscguy/buyer/` | Buyer CRUD (ModelViewSet) |
-# Create virtual environment
-python -m venv venv
-source venv/bin/activate # On Windows: venv\Scripts\activate
+### Pagination
-# Install in development mode
-pip install -e ".[dev]"
+Receipt listing supports cursor-based pagination:
+
+```
+GET /fiscguy/receipts/?page_size=20
```
-### Code Quality
+Default page size: `10`. Maximum: `100`.
-```bash
-# Format with Black
-black fiscguy
+---
-# Sort imports with isort
-isort fiscguy
+## Error Handling
-# Lint with flake8
-flake8 fiscguy
+All operations raise typed exceptions. Import them from `fiscguy.exceptions`:
-# Type checking with mypy
-mypy fiscguy
+```python
+from fiscguy.exceptions import (
+ ReceiptSubmissionError,
+ CloseDayError,
+ FiscalDayError,
+ ConfigurationError,
+ CertificateError,
+ DevicePingError,
+ StatusError,
+)
-# All checks at once
-black fiscguy && isort fiscguy && flake8 fiscguy && mypy fiscguy
+try:
+ close_day()
+except CloseDayError as e:
+ print(f"Close day failed: {e}")
```
-### Project Structure
-
-```
-fiscguy/
-├── models.py # Django ORM models
-├── serializers.py # DRF serializers for validation
-├── views.py # REST API endpoints
-├── zimra_base.py # ZIMRA FDMS HTTP client
-├── zimra_crypto.py # Cryptographic operations
-├── zimra_receipt_handler.py # Receipt formatting & signing
-├── services/ # Business logic layer
-│ ├── receipt_service.py
-│ ├── closing_day_service.py
-│ ├── configuration_service.py
-│ └── status_service.py
-├── management/commands/ # Django management commands
-│ └── init_device.py
-└── tests/ # Unit tests (22+ test cases)
-```
+| Exception | Raised when |
+|-----------|-------------|
+| `ReceiptSubmissionError` | Receipt processing or FDMS submission fails |
+| `CloseDayError` | FDMS rejects the close day request |
+| `FiscalDayError` | Fiscal day cannot be opened or is already open |
+| `ConfigurationError` | Configuration is missing or sync fails |
+| `CertificateError` | Certificate issuance or renewal fails |
+| `DevicePingError` | Ping to FDMS fails |
+| `StatusError` | Status fetch from FDMS fails |
+| `DeviceRegistrationError` | Device registration with ZIMRA fails |
+| `CryptoError` | RSA signing or hashing fails |
---
-## 📚 Documentation
+## Models
-FiscGuy has comprehensive documentation for all audiences:
+FiscGuy adds the following tables to your database:
-| Document | For | Content |
-|----------|-----|---------|
-| **[USER_GUIDE.md](USER_GUIDE.md)** | Users & Integrators | Installation, API reference, examples, troubleshooting, FAQ |
-| **[ARCHITECTURE.md](ARCHITECTURE.md)** | Developers | Technical details, data models, service layer, cryptography |
-| **[INSTALL.md](INSTALL.md)** | DevOps & Setup | Detailed installation and configuration |
-| **[CONTRIBUTING.md](CONTRIBUTING.md)** | Contributors | Development specifications and ERP integration |
-| **[DOCS_INDEX.md](DOCS_INDEX.md)** | Everyone | Documentation navigation and quick reference |
+| Model | Description |
+|-------|-------------|
+| `Device` | Fiscal device registration details |
+| `Configuration` | Taxpayer configuration synced from FDMS |
+| `Certs` | Device certificate and private key |
+| `Taxes` | Tax types synced from FDMS on day open |
+| `FiscalDay` | Daily fiscal period with receipt counter |
+| `FiscalCounter` | Running totals per tax / payment method |
+| `Receipt` | Submitted receipts with hash, signature, QR code |
+| `ReceiptLine` | Individual line items on a receipt |
+| `Buyer` | Optional buyer registration data |
-**Start here:** [DOCS_INDEX.md](DOCS_INDEX.md) for guided navigation.
-
----
-
-## 🐛 Error Handling
+Access them directly:
```python
-from rest_framework.exceptions import ValidationError
-from fiscguy.services.receipt_service import ReceiptService
+from fiscguy.models import Device, Receipt, FiscalDay, Taxes
-try:
- service = ReceiptService()
- receipt = service.create_receipt(data)
-except ValidationError as e:
- print(f"Validation Error: {e.detail}")
-except RuntimeError as e:
- print(f"Runtime Error: {e}")
+device = Device.objects.first()
+open_days = FiscalDay.objects.filter(is_open=True)
+receipts = Receipt.objects.select_related("buyer").prefetch_related("lines")
```
-Common exceptions:
-
-- `ValidationError` — Invalid input data
-- `RuntimeError` — No device registered or fiscal day issues
-- `ZIMRAException` — ZIMRA API communication errors
-
---
-## 🤝 Contributing
-
-We welcome contributions! Here's how to get started:
+## Management Commands
-1. **Fork** the repository
-2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
-3. **Write** tests for new features
-4. **Run** code quality checks: `black . && isort . && flake8 . && mypy .`
-5. **Commit** with descriptive messages: `git commit -m "feat: add amazing feature"`
-6. **Push** to your fork and **open a PR**
+### `init_device`
-For detailed guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
+Interactive device setup — run once per device:
-### Code Standards
+```bash
+python manage.py init_device
+```
-- **Style Guide:** [PEP 8](https://pep8.org/) with [Black](https://github.com/psf/black)
-- **Imports:** Sorted with [isort](https://pycqa.github.io/isort/)
-- **Linting:** [flake8](https://flake8.pycqa.org/) and [pylint](https://pylint.readthedocs.io/)
-- **Type Checking:** [mypy](https://www.mypy-lang.org/)
-- **Test Coverage:** 90%+ required
-- **Testing Framework:** [pytest](https://pytest.org/)
+The command will:
+1. Prompt for `org_name`, `activation_key`, `device_id`, `device_model_name`, `device_model_version`, `device_serial_number`
+2. Ask whether to use production or testing FDMS
+3. Generate an RSA key pair and CSR
+4. Register the device with ZIMRA to obtain a signed certificate
+5. Fetch and persist configuration and taxes
---
-## 📄 License
+## Testing
-FiscGuy is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
+```bash
+# Run all tests
+pytest
----
+# With coverage report
+pytest --cov=fiscguy --cov-report=html
-## 🤔 FAQ
+# Run a specific test file
+pytest fiscguy/tests/test_views.py
-**Q: Do I need to open a fiscal day manually?**
-A: No! FiscGuy automatically opens a fiscal day when you submit your first receipt of the day.
+# Run a specific test
+pytest fiscguy/tests/test_closing_day_service.py::TestBuildSaleByTax
+```
-**Q: Can I use FiscGuy without Django?**
-A: FiscGuy is built for Django. If you need a standalone library, check our API layer at `fiscguy/zimra_base.py`.
+All tests mock ZIMRA API calls and crypto operations — no network access required.
-**Q: What's the difference between receipts, credit notes, and debit notes?**
-A:
-- **Receipt** — Normal sale (positive amount)
-- **Credit Note** — Refund/return (negative amount)
-- **Debit Note** — Not mandatory; rarely used
+---
-**Q: How do I handle ZIMRA being offline?**
-A: Receipts are cached locally and automatically submitted when ZIMRA comes back online.
+## Documentation
-**Q: Can I switch from test to production?**
-A: Yes! Run `python manage.py init_device` and confirm the environment switch. All test data will be deleted.
+Full documentation lives in the `docs/` folder:
-More FAQs in [USER_GUIDE.md](USER_GUIDE.md#faq).
+| Document | Description |
+|----------|-------------|
+| [`docs/installation.md`](docs/installation.md) | Detailed installation and setup guide |
+| [`docs/receipt-types.md`](docs/receipt-types.md) | Fiscal Invoice, Credit Note, Debit Note rules |
+| [`docs/fiscal-counters.md`](docs/fiscal-counters.md) | How counters work and how they are calculated |
+| [`docs/closing-day.md`](docs/closing-day.md) | Closing day hash string and signature spec |
+| [`docs/certificate-management.md`](docs/certificate-management.md) | Certificate lifecycle and renewal |
+| [`docs/error-reference.md`](docs/error-reference.md) | All exceptions and what causes them |
+| [`CHANGELOG.md`](CHANGELOG.md) | Version history |
+| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Contributing guidelines |
---
-## 💬 Support & Community
+## Contributing
-- 📧 **Email:** cassymyo@gmail.com
-- 🐛 **Issues:** [GitHub Issues](https://github.com/digitaltouchcode/fisc/issues)
-- 💬 **Discussions:** [GitHub Discussions](https://github.com/digitaltouchcode/fisc/discussions)
-- 📚 **Documentation:** [DOCS_INDEX.md](DOCS_INDEX.md)
+Contributions are welcome. Please read [`CONTRIBUTING.md`](CONTRIBUTING.md) first.
----
-
-## 🙏 Acknowledgments
+```bash
+# Set up dev environment
+git clone https://github.com/digitaltouchcode/fisc.git
+cd fisc
+pip install -e ".[dev]"
+pre-commit install
-FiscGuy is built on the excellent Django and Django REST Framework ecosystems. Special thanks to the ZIMRA Authority for the FDMS API specifications.
+# Before submitting a PR
+black fiscguy
+isort fiscguy
+flake8 fiscguy
+pytest
+```
---
-
+## License
-**Made with ❤️ by Casper Moyo**
+MIT — see [LICENSE](LICENSE).
-[⭐ Star us on GitHub](https://github.com/digitaltouchcode/fisc)
+---
+
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
deleted file mode 100644
index 949f524..0000000
--- a/USER_GUIDE.md
+++ /dev/null
@@ -1,725 +0,0 @@
-# FiscGuy User & Integration Guide
-
-**FiscGuy** is a production-ready Python library for integrating with ZIMRA (Zimbabwe Revenue Authority) fiscal devices. It simplifies fiscal operations through a clean REST API and robust business logic.
-
-**Table of Contents:**
-- [Features](#features)
-- [Installation](#installation)
-- [Quick Start](#quick-start)
-- [API Endpoints](#api-endpoints)
-- [Usage Examples](#usage-examples)
-- [Concepts](#concepts)
-- [Troubleshooting](#troubleshooting)
-- [FAQ](#faq)
-
----
-
-## Features
-
-✅ **Secure Device Integration** - Certificate-based mutual TLS with ZIMRA FDMS
-
-✅ **Receipt Management** - Create, sign, and submit receipts with multiple tax types
-
-✅ **Fiscal Day Operations** - Automatic fiscal day management with counter tracking
-
-✅ **Device Configuration** - Sync taxpayer info and tax rates from ZIMRA
-
-✅ **Credit/Debit Notes** - Issue refunds and adjustments per ZIMRA spec
-
-✅ **Multi-Currency Support** - Handle USD and ZWG transactions
-
-✅ **QR Code Generation** - Auto-generate receipt verification QR codes
-
-✅ **Fully Tested** - 90%+ code coverage, 22+ test cases
-
-✅ **Production Ready** - Used in live ZIMRA deployments
-
----
-
-## Installation
-
-### Via PyPI
-
-```bash
-pip install fiscguy
-```
-
-### From Source
-
-```bash
-git clone https://github.com/digitaltouchcode/fisc.git
-cd fisc
-pip install -e .
-```
-
-### Requirements
-
-- Python 3.11+ (tested on 3.11, 3.12, 3.13)
-- Django 4.2+
-- Django REST Framework 3.14+
-
----
-
-## Quick Start
-
-### Step 1: Add to Django Settings
-
-```python
-# settings.py
-INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'rest_framework',
- 'fiscguy', # Add this
-]
-```
-
-### Step 2: Run Migrations
-
-```bash
-python manage.py makemigrations fiscguy
-python manage.py migrate fiscguy
-```
-
-### Step 3: Include URLs
-
-```python
-# urls.py
-from django.urls import path, include
-
-urlpatterns = [
- path('api/', include('fiscguy.urls')),
-]
-```
-
-### Step 4: Register Your Device
-
-```bash
-python manage.py init_device
-```
-
-This interactive command will:
-- Collect device information (org name, device ID, model)
-- Generate and register certificates with ZIMRA
-- Sync device configuration and tax rates
-- Confirm successful registration
-
-**⚠️ Environment Switching:**
-If switching from test to production (or vice versa), the command warns you and requires confirmation to delete all test/old production data.
-
-### Step 5: Make Your First Request
-
-```bash
-curl -X GET http://localhost:8000/api/configuration/ \
- -H "Content-Type: application/json"
-```
-
-You should receive your device configuration.
-
----
-
-## API Endpoints
-
-### Receipt Management
-
-#### Create & Submit Receipt (Auto-opens day if needed)
-```
-POST /api/receipts/
-Content-Type: application/json
-
-{
- "receipt_type": "fiscalinvoice",
- "total_amount": "100.00",
- "currency": "USD",
- "payment_terms": "Cash",
- "lines": [
- {
- "product": "Product Name",
- "quantity": "1",
- "unit_price": "100.00",
- "line_total": "100.00",
- "tax_name": "standard rated 15.5%"
- }
- ]
-}
-
-Returns: 201 Created
-{
- "id": 1,
- "device": 1,
- "receipt_number": "R-00000001",
- "receipt_type": "fiscalinvoice",
- "total_amount": "100.00",
- "qr_code": "https://...",
- "code": "ABC1234567890",
- "hash_value": "base64...",
- "signature": "base64...",
- "zimra_inv_id": "ZIM-123456",
- "submitted": true,
- "created_at": "2026-04-01T10:30:00Z"
-}
-```
-
-#### List Receipts (Paginated)
-```
-GET /api/receipts/?page_size=20
-
-Returns: 200 OK
-{
- "next": "https://api/receipts/?cursor=...",
- "previous": null,
- "results": [...]
-}
-```
-
-#### Get Receipt Details
-```
-GET /api/receipts/{id}/
-
-Returns: 200 OK
-{
- "id": 1,
- "receipt_number": "R-00000001",
- "lines": [
- {
- "id": 1,
- "product": "Product",
- "quantity": "1",
- "unit_price": "100.00",
- "line_total": "100.00",
- "tax_amount": "15.50",
- "tax_type": "standard rated 15.5%"
- }
- ],
- "buyer": null,
- ...
-}
-```
-
-### Fiscal Day Management
-
-#### Open Fiscal Day
-```
-POST /api/open-day/
-
-Returns: 200 OK
-{
- "fiscal_day_number": 1,
- "is_open": true,
- "message": "Fiscal day opened"
-}
-```
-
-**Note:** Automatically called when submitting the first receipt of the day.
-
-#### Close Fiscal Day
-```
-POST /api/close-day/
-
-Returns: 200 OK
-{
- "fiscal_day_number": 1,
- "is_open": false,
- "receipt_count": 15,
- "message": "Fiscal day closed"
-}
-```
-
-**What happens:**
-- Sums all receipt counters (by type, currency, tax)
-- Sends closing hash to ZIMRA
-- Resets fiscal day for next day's receipts
-
-### Device Management
-
-#### Get Device Status
-```
-GET /api/get-status/
-
-Returns: 200 OK
-{
- "device_id": "ABC123",
- "org_name": "My Business",
- "is_online": true,
- "open_fiscal_day": 1,
- "last_receipt_no": "R-00000042",
- "last_receipt_global_no": 42
-}
-```
-
-#### Get Device Configuration
-```
-GET /api/configuration/
-
-Returns: 200 OK
-{
- "id": 1,
- "device": 1,
- "tax_payer_name": "My Business Ltd",
- "tin_number": "1234567890",
- "vat_number": "VAT123",
- "address": "123 Main St",
- "phone_number": "+263123456789",
- "email": "info@mybusiness.com"
-}
-```
-
-#### Sync Configuration & Taxes
-```
-POST /api/sync-config/
-
-Returns: 200 OK
-{
- "config_synced": true,
- "taxes_synced": true,
- "tax_count": 5,
- "message": "Configuration synchronized"
-}
-```
-
-### Taxes
-
-#### List Available Taxes
-```
-GET /api/taxes/
-
-Returns: 200 OK
-[
- {
- "id": 1,
- "code": "STD",
- "name": "standard rated 15.5%",
- "tax_id": 1,
- "percent": "15.50"
- },
- {
- "id": 2,
- "code": "ZRO",
- "name": "zero rated 0%",
- "tax_id": 4,
- "percent": "0.00"
- },
- {
- "id": 3,
- "code": "EXM",
- "name": "exempt 0%",
- "tax_id": 5,
- "percent": "0.00"
- }
-]
-```
-
-### Buyers (Optional)
-
-#### List Buyers
-```
-GET /api/buyer/
-
-Returns: 200 OK
-[
- {
- "id": 1,
- "name": "John's Retail",
- "tin_number": "1234567890",
- "trade_name": "John's Store",
- "email": "john@retail.com",
- "address": "456 Commerce Ave",
- "phonenumber": "+263987654321"
- }
-]
-```
-
-#### Create Buyer
-```
-POST /api/buyer/
-Content-Type: application/json
-
-{
- "name": "Jane's Shop",
- "tin_number": "0987654321",
- "trade_name": "Jane's Retail",
- "email": "jane@shop.com",
- "address": "789 Business St",
- "phonenumber": "+263111111111"
-}
-
-Returns: 201 Created
-```
-
-#### Update Buyer
-```
-PATCH /api/buyer/{id}/
-
-Returns: 200 OK
-```
-
-#### Delete Buyer
-```
-DELETE /api/buyer/{id}/
-
-Returns: 204 No Content
-```
-
----
-
-## Usage Examples
-
-### Example 1: Simple Cash Receipt
-
-```bash
-curl -X POST http://localhost:8000/api/receipts/ \
- -H "Content-Type: application/json" \
- -d '{
- "receipt_type": "fiscalinvoice",
- "total_amount": "150.00",
- "currency": "USD",
- "payment_terms": "Cash",
- "lines": [
- {
- "product": "Bread",
- "quantity": "2",
- "unit_price": "50.00",
- "line_total": "100.00",
- "tax_name": "standard rated 15.5%"
- },
- {
- "product": "Milk",
- "quantity": "1",
- "unit_price": "43.48",
- "line_total": "50.00",
- "tax_name": "exempt 0%"
- }
- ]
- }'
-```
-
-**Response:**
-```json
-{
- "id": 5,
- "receipt_number": "R-00000005",
- "receipt_type": "fiscalinvoice",
- "total_amount": "150.00",
- "submitted": true,
- "zimra_inv_id": "ZIM-789012"
-}
-```
-
-### Example 2: Receipt with Buyer
-
-```bash
-curl -X POST http://localhost:8000/api/receipts/ \
- -H "Content-Type: application/json" \
- -d '{
- "receipt_type": "fiscalinvoice",
- "total_amount": "500.00",
- "currency": "USD",
- "payment_terms": "BankTransfer",
- "buyer": {
- "name": "Tech Solutions Ltd",
- "tin_number": "1234567890",
- "trade_name": "Tech Shop",
- "email": "tech@example.com",
- "address": "123 Tech Park",
- "phonenumber": "+263123456789"
- },
- "lines": [
- {
- "product": "Laptop",
- "quantity": "1",
- "unit_price": "400.00",
- "line_total": "400.00",
- "tax_name": "standard rated 15.5%"
- },
- {
- "product": "Warranty",
- "quantity": "1",
- "unit_price": "100.00",
- "line_total": "100.00",
- "tax_name": "standard rated 15.5%"
- }
- ]
- }'
-```
-
-### Example 3: Credit Note (Refund)
-
-```bash
-# First, get the receipt number to refund
-curl -X GET http://localhost:8000/api/receipts/
-
-# Then issue a credit note
-curl -X POST http://localhost:8000/api/receipts/ \
- -H "Content-Type: application/json" \
- -d '{
- "receipt_type": "creditnote",
- "credit_note_reference": "R-00000005",
- "credit_note_reason": "Customer returned item",
- "total_amount": "-100.00",
- "currency": "USD",
- "payment_terms": "Cash",
- "lines": [
- {
- "product": "Bread (Returned)",
- "quantity": "2",
- "unit_price": "-50.00",
- "line_total": "-100.00",
- "tax_name": "standard rated 15.5%"
- }
- ]
- }'
-```
-
-**Key differences:**
-- `receipt_type`: "creditnote"
-- `total_amount`: negative
-- `line_total` and `unit_price`: negative
-- `credit_note_reference`: original receipt number (must exist)
-
-### Example 4: Integration with Django Code
-
-```python
-from fiscguy.models import Receipt, ReceiptLine, Buyer
-from fiscguy.services.receipt_service import ReceiptService
-from fiscguy.models import Device
-
-# Get the device
-device = Device.objects.first()
-
-# Create receipt data
-receipt_data = {
- "device": device.id,
- "receipt_type": "fiscalinvoice",
- "total_amount": "100.00",
- "currency": "USD",
- "payment_terms": "Cash",
- "lines": [
- {
- "product": "Service",
- "quantity": "1",
- "unit_price": "100.00",
- "line_total": "100.00",
- "tax_name": "standard rated 15.5%"
- }
- ]
-}
-
-# Create and submit receipt
-service = ReceiptService(device)
-receipt, submission_result = service.create_and_submit_receipt(receipt_data)
-
-print(f"Receipt created: {receipt.receipt_number}")
-print(f"Submitted to ZIMRA: {receipt.submitted}")
-print(f"ZIMRA ID: {receipt.zimra_inv_id}")
-```
-
----
-
-## Concepts
-
-### Fiscal Device
-
-A physical or logical device registered with ZIMRA. Each device has:
-- **Unique device ID** - Assigned during registration
-- **Certificates** - For ZIMRA authentication (test and/or production)
-- **Configuration** - Taxpayer info (TIN, name, address, VAT number)
-- **Fiscal Days** - Daily accounting periods
-- **Receipts** - All issued receipts
-
-### Fiscal Day
-
-An accounting period (usually daily) during which:
-1. Receipts are issued and signed with cryptographic material
-2. Receipt counters accumulate (by type, currency, tax)
-3. Day is closed with a closing hash sent to ZIMRA
-4. Cannot reopen a closed fiscal day
-
-**Important:** First receipt automatically opens the day if needed.
-
-### Receipt Types
-
-| Type | Description | Receiver | Amount Sign |
-|------|-------------|----------|-------------|
-| **Fiscal Invoice** | Normal sale | Customer | Positive (+) |
-| **Credit Note** | Refund/discount | Customer | Negative (-) |
-| **Debit Note** | Surcharge/adjustment | Customer | Positive (+) |
-
-### Receipt Counters
-
-FiscGuy tracks counters by:
-- **Type**: SaleByTax, SaleTaxByTax, CreditNoteByTax, etc.
-- **Currency**: USD or ZWG
-- **Tax Rate**: Standard, Zero-Rated, Exempt, Withholding
-
-Counters are summed at day-close and sent to ZIMRA.
-
-### Payment Methods
-
-- Cash
-- Card
-- Bank Transfer
-- Mobile Wallet
-- Coupon
-- Credit
-- Other
-
-### Tax Types (Synced from ZIMRA)
-
-- **Standard Rated** (typically 15.5%)
-- **Zero Rated** (0%, e.g., exports)
-- **Exempt** (0%, e.g., education)
-- **Withholding** (applied by buyer)
-
----
-
-## Troubleshooting
-
-### Issue: "No open fiscal day and FDMS is unreachable"
-
-**Cause:** Network error or ZIMRA is offline during first receipt submission.
-
-**Solution:**
-1. Check internet connectivity
-2. Verify ZIMRA API availability
-3. Ensure device certificates are valid
-4. Manually open day: `POST /api/open-day/`
-
-### Issue: "ZIMRA configuration missing"
-
-**Cause:** Device configuration not synced.
-
-**Solution:**
-```bash
-python manage.py init_device
-# Or:
-curl -X POST http://localhost:8000/api/sync-config/
-```
-
-### Issue: "TIN number is incorrect, must be ten digit"
-
-**Cause:** Buyer TIN is not exactly 10 digits.
-
-**Solution:**
-- Format TIN as 10 digits (e.g., `0123456789`)
-- Pad with leading zeros if needed
-
-### Issue: "Tax with name 'X' not found"
-
-**Cause:** Requested tax doesn't exist in database.
-
-**Solution:**
-1. Check available taxes: `GET /api/taxes/`
-2. Use exact tax name from list
-3. Sync taxes: `POST /api/sync-config/`
-
-### Issue: "Referenced receipt does not exist" (Credit Note)
-
-**Cause:** Trying to create credit note for receipt that doesn't exist locally.
-
-**Solution:**
-- Verify original receipt number is correct
-- Original receipt must be submitted to ZIMRA before creating credit note
-
-### Issue: Timeout or "FDMS error" in logs
-
-**Cause:** ZIMRA API timeout (>30 seconds).
-
-**Solution:**
-- Check network latency to ZIMRA servers
-- Retry the request
-- Monitor ZIMRA status page
-
-### Issue: "Device is not registered"
-
-**Cause:** Device table is empty.
-
-**Solution:**
-```bash
-python manage.py init_device
-```
-
-### Issue: Receipts not marked as `submitted=True`
-
-**Cause:** ZIMRA API call failed or device is offline.
-
-**Solution:**
-- Check ZIMRA connectivity
-- Review server logs for error details
-- Re-submit receipt (transaction ensures atomicity)
-
----
-
-## FAQ
-
-### Q: Do I need to manually open fiscal days?
-
-**A:** No, the first receipt of the day automatically opens it. You only manually open if needed.
-
-### Q: Can I use multiple devices?
-
-**A:** Yes, FiscGuy supports multiple devices. Each device has its own config and receipts. Note: The API uses `Device.objects.first()`, so you may want to extend views for device selection.
-
-### Q: What happens if ZIMRA is offline?
-
-**A:** Receipts fail submission with `ReceiptSubmissionError`. The receipt is rolled back (not saved). Retry when ZIMRA is back online.
-
-### Q: Can I issue credit notes for receipts from another system?
-
-**A:** No, the original receipt must exist in FiscGuy's database and be submitted to ZIMRA.
-
-### Q: What's the difference between zero-rated and exempt taxes?
-
-**A:** Both are 0%, but:
-- **Zero-Rated**: Used for exports, VAT recovery allowed
-- **Exempt**: Used for education/health, VAT recovery NOT allowed
-- Functionally, FiscGuy treats both as 0% tax
-
-### Q: How do I handle multi-currency transactions?
-
-**A:** Set `currency` field per receipt (USD or ZWG). Counters are tracked separately by currency.
-
-### Q: Can I edit receipts after submission?
-
-**A:** No, issued receipts are immutable per ZIMRA spec. Issue a credit note to refund/adjust.
-
-### Q: Where are QR codes stored?
-
-**A:** In the `media/Zimra_qr_codes/` directory (configurable via Django settings). Also accessible via API in `receipt.qr_code`.
-
-### Q: What's the transaction ID (zimra_inv_id)?
-
-**A:** The ID assigned by ZIMRA during submission. Use this to match receipts in ZIMRA reports.
-
-### Q: How do I check remaining API rate limits?
-
-**A:** FiscGuy doesn't enforce limits, but ZIMRA may. Check ZIMRA documentation or contact support.
-
-### Q: Is there a webhook for receipt updates?
-
-**A:** No, poll the API: `GET /api/receipts/` or `GET /api/receipts/{id}/`
-
-### Q: Can I use FiscGuy with asyncio/celery?
-
-**A:** Yes, but ensure database transactions are atomic. See ARCHITECTURE.md for transaction patterns.
-
----
-
-## Getting Help
-
-- **Documentation:** See ARCHITECTURE.md for technical details
-- **Issues:** https://github.com/digitaltouchcode/fisc/issues
-- **Email:** cassymyo@gmail.com
-- **Examples:** See `fiscguy/tests/` for test cases
-
----
-
-## License
-
-MIT License - See LICENSE file for details
-
----
-
-**Last Updated:** April 2026
-**Version:** 0.1.6
-**Maintainers:** Casper Moyo (@cassymyo)
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..b255534
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,921 @@
+# FiscGuy — Engineering Architecture
+
+> Internal engineering reference for contributors and maintainers.
+> Version 0.1.6 · Last updated April 2026 · Maintainer: Casper Moyo
+
+---
+
+## Table of Contents
+
+1. [System Overview](#1-system-overview)
+2. [Layer Architecture](#2-layer-architecture)
+3. [Component Map](#3-component-map)
+4. [Data Models](#4-data-models)
+5. [Receipt Processing Pipeline](#5-receipt-processing-pipeline)
+6. [Fiscal Day Lifecycle](#6-fiscal-day-lifecycle)
+7. [Closing Day & Signature Spec](#7-closing-day--signature-spec)
+8. [Cryptography](#8-cryptography)
+9. [ZIMRA Client](#9-zimra-client)
+10. [Fiscal Counters](#10-fiscal-counters)
+11. [Error Handling](#11-error-handling)
+12. [Database Design](#12-database-design)
+13. [Development Guidelines](#13-development-guidelines)
+
+---
+
+## 1. System Overview
+
+FiscGuy is a Django library that wraps the full ZIMRA Fiscal Device Management System (FDMS) API. It handles every phase of fiscal device integration: device registration, certificate management, fiscal day management, receipt signing and submission, and fiscal counter tracking.
+
+```
+┌──────────────────────────────────────────────────────────────────────────┐
+│ Host Django Application │
+│ │
+│ from fiscguy import open_day, submit_receipt, close_day │
+│ urlpatterns += [path("fiscguy/", include("fiscguy.urls"))] │
+└────────────────────────────────┬─────────────────────────────────────────┘
+ │
+ ┌────────────▼────────────┐
+ │ FiscGuy Library │
+ │ │
+ │ REST API → Services │
+ │ Services → ZIMRA │
+ │ Services → DB │
+ └────────────┬────────────┘
+ │ HTTPS + mTLS
+ ┌────────────▼────────────┐
+ │ ZIMRA FDMS │
+ │ │
+ │ fdmsapitest.zimra.co.zw │
+ │ fdmsapi.zimra.co.zw │
+ └──────────────────────────┘
+```
+
+---
+
+## 2. Layer Architecture
+
+FiscGuy follows a strict four-layer architecture. Each layer has a single responsibility and communicates only with the layer directly below it.
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ │
+│ REST API LAYER · views.py │
+│ │
+│ HTTP in → validate device exists → delegate to service │
+│ Handle typed exceptions → return DRF Response │
+│ Never contains business logic │
+│ │
+├─────────────────────────────────────────────────────────────────────┤
+│ │
+│ SERVICE LAYER · services/ │
+│ │
+│ ReceiptService OpenDayService ClosingDayService │
+│ ConfigurationService StatusService PingService │
+│ CertificateService │
+│ │
+│ All business logic lives here. Atomic transactions. │
+│ Raises typed FiscalisationError subclasses on failure. │
+│ │
+├─────────────────────────────────────────────────────────────────────┤
+│ │
+│ ZIMRA INTEGRATION LAYER · zimra_*.py │
+│ │
+│ ZIMRAClient HTTP to FDMS, mTLS, sessions │
+│ ZIMRAReceiptHandler Full receipt pipeline │
+│ ZIMRACrypto RSA signing, SHA-256, MD5, QR │
+│ │
+├─────────────────────────────────────────────────────────────────────┤
+│ │
+│ DATA LAYER · models.py │
+│ │
+│ Device Configuration Certs FiscalDay FiscalCounter │
+│ Receipt ReceiptLine Taxes Buyer │
+│ │
+└─────────────────────────────────────────────────────────────────────┘
+ │
+ ▼
+ SQLite / PostgreSQL / MySQL
+```
+
+---
+
+## 3. Component Map
+
+```
+fiscguy/
+│
+├── views.py REST endpoints (thin HTTP layer)
+├── urls.py URL routing
+├── models.py All Django models
+├── serializers.py DRF serializers (validation + create)
+├── exceptions.py Typed exception hierarchy
+├── apps.py Django app config
+│
+├── services/
+│ ├── receipt_service.py Receipt create + submit orchestration
+│ ├── open_day_service.py Fiscal day opening
+│ ├── closing_day_service.py Fiscal day closing + counter hash
+│ ├── configuration_service.py Tax + config sync from FDMS
+│ ├── status_service.py FDMS status queries
+│ ├── ping_service.py FDMS connectivity check
+│ └── certs_service.py Certificate renewal
+│
+├── zimra_base.py ZIMRAClient — HTTP to FDMS
+├── zimra_crypto.py ZIMRACrypto — RSA/SHA256/MD5
+├── zimra_receipt_handler.py ZIMRAReceiptHandler — full pipeline
+│
+├── utils/
+│ ├── cert_temp_manager.py Temp PEM file lifecycle
+│ └── datetime_now.py Timestamp helpers
+│
+├── management/
+│ └── commands/
+│ └── init_device.py Interactive device registration
+│
+└── tests/
+ ├── conftest.py
+ ├── test_views.py
+ ├── test_services.py
+ ├── test_closing_day_service.py
+ └── test_zimra_base.py
+```
+
+---
+
+## 4. Data Models
+
+### Entity Relationship
+
+```
+ ┌──────────────┐
+ │ Device │
+ │──────────────│
+ │ org_name │
+ │ device_id ◄──┼── unique
+ │ activation_key│
+ │ production │
+ └──────┬───────┘
+ │
+ ┌─────────────────┼──────────────────────┐
+ │ │ │
+ ┌─────────▼──────┐ ┌───────▼──────┐ ┌──────────▼──────┐
+ │ Configuration │ │ Certs │ │ FiscalDay │
+ │────────────────│ │──────────────│ │─────────────────│
+ │ tax_payer_name │ │ csr │ │ day_no │
+ │ tin_number │ │ certificate │ │ receipt_counter │
+ │ vat_number │ │ cert_key │ │ is_open │
+ │ address │ │ production │ └────────┬────────┘
+ │ url │ └──────────────┘ │
+ └────────────────┘ ┌─────────▼──────────┐
+ │ FiscalCounter │
+ ┌───────────────────────────│────────────────────│
+ │ │ counter_type │
+ │ │ currency │
+ │ │ tax_id │
+ │ │ tax_percent │
+ │ │ money_type │
+ │ │ value │
+ │ └────────────────────┘
+ ┌─────────▼──────┐
+ │ Receipt │
+ │────────────────│ ┌──────────────┐
+ │ receipt_number │◄──────│ ReceiptLine │
+ │ receipt_type │ 1..* │──────────────│
+ │ total_amount │ │ product │
+ │ currency │ │ quantity │
+ │ global_number │ │ unit_price │
+ │ hash_value │ │ line_total │
+ │ signature │ │ tax_amount │
+ │ qr_code │ │ tax_type ────┼──► Taxes
+ │ submitted │ └──────────────┘
+ │ payment_terms │
+ │ buyer ─────────┼──► Buyer
+ └────────────────┘
+```
+
+### Model Reference
+
+#### `Device`
+The root entity. Every other model links back to it.
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `org_name` | CharField | Organisation name |
+| `device_id` | CharField | Unique. Assigned by ZIMRA |
+| `activation_key` | CharField | Used during registration |
+| `device_model_name` | CharField | Sent as HTTP header to FDMS |
+| `device_model_version` | CharField | Sent as HTTP header to FDMS |
+| `device_serial_number` | CharField | |
+| `production` | BooleanField | Switches FDMS URL test ↔ production |
+
+#### `Certs`
+Stores the device's X.509 certificate and RSA private key. OneToOne with Device.
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `csr` | TextField | PEM-encoded Certificate Signing Request |
+| `certificate` | TextField | PEM-encoded X.509 certificate from ZIMRA |
+| `certificate_key` | TextField | PEM-encoded RSA private key — **never expose** |
+| `production` | BooleanField | Whether this is a production cert |
+
+> ⚠️ **Security:** `certificate_key` is stored plaintext. Encryption at rest is planned for v0.1.7. Do not expose via API or logs.
+
+#### `FiscalDay`
+One row per trading day. Only one can be `is_open=True` per device at a time.
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `day_no` | IntegerField | Sourced from FDMS (`lastFiscalDayNo + 1`) |
+| `receipt_counter` | IntegerField | Increments on each submitted receipt |
+| `is_open` | BooleanField | Exactly one open day per device |
+
+Constraint: `unique_together = (device, day_no)`.
+Index: `(device, is_open)` — used on every receipt submission.
+
+#### `FiscalCounter`
+Accumulates running totals per tax group or payment method within a fiscal day.
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `fiscal_counter_type` | CharField | See counter type enum below |
+| `fiscal_counter_currency` | CharField | `USD` or `ZWG` |
+| `fiscal_counter_tax_id` | IntegerField | Null for BalanceByMoneyType |
+| `fiscal_counter_tax_percent` | DecimalField | Null for exempt and BalanceByMoneyType |
+| `fiscal_counter_money_type` | CharField | Only for BalanceByMoneyType |
+| `fiscal_counter_value` | DecimalField | Running total, can be negative |
+
+Counter type enum (ZIMRA spec section 5.4.4):
+
+| Value | Enum order | Tracks |
+|-------|-----------|--------|
+| `SaleByTax` | 0 | Sales amount per tax |
+| `SaleTaxByTax` | 1 | Tax amount from sales |
+| `CreditNoteByTax` | 2 | Credit note amounts (negative) |
+| `CreditNoteTaxByTax` | 3 | Tax from credit notes (negative) |
+| `DebitNoteByTax` | 4 | Debit note amounts |
+| `DebitNoteTaxByTax` | 5 | Tax from debit notes |
+| `BalanceByMoneyType` | 6 | Total by payment method |
+
+#### `Receipt`
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `receipt_number` | CharField | `R-{global_number:08d}`. Unique |
+| `receipt_type` | CharField | `fiscalinvoice` / `creditnote` / `debitnote` |
+| `total_amount` | DecimalField | Negative for credit notes |
+| `global_number` | IntegerField | From FDMS `lastReceiptGlobalNo + 1` |
+| `hash_value` | CharField | SHA-256 of signature string, base64 |
+| `signature` | TextField | RSA signature, base64 |
+| `qr_code` | ImageField | PNG saved to `Zimra_qr_codes/` |
+| `code` | CharField | 16-char verification code from signature |
+| `zimra_inv_id` | CharField | FDMS-assigned receipt ID |
+| `submitted` | BooleanField | False if queued offline |
+
+#### `Taxes`
+Synced from FDMS on every `open_day()` and `init_device`. Do not edit manually.
+
+---
+
+## 5. Receipt Processing Pipeline
+
+```
+POST /receipts/
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ ReceiptView.post() │
+│ Get device → call ReceiptService │
+└──────────────────────────┬──────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ ReceiptService.create_and_submit_receipt() @transaction.atomic│
+│ │
+│ 1. Inject device ID into payload │
+│ 2. ReceiptCreateSerializer.is_valid() │
+│ ├─ Validate receipt type, currency, amounts │
+│ ├─ Credit note: check reference exists, amount sign │
+│ └─ Buyer TIN: must be 10 digits if provided │
+│ 3. serializer.save() → Receipt + ReceiptLine rows created │
+│ 4. Re-fetch with select_related + prefetch_related │
+│ 5. ZIMRAReceiptHandler.process_and_submit() │
+│ │
+│ ┌ If ReceiptSubmissionError raised ──────────────────────────┐ │
+│ │ @transaction.atomic rolls back Receipt + ReceiptLine rows │ │
+│ └────────────────────────────────────────────────────────────┘ │
+└──────────────────────────┬──────────────────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────────────────────────────────┐
+│ ZIMRAReceiptHandler.process_and_submit() │
+│ │
+│ ┌─ _ensure_fiscal_day_open() ──────────────────────────────┐ │
+│ │ Query FiscalDay.is_open=True │ │
+│ │ If none: OpenDayService.open_day() → auto-open │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ _get_next_global_number() ──────────────────────────────┐ │
+│ │ GET /getStatus → lastReceiptGlobalNo │ │
+│ │ Compare with local last → log warning if mismatch │ │
+│ │ Return fdms_last + 1 │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ _build_receipt_data() ──────────────────────────────────┐ │
+│ │ Build receiptLines list │ │
+│ │ Calculate tax groups (salesAmountWithTax, taxAmount) │ │
+│ │ Build receiptTaxes list │ │
+│ │ Resolve previousReceiptHash (chain) │ │
+│ │ generate_receipt_signature_string() → signature_string │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ ZIMRACrypto.generate_receipt_hash_and_signature() ──────┐ │
+│ │ hash = base64(SHA256(signature_string)) │ │
+│ │ sig = base64(RSA_SIGN(signature_string, private_key)) │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ _generate_qr_code() ────────────────────────────────────┐ │
+│ │ verification_code = MD5(signature_bytes)[:16] │ │
+│ │ qr_url = {fdms_base}/{device_id}{date}{global_no}{code} │ │
+│ │ Save PNG to receipt.qr_code │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ _update_fiscal_counters() ──────────────────────────────┐ │
+│ │ For each tax group in receiptTaxes: │ │
+│ │ FiscalCounter get_or_create → F() increment │ │
+│ │ BalanceByMoneyType += paymentAmount │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+│ ┌─ _submit_to_fdms() ──────────────────────────────────────┐ │
+│ │ POST /SubmitReceipt │ │
+│ │ On success: FiscalDay.receipt_counter += 1 │ │
+│ │ Return FDMS response (receiptID, serverSignature, etc.) │ │
+│ └──────────────────────────────────────────────────────────┘ │
+└──────────────────────────┬──────────────────────────────────────┘
+ │
+ ▼
+ receipt.submitted = True
+ receipt.zimra_inv_id = ...
+ receipt.save()
+ Return 201
+```
+
+---
+
+## 6. Fiscal Day Lifecycle
+
+```
+ ┌──────────────────────────────┐
+ │ No open fiscal day (start) │
+ └──────────────┬───────────────┘
+ │
+ POST /open-day/ or auto-open
+ │
+ ┌──────────────▼───────────────┐
+ │ OpenDayService.open_day() │
+ │ │
+ │ GET /getStatus │
+ │ → lastFiscalDayNo │
+ │ next_day_no = last + 1 │
+ │ │
+ │ POST /openDay { │
+ │ fiscalDayNo: N, │
+ │ fiscalDayOpened: datetime │
+ │ } │
+ │ │
+ │ FiscalDay.objects.create( │
+ │ day_no=N, is_open=True │
+ │ ) │
+ └──────────────┬───────────────┘
+ │
+ ┌──────────────▼───────────────┐
+ │ Fiscal Day OPEN │
+ │ is_open = True │
+ │ receipt_counter = 0 │
+ │ │
+ │ ← receipts submitted │
+ │ ← counters accumulate │
+ │ ← receipt_counter++ │
+ └──────────────┬───────────────┘
+ │
+ POST /close-day/
+ │
+ ┌──────────────▼───────────────┐
+ │ ClosingDayService.close_day()│
+ │ │
+ │ Build counter string │
+ │ Assemble closing string │
+ │ SHA-256 + RSA sign │
+ │ │
+ │ POST /CloseDay { payload } │
+ │ │
+ │ sleep(10) │
+ │ GET /getStatus │
+ │ → fiscalDayStatus │
+ └──────────────┬───────────────┘
+ │
+ ┌───────────────────┼──────────────────────┐
+ │ │ │
+ FiscalDayClosed FiscalDayCloseFailed Unexpected
+ │ │ │
+ is_open=False raise CloseDayError raise CloseDayError
+ return status (day stays open,
+ retry allowed)
+```
+
+### Day No Resolution
+
+`OpenDayService` always defers to FDMS for the authoritative day number:
+
+```
+GET /getStatus → lastFiscalDayNo = N
+
+Local last day_no == N ?
+ Yes → proceed, next = N + 1
+ No → log WARNING "Local/FDMS day_no mismatch", still use N + 1
+```
+
+---
+
+## 7. Closing Day & Signature Spec
+
+Per ZIMRA API spec section 13.3.1.
+
+### Closing String Assembly
+
+```
+closing_string = (
+ str(device.device_id)
+ + str(fiscal_day.day_no)
+ + fiscal_day.created_at.strftime("%Y-%m-%d") ← day OPEN date, not today
+ + sale_by_tax_string
+ + sale_tax_by_tax_string
+ + credit_note_by_tax_string
+ + credit_note_tax_by_tax_string
+ + balance_by_money_type_string
+).upper()
+```
+
+> ⚠️ **Critical:** `fiscalDayDate` must be the date the day was **opened**, not today's date. Using today's date causes `CountersMismatch` if the day spans midnight.
+
+### Counter String Format
+
+Each counter line: `TYPE + CURRENCY + [TAX_PERCENT or MONEY_TYPE] + VALUE_IN_CENTS`
+
+```
+SALEBYTAXUSD15.00115000
+SALEBYTAXUSD0.005000
+SALEBYTAXUSDEXEMPT_NOTREALFIELD ← exempt: empty tax part
+SALEBYTAXUSD67475 ← exempt example (empty between USD and value)
+BALANCEBYMONEYTYPEUSDLCASH69975 ← note the L between currency and money type
+BALANCEBYMONEYTYPEZWGLCARD69975
+```
+
+**Rules:**
+
+| Rule | Detail |
+|------|--------|
+| All uppercase | `.upper()` applied to entire string |
+| Amounts in cents | `int(round(value * 100))` — preserves sign |
+| Tax percent format | Always two decimal places: `15` → `15.00`, `0` → `0.00`, `14.5` → `14.50` |
+| Exempt tax percent | Empty string — nothing between currency and value |
+| BalanceByMoneyType | Literal `L` between currency and money type (`USDLCASH`, `ZWGLCARD`) |
+| Zero-value counters | Excluded entirely (spec section 4.11) |
+| Sort order | Type enum ASC → currency alpha ASC → taxID/moneyType ASC |
+
+### Sort Order (spec section 13.3.1)
+
+```
+FiscalCounterType enum order:
+ SaleByTax(0) → SaleTaxByTax(1) → CreditNoteByTax(2) →
+ CreditNoteTaxByTax(3) → DebitNoteByTax(4) →
+ DebitNoteTaxByTax(5) → BalanceByMoneyType(6)
+
+Within each type:
+ currency ASC (USD before ZWG)
+ taxID ASC (for byTax) / moneyType ASC alpha (for BalanceByMoneyType)
+```
+
+### Signature Generation
+
+```
+hash = base64( SHA256( closing_string.encode("utf-8") ) )
+signature = base64( RSA_PKCS1v15_SIGN( closing_string, private_key ) )
+
+payload = {
+ "fiscalDayDeviceSignature": {
+ "hash": hash,
+ "signature": signature
+ }
+}
+```
+
+### Common Close Day Errors
+
+| Error | Root cause |
+|-------|-----------|
+| `CountersMismatch` | Wrong date, missing `L`, wrong tax percent format, unsorted counters, zero counters included |
+| `BadCertificateSignature` | Certificate expired / wrong private key used |
+| `FiscalDayCloseFailed` | FDMS validation failed — check `fiscalDayClosingErrorCode` |
+
+---
+
+## 8. Cryptography
+
+### ZIMRACrypto
+
+**Location:** `zimra_crypto.py`
+
+**Library:** `cryptography` (replaces deprecated `pyOpenSSL`)
+
+```
+┌──────────────────────────────────────────────────────┐
+│ ZIMRACrypto │
+│ │
+│ private_key_path ──► CertTempManager │
+│ (temp file from Certs model) │
+│ │
+│ load_private_key() ──► RSAPrivateKey (cached) │
+│ │
+│ get_hash(data) ──► SHA256(data) → base64 │
+│ │
+│ sign_data(data) ──► RSA PKCS1v15 → base64 │
+│ │
+│ generate_receipt_hash_and_signature(string) │
+│ → { hash: str, signature: str } │
+│ │
+│ generate_verification_code(signature_b64) │
+│ → MD5(sig_bytes).hexdigest()[:16].upper() │
+│ → formatted as XXXX-XXXX-XXXX-XXXX │
+│ │
+│ generate_key_and_csr(device) │
+│ → RSA 2048 key pair │
+│ → CSR with CN=ZIMRA-{serial}-{device_id} │
+└──────────────────────────────────────────────────────┘
+```
+
+### Receipt Signature String
+
+Per ZIMRA spec section 13.2.1:
+
+```
+{deviceID}
+{receiptType} ← UPPERCASE e.g. FISCALINVOICE
+{receiptCurrency} ← UPPERCASE e.g. USD
+{receiptGlobalNo}
+{receiptDate} ← YYYY-MM-DDTHH:mm:ss
+{receiptTotal_in_cents} ← negative for credit notes
+{receiptTaxes} ← concatenated, ordered by taxID ASC
+{previousReceiptHash} ← omitted if first receipt of day
+```
+
+Tax line format: `taxCode + taxPercent + taxAmount_cents + salesAmountWithTax_cents`
+
+### Private Key Lifecycle
+
+```
+init_device
+ │
+ ▼
+ZIMRACrypto.generate_key_and_csr()
+ │ RSA 2048 key pair generated in memory
+ │ CSR built and signed
+ │
+ ▼
+ZIMRAClient.register_device()
+ │ CSR sent to FDMS
+ │ Signed certificate returned
+ │
+ ▼
+Certs.objects.create(
+ csr=csr_pem,
+ certificate=cert_pem,
+ certificate_key=private_key_pem
+)
+ │
+ ▼
+ZIMRACrypto (at runtime)
+ │
+ ▼
+CertTempManager
+ │ Writes cert + key to tempfile.mkdtemp()
+ │ Returns path for load_private_key()
+ │
+ ▼
+ZIMRAClient.session.cert = pem_path ← mTLS
+ │
+ ▼
+ZIMRAClient.close() / __del__
+ │ shutil.rmtree(temp_dir) ← cleanup
+```
+
+---
+
+## 9. ZIMRA Client
+
+### ZIMRAClient
+
+**Location:** `zimra_base.py`
+
+```
+┌───────────────────────────────────────────────────────┐
+│ ZIMRAClient │
+│ │
+│ __init__(device) │
+│ ├─ Load Configuration (cached @property) │
+│ ├─ Load Certs (cached @property) │
+│ ├─ Set base_url / public_url based on production │
+│ ├─ Write temp PEM from Certs │
+│ └─ Create requests.Session with cert + headers │
+│ │
+│ Endpoints: │
+│ ┌─────────────────────────────────────────────────┐ │
+│ │ register_device(payload) → public, no cert │ │
+│ │ get_status() → GET /getStatus │ │
+│ │ get_config() → GET /getConfig │ │
+│ │ ping() → POST /ping │ │
+│ │ open_day(payload) → POST /openDay │ │
+│ │ close_day(payload) → POST /CloseDay │ │
+│ │ submit_receipt(payload) → POST /SubmitReceipt │ │
+│ │ issue_certificate(payload)→ POST /issueCert │ │
+│ └─────────────────────────────────────────────────┘ │
+│ │
+│ _request(method, endpoint) │
+│ ├─ Build full URL │
+│ ├─ session.request(timeout=30) │
+│ ├─ response.raise_for_status() │
+│ └─ On error: log + re-raise requests.RequestException│
+│ │
+│ Lifecycle: │
+│ close() → session.close() + rmtree(temp_dir) │
+│ __enter__ / __exit__ → context manager │
+│ __del__ → close() on GC │
+└───────────────────────────────────────────────────────┘
+```
+
+### URLs
+
+| Environment | Device API | Public API |
+|-------------|-----------|-----------|
+| Testing | `https://fdmsapitest.zimra.co.zw/Device/v1/{device_id}` | `https://fdmsapitest.zimra.co.zw/Public/v1/{device_id}` |
+| Production | `https://fdmsapi.zimra.co.zw/Device/v1/{device_id}` | `https://fdmsapi.zimra.co.zw/Public/v1/{device_id}` |
+
+All Device API requests use mutual TLS. The Public API (`RegisterDevice`) uses plain HTTPS with no client cert.
+
+---
+
+## 10. Fiscal Counters
+
+### Update Flow (per receipt)
+
+```
+ZIMRAReceiptHandler._update_fiscal_counters_inner()
+
+For each tax group in receiptTaxes:
+│
+├─ receipt_type == "fiscalinvoice"
+│ ├─ SaleByTax += salesAmountWithTax (per tax group)
+│ └─ SaleTaxByTax += taxAmount (non-exempt, non-zero only)
+│
+├─ receipt_type == "creditnote"
+│ ├─ CreditNoteByTax += -salesAmountWithTax (negative)
+│ └─ CreditNoteTaxByTax += -taxAmount (negative, non-exempt, non-zero)
+│
+└─ receipt_type == "debitnote"
+ ├─ DebitNoteByTax += salesAmountWithTax
+ └─ DebitNoteTaxByTax += taxAmount
+
+Always (all types):
+ BalanceByMoneyType += paymentAmount (negative for credit notes)
+```
+
+### Race Condition Prevention
+
+Counter updates use Django `F()` expressions for atomic DB-level increments, preventing lost updates under concurrent receipt submission:
+
+```python
+# Instead of:
+counter.fiscal_counter_value += amount # ← race condition
+counter.save()
+
+# FiscGuy uses:
+FiscalCounter.objects.filter(...).update(
+ fiscal_counter_value=F("fiscal_counter_value") + amount
+)
+```
+
+### get_or_create Key
+
+Each unique combination gets its own row:
+
+```python
+FiscalCounter.objects.get_or_create(
+ fiscal_counter_type=counter_type,
+ fiscal_counter_currency=currency,
+ fiscal_counter_tax_id=tax_id,
+ fiscal_counter_tax_percent=tax_percent,
+ fiscal_counter_money_type=money_type,
+ fiscal_day=fiscal_day,
+ defaults={"fiscal_counter_value": amount},
+)
+```
+
+---
+
+## 11. Error Handling
+
+### Exception Hierarchy
+
+```
+FiscalisationError
+├── ReceiptSubmissionError Receipt can't be processed or submitted
+├── CloseDayError Day close rejected by FDMS
+├── FiscalDayError Day open failed
+├── ConfigurationError Config missing or sync failed
+├── CertificateError Cert issuance/renewal failed
+├── DevicePingError Ping to FDMS failed
+├── StatusError Status fetch failed
+├── DeviceRegistrationError Registration with ZIMRA failed
+├── CryptoError RSA/hash operation failed
+├── CertNotFoundError No cert found in DB
+├── PersistenceError DB write failed
+├── ZIMRAAPIError Generic FDMS API error
+├── ValidationError Data validation failed
+├── AuthenticationError mTLS auth failed
+├── TaxError Tax CRUD failed
+├── DeviceNotFoundError Device not in DB
+├── ZIMRAClientError Client-level failure
+└── TenantNotFoundError Multi-tenant lookup failed
+```
+
+### View Error Mapping
+
+```
+ReceiptSubmissionError → 422 Unprocessable Entity
+CloseDayError → 422 Unprocessable Entity
+FiscalDayError → 400 Bad Request
+ConfigurationError → 500 Internal Server Error
+CertificateError → 422 Unprocessable Entity
+DevicePingError → 500 Internal Server Error
+StatusError → 500 Internal Server Error
+No device found → 404 Not Found
+No open fiscal day → 400 Bad Request
+Exception (catch-all) → 500 Internal Server Error
+```
+
+---
+
+## 12. Database Design
+
+### Indexes
+
+```
+Device:
+ device_id UNIQUE
+
+FiscalDay:
+ (device_id, day_no) UNIQUE
+ (device_id, is_open) INDEX ← every receipt submission queries this
+
+FiscalCounter:
+ (device_id, fiscal_day_id) INDEX
+
+Receipt:
+ receipt_number UNIQUE
+ (device_id, -created_at) INDEX ← paginated receipt listing
+
+ReceiptLine:
+ (receipt_id) INDEX
+
+Taxes:
+ (tax_id) INDEX ← looked up on every receipt line
+
+Buyer:
+ (tin_number) INDEX
+```
+
+### Query Patterns
+
+| Operation | Query |
+|-----------|-------|
+| Get open fiscal day | `FiscalDay.objects.filter(device=d, is_open=True).first()` |
+| Get open day (with lock) | `select_for_update().filter(device=d, is_open=True).first()` |
+| Get receipt with lines | `select_related("buyer").prefetch_related("lines")` |
+| Build tax map | `{t.tax_id: t.name for t in Taxes.objects.all()}` |
+| Upsert counter | `get_or_create(...)` then `F()` update |
+
+---
+
+## 13. Development Guidelines
+
+### Adding a New Service
+
+1. Create `fiscguy/services/my_service.py`
+2. Accept `device: Device` in `__init__`
+3. Raise typed `FiscalisationError` subclasses — never raw `Exception`
+4. Wrap DB writes in `transaction.atomic`
+5. Add a view in `views.py` and route in `urls.py`
+6. Add tests in `fiscguy/tests/`
+
+```python
+class MyService:
+ def __init__(self, device: Device):
+ self.device = device
+ self.client = ZIMRAClient(device)
+
+ @transaction.atomic
+ def do_thing(self) -> dict:
+ try:
+ result = self.client.some_endpoint()
+ except requests.RequestException as exc:
+ raise MyError("FDMS call failed") from exc
+
+ MyModel.objects.create(device=self.device, ...)
+ return result
+```
+
+### Adding a New Endpoint
+
+```python
+# views.py
+class MyView(APIView):
+ def post(self, request):
+ device = Device.objects.first()
+ if not device:
+ return Response({"error": "No device registered"}, status=404)
+ try:
+ result = MyService(device).do_thing()
+ return Response(result, status=200)
+ except MyError as exc:
+ logger.error(f"My thing failed: {exc}")
+ return Response({"error": str(exc)}, status=422)
+ except Exception:
+ logger.exception("Unexpected error")
+ return Response({"error": "Internal server error"}, status=500)
+
+# urls.py
+path("my-endpoint/", MyView.as_view(), name="my-endpoint"),
+```
+
+### Logging
+
+Use `loguru`. Follow these levels:
+
+| Level | When |
+|-------|------|
+| `logger.info()` | Normal operations — receipt submitted, day opened |
+| `logger.warning()` | Recoverable issues — FDMS/local mismatch, offline queue |
+| `logger.error()` | Handled failures — receipt rejected, close failed |
+| `logger.exception()` | Unexpected errors — always in `except` blocks, includes traceback |
+
+Never log private keys, raw certificates, or full receipt payloads at INFO level.
+
+### Migrations
+
+```bash
+# After model changes
+python manage.py makemigrations fiscguy
+
+# Apply
+python manage.py migrate
+
+# Never edit existing migrations
+# Always create a new migration for changes
+```
+
+### Testing
+
+```bash
+pytest # all tests
+pytest --cov=fiscguy --cov-report=html # with coverage
+pytest fiscguy/tests/test_closing_day_service.py # single file
+pytest -k "test_build_sale_by_tax" # single test
+```
+
+Mock external calls at the boundary — patch `ZIMRAClient`, `ZIMRACrypto`, and `requests`. Never make real FDMS calls in tests.
+
+```python
+@patch("fiscguy.services.open_day_service.ZIMRAClient")
+def test_open_day_success(self, MockClient):
+ MockClient.return_value.get_status.return_value = {"lastFiscalDayNo": 5}
+ MockClient.return_value.open_day.return_value = {"fiscalDayNo": 6}
+ ...
+```
+
+### Code Style
+
+- Line length: 100 (Black)
+- Imports: isort with Black profile
+- Private methods: prefix `_`
+- Type hints on all public method signatures
+- Docstrings on all public classes and methods
+
+```bash
+black fiscguy && isort fiscguy && flake8 fiscguy && mypy fiscguy
+```
+
+---
+
+> **Internal use only.** Do not publish this document.
+> Maintainer: Casper Moyo · cassymyo@gmail.com
+> Version: 0.1.6 · April 2026
diff --git a/docs/certificate-management.md b/docs/certificate-management.md
new file mode 100644
index 0000000..be1dc2a
--- /dev/null
+++ b/docs/certificate-management.md
@@ -0,0 +1,101 @@
+# Certificate Management
+
+FiscGuy uses mutual TLS authentication with ZIMRA FDMS. The device must hold a valid certificate issued by ZIMRA to submit any signed request.
+
+---
+
+## How Certificates Work
+
+1. `init_device` generates an RSA key pair and a Certificate Signing Request (CSR)
+2. The CSR is sent to ZIMRA FDMS `RegisterDevice` endpoint
+3. ZIMRA returns a signed certificate
+4. The certificate and private key are stored in the `Certs` model
+5. Every request to FDMS uses the certificate for mutual TLS authentication
+
+---
+
+## Certificate Storage
+
+Certificates are stored in the `Certs` model:
+
+```python
+from fiscguy.models import Certs
+
+cert = Certs.objects.first()
+print(cert.certificate) # PEM-encoded certificate
+print(cert.certificate_key) # PEM-encoded private key
+print(cert.csr) # Original CSR
+print(cert.production) # True = production, False = testing
+```
+
+At runtime, `ZIMRAClient` writes the certificate and key to a temporary PEM file used by the `requests` session. The temp file is cleaned up when the client is closed.
+
+---
+
+## Certificate Renewal
+
+Certificates expire. When they do, all signed requests will fail with `BadCertificateSignature` or an authentication error.
+
+### Via REST endpoint
+
+```
+POST /fiscguy/issue-certificate/
+```
+
+Response on success:
+```json
+{"message": "Certificate issued successfully"}
+```
+
+### Via Python
+
+```python
+from fiscguy.services.certs_service import CertificateService
+from fiscguy.models import Device
+
+device = Device.objects.first()
+CertificateService(device).issue_certificate()
+```
+
+### Raises
+
+| Exception | Cause |
+|-----------|-------|
+| `CertificateError` | FDMS rejected the renewal request |
+| `Exception` | Unexpected error during renewal |
+
+---
+
+## Key Generation
+
+FiscGuy supports two key algorithms as per ZIMRA spec section 12:
+
+| Algorithm | Spec reference |
+|-----------|----------------|
+| RSA 2048 | Section 12.1.2 |
+| ECC ECDSA secp256r1 (P-256) | Section 12.1.1 |
+
+The `cryptography` library is used for all key generation and signing. `pyOpenSSL` is no longer used.
+
+---
+
+## Security Notes
+
+- The private key never leaves the device. Only the CSR is sent to ZIMRA.
+- Do not commit `Certs` data to version control.
+- In production, consider encrypting `Certs.certificate` and `Certs.certificate_key` at rest using a library like `cryptography.fernet`. See the project roadmap for planned support.
+- The temporary PEM file is written to a `tempfile.mkdtemp()` directory and deleted when `ZIMRAClient.close()` is called.
+
+---
+
+## Checking Certificate Status
+
+```python
+from fiscguy import get_status
+
+status = get_status()
+# Check for certificate-related errors in the response
+print(status)
+```
+
+If the certificate is expired or invalid, `get_status()` will raise `StatusError` with an authentication error from FDMS.
diff --git a/docs/closing-day.md b/docs/closing-day.md
new file mode 100644
index 0000000..4899535
--- /dev/null
+++ b/docs/closing-day.md
@@ -0,0 +1,156 @@
+# Closing a Fiscal Day
+
+At the end of each trading day, the fiscal day must be closed by submitting a signed summary of all fiscal counters to ZIMRA FDMS.
+
+---
+
+## Quick Close
+
+```python
+from fiscguy import close_day
+
+result = close_day()
+# {"fiscalDayStatus": "FiscalDayClosed", ...}
+```
+
+Or via REST:
+
+```
+POST /fiscguy/close-day/
+```
+
+---
+
+## What Happens During Close
+
+`ClosingDayService.close_day()` performs these steps in order:
+
+1. **Build counter strings** — each counter type is serialised into the ZIMRA closing string format
+2. **Assemble the closing string** — `deviceID + fiscalDayNo + fiscalDayDate + counters`
+3. **Hash and sign** — SHA-256 hash of the string, signed with the device RSA private key
+4. **Build payload** — closing string, signature, fiscal day counters, receipt counter
+5. **Submit to FDMS** — `POST /CloseDay`
+6. **Poll for status** — waits 10 seconds then calls `GET /getStatus`
+7. **Update database** — marks `FiscalDay.is_open = False` on success
+
+---
+
+## Closing String Specification
+
+From ZIMRA API spec section 13.3.1. Fields concatenated in this exact order:
+
+| Order | Field | Format |
+|-------|-------|--------|
+| 1 | `deviceID` | Integer as-is |
+| 2 | `fiscalDayNo` | Integer as-is |
+| 3 | `fiscalDayDate` | `YYYY-MM-DD` — **date the fiscal day was opened**, not today |
+| 4 | `fiscalDayCounters` | Concatenated counter string (see below) |
+
+All text **uppercase**. No separators between fields.
+
+### Counter String
+
+Each counter line: `TYPE || CURRENCY || [TAX_PERCENT or MONEY_TYPE] || VALUE_IN_CENTS`
+
+**Sort order:**
+1. Counter type — ascending by enum value (`SaleByTax=0` → `BalanceByMoneyType=6`)
+2. Currency — alphabetical ascending
+3. TaxID — ascending (for byTax types) / MoneyType — ascending (for BalanceByMoneyType)
+
+**Tax percent formatting:**
+- Integer percent: always two decimals — `15` → `15.00`, `0` → `0.00`
+- Decimal percent: `14.5` → `14.50`
+- Exempt (no percent): empty string — nothing between currency and value
+
+**BalanceByMoneyType:** has a literal `L` between currency and money type:
+
+```
+BALANCEBYMONEYTYPEUSDLCASH3700
+BALANCEBYMONEYTYPEZWGLCARD1500000
+BALANCEBYMONEYTYPEZWGLCASH2000000
+```
+
+**Zero-value counters:** excluded entirely (per spec section 4.11).
+
+**Amounts:** in cents, preserving sign. `-699.75` → `-69975`.
+
+### Full Example
+
+From ZIMRA spec section 13.3.1:
+
+```
+321842019-09-23
+SALEBYTAXZWL2300000
+SALEBYTAXZWL0.001200000
+SALEBYTAXUSD14.502500
+SALEBYTAXZWL15.001200
+SALETAXBYTAXUSD15.00250
+SALETAXBYTAXZWL15.00230000
+BALANCEBYMONEYTYPEUSDLCASH3700
+BALANCEBYMONEYTYPEZWLCASH2000000
+BALANCEBYMONEYTYPEZWLCARD1500000
+```
+
+Hash (SHA-256, base64): `OdT8lLI0JXhXl1XQgr64Zb1ltFDksFXThVxqM6O8xZE=`
+
+---
+
+## Common Close Day Errors
+
+### `CountersMismatch`
+
+FDMS computed different counter values from what was submitted.
+
+**Causes:**
+- `fiscalDayDate` in the closing string uses today's date instead of the fiscal day open date
+- Tax percent not formatted as two decimal places (`15` instead of `15.00`)
+- `BalanceByMoneyType` missing the `L` separator
+- Counters not sorted in the correct order
+- Credit note counter not negated, or using `receiptTotal` instead of per-tax `salesAmountWithTax`
+- Zero-value counters included in the payload
+
+### `BadCertificateSignature`
+
+FDMS cannot verify the device signature.
+
+**Causes:**
+- Wrong private key used for signing (key doesn't match the registered certificate)
+- Certificate has expired — run `POST /fiscguy/issue-certificate/`
+- Certificate has been revoked
+
+### `FiscalDayCloseFailed`
+
+FDMS accepted the request but validation failed. The day remains open and can be retried.
+
+Check `fiscalDayClosingErrorCode` in the response for the specific reason.
+
+---
+
+## Fiscal Day Status Values
+
+| Status | Meaning |
+|--------|---------|
+| `FiscalDayOpened` | Day is open, receipts can be submitted |
+| `FiscalDayCloseInitiated` | Close request submitted, processing |
+| `FiscalDayClosed` | Day closed successfully |
+| `FiscalDayCloseFailed` | Close attempt failed — day remains open, retry allowed |
+
+---
+
+## Retrying a Failed Close
+
+If `close_day()` raises `CloseDayError` with `FiscalDayCloseFailed`, the day remains open and you can correct the issue and retry:
+
+```python
+from fiscguy import close_day
+from fiscguy.exceptions import CloseDayError
+
+try:
+ close_day()
+except CloseDayError as e:
+ print(f"Close failed: {e}")
+ # Investigate, fix, then retry:
+ close_day()
+```
+
+FDMS allows close retries when the fiscal day status is `FiscalDayOpened` or `FiscalDayCloseFailed`.
diff --git a/docs/error-reference.md b/docs/error-reference.md
new file mode 100644
index 0000000..e4f5519
--- /dev/null
+++ b/docs/error-reference.md
@@ -0,0 +1,176 @@
+# Error Reference
+
+All FiscGuy exceptions inherit from `FiscalisationError`. Import them from `fiscguy.exceptions`.
+
+---
+
+## Exception Hierarchy
+
+```
+FiscalisationError
+├── ReceiptSubmissionError
+├── CloseDayError
+├── FiscalDayError
+├── ConfigurationError
+├── CertificateError
+├── DevicePingError
+├── StatusError
+├── DeviceRegistrationError
+├── CryptoError
+├── CertNotFoundError
+├── PersistenceError
+├── ZIMRAAPIError
+├── ValidationError
+├── AuthenticationError
+├── TaxError
+├── DeviceNotFoundError
+├── TenantNotFoundError
+└── ZIMRAClientError
+```
+
+---
+
+## Common Exceptions
+
+### `ReceiptSubmissionError`
+
+Raised when a receipt cannot be processed or submitted.
+
+```python
+from fiscguy.exceptions import ReceiptSubmissionError
+
+try:
+ submit_receipt(data)
+except ReceiptSubmissionError as e:
+ print(e)
+```
+
+Common causes:
+- No open fiscal day — call `open_day()` first
+- Invalid receipt data (missing required fields, wrong types)
+- FDMS rejected the receipt (validation errors)
+- FDMS unreachable and auto-queue failed
+- Credit note references a non-existent original receipt
+- Credit note amount exceeds original receipt amount
+
+---
+
+### `CloseDayError`
+
+Raised when the fiscal day cannot be closed.
+
+```python
+from fiscguy.exceptions import CloseDayError
+
+try:
+ close_day()
+except CloseDayError as e:
+ print(e)
+```
+
+Common causes and fixes:
+
+| Error code | Cause | Fix |
+|------------|-------|-----|
+| `CountersMismatch` | Closing string counters don't match FDMS records | Check closing string format — date, tax percent format, L separator |
+| `BadCertificateSignature` | Device signature cannot be verified | Certificate expired or wrong key — renew certificate |
+| `FiscalDayCloseFailed` | FDMS validation failed | Check `fiscalDayClosingErrorCode` in logs |
+| Empty response | FDMS returned nothing | Retry after a delay |
+
+---
+
+### `FiscalDayError`
+
+Raised when a fiscal day cannot be opened.
+
+Common causes:
+- A fiscal day is already open
+- FDMS rejected the open request
+- Previous fiscal day was not closed
+
+---
+
+### `ConfigurationError`
+
+Raised when configuration is missing or sync fails.
+
+Common causes:
+- `init_device` was not run
+- FDMS unreachable during configuration sync
+- Configuration sync after `open_day()` failed (day opened, config not updated)
+
+---
+
+### `CertificateError`
+
+Raised when certificate issuance or renewal fails.
+
+Common causes:
+- FDMS rejected the certificate request
+- Device not registered with ZIMRA
+- Network failure during certificate request
+
+---
+
+### `DevicePingError`
+
+Raised when the device ping to FDMS fails.
+
+---
+
+### `StatusError`
+
+Raised when the status fetch from FDMS fails.
+
+---
+
+### `DeviceRegistrationError`
+
+Raised during `init_device` if ZIMRA rejects the registration request.
+
+Common causes:
+- Invalid activation key
+- Device ID already registered
+- Network failure
+
+---
+
+### `CryptoError`
+
+Raised when RSA signing, hashing, or key generation fails.
+
+---
+
+## HTTP Status Codes (REST API)
+
+| HTTP Status | Meaning |
+|-------------|---------|
+| `200 OK` | Success |
+| `201 Created` | Receipt submitted successfully |
+| `400 Bad Request` | Invalid request — fiscal day already open, no open day to close |
+| `404 Not Found` | No device registered |
+| `405 Method Not Allowed` | Wrong HTTP method |
+| `422 Unprocessable Entity` | FDMS rejected the request |
+| `500 Internal Server Error` | Unexpected server error |
+
+---
+
+## Logging
+
+FiscGuy uses `loguru` for structured logging. All service operations log at appropriate levels:
+
+```python
+# In your Django project — configure loguru sink
+from loguru import logger
+
+logger.add("fiscguy.log", level="INFO", rotation="1 day")
+```
+
+Key log events:
+
+| Level | Event |
+|-------|-------|
+| `INFO` | Receipt submitted, day opened/closed, client initialised |
+| `WARNING` | FDMS offline (receipt queued), global number mismatch |
+| `ERROR` | Receipt submission failed, close day failed |
+| `EXCEPTION` | Unexpected errors with full traceback |
diff --git a/docs/fiscal-counters.md b/docs/fiscal-counters.md
new file mode 100644
index 0000000..36e4e95
--- /dev/null
+++ b/docs/fiscal-counters.md
@@ -0,0 +1,127 @@
+# Fiscal Counters
+
+Fiscal counters are running totals that accumulate throughout a fiscal day. At close of day they are submitted to ZIMRA as part of the closing payload and used to verify the hash signature.
+
+---
+
+## Counter Types
+
+| Counter | Tracks | By Tax | By Currency | By Payment |
+|---------|--------|--------|-------------|------------|
+| `SaleByTax` | Total sales amount including tax | ✓ | ✓ | |
+| `SaleTaxByTax` | Tax portion of sales | ✓ | ✓ | |
+| `CreditNoteByTax` | Total credit note amounts | ✓ | ✓ | |
+| `CreditNoteTaxByTax` | Tax portion of credit notes | ✓ | ✓ | |
+| `DebitNoteByTax` | Total debit note amounts | ✓ | ✓ | |
+| `DebitNoteTaxByTax` | Tax portion of debit notes | ✓ | ✓ | |
+| `BalanceByMoneyType` | Total collected by payment method | | ✓ | ✓ |
+
+---
+
+## How Counters Are Updated
+
+Every time a receipt is submitted, `_update_fiscal_counters_inner` runs automatically. You never need to update counters manually.
+
+### Fiscal Invoice
+
+```
+SaleByTax += salesAmountWithTax (per tax group)
+SaleTaxByTax += taxAmount (per tax group, non-exempt/non-zero only)
+BalanceByMoneyType += paymentAmount (per payment method)
+```
+
+### Credit Note
+
+Credit note values are **negative** — each counter decreases:
+
+```
+CreditNoteByTax += salesAmountWithTax (negative, per tax group)
+CreditNoteTaxByTax += taxAmount (negative, non-exempt/non-zero only)
+BalanceByMoneyType += paymentAmount (negative)
+```
+
+### Debit Note
+
+```
+DebitNoteByTax += salesAmountWithTax (per tax group)
+DebitNoteTaxByTax += taxAmount (non-exempt/non-zero only)
+BalanceByMoneyType += paymentAmount
+```
+
+---
+
+## Counter Rows in the Database
+
+Each unique combination of `(counter_type, currency, tax_id, tax_percent, money_type, fiscal_day)` gets its own `FiscalCounter` row. On first encounter it is created; on subsequent receipts it is incremented.
+
+```python
+from fiscguy.models import FiscalCounter, FiscalDay
+
+day = FiscalDay.objects.filter(is_open=True).first()
+counters = day.counters.all()
+
+for c in counters:
+ print(c.fiscal_counter_type, c.fiscal_counter_currency, c.fiscal_counter_value)
+```
+
+---
+
+## Zero-Value Counters
+
+Per ZIMRA spec (section 4.11): **zero-value counters must not be submitted** to FDMS. FiscGuy automatically excludes them from the closing payload and closing string.
+
+---
+
+## Closing String Format
+
+At `close_day()`, all counters are concatenated into a single string for signing. The format per ZIMRA spec section 13.3.1:
+
+```
+{deviceID}{fiscalDayNo}{fiscalDayDate}{counters...}
+```
+
+Each counter line is:
+
+```
+{TYPE}{CURRENCY}[L]{TAX_PERCENT_OR_MONEY_TYPE}{VALUE_IN_CENTS}
+```
+
+Rules:
+- All text **uppercase**
+- Amounts in **cents** (multiply by 100, integer, negative for credit notes)
+- Tax percent always **two decimal places** (`15.00`, `0.00`, `14.50`)
+- Exempt entries use **empty string** for tax percent (nothing between currency and value)
+- `BalanceByMoneyType` has a literal **`L`** between currency and money type (e.g. `BALANCEBYMONEYTYPEUSDLCASH3700`)
+- Ordered by: counter type ascending → currency ascending → taxID/moneyType ascending
+
+Example:
+
+```
+23265842026-03-30
+SALEBYTAXZWG0.005000
+SALEBYTAXZWG15.50134950
+SALETAXBYTAXZWG15.5018110
+BALANCEBYMONEYTYPEZWGLCARD69975
+BALANCEBYMONEYTYPEZWGLCASH69975
+```
+
+(joined as one string, no newlines)
+
+---
+
+## Resetting Counters
+
+Counters reset automatically when a fiscal day is closed. The next `open_day()` starts fresh from zero.
+
+If you need to inspect counters mid-day:
+
+```python
+from fiscguy.models import FiscalDay, FiscalCounter
+
+fiscal_day = FiscalDay.objects.filter(is_open=True).first()
+print(fiscal_day.counters.all().values(
+ "fiscal_counter_type",
+ "fiscal_counter_currency",
+ "fiscal_counter_value",
+))
+```
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 0000000..aa790d6
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,201 @@
+# Installation & Setup
+
+## Requirements
+
+- Python 3.11, 3.12, or 3.13
+- Django 4.2+
+- Django REST Framework 3.14+
+
+---
+
+## Install
+
+### From PyPI
+
+```bash
+pip install fiscguy
+```
+
+### From source
+
+```bash
+git clone https://github.com/digitaltouchcode/fisc.git
+cd fisc
+pip install -e ".[dev]"
+```
+
+---
+
+## Django Setup
+
+### 1. Add to `INSTALLED_APPS`
+
+```python
+# settings.py
+INSTALLED_APPS = [
+ "django.contrib.contenttypes",
+ "django.contrib.auth",
+ "rest_framework",
+ "fiscguy",
+ # ... your apps
+]
+```
+
+### 2. Run migrations
+
+```bash
+python manage.py migrate
+```
+
+### 3. Include URLs
+
+```python
+# your project urls.py
+from django.urls import path, include
+
+urlpatterns = [
+ path("fiscguy/", include("fiscguy.urls")),
+]
+```
+
+### 4. Media files (for QR codes)
+
+FiscGuy saves receipt QR codes to `MEDIA_ROOT`. Configure it in settings:
+
+```python
+MEDIA_URL = "/media/"
+MEDIA_ROOT = BASE_DIR / "media"
+```
+
+And serve media in development:
+
+```python
+# urls.py
+from django.conf import settings
+from django.conf.urls.static import static
+
+urlpatterns = [
+ ...
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+```
+
+---
+
+## Device Initialisation
+
+Run once per device. This is the most important setup step:
+
+```bash
+python manage.py init_device
+```
+
+You will be prompted for:
+
+| Prompt | Example | Description |
+|--------|---------|-------------|
+| Organisation name | `ACME Ltd` | Your company name |
+| Activation key | `ABC-123-XYZ` | Provided by ZIMRA |
+| Device ID | `23265` | Provided by ZIMRA |
+| Device model name | `FiscGuy-v1` | Your device model |
+| Device model version | `1.0.0` | Your device version |
+| Device serial number | `SN0001` | Your device serial |
+| Production? | `y/n` | Use production or test FDMS |
+
+The command then:
+1. Creates the `Device` record in your database
+2. Generates an RSA key pair and CSR
+3. Registers the device with ZIMRA FDMS
+4. Obtains a signed certificate and stores it in `Certs`
+5. Fetches and persists taxpayer configuration and taxes
+
+---
+
+## Verify Setup
+
+```python
+from fiscguy.models import Device, Configuration, Taxes
+
+# Device should exist
+device = Device.objects.first()
+print(device) # "ACME Ltd - 23265"
+
+# Config should be populated
+config = Configuration.objects.first()
+print(config.tax_payer_name)
+
+# Taxes should be populated
+print(Taxes.objects.all())
+```
+
+---
+
+## Environment: Test vs Production
+
+`init_device` asks whether to use the production or testing FDMS. This sets `Device.production` and `Certs.production`, which determines which FDMS URL is used:
+
+| Environment | URL |
+|-------------|-----|
+| Testing | `https://fdmsapitest.zimra.co.zw` |
+| Production | `https://fdmsapi.zimra.co.zw` |
+
+To switch environments, re-run `init_device`.
+
+---
+
+## Development Dependencies
+
+```bash
+pip install -e ".[dev]"
+```
+
+Includes: `pytest`, `pytest-django`, `pytest-cov`, `black`, `isort`, `flake8`, `pylint`, `mypy`, `django-stubs`.
+
+### Pre-commit hooks
+
+```bash
+pre-commit install
+```
+
+Runs `black`, `isort`, and `flake8` on every commit.
+
+---
+
+## Troubleshooting
+
+### `RuntimeError: No Device found`
+
+Run `python manage.py init_device`.
+
+### `RuntimeError: ZIMRA configuration missing`
+
+The `Configuration` record is missing. Either `init_device` didn't complete, or run:
+
+```python
+from fiscguy import get_configuration
+get_configuration()
+```
+
+### `MalformedFraming: Unable to load PEM file`
+
+The certificate stored in `Certs` is corrupted or missing. Re-run `init_device`.
+
+### `No open fiscal day`
+
+Open a fiscal day before submitting receipts:
+
+```python
+from fiscguy import open_day
+open_day()
+```
+
+### Certificate expired
+
+```bash
+# via REST endpoint
+POST /fiscguy/issue-certificate/
+
+# or via Python
+from fiscguy.services.certs_service import CertificateService
+from fiscguy.models import Device
+CertificateService(Device.objects.first()).issue_certificate()
+```
diff --git a/docs/receipt-types.md b/docs/receipt-types.md
new file mode 100644
index 0000000..70626e3
--- /dev/null
+++ b/docs/receipt-types.md
@@ -0,0 +1,186 @@
+# Receipt Types
+
+FiscGuy supports three receipt types as defined by the ZIMRA Fiscal Device Gateway API.
+
+---
+
+## Fiscal Invoice (`fiscalinvoice`)
+
+A standard sale receipt. The most common receipt type.
+
+```python
+from fiscguy import submit_receipt
+
+receipt = submit_receipt({
+ "receipt_type": "fiscalinvoice",
+ "currency": "USD",
+ "total_amount": "115.00",
+ "payment_terms": "Cash",
+ "lines": [
+ {
+ "product": "Product Name",
+ "quantity": "1",
+ "unit_price": "115.00",
+ "line_total": "115.00",
+ "tax_amount": "15.00",
+ "tax_name": "standard rated 15%",
+ }
+ ],
+})
+```
+
+**Counter impact:**
+
+| Counter | Value |
+|---------|-------|
+| `SaleByTax` | `+salesAmountWithTax` per tax group |
+| `SaleTaxByTax` | `+taxAmount` per tax group (non-exempt, non-zero only) |
+| `BalanceByMoneyType` | `+paymentAmount` |
+
+**Rules:**
+- `receiptTotal` must be `>= 0`
+- `receiptLinePrice` must be `> 0` for Sale lines
+- `paymentAmount` must be `>= 0`
+
+---
+
+## Credit Note (`creditnote`)
+
+A return or reversal against a previously issued fiscal invoice.
+
+```python
+receipt = submit_receipt({
+ "receipt_type": "creditnote",
+ "currency": "USD",
+ "total_amount": "-115.00", # Must be <= 0
+ "payment_terms": "Cash",
+ "credit_note_reason": "Customer returned goods — defective",
+ "credit_note_reference": "R-00000142", # Original receipt number
+ "lines": [
+ {
+ "product": "Product Name",
+ "quantity": "1",
+ "unit_price": "-115.00", # Must be < 0 for Sale lines
+ "line_total": "-115.00",
+ "tax_amount": "-15.00",
+ "tax_name": "standard rated 15%",
+ }
+ ],
+})
+```
+
+**Counter impact:** Credit note amounts are **negative**, so each counter decreases:
+
+| Counter | Value |
+|---------|-------|
+| `CreditNoteByTax` | `+salesAmountWithTax` (negative) per tax group |
+| `CreditNoteTaxByTax` | `+taxAmount` (negative) per tax group (non-exempt, non-zero only) |
+| `BalanceByMoneyType` | `+paymentAmount` (negative) |
+
+**Rules (ZIMRA spec):**
+- `receiptTotal` must be `<= 0`
+- `receiptNotes` (credit_note_reason) is **mandatory**
+- `creditDebitNote` reference to original invoice is **mandatory**
+- Original receipt must exist in FDMS (RCPT032)
+- Original receipt must have been issued within the last 12 months (RCPT033)
+- Total credit amount must not exceed the original receipt amount net of prior credits (RCPT035)
+- Tax types must be a subset of those on the original invoice — you cannot introduce new tax types (RCPT036)
+- Currency must match the original invoice (RCPT043)
+- `receiptLinePrice` must be `< 0` for Sale lines
+- `paymentAmount` must be `<= 0`
+
+---
+
+## Debit Note (`debitnote`)
+
+An upward adjustment against a previously issued fiscal invoice (e.g. additional charges).
+
+```python
+receipt = submit_receipt({
+ "receipt_type": "debitnote",
+ "currency": "USD",
+ "total_amount": "23.00",
+ "payment_terms": "Card",
+ "credit_note_reason": "Additional delivery charge",
+ "credit_note_reference": "R-00000142",
+ "lines": [
+ {
+ "product": "Delivery fee",
+ "quantity": "1",
+ "unit_price": "23.00",
+ "line_total": "23.00",
+ "tax_amount": "3.00",
+ "tax_name": "standard rated 15%",
+ }
+ ],
+})
+```
+
+**Counter impact:**
+
+| Counter | Value |
+|---------|-------|
+| `DebitNoteByTax` | `+salesAmountWithTax` per tax group |
+| `DebitNoteTaxByTax` | `+taxAmount` per tax group |
+| `BalanceByMoneyType` | `+paymentAmount` |
+
+---
+
+## Payment Methods
+
+| Value | Description |
+|-------|-------------|
+| `Cash` | Physical cash |
+| `Card` | Credit or debit card |
+| `MobileWallet` | Mobile money (EcoCash, etc.) |
+| `BankTransfer` | Direct bank transfer |
+| `Coupon` | Voucher or coupon |
+| `Credit` | Credit account |
+| `Other` | Any other method |
+
+---
+
+## Tax Types
+
+Taxes are fetched from FDMS on every `open_day()` and stored in the `Taxes` model.
+
+```python
+from fiscguy.models import Taxes
+
+for tax in Taxes.objects.all():
+ print(f"{tax.tax_id}: {tax.name} @ {tax.percent}%")
+```
+
+Typical ZIMRA tax types:
+
+| Tax ID | Name | Percent |
+|--------|------|---------|
+| 1 | Exempt | 0% |
+| 2 | Zero Rated 0% | 0% |
+| 3+ | Standard Rated | 15% or 15.5% |
+
+When building a receipt line, pass the `tax_name` exactly as it appears in `Taxes.name` so the correct `tax_id` and `tax_percent` are resolved.
+
+---
+
+## Buyer Data (Optional)
+
+Attach buyer registration data to any receipt type:
+
+```python
+# Create a buyer first
+from fiscguy.models import Buyer
+buyer = Buyer.objects.create(
+ name="ACME Corp",
+ tin_number="1234567890",
+ email="accounts@acme.co.zw",
+)
+
+# Pass buyer ID in receipt payload
+receipt = submit_receipt({
+ ...
+ "buyer": buyer.id,
+})
+```
+
+ZIMRA requires both `buyerRegisterName` and `buyerTIN` if buyer data is included (RCPT043).
diff --git a/fiscguy/exceptions.py b/fiscguy/exceptions.py
index f42d40a..10216c3 100644
--- a/fiscguy/exceptions.py
+++ b/fiscguy/exceptions.py
@@ -1,114 +1,118 @@
class FiscalisationError(Exception):
- """Base exception for all fiscalisation service errors."""
+ """Base exception for all fiscalisation-related errors."""
pass
class CertNotFoundError(FiscalisationError):
- """Raised when a certificate is not found."""
+ """Raised when a required certificate cannot be found."""
pass
class CryptoError(FiscalisationError):
- """Raised when cryptographic operations fail."""
+ """Raised when a cryptographic operation fails."""
pass
class PersistenceError(FiscalisationError):
- """Raised when database persistence operations fail."""
+ """Raised when a database persistence operation fails."""
pass
class RegistrationError(FiscalisationError):
- """Raised when device registration fails."""
+ """Raised when a general registration process fails."""
pass
class DeviceNotFoundError(FiscalisationError):
- """Raised when a device is not found."""
+ """Raised when a requested device cannot be found."""
pass
class TenantNotFoundError(FiscalisationError):
- """Raised when a tenant is not found."""
+ """Raised when a tenant cannot be found."""
pass
class ZIMRAAPIError(FiscalisationError):
- """Raised when ZIMRA API calls fail."""
+ """Raised when a ZIMRA API request fails or returns an error."""
pass
class ValidationError(FiscalisationError):
- """Raised when data validation fails."""
+ """Raised when input data fails validation checks."""
pass
class AuthenticationError(FiscalisationError):
- """Raised when authentication fails."""
+ """Raised when authentication fails or credentials are invalid."""
pass
class ConfigurationError(FiscalisationError):
- """Raised when configuration is invalid or missing."""
+ """Raised when required configuration is missing or invalid."""
pass
class TaxError(FiscalisationError):
- """Raised when tax crud operations fail."""
+ """Raised when tax-related operations fail."""
pass
class FiscalDayError(FiscalisationError):
- """Raised when fiscal day opening fails"""
+ """Raised when opening a fiscal day fails."""
pass
class ReceiptSubmissionError(FiscalisationError):
- """Rasied when a receipt submission fails"""
+ """Raised when submission of a receipt fails."""
pass
class DeviceRegistrationError(FiscalisationError):
- """Raised when device registration fails"""
+ """Raised when device registration fails."""
pass
class CertificateError(FiscalisationError):
- """Raised when they is a cerificate error"""
+ """Raised when there is a certificate-related error."""
pass
class StatusError(FiscalisationError):
+ """Raised when an invalid or unexpected status is encountered."""
+
pass
class DevicePingError(FiscalisationError):
+ """Raised when a device ping or connectivity check fails."""
+
pass
class ZIMRAClientError(FiscalisationError):
+ """Raised when the ZIMRA client encounters an internal error."""
+
pass
class CloseDayError(FiscalisationError):
- pass
+ """Raised when closing a fiscal day fails."""
-
-class CertificateError(FiscalisationError):
pass