Skip to main content

Billing::Agreement

Purpose

A Service Agreement captures the negotiated commercial terms between Jod and an Org::Company before any invoicing can happen. It records what was agreed (platform fee rates, unit prices, discounts) and ties those terms to specific entitlement instruments (Gig, Placement, Workforce).

Agreements exist to decouple negotiation from invoicing: sales can negotiate and sign terms today, while invoices are generated later using the agreement's terms as input.

Model Context

ContextDetails
AggregateBilling::Agreement (root) + Billing::AgreementTerm (child)
LayerCommercial Documents
Upstream dependenciesBilling::Account (must exist), Billing::Entitlement (terms reference instruments)
Downstream dependentsBilling::Invoice (uses agreement terms to compute invoice line items)

State Machine

FromToTriggerNotes
(new)activeUC-1: Agreement createdDefault status on creation
activesupersededUC-3: New agreement replaces this oneOld agreement preserved for audit
activeterminatedUC-5: Admin terminatesNo further invoices allowed

Use Cases

IDUse CaseTriggerActor
UC-1Create an agreement with negotiated commercial terms for a companySales finalizes negotiated terms after signingAdmin (sales)
UC-2Edit an agreement before any invoice references itSales needs to correct a mistake before invoicingAdmin (sales)
UC-3Supersede an agreement when terms are renegotiatedCompany renegotiates terms (renewal, rate change)Admin (sales)
UC-4View agreement details and term history for a companyAdmin needs to review current or past termsAdmin
UC-5Terminate an agreement when a company relationship endsCompany relationship ends or agreement voidedAdmin
UC-6Use agreement terms to compute invoice line item pricingCompany wants to purchase creditsAdmin / System

UC-1: Create an agreement with negotiated commercial terms for a company

FieldDetails
ActorIdentities::Admin (sales/ops)
TriggerSales finalizes negotiated commercial terms with a company after signing

Preconditions:

  • Billing::Account exists for the Org::Company
  • Admin has the signed agreement document (PDF)

System Behaviour:

  1. Admin enters agreement details:
    • code — human-readable identifier (e.g. SG-GIG-2026-0001)
    • document_url — S3 path to signed PDF
    • effective_from — when the agreement takes effect
    • effective_to — optional expiry (null = open-ended)
  2. Admin defines one or more terms, each specifying:
    • which Billing::Entitlement instrument it applies to
    • term_key — what is being negotiated (fee_rate, unit_price, discount_rate)
    • term_value + term_unit — the negotiated value (e.g. 2000 bps = 20% fee rate)
  3. System validates inputs (see Business Rules)
  4. Agreement + terms created in a single DB transaction
  5. Admin audit trail recorded (admin_created_by)

Business Rules:

  • At least one AgreementTerm is required
  • Each (agreement, entitlement, term_key) combination must be unique
  • term_value must be a positive integer
  • term_key must be one of: fee_rate, discount_rate, unit_price
  • term_unit must be one of: bps, cents, credits
  • Terms capture negotiated commercial values only — not tax (tax is determined by Billing::LegalEntity + Billing::ProductPrice at invoice time)

Postconditions:

  • Agreement is persisted and visible on the billing account detail page
  • No entitlements are granted — that only happens via invoice posting
  • The agreement is available for use when creating future invoices

UC-2: Edit an agreement before any invoice references it

FieldDetails
ActorIdentities::Admin
TriggerSales needs to correct a mistake or update terms before any invoice has been created using this agreement

Preconditions:

  • Agreement exists
  • No invoice has been created referencing this agreement

System Behaviour:

  1. Admin modifies agreement fields (code, dates, document_url) and/or terms
  2. System verifies no invoices reference this agreement
  3. Agreement + terms updated in a single DB transaction
  4. Admin audit trail recorded (admin_updated_by)

Business Rules:

  • If any Billing::Invoice references this agreement, the edit is rejected — admin must create a new agreement instead (see UC-3)
  • All creation-time validations still apply

Postconditions:

  • Agreement reflects updated values
  • Audit trail shows who changed what and when

Open Questions:

  • Should we track edit history (versioning) or is the admin audit trail sufficient?
  • Do we allow adding/removing terms on edit, or only changing values?

UC-3: Supersede an agreement when terms are renegotiated

FieldDetails
ActorIdentities::Admin
TriggerCompany renegotiates terms (annual renewal, rate change, contract amendment)

Preconditions:

  • At least one existing agreement for this billing account

System Behaviour:

  1. Admin creates a new agreement with updated terms (same flow as UC-1)
  2. System marks the previous agreement's status as superseded
  3. The new agreement becomes the one used for future invoices

Business Rules:

  • Invoices already issued under the old agreement are not affected — they snapshot terms at issuance time
  • The old agreement remains visible and auditable (never deleted)
  • effective_from of the new agreement should be >= today (no retroactive superseding)

Postconditions:

  • Old agreement status = superseded, retains all historical data
  • New agreement is the active one for future invoice creation

Open Questions:

  • Should the system auto-detect the "active" agreement based on effective_from/effective_to date ranges, or should there be an explicit status field (active, superseded, terminated)?
  • Can a company have multiple simultaneously active agreements for different entitlement types (e.g. one agreement for Gig terms, another for Placement terms)? Or is it always one agreement per account?
  • Should superseding require the admin to explicitly link the new agreement to the one it replaces (supersedes_agreement_id)?

UC-4: View agreement details and term history for a company

FieldDetails
ActorIdentities::Admin
TriggerAdmin needs to review current or past terms for a company

Preconditions:

  • Billing::Account exists

System Behaviour:

  1. Admin navigates to Billing Account detail page → Agreements tab
  2. System displays all agreements for the account, ordered by effective_from descending
  3. Each agreement shows:
    • Code, effective dates, document link
    • All terms with formatted values (e.g. "20%", "$5.00", "1000 credits")

Business Rules:

  • Superseded and historical agreements remain visible (audit requirement)
  • Admin can open the signed agreement PDF via document_url

Postconditions:

  • Read-only operation — no data changes

UC-5: Terminate an agreement when a company relationship ends

FieldDetails
ActorIdentities::Admin
TriggerCompany relationship ends, or agreement needs to be voided

Preconditions:

  • Agreement exists and is active

System Behaviour:

  1. Admin terminates the agreement, providing a reason
  2. System marks agreement status as terminated
  3. No further invoices can reference this agreement

Business Rules:

  • Existing invoices are not affected
  • If this was the only active agreement, the company cannot be invoiced until a new agreement is created
  • Reason for termination should be recorded (metadata or notes field)

Postconditions:

  • Agreement status = terminated
  • Company's billing account still exists and retains entitlement balances

UC-6: Use agreement terms to compute invoice line item pricing

FieldDetails
ActorIdentities::Admin (or system, for automated renewals)
TriggerCompany wants to purchase credits

Preconditions:

  • Active agreement exists for this billing account

System Behaviour:

  1. System identifies the active agreement for the billing account
  2. Agreement terms inform invoice line item computation:
    • Gig: fee_rate term → platform fee line item calculation
    • Placement: unit_price term → placement credit unit pricing
    • Any: discount_rate term → applied as discount on the invoice
  3. Invoice items snapshot the agreement terms at creation time

Business Rules:

  • Invoice must reference a specific billing_agreement_id for audit trail
  • Snapshotted terms on the invoice are immutable — even if the agreement is later superseded
  • If no active agreement exists, invoice creation is blocked

Model Interaction:

  • Billing::Invoice → belongs_to Billing::Agreement
  • Billing::InvoiceItem snapshots the relevant term_value and term_unit from the agreement

Invariants

Rules that must always hold, regardless of use case:

  1. An agreement must belong to exactly one Billing::Account
  2. An agreement must have at least one AgreementTerm
  3. Each (agreement, entitlement, term_key) combination is unique (enforced by DB index idx_agreement_terms_unique_per_instrument)
  4. Agreements are never deleted — only superseded or terminated
  5. Agreement terms capture negotiated values only — tax is determined elsewhere
  6. Admin audit trail (admin_created_by, admin_updated_by) is always populated

Model Interactions

Related ModelRelationshipInteraction
Billing::AccountAgreement belongs_to AccountAccount must exist before agreement. One account can have many agreements (historical).
Billing::AgreementTermAgreement has_many AgreementTermsCreated together via nested attributes. Terms define the commercial values for each entitlement instrument.
Billing::EntitlementAgreementTerm belongs_to EntitlementEach term is scoped to a specific instrument (gig, placement, workforce).
Billing::InvoiceInvoice references AgreementActive agreement's terms inform invoice pricing. Invoice snapshots terms at creation.
Identities::AdminAgreement tracks created_by / updated_byAudit trail for who created or last modified the agreement.

Schema Gaps

Items identified during this specification that need resolution before related features can be built:

GapImpactSuggested Resolution
No status column on billing_agreementsCannot distinguish active vs superseded vs terminatedAdd status enum column with default active
No logic to determine "active" agreement for an accountInvoice creation (UC-6) needs to know which agreement to useEither use status field or derive from effective_from/effective_to
No update endpointAdmin cannot correct mistakes (UC-2)Add TeamUpdateManager with invoice-reference guard
No supersedes_agreement_id columnCannot trace agreement lineageOptional — could rely on effective_from ordering instead