Ecto schema for the accrue_invoices table.
Phase 3 upgrades :status to an Ecto.Enum and introduces a dual-path
changeset (D3-17) so user-facing operations enforce the legal state
machine while webhook reconciles accept any status from Stripe.
Legal user-path transitions
draft -> open | void
open -> paid | uncollectible | void
paid, uncollectible, void -> (terminal)Changeset paths
changeset/2— enforces the transition table; use for user-originated writes (finalize_invoice,pay_invoice,void_invoice).force_status_changeset/2— bypasses transition validation; use only from the webhook reconcile path where Stripe is canonical.
Summary
Functions
Builds a user-path changeset. Enforces the legal transition table —
illegal transitions add an error on :status.
Phase 4 Plan 05 (BILL-28) — webhook-path discount denormalization.
Mirrors Stripe's discount_minor + total_discount_amounts into
local columns. Stripe is canonical (D2-29) — no local math, no
validate_number guard. Callers pass nil-safe attrs so the cast
preserves existing values when the webhook doesn't carry them.
Builds a webhook-path changeset. Stripe is canonical in this path, so the transition table is bypassed. Use only from the webhook reconcile path.
Canonical list of invoice statuses.
Types
@type t() :: %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() }
Functions
@spec changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Builds a user-path changeset. Enforces the legal transition table —
illegal transitions add an error on :status.
@spec force_discount_changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Phase 4 Plan 05 (BILL-28) — webhook-path discount denormalization.
Mirrors Stripe's discount_minor + total_discount_amounts into
local columns. Stripe is canonical (D2-29) — no local math, no
validate_number guard. Callers pass nil-safe attrs so the cast
preserves existing values when the webhook doesn't carry them.
@spec force_status_changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Builds a webhook-path changeset. Stripe is canonical in this path, so the transition table is bypassed. Use only from the webhook reconcile path.
@spec statuses() :: [atom()]
Canonical list of invoice statuses.