--- since: 1.2.1 --- # Finance App — Technical Documentation **Application:** `finance` **Project:** DjangoPlay **Type:** Django application **Primary domain:** Financial identity, invoicing, taxation, payments, credit notes, and scheduled billing **Status:** Active implementation **Source of truth:** `webapp/finance/` --- ## 1. Overview The `finance` app is the consolidated financial domain of DjangoPlay. It provides: - Financial identity data for business entities - Structured billing addresses and contacts - Tax profiles and tax identifiers - Configurable tax rates - Country-specific tax-policy resolution - Invoice creation and lifecycle management - Invoice line-item calculation - Payment recording and outstanding-balance management - Credit notes for post-finalization adjustments - Recurring billing schedules - Financial document numbering - Finance configuration - REST API access - Django Admin management - Scheduled invoice generation - Development/test data generation through `devtools` The app replaces the former `fincore` and `invoices` applications. The current architecture intentionally keeps the finance domain inside the Django project. It is structured so that the domain can be extracted later if a genuine second consumer requires a standalone package. --- ## 2. Architectural Goals The finance architecture follows these principles: 1. **Models are persistence-focused.** 2. **Business rules live in services and workflows.** 3. **Invoice lifecycle transitions have one state-machine implementation.** 4. **Financial mutations occur through explicit workflows.** 5. **Tax calculation is policy-driven rather than hardcoded into invoices.** 6. **Tax rates are database configuration rather than source-code constants.** 7. **Finalized invoices are immutable.** 8. **Credit notes are used for post-finalization reductions.** 9. **Totals are calculated deterministically.** 10. **Financial document numbering is centralized and concurrency-safe.** 11. **Audit/history behavior follows the application's common model conventions.** 12. **The API layer delegates mutations to workflows rather than calling model `.save()` directly.** 13. **Configuration that may legitimately change operationally is database-backed.** 14. **DTOs are introduced only at actual service boundaries.** --- ## 3. Scope ### Included - Finance configuration - Entity-to-finance mapping - Addresses - Contacts - Tax profiles - Tax rates - Payment methods - Invoices - Invoice line items - Payments - Credit notes - Billing schedules - Document sequences - Tax policy resolution - India GST calculation - No-tax fallback policy - Invoice state management - Invoice totals - API endpoints - Admin management - Scheduled invoice generation - Finance data-generation services ### Currently outside the domain The following are intentionally not implemented as part of the current finance app: - Double-entry accounting ledger - General ledger - Journal entries - Accounts payable - Accounts receivable subledger - Bank reconciliation - Payment-provider integrations - Payment allocation across multiple invoices - Refund workflow - Debit-note workflow - Financial reporting warehouse - Financial analytics platform - Event sourcing - CQRS - Event bus - AI/ML financial processing - Multi-tenant runtime isolation - Plugin marketplace These should only be introduced when there is a concrete product requirement. --- ## 4. Application Dependencies The finance application participates in the following dependency chain: ```text authx / users / teamcentral | v core / audit | v policyengine | v locations | v entities | v finance ``` ### Primary dependencies #### `core` Provides common model infrastructure such as: - `TimeStampedModel` - `AuditFieldsModel` - `ActiveManager` - soft-delete behavior - audit lifecycle integration #### `audit` Tracks financial model lifecycle events and historical changes. #### `locations` Provides geographic reference data used by finance: - country - region - subregion - city #### `entities` Provides the business entities that participate in financial transactions. Examples: - invoice issuer - invoice recipient - billing customer - finance entity mapping #### `policyengine` Provides application-level permission infrastructure. Finance adds a local `FinanceActionPermission` because custom money-related API actions require explicit permission handling. --- ## 5. Application Structure ```text finance/ ├── __init__.py ├── apps.py ├── constants.py ├── urls.py │ ├── models/ │ ├── __init__.py │ ├── configuration.py │ ├── entity_mapping.py │ ├── address.py │ ├── contact.py │ ├── tax_profile.py │ ├── tax_rate.py │ ├── document_sequence.py │ ├── invoice.py │ ├── line_item.py │ ├── payment.py │ ├── payment_method.py │ ├── billing_schedule.py │ └── credit_note.py │ ├── services/ │ ├── __init__.py │ ├── dto.py │ ├── exceptions.py │ ├── config.py │ ├── compliance.py │ ├── numbering.py │ ├── state_machine.py │ │ │ ├── calculators/ │ │ ├── __init__.py │ │ ├── totals.py │ │ └── tax.py │ │ │ ├── tax/ │ │ ├── __init__.py │ │ ├── resolver.py │ │ ├── rate_lookup.py │ │ └── policies/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── india_gst.py │ │ └── no_tax.py │ │ │ └── workflows/ │ ├── __init__.py │ ├── identity_workflow.py │ ├── invoice_workflow.py │ ├── payment_workflow.py │ ├── credit_note_workflow.py │ └── billing_schedule_workflow.py │ ├── api/ │ ├── __init__.py │ ├── serializers.py │ ├── views.py │ ├── urls.py │ ├── permissions.py │ └── exceptions.py │ ├── admin/ │ ├── __init__.py │ ├── configuration.py │ ├── address.py │ ├── contact.py │ ├── tax_profile.py │ ├── tax_rate.py │ ├── payment_method.py │ ├── invoice.py │ ├── line_item.py │ ├── payment.py │ ├── billing_schedule.py │ ├── credit_note.py │ ├── _permissions.py │ └── _workflow_action_views.py │ ├── forms/ │ ├── __init__.py │ ├── configuration.py │ ├── address.py │ ├── contact.py │ ├── tax_profile.py │ ├── tax_rate.py │ └── payment_method.py │ ├── management/ │ └── commands/ │ ├── __init__.py │ ├── generate_scheduled_invoices.py │ └── seed_finance_tax_rates.py │ └── migrations/ ├── 0001_initial.py ├── 0002_initial.py ├── 0003_initial.py └── __init__.py ``` --- ## 6. Domain Model ### 6.1 FinanceConfiguration **Table:** `finance_configuration` Singleton configuration for finance-wide operational defaults. Fields: | Field | Purpose | |---|---| | `default_country` | Default country used for tax-policy resolution | | `default_issuing_entity` | Issuer used for system-generated scheduled invoices | The singleton is enforced through `pk=1`. Configuration is cached for five minutes. Use: ```python FinanceConfiguration.load() ``` Do not use: ```python FinanceConfiguration.objects.first() ``` inside domain services. The configuration is intended to be changed through Django Admin rather than hardcoded into application logic. --- ### 6.2 FinanceEntityMapping **Table:** `finance_entity_mapping` Provides a finance-specific mapping layer between financial resources and entity-like objects. Fields: - `entity_uuid` - `entity_type` - `entity_id` - `content_type` It is used by: - addresses - contacts - tax profiles This allows finance identity data to be associated with an entity without coupling every finance identity model directly to one concrete entity implementation. --- ### 6.3 Address **Table:** `finance_address` Represents a structured financial address. Fields include: - `entity_mapping` - `address_type` - `street_address` - `city` - `postal_code` - `country` - `region` - `subregion` - `is_default` Address validation belongs to: ```text finance.services.compliance ``` Address creation/update belongs to: ```text finance.services.workflows.identity_workflow ``` --- ### 6.4 Contact **Table:** `finance_contact` Represents a financial/business contact. Fields include: - `entity_mapping` - `name` - `email` - `phone_number` - `role` - `country` - `is_primary` Validation is performed through the compliance service. --- ### 6.5 TaxProfile **Table:** `finance_tax_profile` Stores entity-level tax identity and exemption information. Fields include: - `entity_mapping` - `tax_identifier` - `tax_identifier_type` - `is_tax_exempt` - `tax_exemption_reason` - `tax_exemption_document` - `country` - `region` A `TaxProfile` describes **who the entity is for tax purposes**. It does not define the tax rate. --- ### 6.6 TaxRate **Table:** `finance_tax_rate` Stores effective-dated tax-rate configuration. Fields: - `country_code` - `region_code` - `hsn_sac_code` - `rate_type` - `rate_percent` - `effective_from` - `effective_to` The distinction is important: ```text TaxProfile = entity tax identity TaxRate = tax configuration/reference data ``` Tax rates are managed independently so that one rate can apply to many entities and invoices. --- ### 6.7 DocumentSequence **Table:** `finance_document_sequence` Stores the current sequence for financial document numbering. Unique key: ```text issuer + document_type + period ``` Supported document types: - `INVOICE` - `CREDIT_NOTE` - `DEBIT_NOTE` `DEBIT_NOTE` is currently reserved for future workflow support. --- ### 6.8 Invoice **Table:** `finance_invoice` The primary financial document. Core relationships: ```text Invoice ├── issuer -> entities.Entity ├── recipient -> entities.Entity ├── billing_address -> finance.Address ├── billing_country -> locations.CustomCountry ├── billing_region -> locations.CustomRegion ├── line_items -> finance.LineItem ├── payments -> finance.Payment └── credit_notes -> finance.CreditNote ``` Important fields: - `invoice_number` - `description` - `issuer` - `recipient` - `billing_address` - `billing_country` - `billing_region` - `issue_date` - `due_date` - `status` - `payment_terms` - `currency` - `subtotal_amount` - `tax_total_amount` - `total_amount` - `tax_exemption_status` - `tax_policy_name` - `tax_breakdown` - `issuer_gstin` - `recipient_gstin` Invoice totals are stored for efficient access, but are produced by the workflow/calculator layer rather than model `save()` logic. --- ### 6.9 LineItem **Table:** `finance_line_item` Represents an invoice line. Fields: - `invoice` - `description` - `hsn_sac_code` - `quantity` - `unit_price` - `discount` - `subtotal_amount` - `tax_total_amount` - `total_amount` - `tax_breakdown` Database constraints enforce: - quantity > 0 - unit price >= 0 - discount >= 0 Line items are created/removed through invoice workflows. --- ### 6.10 PaymentMethod **Table:** `finance_payment_method` Reference data describing accepted payment methods. Examples depend on the configured choices in `finance.constants`. The API exposes this resource as read-only. Operational management is performed through Admin. --- ### 6.11 Payment **Table:** `finance_payment` Records a payment against an invoice. Fields: - `invoice` - `amount` - `payment_date` - `payment_method` - `payment_reference` - `status` Payment creation is handled by: ```text finance.services.workflows.payment_workflow.record_payment() ``` The workflow prevents payment amounts from exceeding the outstanding invoice balance. --- ### 6.12 CreditNote **Table:** `finance_credit_note` Represents a financial adjustment against a finalized invoice. Fields: - `invoice` - `credit_note_number` - `amount` - `reason` - `issued_at` A credit note is the sanctioned mechanism for reducing a finalized invoice. It cannot exceed the invoice's total credited amount. --- ### 6.13 BillingSchedule **Table:** `finance_billing_schedule` Represents recurring billing. Fields: - `customer` - `description` - `frequency` - `start_date` - `end_date` - `next_billing_date` - `amount` - `status` Supported frequencies are defined by `finance.constants`. The schedule stores the **customer**, not the issuer. The issuer is resolved from: ```text FinanceConfiguration.default_issuing_entity ``` This prevents the system from guessing who should issue a scheduled invoice. --- ## 7. Invoice Lifecycle The invoice state machine is defined exclusively in: ```text finance.services.state_machine ``` ### States ```text DRAFT PENDING SENT PARTIALLY_PAID PAID OVERDUE CANCELLED VOID REFUNDED ``` ### Transition graph ```text DRAFT ├──> PENDING └──> CANCELLED PENDING ├──> DRAFT ├──> SENT └──> CANCELLED SENT ├──> PARTIALLY_PAID ├──> PAID ├──> OVERDUE └──> VOID PARTIALLY_PAID ├──> PAID ├──> OVERDUE └──> VOID OVERDUE ├──> PARTIALLY_PAID ├──> PAID └──> VOID PAID └──> REFUNDED CANCELLED └── terminal VOID └── terminal REFUNDED └── terminal ``` ### Editable states Only: ```text DRAFT PENDING ``` are editable. ### Terminal states ```text CANCELLED VOID REFUNDED ``` No other code should directly assign: ```python invoice.status = ... ``` Use the state machine/workflow instead. --- ## 8. Invoice Immutability Once an invoice reaches `SENT`, direct invoice editing is prohibited. This protects the historical integrity of a finalized financial document. For example: ```text DRAFT/PENDING | | edit line items v SENT | | adjustment required v CreditNote ``` The system does not support editing a finalized invoice to change its amount. Credit notes provide the controlled adjustment mechanism. --- ## 9. Invoice Creation Workflow The canonical invoice creation path is: ```text API / scheduled billing | v InvoiceCreateInput | v invoice_workflow.create_invoice() | +--> validate input | +--> load issuer | +--> load recipient | +--> load billing address | +--> generate document number | +--> create invoice | +--> calculate line subtotals | +--> resolve tax policy | +--> calculate line taxes | +--> create line items | +--> calculate invoice totals | v Invoice ``` An invoice must contain at least one line item. --- ## 10. Invoice Numbering Document numbering is centralized in: ```text finance.services.numbering.generate_document_number() ``` ### Format ```text {PREFIX}-{ISSUER_ID}-{YEAR}-{SEQUENCE:06d} ``` Examples: ```text INV-7963-2026-000123 CN-7963-2026-000045 ``` ### Prefixes | Document | Prefix | |---|---| | Invoice | `INV` | | Credit Note | `CN` | | Debit Note | `DN` | The sequence is scoped by: ```text issuer document_type year ``` The sequence row is locked using: ```python select_for_update() ``` inside a database transaction. This prevents concurrent requests from receiving the same document number. --- ## 11. Totals Calculation Totals are calculated by pure functions in: ```text finance.services.calculators.totals ``` ### Line subtotal ```text (quantity × unit_price) − discount ``` The result cannot be negative. ### Invoice subtotal ```text sum(line_item.subtotal_amount) ``` ### Invoice tax total ```text sum(line_item.tax_total_amount) ``` ### Invoice grand total ```text subtotal + tax_total ``` ### Outstanding balance ```text invoice_total − completed_payments ``` The minimum outstanding balance is zero. --- ## 12. Tax Architecture Tax processing is deliberately separated into: ```text Resolver | v Policy | v Rate Lookup | v TaxRate ``` ### Components ```text services/tax/resolver.py services/tax/rate_lookup.py services/tax/policies/base.py services/tax/policies/india_gst.py services/tax/policies/no_tax.py ``` --- ## 13. Tax Policy Resolver The policy registry currently contains: ```python POLICY_REGISTRY = { "IN": IndiaGSTPolicy, } ``` The resolver determines the policy from the country code. The default country is obtained from: ```text FinanceConfiguration.default_country ``` It is not hardcoded as the operational default. ### Unmapped countries The current configuration has: ```python FALLBACK_TO_NO_TAX = True ``` Therefore an unmapped country resolves to `NoTaxPolicy`. This is useful while only a subset of countries has implemented tax policies. For a production environment with multiple actively billed countries, consider disabling the fallback so missing tax support becomes an explicit configuration error rather than a zero-tax result. --- ## 14. India GST The current implemented country policy is: ```text IndiaGSTPolicy ``` GST is determined from: - supplier country - supplier region - place-of-supply region - HSN/SAC code - tax exemption status - effective tax rate ### Interstate supply If supplier and place-of-supply regions differ: ```text IGST ``` is calculated. ### Intrastate supply If the supply is within the same state: ```text CGST + SGST ``` is calculated. The total configured GST rate is split equally between CGST and SGST. Example: ```text GST rate = 18% CGST = 9% SGST = 9% ``` Interstate: ```text IGST = 18% ``` --- ## 15. Tax Rate Lookup The rate lookup service is the only service that queries `TaxRate` for GST calculation. Lookup precedence: ```text 1. HSN/SAC-specific effective rate 2. General country effective rate 3. TaxRateNotConfigured ``` An effective rate must satisfy: ```text effective_from <= calculation_date AND effective_to is NULL OR effective_to >= calculation_date ``` If no applicable rate exists, the service raises: ```text TaxRateNotConfigured ``` The system does not silently invent a tax rate. --- ## 16. Tax Snapshots Invoices and line items store tax calculation results as JSON snapshots. Example conceptual structure: ```json [ { "label": "CGST", "rate_percent": "9.00", "amount": "90.00" }, { "label": "SGST", "rate_percent": "9.00", "amount": "90.00" } ] ``` The invoice also stores: ```text tax_policy_name tax_breakdown ``` The purpose is historical reproducibility. If tax configuration changes later, an already finalized invoice retains the tax calculation that was applied to it. --- ## 17. Tax Exemption If the invoice is tax-exempt, the tax policy returns: ```text total_tax = 0 ``` and an empty tax-line breakdown. The invoice's exemption status is preserved as part of the invoice data. --- ## 18. Identity and Compliance Layer Validation is centralized in: ```text finance.services.compliance ``` The main validation functions cover: ```text validate_address() validate_contact() validate_tax_profile() ``` The corresponding mutations are performed by: ```text finance.services.workflows.identity_workflow ``` ### Correct pattern ```text DTO | v compliance validation | v identity workflow | v model persistence ``` ### Avoid ```python Address.objects.create(...) TaxProfile.objects.create(...) Contact.objects.create(...) ``` from application/business code. The workflow guarantees validation and audit-user stamping are consistently applied. --- ## 19. Payment Workflow Payments are created through: ```text payment_workflow.record_payment() ``` The workflow: 1. Rejects payments for `DRAFT` and `PENDING` invoices. 2. Rejects terminal invoices. 3. Requires a positive payment amount. 4. Calculates completed payments. 5. Calculates outstanding balance. 6. Rejects overpayment. 7. Loads an active payment method. 8. Creates the payment. 9. Transitions the invoice: - to `PARTIALLY_PAID` when balance remains - to `PAID` when fully paid Conceptually: ```text Invoice total | v Outstanding balance | +---- payment < balance ----> PARTIALLY_PAID | +---- payment = balance ----> PAID | +---- payment > balance ----> REJECT ``` --- ## 20. Credit Note Workflow Credit notes are created through: ```text credit_note_workflow.issue_credit_note() ``` Eligible invoice states: ```text SENT PARTIALLY_PAID PAID OVERDUE ``` A credit note: - must be positive - must reference an eligible invoice - cannot make cumulative credits exceed the invoice total - receives a centralized document number - is created transactionally Conceptually: ```text Finalized Invoice | v Credit Note | v Reduced economic exposure ``` The invoice itself is not rewritten. --- ## 21. Billing Schedule Workflow Scheduled billing is implemented by: ```text billing_schedule_workflow.generate_due_invoices() ``` The process is: ```text ACTIVE BillingSchedule | | next_billing_date <= today v resolve configured issuer | v load customer's default address | v create invoice through normal invoice workflow | v advance next_billing_date | v mark COMPLETED when end_date is passed ``` The generated invoice uses the same: - document numbering - tax calculation - totals calculation - audit behavior - invoice workflow as a manually created invoice. This avoids having a second invoice-generation implementation. --- ## 22. Billing Frequency The schedule frequency is defined by: ```text finance.constants.BILLING_FREQUENCY_CHOICES ``` Calendar arithmetic prefers `dateutil.relativedelta`. This means monthly schedules use calendar-month arithmetic rather than blindly adding 30 days. For example: ```text 2026-01-31 + 1 month ``` uses calendar-aware handling instead of assuming: ```text 2026-03-02 ``` from a fixed 30-day increment. --- ## 23. REST API The finance API is registered through: ```text finance/api/urls.py ``` and included through the project's finance URL configuration. Current API resources: ```text /invoices/ /addresses/ /contacts/ /tax-profiles/ /payment-methods/ /tax-rates/ ``` The exact external prefix is determined by the project's root URL configuration. --- ## 24. Invoice API ### List invoices ```http GET /invoices/ ``` ### Retrieve invoice ```http GET /invoices/{id}/ ``` ### Create invoice ```http POST /invoices/ ``` Creation delegates to: ```text invoice_workflow.create_invoice() ``` ### Finalize ```http POST /invoices/{id}/finalize/ ``` ### Cancel ```http POST /invoices/{id}/cancel/ ``` ### Void ```http POST /invoices/{id}/void/ ``` ### Record payment ```http POST /invoices/{id}/record-payment/ ``` ### Issue credit note ```http POST /invoices/{id}/issue-credit-note/ ``` ### Add line item ```http POST /invoices/{id}/line-items/ ``` ### Remove line item ```http DELETE /invoices/{id}/line-items/{line_item_id}/ ``` There is intentionally no generic: ```http PUT /invoices/{id}/ PATCH /invoices/{id}/ ``` Invoice mutations are exposed as explicit domain actions. --- ## 25. Address API ### List ```http GET /addresses/ ``` ### Retrieve ```http GET /addresses/{id}/ ``` ### Create ```http POST /addresses/ ``` Creation delegates to: ```text identity_workflow.create_address() ``` --- ## 26. Contact API ### List ```http GET /contacts/ ``` ### Retrieve ```http GET /contacts/{id}/ ``` ### Create ```http POST /contacts/ ``` Creation delegates to: ```text identity_workflow.create_contact() ``` --- ## 27. Tax Profile API ### List ```http GET /tax-profiles/ ``` ### Retrieve ```http GET /tax-profiles/{id}/ ``` ### Create ```http POST /tax-profiles/ ``` Creation delegates to: ```text identity_workflow.create_tax_profile() ``` --- ## 28. Reference Data APIs ### Payment methods ```http GET /payment-methods/ GET /payment-methods/{id}/ ``` ### Tax rates ```http GET /tax-rates/ GET /tax-rates/{id}/ ``` These APIs are read-only. Management is performed through Django Admin. --- ## 29. API Mutation Rule The finance API follows a strict rule: ```text HTTP request | v DRF serializer | v DTO | v workflow | v model persistence ``` Not: ```text HTTP request | v ModelSerializer.save() | v model ``` This guarantees that API callers cannot bypass: - state-machine rules - tax calculation - compliance validation - numbering - payment validation - credit-note rules - audit stamping --- ## 30. Permissions Finance uses the shared policy-engine permission system. Invoice custom actions use: ```text FinanceActionPermission ``` This exists because custom DRF actions such as: ```text finalize cancel void record_payment issue_credit_note add_line_item remove_line_item ``` are not necessarily covered by a generic CRUD action map. Financial mutations therefore require explicit permission handling. --- ## 31. API Error Handling Finance domain exceptions are handled through: ```text finance.api.exceptions.handle_finance_errors ``` The API returns a consistent structure: ```json { "code": "error_code", "message": "Human-readable message", "details": {} } ``` This keeps domain errors separate from raw Django/DRF implementation exceptions. --- ## 32. DTO Layer DTOs are defined in: ```text finance.services.dto ``` Current DTO categories include: ```text TaxContext TaxLineBreakdown TaxBreakdown AddressInput TaxProfileInput ContactInput LineItemInput InvoiceCreateInput PaymentInput CreditNoteInput ``` DTOs are frozen dataclasses. They are used where a real service boundary exists. The project deliberately does not maintain one DTO for every database model. --- ## 33. Exception Hierarchy Finance-specific domain errors are centralized in: ```text finance.services.exceptions ``` Examples of domain failures include: - invalid state transitions - invalid invoice status for payment - invalid invoice status for credit note - overpayment - invalid amounts - missing tax configuration - missing tax policy - missing finance configuration - missing issuing entity The objective is to expose domain failures explicitly rather than relying on generic database errors. --- ## 34. Audit and History Finance models use the common application lifecycle infrastructure. Most financial models provide: ```text objects all_objects ``` where: ```text objects = active records all_objects = all records, including soft-deleted records ``` Historical tracking is provided through: ```python HistoricalRecords() ``` This is especially important for financial records because historical changes need to remain inspectable. Soft deletion should be used consistently with the application's `TimeStampedModel` behavior. --- ## 35. Database Integrity The finance schema uses database constraints in addition to service-level validation. Examples include: ### Invoice line items ```text quantity > 0 unit_price >= 0 discount >= 0 ``` ### Billing schedules ```text amount >= 0 ``` ### Credit notes ```text amount > 0 ``` ### Tax rates Active tax-rate uniqueness is constrained across: ```text country region HSN/SAC effective_from rate_type ``` ### Payments Active payment references are unique per invoice when a reference is supplied. ### Document sequences A sequence is unique per: ```text issuer document_type period ``` --- ## 36. Database Tables The current finance domain uses these primary tables: ```text finance_configuration finance_entity_mapping finance_address finance_contact finance_tax_profile finance_tax_rate finance_document_sequence finance_invoice finance_line_item finance_payment_method finance_payment finance_credit_note finance_billing_schedule ``` Historical tables are generated by `django-simple-history`. --- ## 37. Migration State The current app contains: ```text 0001_initial.py 0002_initial.py 0003_initial.py ``` These migrations represent the current finance schema. No separate historical migration chain from `fincore` or `invoices` should be introduced into the active application architecture. --- ## 38. Django Admin Finance provides administrative interfaces for: - Finance Configuration - Addresses - Contacts - Tax Profiles - Tax Rates - Payment Methods - Invoices - Line Items - Payments - Billing Schedules - Credit Notes Configuration/reference records that intentionally belong in Admin include: ```text FinanceConfiguration TaxRate PaymentMethod ``` This allows operational changes without changing application source code. --- ## 39. Management Commands ### Generate scheduled invoices ```bash python manage.py generate_scheduled_invoices ``` This executes the scheduled billing workflow. ### Seed finance tax rates ```bash python manage.py seed_finance_tax_rates ``` This command is designed to seed the baseline India tax configuration idempotently. It is intended to prevent a fresh environment from reaching invoice creation without the required general India GST rate. --- ## 40. Development Data Generation Finance development/test data generation is intentionally implemented under: ```text devtools/services/finance/ ``` The current services include: ```text context.py reference_data_service.py identity_generation_service.py billing_schedule_service.py invoice_generation_service.py generation_service.py ``` The generators use the real finance workflows wherever financial business behavior is involved. This is important because generated data should exercise the same: - validation - numbering - tax - totals - state transitions - audit behavior as production-created data. --- ## 41. Finance Data Generation Flow Conceptually: ```text Reference Data | v Entities | v Finance Identity (Address / Contact / TaxProfile) | v Billing Schedules | v Invoices | v Payments / Credit Notes ``` The orchestration context allows multiple generation services to share: - random-number state - caches - generated objects - execution context --- ## 42. Configuration Rules ### Default country Do not hardcode: ```python "IN" ``` as the operational default in service code. Use: ```text FinanceConfiguration.default_country ``` ### Scheduled invoice issuer Do not infer the issuer from a billing schedule. Use: ```text FinanceConfiguration.default_issuing_entity ``` If scheduled billing requires an issuer and none is configured, the workflow should fail explicitly. --- ## 43. Service Ownership Rules ### Invoice creation ```text invoice_workflow.create_invoice() ``` ### Invoice editing ```text invoice_workflow.add_line_item() invoice_workflow.remove_line_item() ``` ### Invoice state changes ```text invoice_workflow.finalize_invoice() invoice_workflow.cancel_invoice() invoice_workflow.void_invoice() invoice_workflow.mark_overdue() ``` ### Payments ```text payment_workflow.record_payment() ``` ### Credit notes ```text credit_note_workflow.issue_credit_note() ``` ### Scheduled invoices ```text billing_schedule_workflow.generate_due_invoices() ``` ### Identity ```text identity_workflow.create_address() identity_workflow.update_address() identity_workflow.create_contact() identity_workflow.update_contact() identity_workflow.create_tax_profile() identity_workflow.update_tax_profile() ``` ### Numbering ```text numbering.generate_document_number() ``` ### Tax ```text tax.resolver tax.rate_lookup tax.policies.* ``` --- ## 44. Rules for Future Development When extending the finance application: ### Rule 1 — Do not put business rules in models Avoid: ```python def save(...): # business workflow ``` Business rules belong in services/workflows. --- ### Rule 2 — Do not mutate invoice status directly Avoid: ```python invoice.status = "PAID" invoice.save() ``` Use the state machine/workflow. --- ### Rule 3 — Do not edit finalized invoices If a finalized invoice needs a reduction: ```text CreditNote ``` should be used. --- ### Rule 4 — Do not calculate tax inside views Views should call workflows. Tax calculation belongs under: ```text services/tax/ services/calculators/tax.py ``` --- ### Rule 5 — Do not query TaxRate from arbitrary application code Use: ```text services.tax.rate_lookup ``` so rate-selection logic remains centralized. --- ### Rule 6 — Do not create financial document numbers manually Use: ```text services.numbering ``` --- ### Rule 7 — Do not bypass identity workflows Address, contact, and tax-profile mutations should pass through: ```text identity_workflow ``` --- ### Rule 8 — Prefer database configuration for operational defaults Examples: ```text default country default issuing entity tax rates payment methods ``` should not become hardcoded deployment assumptions. --- ### Rule 9 — Keep calculators pure Calculation functions should avoid: - database access - caching - side effects - model mutation This makes them deterministic and easy to test. --- ### Rule 10 — Add abstractions only when a real requirement exists Do not introduce: - repository layers - generic interfaces - event buses - package adapters - CQRS - generic DTO hierarchies unless a real use case requires them. --- ## 45. Adding a New Country Tax Policy To add a new country's tax support: ### Step 1 Create a policy under: ```text finance/services/tax/policies/ ``` For example: ```text vat_eu.py ``` ### Step 2 Implement the `TaxPolicy` interface. ### Step 3 Add the country to: ```text finance/services/tax/resolver.py ``` Example: ```python POLICY_REGISTRY = { "IN": IndiaGSTPolicy, "DE": GermanyVATPolicy, } ``` ### Step 4 Implement the required rate lookup/configuration model behavior. ### Step 5 Add appropriate tests. The invoice workflow should not need country-specific branching. --- ## 46. Adding a New Financial Document Type Document numbering already supports: ```text DEBIT_NOTE ``` through: ```python DOCUMENT_PREFIXES ``` However, adding a document type should include: 1. Domain model if required 2. Workflow 3. State/lifecycle rules if applicable 4. API exposure if required 5. Admin support 6. Audit/history support 7. Validation 8. Tests 9. Numbering integration Do not implement a new document type by copying invoice logic into another view. --- ## 47. Transaction Boundaries Financial mutations are transactional. The following workflows use database transactions: ```text create_invoice add_line_item remove_line_item finalize_invoice cancel_invoice void_invoice record_payment issue_credit_note create_address update_address create_tax_profile update_tax_profile create_contact update_contact generate_due_invoices ``` This ensures a partially completed financial operation does not leave the database in an inconsistent state. --- ## 48. Concurrency Considerations Document numbering is explicitly concurrency-safe. The sequence row is locked using: ```python select_for_update() ``` inside an atomic transaction. This protects against: ```text Request A -> sequence 123 Request B -> sequence 123 ``` and ensures distinct sequence numbers are issued. Other high-contention financial workflows should preserve transactional integrity when future concurrency-sensitive features are introduced. --- ## 49. Operational Flow A normal manually created invoice follows: ```text Entity | +--> Address | +--> Tax Profile | v Invoice Creation | +--> Document Number | +--> Line Items | +--> Tax Policy | +--> Tax Rate | +--> Tax Breakdown | +--> Totals | v DRAFT | v PENDING | v SENT | +-------------------+ | | v v PARTIALLY_PAID PAID | | v v PAID REFUNDED ``` Cancellation/void paths are controlled by the state machine. --- ## 50. Scheduled Billing Flow ```text Scheduler | v generate_scheduled_invoices | v generate_due_invoices() | v Find ACTIVE schedules | v next_billing_date <= as_of | v Resolve default issuing entity | v Resolve customer default address | v invoice_workflow.create_invoice() | +--> numbering +--> tax +--> totals +--> audit | v Advance next_billing_date | v Complete schedule when end_date reached ``` --- ## 51. Financial Integrity Model The finance architecture deliberately separates three concerns: ```text Identity | +--> Address +--> Contact +--> TaxProfile Reference Configuration | +--> FinanceConfiguration +--> TaxRate +--> PaymentMethod Financial Transactions | +--> Invoice +--> LineItem +--> Payment +--> CreditNote +--> BillingSchedule ``` This prevents tax configuration, entity identity, and transaction state from becoming one coupled model structure. --- ## 52. Current Limitations The current implementation has several deliberate limitations. ### Payment allocation Payments currently belong directly to one invoice. A separate allocation model is not implemented. ### Refunds `REFUNDED` exists as an invoice state, but a dedicated refund workflow/payment-refund model is not currently implemented. ### Debit notes Numbering support exists, but debit-note business workflow/model behavior is not yet implemented. ### Multi-country tax The resolver is extensible, but India GST is the only fully implemented country policy. ### Financial accounting The application is an operational finance/invoicing layer, not a full accounting ledger. --- ## 53. Recommended Testing Strategy Tests should be organized around domain behavior rather than only model coverage. ### State machine tests Verify: - every legal transition - every illegal transition - editable states - terminal states ### Tax tests Verify: - India interstate GST - India intrastate GST - HSN/SAC-specific rate - general fallback rate - effective dates - exemption - missing rate - unmapped country ### Invoice tests Verify: - invoice requires line items - line subtotal - tax - grand total - line-item addition - line-item removal - cannot remove final line - finalized invoice cannot be edited ### Payment tests Verify: - positive amount - valid invoice states - outstanding balance - partial payment - full payment - overpayment rejection ### Credit note tests Verify: - eligible states - positive amount - cumulative credit limit - numbering ### Billing tests Verify: - due schedules - future schedules - end dates - calendar-based frequency advancement - configured issuer - missing issuer - missing customer address ### Numbering tests Verify: - prefixes - yearly reset - issuer isolation - document-type isolation - concurrent generation --- ## 54. Architecture Summary The finance application can be summarized as: ```text +------------------+ | API | | DRF ViewSets | +--------+---------+ | v +------------------+ | Serializers | | DTOs | +--------+---------+ | v +-----------------------------+ | Workflows | |-----------------------------| | Identity | | Invoice | | Payment | | Credit Note | | Billing Schedule | +------+-----------------------+ | +--------------+--------------+ | | | v v v +-------------+ +------------+ +------------+ | State | | Calculators| | Tax Engine | | Machine | | Totals/Tax | | Policies | +-------------+ +------------+ +------------+ | | | +--------------+--------------+ | v +--------------+ | Django ORM | | Finance | | Models | +------+-------+ | v +--------------+ | PostgreSQL | +--------------+ ``` --- ## 55. Core Architectural Principle The finance application should always preserve this boundary: ```text Models = persistence Services = domain logic Workflows = state-changing business operations Calculators = deterministic financial mathematics Tax Policies = jurisdiction-specific tax behavior API = transport and authorization Admin = operational configuration and administration ``` The most important implementation rule is: > **Financial business operations must enter through an explicit workflow rather than through direct model mutation.** This is the central design constraint that keeps invoice lifecycle, tax calculation, numbering, payments, credit notes, compliance, and audit behavior consistent across API, Admin, scheduled jobs, and development data generation.