djangoplay-web / Apps / Finance App — Technical Documentation
DocsDjangoPlay WebAppsFinance App — Technical Documentation

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 implementatio...

26 min readApplies to v1.2.2
On this page ▾
  1. 1. Overview
  2. 2. Architectural Goals
  3. 3. Scope
  4. Included
  5. Currently outside the domain
  6. 4. Application Dependencies
  7. Primary dependencies
  8. 5. Application Structure
  9. 6. Domain Model
  10. 6.1 FinanceConfiguration
  11. 6.2 FinanceEntityMapping
  12. 6.3 Address
  13. 6.4 Contact
  14. 6.5 TaxProfile
  15. 6.6 TaxRate
  16. 6.7 DocumentSequence
  17. 6.8 Invoice
  18. 6.9 LineItem
  19. 6.10 PaymentMethod
  20. 6.11 Payment
  21. 6.12 CreditNote
  22. 6.13 BillingSchedule
  23. 7. Invoice Lifecycle
  24. States
  25. Transition graph
  26. Editable states
  27. Terminal states
  28. 8. Invoice Immutability
  29. 9. Invoice Creation Workflow
  30. 10. Invoice Numbering
  31. Format
  32. Prefixes
  33. 11. Totals Calculation
  34. Line subtotal
  35. Invoice subtotal
  36. Invoice tax total
  37. Invoice grand total
  38. Outstanding balance
  39. 12. Tax Architecture
  40. Components
  41. 13. Tax Policy Resolver
  42. Unmapped countries
  43. 14. India GST
  44. Interstate supply
  45. Intrastate supply
  46. 15. Tax Rate Lookup
  47. 16. Tax Snapshots
  48. 17. Tax Exemption
  49. 18. Identity and Compliance Layer
  50. Correct pattern
  51. Avoid
  52. 19. Payment Workflow
  53. 20. Credit Note Workflow
  54. 21. Billing Schedule Workflow
  55. 22. Billing Frequency
  56. 23. REST API
  57. 24. Invoice API
  58. List invoices
  59. Retrieve invoice
  60. Create invoice
  61. Finalize
  62. Cancel
  63. Void
  64. Record payment
  65. Issue credit note
  66. Add line item
  67. Remove line item
  68. 25. Address API
  69. List
  70. Retrieve
  71. Create
  72. 26. Contact API
  73. List
  74. Retrieve
  75. Create
  76. 27. Tax Profile API
  77. List
  78. Retrieve
  79. Create
  80. 28. Reference Data APIs
  81. Payment methods
  82. Tax rates
  83. 29. API Mutation Rule
  84. 30. Permissions
  85. 31. API Error Handling
  86. 32. DTO Layer
  87. 33. Exception Hierarchy
  88. 34. Audit and History
  89. 35. Database Integrity
  90. Invoice line items
  91. Billing schedules
  92. Credit notes
  93. Tax rates
  94. Payments
  95. Document sequences
  96. 36. Database Tables
  97. 37. Migration State
  98. 38. Django Admin
  99. 39. Management Commands
  100. Generate scheduled invoices
  101. Seed finance tax rates
  102. 40. Development Data Generation
  103. 41. Finance Data Generation Flow
  104. 42. Configuration Rules
  105. Default country
  106. Scheduled invoice issuer
  107. 43. Service Ownership Rules
  108. Invoice creation
  109. Invoice editing
  110. Invoice state changes
  111. Payments
  112. Credit notes
  113. Scheduled invoices
  114. Identity
  115. Numbering
  116. Tax
  117. 44. Rules for Future Development
  118. Rule 1 — Do not put business rules in models
  119. Rule 2 — Do not mutate invoice status directly
  120. Rule 3 — Do not edit finalized invoices
  121. Rule 4 — Do not calculate tax inside views
  122. Rule 5 — Do not query TaxRate from arbitrary application code
  123. Rule 6 — Do not create financial document numbers manually
  124. Rule 7 — Do not bypass identity workflows
  125. Rule 8 — Prefer database configuration for operational defaults
  126. Rule 9 — Keep calculators pure
  127. Rule 10 — Add abstractions only when a real requirement exists
  128. 45. Adding a New Country Tax Policy
  129. Step 1
  130. Step 2
  131. Step 3
  132. Step 4
  133. Step 5
  134. 46. Adding a New Financial Document Type
  135. 47. Transaction Boundaries
  136. 48. Concurrency Considerations
  137. 49. Operational Flow
  138. 50. Scheduled Billing Flow
  139. 51. Financial Integrity Model
  140. 52. Current Limitations
  141. Payment allocation
  142. Refunds
  143. Debit notes
  144. Multi-country tax
  145. Financial accounting
  146. 53. Recommended Testing Strategy
  147. State machine tests
  148. Tax tests
  149. Invoice tests
  150. Payment tests
  151. Credit note tests
  152. Billing tests
  153. Numbering tests
  154. 54. Architecture Summary
  155. 55. Core Architectural Principle

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.


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.