Source code for subscriptionkore.core.models.invoice

"""Invoice domain model."""

from __future__ import annotations

from datetime import datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, Field
from ulid import ULID

from subscriptionkore.core.models.value_objects import Currency, DateRange, Money, ProviderReference


[docs] class InvoiceStatus(StrEnum): """Invoice status values.""" DRAFT = "draft" OPEN = "open" PAID = "paid" VOID = "void" UNCOLLECTIBLE = "uncollectible"
[docs] class InvoiceLineItem(BaseModel): """Line item on an invoice.""" id: str = Field(default_factory=lambda: str(ULID())) description: str quantity: int = 1 unit_amount: Money amount: Money period: DateRange | None = None proration: bool = False metadata: dict[str, Any] = Field(default_factory=dict)
[docs] class Invoice(BaseModel): """Invoice domain entity.""" id: str = Field(default_factory=lambda: str(ULID())) customer_id: str subscriptionkore_id: str | None = None provider_ref: ProviderReference status: InvoiceStatus = InvoiceStatus.DRAFT subtotal: Money tax: Money = Field(default_factory=lambda: Money.zero()) discount_amount: Money = Field(default_factory=lambda: Money.zero()) total: Money amount_paid: Money = Field(default_factory=lambda: Money.zero()) amount_due: Money currency: Currency = Currency.USD line_items: list[InvoiceLineItem] = Field(default_factory=list) period: DateRange | None = None due_date: datetime | None = None paid_at: datetime | None = None invoice_pdf_url: str | None = None hosted_invoice_url: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) created_at: datetime = Field(default_factory=datetime.utcnow)
[docs] def is_paid(self) -> bool: """Check if invoice is fully paid.""" return self.status == InvoiceStatus.PAID
[docs] def is_open(self) -> bool: """Check if invoice is open for payment.""" return self.status == InvoiceStatus.OPEN
[docs] def is_overdue(self) -> bool: """Check if invoice is past due date.""" if self.due_date is None or self.is_paid(): return False return datetime.utcnow() > self.due_date
[docs] def remaining_balance(self) -> Money: """Get remaining balance to be paid.""" return self.amount_due - self.amount_paid