swiftops-backend / docs /agent /implementation-notes /CONTRACTOR_INVOICING_COMPLETE.md
kamau1's picture
feat(project): add complete project setup workflow with service methods and API endpoints for regions, roles, subcontractors, and finalization including validation and authorization
4835b24

Enterprise-Grade Contractor Invoicing System

Implementation Complete βœ…

Overview

A fully-featured, enterprise-grade invoicing system with version tracking, audit logging, ticket linking, and partial payment support. Every invoice edit creates a new version using a copy-on-write strategy for complete audit trails.


🎯 Enterprise Features Implemented

βœ… 1. Version Tracking (Copy-on-Write)

  • Every edit creates a new version of the invoice
  • Original invoice preserved forever (immutable audit trail)
  • Version chain: v1 ← v2 ← v3 ← v4 (latest)
  • Only latest version has is_latest_version = TRUE
  • All versions share same invoice_number
  • previous_version_id links to previous version
  • revision_notes explain what changed

Database Fields:

version INTEGER DEFAULT 1 NOT NULL
previous_version_id UUID REFERENCES contractor_invoices(id)
is_latest_version BOOLEAN DEFAULT TRUE NOT NULL
revision_notes TEXT

βœ… 2. Audit Logging

  • All changes logged to audit_logs table
  • Tracks: WHO did WHAT, WHEN, and WHAT changed
  • Changes stored as JSONB diff: {"old": {...}, "new": {...}}
  • Integrates with existing audit system

βœ… 3. Ticket Linking/Unlinking

  • Automatic ticket sync: Adding line item β†’ sets ticket.contractor_invoice_id
  • Automatic unlinking: Removing line item β†’ clears ticket invoice reference
  • Prevents double-invoicing: Tickets can only be on ONE active invoice
  • Fields updated:
    • ticket.contractor_invoice_id
    • ticket.is_invoiced
    • ticket.invoiced_at

βœ… 4. Partial Payments

  • Multiple payments supported: Record payments over time
  • Automatic totals: amount_due = total_amount - amount_paid (computed)
  • Auto status updates:
    • partially_paid: when 0 < amount_paid < total_amount
    • paid: when amount_paid >= total_amount
  • Payment history preserved across versions

βœ… 5. Flexible Line Items

Invoice can include:

  • Tickets: Installation/repair work
  • Vehicles: Vehicle rental charges
  • Labor: Labor costs
  • Materials: Material costs
  • Other: Miscellaneous charges

Each line item: {id, type, ticket_id?, description, quantity, unit_price, total}

βœ… 6. Automatic Totals

  • subtotal = Sum of all line items
  • tax_amount = (subtotal Γ— tax_rate) / 100
  • total_amount = subtotal + tax_amount - discount_amount
  • amount_due = total_amount - amount_paid (computed column)
  • Recalculated on every version

βœ… 7. Status Workflow

draft β†’ sent β†’ paid
       ↓
       partially_paid β†’ paid
       ↓
       overdue / disputed / cancelled / on_hold

βœ… 8. Invoice Numbering

Auto-generated format: INV-{CONTRACTOR}-{YEAR}-{SEQUENCE}

Example: INV-ACME-2025-00142

βœ… 9. Soft Delete

  • Invoice never truly deleted (sets deleted_at)
  • All versions soft deleted together
  • Tickets automatically unlinked
  • Audit trail preserved

πŸ“ Files Created

1. Migration

File: migrations/007_add_invoice_versioning.sql

  • Adds versioning fields to contractor_invoices
  • Creates indexes for version queries
  • Includes comments for documentation

2. Model

File: src/app/models/contractor_invoice.py

  • SQLAlchemy ORM model
  • Relationships: contractor, client, project, created_by, previous_version
  • Computed properties: amount_due, is_fully_paid, is_partially_paid, is_overdue
  • Helper methods: get_line_item_by_id(), get_ticket_ids()
  • Table constraints and indexes

3. Schemas

File: src/app/schemas/contractor_invoice.py

Key Schemas:

  • LineItemBase, TicketLineItemCreate, GenericLineItemCreate
  • ContractorInvoiceCreate, ContractorInvoiceUpdate, ContractorInvoiceResponse
  • ContractorInvoiceSummary (lightweight for lists)
  • AddLineItemRequest, RemoveLineItemRequest, UpdateLineItemRequest
  • RecordPaymentRequest, ChangeStatusRequest
  • InvoiceVersionSummary, InvoiceVersionHistoryResponse
  • InvoiceSearchFilters (with validation)

Validation:

  • Date relationships (due_date >= issue_date, etc.)
  • Positive amounts
  • Tax rate 0-100%
  • Line item totals auto-calculated

4. Service

File: src/app/services/contractor_invoice_service.py

Key Methods:

  • create_invoice() - Create draft with auto-generated invoice number
  • update_invoice() - Create new version with changes
  • add_line_item() - Add item, create version, link ticket
  • remove_line_item() - Remove item, create version, unlink ticket
  • record_payment() - Add payment, auto-update status
  • change_status() - Change status, create version
  • get_invoice_by_id() - Get single invoice
  • get_version_history() - Get all versions of an invoice
  • list_invoices() - List with filters
  • generate_invoice_number() - Auto-generate unique numbers

Authorization:

  • platform_admin: Full access
  • project_manager: Manage their contractor's invoices
  • dispatcher: Manage their contractor's invoices

5. API Endpoints

File: src/app/api/v1/contractor_invoices.py

Endpoints:

POST   /contractor-invoices                           - Create invoice
GET    /contractor-invoices/{id}                      - Get invoice
GET    /contractor-invoices                           - List invoices (filters)
PUT    /contractor-invoices/{id}                      - Update invoice
DELETE /contractor-invoices/{id}                      - Soft delete
POST   /contractor-invoices/{id}/line-items           - Add line item
DELETE /contractor-invoices/{id}/line-items/{item_id} - Remove line item
POST   /contractor-invoices/{id}/payments             - Record payment
PUT    /contractor-invoices/{id}/status               - Change status
GET    /contractor-invoices/{id}/versions             - Version history
GET    /contractor-invoices/{id}/versions/{version}   - Get specific version

6. Updated Files

  • src/app/models/ticket.py - Added contractor_invoice relationship
  • src/app/api/v1/router.py - Registered invoice router

πŸ”§ Database Schema

Updated contractor_invoices Table

CREATE TABLE contractor_invoices (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Parties
    contractor_id UUID NOT NULL REFERENCES contractors(id),
    client_id UUID NOT NULL REFERENCES clients(id),
    project_id UUID REFERENCES projects(id),
    
    -- Invoice Details
    invoice_number TEXT NOT NULL UNIQUE,
    invoice_title TEXT,
    
    -- Billing Period
    billing_period_start DATE NOT NULL,
    billing_period_end DATE NOT NULL,
    
    -- Amounts
    subtotal DECIMAL(12, 2) NOT NULL,
    tax_rate DECIMAL(5, 2) DEFAULT 0,
    tax_amount DECIMAL(12, 2) DEFAULT 0,
    discount_amount DECIMAL(12, 2) DEFAULT 0,
    total_amount DECIMAL(12, 2) NOT NULL,
    amount_paid DECIMAL(12, 2) DEFAULT 0,
    amount_due DECIMAL(12, 2) GENERATED ALWAYS AS (total_amount - amount_paid) STORED,
    currency TEXT DEFAULT 'KES',
    
    -- Line Items (JSONB)
    line_items JSONB DEFAULT '[]',
    
    -- Status & Dates
    status contractor_invoice_status DEFAULT 'draft',
    issue_date DATE NOT NULL,
    due_date DATE NOT NULL,
    sent_date DATE,
    paid_date DATE,
    
    -- Payment Details
    payment_method TEXT,
    payment_reference TEXT,
    payment_notes TEXT,
    
    -- Metadata
    notes TEXT,
    terms_and_conditions TEXT,
    additional_metadata JSONB DEFAULT '{}',
    
    -- Audit
    created_by_user_id UUID REFERENCES users(id),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at TIMESTAMP WITH TIME ZONE,
    
    -- VERSIONING (NEW)
    version INTEGER DEFAULT 1 NOT NULL,
    previous_version_id UUID REFERENCES contractor_invoices(id),
    is_latest_version BOOLEAN DEFAULT TRUE NOT NULL,
    revision_notes TEXT
);

Indexes

CREATE INDEX idx_contractor_invoices_version ON contractor_invoices (id, version);
CREATE INDEX idx_contractor_invoices_latest ON contractor_invoices (is_latest_version) 
    WHERE is_latest_version = TRUE AND deleted_at IS NULL;
CREATE INDEX idx_contractor_invoices_previous_version ON contractor_invoices (previous_version_id) 
    WHERE previous_version_id IS NOT NULL;

πŸ“Š Line Items Format (JSONB)

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "ticket",
    "ticket_id": "660e8400-e29b-41d4-a716-446655440001",
    "description": "Installation at Site XYZ",
    "quantity": 1,
    "unit_price": 5000.00,
    "total": 5000.00
  },
  {
    "id": "770e8400-e29b-41d4-a716-446655440002",
    "type": "vehicle",
    "description": "Vehicle rental - 3 days",
    "quantity": 3,
    "unit_price": 1500.00,
    "total": 4500.00
  },
  {
    "id": "880e8400-e29b-41d4-a716-446655440003",
    "type": "material",
    "description": "Fiber optic cable - 100m",
    "quantity": 100,
    "unit_price": 50.00,
    "total": 5000.00
  }
]

πŸ”„ Version History Example

Scenario: Invoice Created β†’ Line Item Added β†’ Payment Recorded

Version 1: Initial Creation

{
  "invoice_number": "INV-ACME-2025-00001",
  "version": 1,
  "total_amount": 10000.00,
  "amount_paid": 0.00,
  "status": "draft",
  "line_items": [
    {"id": "...", "type": "ticket", "total": 5000.00},
    {"id": "...", "type": "vehicle", "total": 5000.00}
  ],
  "is_latest_version": true,
  "previous_version_id": null,
  "revision_notes": "Initial version"
}

Version 2: Added Material Line Item

{
  "invoice_number": "INV-ACME-2025-00001",
  "version": 2,
  "total_amount": 15000.00,
  "amount_paid": 0.00,
  "status": "draft",
  "line_items": [
    {"id": "...", "type": "ticket", "total": 5000.00},
    {"id": "...", "type": "vehicle", "total": 5000.00},
    {"id": "...", "type": "material", "total": 5000.00}  // NEW
  ],
  "is_latest_version": true,
  "previous_version_id": "version-1-uuid",
  "revision_notes": "Added material charges"
}

Version 3: Changed Status to Sent

{
  "invoice_number": "INV-ACME-2025-00001",
  "version": 3,
  "total_amount": 15000.00,
  "amount_paid": 0.00,
  "status": "sent",  // CHANGED
  "sent_date": "2025-11-17",  // SET
  "line_items": [...],
  "is_latest_version": true,
  "previous_version_id": "version-2-uuid",
  "revision_notes": "Sent invoice to client"
}

Version 4: Recorded Partial Payment

{
  "invoice_number": "INV-ACME-2025-00001",
  "version": 4,
  "total_amount": 15000.00,
  "amount_paid": 7500.00,  // CHANGED
  "status": "partially_paid",  // AUTO-UPDATED
  "sent_date": "2025-11-17",
  "line_items": [...],
  "payment_method": "mobile_money",
  "payment_reference": "MPESA-XYZ123",
  "is_latest_version": true,
  "previous_version_id": "version-3-uuid",
  "revision_notes": "Recorded payment of 7500.00 KES"
}

πŸ§ͺ Usage Examples

Create Invoice

POST /contractor-invoices

{
  "contractor_id": "uuid",
  "client_id": "uuid",
  "project_id": "uuid",
  "billing_period_start": "2025-11-01",
  "billing_period_end": "2025-11-30",
  "issue_date": "2025-11-17",
  "due_date": "2025-12-17",
  "tax_rate": 16,
  "line_items": [
    {
      "type": "ticket",
      "ticket_id": "uuid",
      "description": "Installation at Site A",
      "quantity": 1,
      "unit_price": 5000
    }
  ]
}

Add Line Item

POST /contractor-invoices/{id}/line-items

{
  "line_item": {
    "type": "vehicle",
    "description": "Vehicle rental - 2 days",
    "quantity": 2,
    "unit_price": 1500
  },
  "revision_notes": "Added vehicle rental charges"
}

Record Payment

POST /contractor-invoices/{id}/payments

{
  "amount": 5000,
  "payment_method": "mobile_money",
  "payment_reference": "MPESA-ABC123",
  "payment_notes": "Partial payment from client",
  "revision_notes": "First installment received"
}

Get Version History

GET /contractor-invoices/{id}/versions

Response:
{
  "invoice_number": "INV-ACME-2025-00001",
  "versions": [
    {
      "id": "uuid-v1",
      "version": 1,
      "total_amount": 10000,
      "status": "draft",
      "revision_notes": "Initial version",
      "created_at": "2025-11-17T10:00:00Z",
      "is_latest_version": false
    },
    {
      "id": "uuid-v2",
      "version": 2,
      "total_amount": 15000,
      "status": "draft",
      "revision_notes": "Added line item",
      "created_at": "2025-11-17T11:00:00Z",
      "is_latest_version": false
    },
    {
      "id": "uuid-v3",
      "version": 3,
      "total_amount": 15000,
      "status": "sent",
      "revision_notes": "Sent to client",
      "created_at": "2025-11-17T12:00:00Z",
      "is_latest_version": true
    }
  ],
  "latest_version_id": "uuid-v3",
  "total_versions": 3
}

πŸ” Authorization

Role Create View Edit Delete Payments
platform_admin βœ… All βœ… All βœ… All βœ… Yes βœ… Yes
project_manager βœ… Own contractor βœ… Own contractor βœ… Own contractor ❌ No βœ… Own contractor
dispatcher βœ… Own contractor βœ… Own contractor βœ… Own contractor ❌ No βœ… Own contractor
client_admin ❌ No βœ… Own client ❌ No ❌ No ❌ No
field_agent ❌ No ❌ No ❌ No ❌ No ❌ No

βœ… Testing Checklist

  • Run migration: 007_add_invoice_versioning.sql
  • Create invoice with line items
  • Verify ticket linking (ticket.contractor_invoice_id set)
  • Add line item β†’ verify new version created
  • Remove line item β†’ verify ticket unlinked
  • Record partial payment β†’ verify status = partially_paid
  • Record full payment β†’ verify status = paid
  • Change status β†’ verify new version created
  • Get version history β†’ verify all versions returned
  • Soft delete β†’ verify tickets unlinked
  • Check audit_logs table β†’ verify all changes logged

πŸš€ Next Steps (Future Enhancements)

  1. PDF Generation: Generate invoice PDFs for client delivery
  2. Email Integration: Auto-send invoices via email
  3. Payment Gateway: Integrate M-Pesa for online payments
  4. Recurring Invoices: Auto-generate invoices monthly
  5. Invoice Templates: Customizable invoice layouts
  6. Multi-Currency: Support for USD, EUR, etc.
  7. Tax Compliance: Integrate with tax systems (KRA eTIMS)
  8. Payment Plans: Installment payment schedules
  9. Late Fees: Auto-calculate penalties for overdue invoices
  10. Client Portal: Let clients view/pay invoices online

πŸ“– Related Documentation

  • Schema: docs/schema/schema.sql (lines 2518-2599)
  • Migration: migrations/007_add_invoice_versioning.sql
  • Model: src/app/models/contractor_invoice.py
  • Schemas: src/app/schemas/contractor_invoice.py
  • Service: src/app/services/contractor_invoice_service.py
  • API: src/app/api/v1/contractor_invoices.py

Implementation Status: βœ… COMPLETE - Enterprise-Grade Invoicing Ready for Production