PSA/docs/billing/invoice_finalization.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

9.6 KiB

Invoice Finalization System

Overview

The invoice finalization system provides a way to mark invoices as finalized once they have been formally issued to clients. This helps maintain data integrity and provides clear audit trails.

Related Documentation:

Finalization Triggers

An invoice becomes finalized when:

  1. Manual finalization - User explicitly finalizes via UI action menu
  2. Email sending - Future feature (not yet implemented)

Important: PDF download does NOT automatically finalize invoices. Users must explicitly finalize invoices through the UI action menu.

Key Features

1. Separate Display

  • Finalized invoices are displayed in a separate table from draft/pending invoices
  • This provides clear visual separation between working and completed invoices

2. Status Management

  • Invoices track their finalization status via the finalized_at timestamp
  • Finalization automatically changes invoice status to 'sent'
  • Users can unfinalize an invoice if needed (moves it back to the main invoice list)
  • Unfinalizing is distinct from reversing a billing period

3. Credit Handling

During finalization, the system automatically:

  • For prepayment invoices (negative totals): Creates credit tracking entries
  • For regular invoices: Applies available credits to reduce the balance
  • Handles credit expiration and allocation logic
  • Records credit transactions in the ledger

4. Data Protection

Current Implementation (Partial Protection):

  • Invoices with 'paid' or 'cancelled' status cannot be modified
  • Line items cannot be added/modified for paid or cancelled invoices

⚠️ Important: The system does NOT currently block modifications based solely on finalized_at timestamp. Finalized invoices that are not yet paid or cancelled can still be modified. This may be enhanced in future versions to provide complete immutability for finalized invoices.

Implementation Details

Database Schema

The invoices table includes:

finalized_at TIMESTAMP WITH TIME ZONE

See IInvoice interface line 15 for TypeScript type definition.

Server Actions

File Location: server/src/lib/actions/invoiceModification.ts

Note: The invoiceActions.ts file exists but is "intentionally left almost blank after refactoring" (line 58). The actual finalization logic is in invoiceModification.ts.

finalizeInvoice Function (lines 43-56)

export async function finalizeInvoice(invoiceId: string): Promise<void> {
  const { knex } = await createTenantKnex();

  await knex('invoices')
    .where({ invoice_id: invoiceId })
    .update({
      finalized_at: new Date().toISOString(),
      status: 'sent'
    });
}

Complex Credit Handling (lines 109-253): The actual implementation includes sophisticated credit handling:

  • For prepayment invoices (negative totals): Creates credit tracking entries
  • For regular invoices: Applies available credits automatically
  • Handles credit expiration logic
  • Records transactions in the ledger

⚠️ Audit Logging Status: Audit logging code exists but is commented out (lines 93-106). The auditLog() function is imported but not currently used.

unfinalizeInvoice Function (lines 256-319)

export async function unfinalizeInvoice(invoiceId: string): Promise<void> {
  const { knex } = await createTenantKnex();

  // Reverses credit applications and status changes
  await knex('invoices')
    .where({ invoice_id: invoiceId })
    .update({
      finalized_at: null,
      status: 'draft'
    });
}

⚠️ Audit Logging Status: Audit logging code exists but is commented out (lines 304-317).

InvoiceService Class

The system also includes an API service layer at server/src/lib/api/services/InvoiceService.ts:

finalizeInvoice method (lines 536-604):

  • Includes validation and permission checks
  • Publishes 'INVOICE_FINALIZED' events (lines 591-600)
  • Provides RESTful API interface
  • Includes HATEOAS links for related operations

UI Components

The invoice finalization UI is split across two dedicated components:

Component Files:

Both components import and use the finalization server actions:

import { finalizeInvoice, unfinalizeInvoice } from '@/lib/actions/invoiceModification';

Draft Filtering Logic (DraftsTab.tsx line 90):

const filteredInvoices = invoices.filter(inv =>
  !inv.finalized_at && normalizeStatus(inv.status) === 'draft'
);

Finalized Filtering Logic (FinalizedTab.tsx line 65):

const filteredInvoices = invoices.filter(inv =>
  inv.finalized_at || inv.status !== 'draft'
);

Bulk Operations: Both tabs support bulk finalize/unfinalize operations for multiple invoices simultaneously.

Component IDs

Actual Implementation (differs from original spec):

  1. Action Menus:
id="draft-row-actions-${record.invoice_id}"      // In DraftsTab
id="finalized-row-actions-${record.invoice_id}"  // In FinalizedTab
  1. Tables:
id="invoice-drafts-table"      // In DraftsTab
id="invoices-finalized-table"  // In FinalizedTab

PDF Generation Integration

File Location: server/src/lib/actions/invoiceGeneration.ts

Current Implementation:

// generateInvoicePDF (lines 644-671)
export async function generateInvoicePDF(invoiceId: string): Promise<{ file_id: string }> {
  const storageService = new StorageService();
  const pdfGenerationService = new PDFGenerationService(
    storageService,
    {
      pdfCacheDir: process.env.PDF_CACHE_DIR
    }
  );

  const fileRecord = await pdfGenerationService.generateAndStore({
    invoiceId
  });

  // NOTE: Does NOT automatically finalize the invoice
  return { file_id: fileRecord.file_id };
}

// downloadInvoicePDF also exists (line 673+)

⚠️ Important: Unlike the original specification, PDF generation does NOT automatically call finalizeInvoice(). Users must manually finalize invoices through the UI action menu.

Testing Requirements

  1. Server Action Tests:

    • ✓ Test finalization with proper user context
    • ✓ Verify unfinalization logic
    • ⚠️ Audit log testing (commented out in code)
    • ✓ Test credit handling for prepayment invoices
    • ✓ Test credit application for regular invoices
  2. Protection Tests:

    • ⚠️ Currently only paid/cancelled status blocks modifications
    • ⚠️ Finalized status does not guarantee immutability
    • ✓ Line items cannot be added/modified for paid or cancelled invoices
  3. UI Tests:

    • ✓ Verify correct table separation (DraftsTab vs FinalizedTab)
    • ✓ Test bulk finalize/unfinalize operations
    • ✓ Validate component IDs match implementation
  4. Integration Tests:

    • ⚠️ PDF generation does NOT automatically finalize
    • ⚠️ Audit trail is incomplete (logging commented out)
    • ✓ Check data consistency after credit operations

Implementation Status

✓ Implemented Features:

  • finalized_at timestamp tracking
  • Manual finalization via UI
  • Status change to 'sent' on finalization
  • Separate display for drafts and finalized invoices
  • Credit handling for prepayment invoices
  • Automatic credit application for regular invoices
  • Bulk finalize/unfinalize operations
  • Event publishing for finalization

⚠️ Partially Implemented:

  • Immutability protection - Only for paid/cancelled, not finalized_at
  • Audit logging - Code exists but is commented out

Not Implemented:

  • Automatic finalization on PDF download
  • Email integration with finalization
  • Complete audit trail
  • Finalization-based immutability

Future Enhancements

  1. Complete Immutability Protection:

    • Block all modifications for finalized invoices
    • Add checks for finalized_at in update operations
    • Comprehensive validation in invoiceModification.ts
  2. Audit Logging:

    • Uncomment and activate audit logging code
    • Track all finalization state changes
    • Record reason for unfinalization
    • Maintain complete history
  3. Email Integration:

    • Add email sending capability
    • Integrate with finalization system
    • Include PDF generation
    • Auto-finalize on email send
  4. Approval Workflow:

    • Optional approval before finalization
    • Multi-level approval process
    • Approval audit trail
    • Workflow runtime integration (already imported in code)
  5. PDF Auto-Finalization:

    • Option to automatically finalize on PDF download
    • Configuration setting for this behavior
    • Update generateInvoicePDF to call finalizeInvoice

Related Files: