Governed operator lists (organizations, grants, people, billing×4) gain
server-side search, status filters, and 50-row pages with true totals
from count(*) OVER(); state is URL-addressable, out-of-range pages
clamp,
and no-match is distinct from true-empty.
People is the eighth flat sidebar entry: /operator/persons lists persons
newest-joined first (excluding the reserved system person), rows linking
to the existing detail.
Billing gains an operator invoice detail at
/operator/billing/invoices/{invoiceID} reusing the member projection;
open invoices past due present as Overdue (derived, filterable, stored
status untouched); all four views lead with the linked organization and
mute object IDs.
Grants filter over the derived Live/Superseded/Inactive state, the SQL
HAVING predicate pinned to the Go derivation by test. Embedded lists
(org composite ledger, Tier changes) adopt the shared controls under
namespaced params with sibling-state-preserving URLs and scoped htmx
swaps that hold the viewport.
Review corrections: blocked ladder Delete renders disabled with tooltip
and mutations fire toasts; collapse triggers paint their open state;
sections use outside headings; plan topology drops the orphan-product
check; domains policy collapses behind a disclosure.
293 lines
8.8 KiB
Go
293 lines
8.8 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: billing_accounts.sql
|
|
|
|
package billing
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
const createBillingAccount = `-- name: CreateBillingAccount :one
|
|
INSERT INTO core.accounts (org_id, name, status, metadata)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING billing_account_id, org_id, name, status, metadata, created_at, updated_at
|
|
`
|
|
|
|
type CreateBillingAccountParams struct {
|
|
OrgID string `json:"org_id"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Metadata json.RawMessage `json:"metadata"`
|
|
}
|
|
|
|
func (q *Queries) CreateBillingAccount(ctx context.Context, arg CreateBillingAccountParams) (Account, error) {
|
|
row := q.db.QueryRowContext(ctx, createBillingAccount,
|
|
arg.OrgID,
|
|
arg.Name,
|
|
arg.Status,
|
|
arg.Metadata,
|
|
)
|
|
var i Account
|
|
err := row.Scan(
|
|
&i.BillingAccountID,
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Status,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getBillingAccountByID = `-- name: GetBillingAccountByID :one
|
|
SELECT billing_account_id, org_id, name, status, metadata, created_at, updated_at FROM core.accounts
|
|
WHERE billing_account_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetBillingAccountByID(ctx context.Context, billingAccountID string) (Account, error) {
|
|
row := q.db.QueryRowContext(ctx, getBillingAccountByID, billingAccountID)
|
|
var i Account
|
|
err := row.Scan(
|
|
&i.BillingAccountID,
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Status,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getLatestInvoiceByBillingAccountID = `-- name: GetLatestInvoiceByBillingAccountID :one
|
|
SELECT invoice_id, billing_account_id, subscription_id, status, amount_due, amount_paid, currency, period_start, period_end, due_date, paid_at, voided_at, created_at, updated_at FROM core.invoices
|
|
WHERE billing_account_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
// Most recent invoice for a billing account (any status). Used by the
|
|
// per-org composite's billing-summary section.
|
|
func (q *Queries) GetLatestInvoiceByBillingAccountID(ctx context.Context, billingAccountID string) (Invoice, error) {
|
|
row := q.db.QueryRowContext(ctx, getLatestInvoiceByBillingAccountID, billingAccountID)
|
|
var i Invoice
|
|
err := row.Scan(
|
|
&i.InvoiceID,
|
|
&i.BillingAccountID,
|
|
&i.SubscriptionID,
|
|
&i.Status,
|
|
&i.AmountDue,
|
|
&i.AmountPaid,
|
|
&i.Currency,
|
|
&i.PeriodStart,
|
|
&i.PeriodEnd,
|
|
&i.DueDate,
|
|
&i.PaidAt,
|
|
&i.VoidedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getLatestSubscriptionByBillingAccountID = `-- name: GetLatestSubscriptionByBillingAccountID :one
|
|
SELECT subscription_id, billing_account_id, status, current_period_start, current_period_end, cancel_at_period_end, canceled_at, ended_at, created_at, updated_at, commitment_end, commitment_renewal, early_termination_policy FROM core.subscriptions
|
|
WHERE billing_account_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
// Most recent subscription for a billing account (any status). Used by the
|
|
// per-org composite's billing-summary section.
|
|
func (q *Queries) GetLatestSubscriptionByBillingAccountID(ctx context.Context, billingAccountID string) (Subscription, error) {
|
|
row := q.db.QueryRowContext(ctx, getLatestSubscriptionByBillingAccountID, billingAccountID)
|
|
var i Subscription
|
|
err := row.Scan(
|
|
&i.SubscriptionID,
|
|
&i.BillingAccountID,
|
|
&i.Status,
|
|
&i.CurrentPeriodStart,
|
|
&i.CurrentPeriodEnd,
|
|
&i.CancelAtPeriodEnd,
|
|
&i.CanceledAt,
|
|
&i.EndedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.CommitmentEnd,
|
|
&i.CommitmentRenewal,
|
|
&i.EarlyTerminationPolicy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getOutstandingBalanceByBillingAccountID = `-- name: GetOutstandingBalanceByBillingAccountID :one
|
|
SELECT COALESCE(SUM(amount_due - amount_paid), 0)::BIGINT AS outstanding_balance
|
|
FROM core.invoices
|
|
WHERE billing_account_id = $1
|
|
AND status = 'open'
|
|
`
|
|
|
|
// Sum of (amount_due - amount_paid) across invoices with status='open' — the
|
|
// only actionable non-terminal invoice status per design/billing/model.md.
|
|
// Returns 0 if no open invoices exist.
|
|
func (q *Queries) GetOutstandingBalanceByBillingAccountID(ctx context.Context, billingAccountID string) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, getOutstandingBalanceByBillingAccountID, billingAccountID)
|
|
var outstanding_balance int64
|
|
err := row.Scan(&outstanding_balance)
|
|
return outstanding_balance, err
|
|
}
|
|
|
|
const listBillingAccountsByOrgID = `-- name: ListBillingAccountsByOrgID :many
|
|
SELECT billing_account_id, org_id, name, status, metadata, created_at, updated_at FROM core.accounts
|
|
WHERE org_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListBillingAccountsByOrgID(ctx context.Context, orgID string) ([]Account, error) {
|
|
rows, err := q.db.QueryContext(ctx, listBillingAccountsByOrgID, orgID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Account{}
|
|
for rows.Next() {
|
|
var i Account
|
|
if err := rows.Scan(
|
|
&i.BillingAccountID,
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Status,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listBillingAccountsPage = `-- name: ListBillingAccountsPage :many
|
|
SELECT a.billing_account_id, a.org_id, a.name, a.status, a.metadata, a.created_at, a.updated_at,
|
|
count(*) OVER() AS total_count
|
|
FROM core.accounts a
|
|
WHERE ($1::text IS NULL
|
|
OR a.name ILIKE '%' || $1::text || '%'
|
|
OR a.org_id = ANY($2::uuid[]))
|
|
ORDER BY a.created_at DESC
|
|
LIMIT $4::int OFFSET $3::int
|
|
`
|
|
|
|
type ListBillingAccountsPageParams struct {
|
|
Q sql.NullString `json:"q"`
|
|
OrgIds []string `json:"org_ids"`
|
|
PageOffset int32 `json:"page_offset"`
|
|
PageLimit int32 `json:"page_limit"`
|
|
}
|
|
|
|
type ListBillingAccountsPageRow struct {
|
|
BillingAccountID string `json:"billing_account_id"`
|
|
OrgID string `json:"org_id"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Metadata json.RawMessage `json:"metadata"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// Operator billing-accounts view, paged and searched (operator-list-scale
|
|
// UX-4; operator-billing-views D8). No status facet: core.accounts.status
|
|
// carries no CHECK-enforced vocabulary and operator-list-scale's "Status
|
|
// filters exist where a status vocabulary exists" requirement does not
|
|
// name accounts, so this query offers search + pagination only.
|
|
//
|
|
// sqlc.narg(q): NULL matches every row; set, a case-insensitive substring
|
|
// match against the account's own name, or its organization via
|
|
// sqlc.narg(org_ids) (design D3: "billing: organization name plus the
|
|
// view's leading identifier"). Organization-name search resolves through
|
|
// a pre-resolved ID array rather than a join to core.organizations: no
|
|
// existing internal/billing query crosses that module boundary
|
|
// (grep 'core\.' internal/billing/queries/ turns up none), so
|
|
// operator_billing.go's matchingOrgIDs resolves matching org IDs in Go and
|
|
// passes them here, mirroring the grants lane's product-name pattern.
|
|
// count(*) OVER() carries the true total for the filtered set (design D2).
|
|
func (q *Queries) ListBillingAccountsPage(ctx context.Context, arg ListBillingAccountsPageParams) ([]ListBillingAccountsPageRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listBillingAccountsPage,
|
|
arg.Q,
|
|
pq.Array(arg.OrgIds),
|
|
arg.PageOffset,
|
|
arg.PageLimit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListBillingAccountsPageRow{}
|
|
for rows.Next() {
|
|
var i ListBillingAccountsPageRow
|
|
if err := rows.Scan(
|
|
&i.BillingAccountID,
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Status,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.TotalCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const updateBillingAccountStatus = `-- name: UpdateBillingAccountStatus :one
|
|
UPDATE core.accounts
|
|
SET status = $2
|
|
WHERE billing_account_id = $1
|
|
RETURNING billing_account_id, org_id, name, status, metadata, created_at, updated_at
|
|
`
|
|
|
|
type UpdateBillingAccountStatusParams struct {
|
|
BillingAccountID string `json:"billing_account_id"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
func (q *Queries) UpdateBillingAccountStatus(ctx context.Context, arg UpdateBillingAccountStatusParams) (Account, error) {
|
|
row := q.db.QueryRowContext(ctx, updateBillingAccountStatus, arg.BillingAccountID, arg.Status)
|
|
var i Account
|
|
err := row.Scan(
|
|
&i.BillingAccountID,
|
|
&i.OrgID,
|
|
&i.Name,
|
|
&i.Status,
|
|
&i.Metadata,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|