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
| Context | Details |
|---|---|
| Aggregate | Billing::Agreement (root) + Billing::AgreementTerm (child) |
| Layer | Commercial Documents |
| Upstream dependencies | Billing::Account (must exist), Billing::Entitlement (terms reference instruments) |
| Downstream dependents | Billing::Invoice (uses agreement terms to compute invoice line items) |
State Machine
| From | To | Trigger | Notes |
|---|---|---|---|
| (new) | active | UC-1: Agreement created | Default status on creation |
active | superseded | UC-3: New agreement replaces this one | Old agreement preserved for audit |
active | terminated | UC-5: Admin terminates | No further invoices allowed |
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Create an agreement with negotiated commercial terms for a company | Sales finalizes negotiated terms after signing | Admin (sales) |
| UC-2 | Edit an agreement before any invoice references it | Sales needs to correct a mistake before invoicing | Admin (sales) |
| UC-3 | Supersede an agreement when terms are renegotiated | Company renegotiates terms (renewal, rate change) | Admin (sales) |
| UC-4 | View agreement details and term history for a company | Admin needs to review current or past terms | Admin |
| UC-5 | Terminate an agreement when a company relationship ends | Company relationship ends or agreement voided | Admin |
| UC-6 | Use agreement terms to compute invoice line item pricing | Company wants to purchase credits | Admin / System |
UC-1: Create an agreement with negotiated commercial terms for a company
| Field | Details |
|---|---|
| Actor | Identities::Admin (sales/ops) |
| Trigger | Sales finalizes negotiated commercial terms with a company after signing |
Preconditions:
Billing::Accountexists for theOrg::Company- Admin has the signed agreement document (PDF)
System Behaviour:
- Admin enters agreement details:
code— human-readable identifier (e.g.SG-GIG-2026-0001)document_url— S3 path to signed PDFeffective_from— when the agreement takes effecteffective_to— optional expiry (null = open-ended)
- Admin defines one or more terms, each specifying:
- which
Billing::Entitlementinstrument 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)
- which
- System validates inputs (see Business Rules)
- Agreement + terms created in a single DB transaction
- Admin audit trail recorded (
admin_created_by)
Business Rules:
- At least one
AgreementTermis required - Each
(agreement, entitlement, term_key)combination must be unique term_valuemust be a positive integerterm_keymust be one of:fee_rate,discount_rate,unit_priceterm_unitmust be one of:bps,cents,credits- Terms capture negotiated commercial values only — not tax (tax is determined by
Billing::LegalEntity+Billing::ProductPriceat 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
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Sales 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:
- Admin modifies agreement fields (code, dates, document_url) and/or terms
- System verifies no invoices reference this agreement
- Agreement + terms updated in a single DB transaction
- Admin audit trail recorded (
admin_updated_by)
Business Rules:
- If any
Billing::Invoicereferences 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
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Company renegotiates terms (annual renewal, rate change, contract amendment) |
Preconditions:
- At least one existing agreement for this billing account
System Behaviour:
- Admin creates a new agreement with updated terms (same flow as UC-1)
- System marks the previous agreement's status as
superseded - 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_fromof 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_todate ranges, or should there be an explicitstatusfield (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
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Admin needs to review current or past terms for a company |
Preconditions:
Billing::Accountexists
System Behaviour:
- Admin navigates to Billing Account detail page → Agreements tab
- System displays all agreements for the account, ordered by
effective_fromdescending - 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
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Company relationship ends, or agreement needs to be voided |
Preconditions:
- Agreement exists and is active
System Behaviour:
- Admin terminates the agreement, providing a reason
- System marks agreement status as
terminated - 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
| Field | Details |
|---|---|
| Actor | Identities::Admin (or system, for automated renewals) |
| Trigger | Company wants to purchase credits |
Preconditions:
- Active agreement exists for this billing account
System Behaviour:
- System identifies the active agreement for the billing account
- Agreement terms inform invoice line item computation:
- Gig:
fee_rateterm → platform fee line item calculation - Placement:
unit_priceterm → placement credit unit pricing - Any:
discount_rateterm → applied as discount on the invoice
- Gig:
- Invoice items snapshot the agreement terms at creation time
Business Rules:
- Invoice must reference a specific
billing_agreement_idfor 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_toBilling::AgreementBilling::InvoiceItemsnapshots the relevantterm_valueandterm_unitfrom the agreement
Invariants
Rules that must always hold, regardless of use case:
- An agreement must belong to exactly one
Billing::Account - An agreement must have at least one
AgreementTerm - Each
(agreement, entitlement, term_key)combination is unique (enforced by DB indexidx_agreement_terms_unique_per_instrument) - Agreements are never deleted — only superseded or terminated
- Agreement terms capture negotiated values only — tax is determined elsewhere
- Admin audit trail (
admin_created_by,admin_updated_by) is always populated
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Billing::Account | Agreement belongs_to Account | Account must exist before agreement. One account can have many agreements (historical). |
Billing::AgreementTerm | Agreement has_many AgreementTerms | Created together via nested attributes. Terms define the commercial values for each entitlement instrument. |
Billing::Entitlement | AgreementTerm belongs_to Entitlement | Each term is scoped to a specific instrument (gig, placement, workforce). |
Billing::Invoice | Invoice references Agreement | Active agreement's terms inform invoice pricing. Invoice snapshots terms at creation. |
Identities::Admin | Agreement tracks created_by / updated_by | Audit 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:
| Gap | Impact | Suggested Resolution |
|---|---|---|
No status column on billing_agreements | Cannot distinguish active vs superseded vs terminated | Add status enum column with default active |
| No logic to determine "active" agreement for an account | Invoice creation (UC-6) needs to know which agreement to use | Either use status field or derive from effective_from/effective_to |
| No update endpoint | Admin cannot correct mistakes (UC-2) | Add TeamUpdateManager with invoice-reference guard |
No supersedes_agreement_id column | Cannot trace agreement lineage | Optional — could rely on effective_from ordering instead |