Leases
leasesLeases of units (assets) to parties with rent in bigint minor units and a frequency, rent charges posted on the ledger once per period, deposits held through ledger holds, renewals, terminations and maintenance requests on the unit.
Billing & commercemanagedcustom
Invoice INV-0042paid
Pro plan€49.00
Seats × 3€27.00
Usage€4.20
Total€80.20
Install
The command also installs updated-at, tenant, access, subjects, ledger, assets, the modules it needs.
npx better-supabase add leasesConfigure
The blocks entries in your config, with the default mode.
import { defineConfig } from "better-supabase/config";
export default defineConfig({
blocks: {
// Required by leases; add installs them too.
"updated-at": {},
tenant: {},
access: {},
subjects: {},
ledger: {},
assets: {},
// Modes: "managed", "custom". The default is "managed".
leases: { mode: "managed" },
},
});Migrations
The files
npx better-supabase add leases writes into your declarative schema with the default layout.14 files
supabase/schemas/900_better_supabase_120_leases.sqlschema700 lines-- better-supabase module: leases
-- @bs-module leases@1 managed
-- Leases of units (assets) to parties with rent in bigint minor units and a frequency, rent charges posted on the ledger once per period, deposits held through ledger holds, renewals, terminations and maintenance requests on the unit.
-- Managed by `better-supabase add`; re-running it overwrites this file.
-- Change it through `blocks` in better-supabase.config.ts and the module's SQL hooks.
create or replace function better_supabase.actor_may_write()
returns boolean
language sql
stable
security invoker
set search_path = ''
as $$
select case
when auth.jwt() is null then true
when auth.jwt() -> 'act' ->> 'kind' = 'support'
then coalesce(auth.jwt() -> 'act' -> 'read_only' = 'false'::jsonb, false)
when auth.jwt() -> 'act' ->> 'kind' = 'agent'
then coalesce(auth.jwt() -> 'act' -> 'read_only', 'false'::jsonb) = 'false'::jsonb
when auth.jwt() -> 'act' ->> 'kind' is not null then false
when auth.jwt() -> 'act' ->> 'session_id' is not null
and auth.jwt() -> 'act' ->> 'kind' is null then false
else true
end
$$;
revoke execute on function better_supabase.actor_may_write() from public;
grant execute on function better_supabase.actor_may_write() to anon, authenticated, service_role;
create or replace function better_supabase.raise_actor_readonly()
returns void
language plpgsql
volatile
set search_path = ''
as $$
begin
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end;
$$;
revoke execute on function better_supabase.raise_actor_readonly() from public;
grant execute on function better_supabase.raise_actor_readonly() to anon, authenticated, service_role;
create schema if not exists better_supabase;
grant usage on schema better_supabase to anon, authenticated, service_role;
-- Leases: units (assets) let to parties, rent posted on the ledger once per
-- period, deposits held through ledger holds, and maintenance requests.
create table if not exists "better_supabase"."lease_agreements" (
"id" uuid primary key default gen_random_uuid(),
"tenant_id" uuid not null,
"unit_id" uuid not null references "better_supabase"."assets" ("id") on delete restrict,
-- The party renting the unit: a registered subject such as a contact.
"party_type" text not null,
"party_id" text not null,
"title" text not null check (length("title") between 1 and 200),
"status" text not null default 'draft' check ("status" in ('draft', 'active', 'ended', 'terminated')),
"starts_on" date not null,
-- Null runs the lease until it is terminated.
"ends_on" date,
-- Minor units of currency per period (ADR 0037).
"rent" bigint not null check ("rent" > 0),
"currency" char(3) not null check ("currency" ~ '^[A-Z]{3}$'),
"frequency" text not null default 'monthly' check ("frequency" in ('weekly', 'monthly', 'quarterly', 'yearly')),
"deposit" bigint not null default 0 check ("deposit" >= 0),
-- The ledger hold that keeps the deposit aside while the lease runs.
"deposit_hold_id" uuid,
"renewed_at" timestamptz,
"terminated_on" date,
"termination_reason" text check ("termination_reason" is null or length("termination_reason") <= 2000),
"metadata" jsonb not null default '{}'::jsonb,
"created_by" uuid,
"created_at" timestamptz not null default now(),
"updated_at" timestamptz not null default now(),
check ("ends_on" is null or "ends_on" > "starts_on")
);
create index if not exists "lease_agreements_tenant_idx" on "better_supabase"."lease_agreements" ("tenant_id", "status", "starts_on" desc);
create index if not exists "lease_agreements_unit_idx" on "better_supabase"."lease_agreements" ("unit_id");
create index if not exists "lease_agreements_party_idx" on "better_supabase"."lease_agreements" ("tenant_id", "party_type", "party_id");
create index if not exists "lease_agreements_deposit_hold_idx" on "better_supabase"."lease_agreements" ("deposit_hold_id") where "deposit_hold_id" is not null;
alter table "better_supabase"."lease_agreements" enable row level security;
revoke all on "better_supabase"."lease_agreements" from anon, authenticated;
grant select on "better_supabase"."lease_agreements" to authenticated;
grant all on "better_supabase"."lease_agreements" to service_role;
drop policy if exists "lease_agreements_read" on "better_supabase"."lease_agreements";
create policy "lease_agreements_read" on "better_supabase"."lease_agreements" for select to authenticated
using ("tenant_id" in (select better_supabase.tenant_ids_with('leases.read')));
-- One row per rent period posted to the ledger, so each period posts once.
create table if not exists "better_supabase"."lease_charges" (
"id" uuid primary key default gen_random_uuid(),
"tenant_id" uuid not null,
"lease_id" uuid not null references "better_supabase"."lease_agreements" ("id") on delete restrict,
"period_start" date not null,
"period_end" date not null,
"amount" bigint not null check ("amount" > 0),
"currency" char(3) not null,
"transaction_id" uuid not null,
"created_at" timestamptz not null default now(),
unique ("lease_id", "period_start"),
check ("period_end" > "period_start")
);
create index if not exists "lease_charges_tenant_idx" on "better_supabase"."lease_charges" ("tenant_id", "period_start" desc);
create index if not exists "lease_charges_transaction_idx" on "better_supabase"."lease_charges" ("transaction_id");
alter table "better_supabase"."lease_charges" enable row level security;
revoke all on "better_supabase"."lease_charges" from anon, authenticated;
grant select on "better_supabase"."lease_charges" to authenticated;
grant all on "better_supabase"."lease_charges" to service_role;
drop policy if exists "lease_charges_read" on "better_supabase"."lease_charges";
create policy "lease_charges_read" on "better_supabase"."lease_charges" for select to authenticated
using ("tenant_id" in (select better_supabase.tenant_ids_with('leases.read')));
create table if not exists "better_supabase"."maintenance_requests" (
"id" uuid primary key default gen_random_uuid(),
"tenant_id" uuid not null,
"unit_id" uuid not null references "better_supabase"."assets" ("id") on delete cascade,
"lease_id" uuid references "better_supabase"."lease_agreements" ("id") on delete set null,
"title" text not null check (length("title") between 1 and 200),
"description" text check ("description" is null or length("description") <= 5000),
"priority" text not null default 'normal' check ("priority" in ('low', 'normal', 'high', 'urgent')),
"status" text not null default 'open' check ("status" in ('open', 'in_progress', 'resolved', 'cancelled')),
"reported_by" uuid,
"assignee_id" uuid,
"resolved_at" timestamptz,
"created_at" timestamptz not null default now(),
"updated_at" timestamptz not null default now()
);
create index if not exists "maintenance_requests_tenant_idx" on "better_supabase"."maintenance_requests" ("tenant_id", "status", "created_at" desc);
create index if not exists "maintenance_requests_unit_idx" on "better_supabase"."maintenance_requests" ("unit_id");
create index if not exists "maintenance_requests_tenancy_idx" on "better_supabase"."maintenance_requests" ("lease_id") where "lease_id" is not null;
alter table "better_supabase"."maintenance_requests" enable row level security;
revoke all on "better_supabase"."maintenance_requests" from anon, authenticated;
grant select on "better_supabase"."maintenance_requests" to authenticated;
grant all on "better_supabase"."maintenance_requests" to service_role;
drop policy if exists "maintenance_requests_read" on "better_supabase"."maintenance_requests";
create policy "maintenance_requests_read" on "better_supabase"."maintenance_requests" for select to authenticated
using ("reported_by" = (select auth.uid()) or "tenant_id" in (select better_supabase.tenant_ids_with('leases.read')));
-- Creates a draft lease of a unit (an asset) to a party (a registered
-- subject). Rent is minor units of currency per period of frequency.
-- fields: the extra columns to set.
drop function if exists "better_supabase"."create_lease"(uuid, uuid, text, text, date, bigint, text, text, date, bigint, text, jsonb, jsonb);
create or replace function "better_supabase"."create_lease"(tenant uuid, unit_id uuid, party_type text, party_id text, starts_on date, rent bigint, currency text, frequency text default 'monthly', ends_on date default null, deposit bigint default 0, title text default null, metadata jsonb default null)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."lease_agreements"%rowtype;
v_unit text;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
if not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', create_lease.tenant, 'leases.manage'), false)) then
raise exception 'not allowed to manage leases' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
select u."name" into v_unit from "better_supabase"."assets" u
where u."id" = create_lease.unit_id and u."tenant_id" = create_lease.tenant and u."retired_at" is null;
if not found then
raise exception 'the unit is unknown, retired or not in this organization' using errcode = '22023', hint = 'LEASE_UNIT_INVALID';
end if;
if create_lease.party_type is null or create_lease.party_id is null
or not false then
raise exception 'the party is unknown or not in this organization' using errcode = '22023', hint = 'LEASE_PARTY_INVALID';
end if;
if create_lease.starts_on is null then
raise exception 'a lease needs starts_on' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if create_lease.ends_on is not null and create_lease.ends_on <= create_lease.starts_on then
raise exception 'ends_on must be after starts_on' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if create_lease.rent is null or create_lease.rent <= 0 then
raise exception 'rent is more than zero minor units' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if create_lease.currency is null or create_lease.currency !~ '^[A-Z]{3}$' then
raise exception 'currency must be a three-letter currency code' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if coalesce(create_lease.frequency, 'monthly') not in ('weekly', 'monthly', 'quarterly', 'yearly') then
raise exception 'frequency is weekly, monthly, quarterly or yearly' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if coalesce(create_lease.deposit, 0) < 0 then
raise exception 'a deposit is zero or more minor units' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
insert into "better_supabase"."lease_agreements" ("tenant_id", "unit_id", "party_type", "party_id", "title", "starts_on", "ends_on",
"rent", "currency", "frequency", "deposit", "metadata", "created_by")
values (create_lease.tenant, create_lease.unit_id, create_lease.party_type, create_lease.party_id,
coalesce(nullif(trim(create_lease.title), ''), left(v_unit, 190) || ' lease'), create_lease.starts_on, create_lease.ends_on,
create_lease.rent, create_lease.currency, coalesce(create_lease.frequency, 'monthly'), coalesce(create_lease.deposit, 0),
coalesce(create_lease.metadata, '{}'::jsonb), auth.uid())
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'partyType', v_row."party_type", 'partyId', v_row."party_id", 'title', v_row."title", 'status', v_row."status", 'startsOn', v_row."starts_on", 'endsOn', v_row."ends_on", 'rent', v_row."rent"::text, 'currency', v_row."currency", 'frequency', v_row."frequency", 'deposit', v_row."deposit"::text, 'depositHoldId', v_row."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text, 'renewedAt', v_row."renewed_at", 'terminatedOn', v_row."terminated_on", 'terminationReason', v_row."termination_reason", 'metadata', v_row."metadata", 'createdBy', v_row."created_by", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."create_lease"(uuid, uuid, text, text, date, bigint, text, text, date, bigint, text, jsonb) from public, anon;
grant execute on function "better_supabase"."create_lease"(uuid, uuid, text, text, date, bigint, text, text, date, bigint, text, jsonb) to authenticated, service_role;
-- Starts a draft lease. Fails while another active lease of the unit
-- overlaps it. A deposit is received on the lease's deposit account and
-- held there until the lease ends.
create or replace function "better_supabase"."activate_lease"(lease_id uuid)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
v_row "better_supabase"."lease_agreements"%rowtype;
v_account uuid;
v_hold "better_supabase"."ledger_holds"%rowtype;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = activate_lease.lease_id for update;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.manage'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
if v_row."status" <> 'draft' then
raise exception 'only a draft lease can be activated' using errcode = '55000', hint = 'LEASE_STATUS_INVALID';
end if;
perform pg_advisory_xact_lock(hashtextextended('leases.unit:' || v_row."unit_id"::text, 0));
if exists (
select 1 from "better_supabase"."lease_agreements" x
where x."unit_id" = v_row."unit_id" and x."id" <> v_row."id" and x."status" = 'active'
and daterange(x."starts_on", x."ends_on") && daterange(v_row."starts_on", v_row."ends_on")
) then
raise exception 'another active lease of this unit overlaps these dates' using errcode = '23P01', hint = 'LEASE_OVERLAP';
end if;
if v_row."deposit" > 0 then
v_account := "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_deposit', 'leases.deposit:' || v_row."id"::text, 'Deposit ' || v_row."title",
v_row."currency"::text, 0::smallint, 'lease', v_row."id"::text, null, false, 'leases.read', 'leases',
jsonb_build_object('leaseId', v_row."id"));
perform "better_supabase"."ledger_post"(v_row."tenant_id", 'lease.deposit',
jsonb_build_array(
jsonb_build_object('account', v_account, 'amount', v_row."deposit"),
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_cash', 'leases.cash:' || v_row."currency", 'Rent received ' || v_row."currency",
v_row."currency"::text, 0::smallint, null, null, null, true, 'leases.read', 'leases', null), 'amount', -v_row."deposit")),
'leases', v_row."id"::text, 'Deposit received', 'leases.deposit:' || v_row."id"::text, null,
jsonb_build_object('leaseId', v_row."id"));
v_hold := "better_supabase"."ledger_hold"(v_account, v_row."deposit", 'leases', v_row."id"::text, null);
end if;
update "better_supabase"."lease_agreements" x set "status" = 'active', "deposit_hold_id" = v_hold."id", "updated_at" = now()
where x."id" = v_row."id"
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'partyType', v_row."party_type", 'partyId', v_row."party_id", 'title', v_row."title", 'status', v_row."status", 'startsOn', v_row."starts_on", 'endsOn', v_row."ends_on", 'rent', v_row."rent"::text, 'currency', v_row."currency", 'frequency', v_row."frequency", 'deposit', v_row."deposit"::text, 'depositHoldId', v_row."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text, 'renewedAt', v_row."renewed_at", 'terminatedOn', v_row."terminated_on", 'terminationReason', v_row."termination_reason", 'metadata', v_row."metadata", 'createdBy', v_row."created_by", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."activate_lease"(uuid) from public, anon;
grant execute on function "better_supabase"."activate_lease"(uuid) to authenticated, service_role;
-- Extends an active lease with a fixed end to a later ends_on, at a new
-- rent when one is given. Periods posted from then on use the new rent.
create or replace function "better_supabase"."renew_lease"(lease_id uuid, ends_on date, rent bigint default null)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."lease_agreements"%rowtype;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = renew_lease.lease_id for update;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.manage'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
if v_row."status" <> 'active' then
raise exception 'only an active lease can be renewed' using errcode = '55000', hint = 'LEASE_STATUS_INVALID';
end if;
if v_row."ends_on" is null then
raise exception 'this lease has no end; it runs until it is terminated' using errcode = '55000', hint = 'LEASE_STATUS_INVALID';
end if;
if renew_lease.ends_on is null or renew_lease.ends_on <= v_row."ends_on" then
raise exception 'a renewal ends after the current end' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if renew_lease.rent is not null and renew_lease.rent <= 0 then
raise exception 'rent is more than zero minor units' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
update "better_supabase"."lease_agreements" x set "ends_on" = renew_lease.ends_on, "rent" = coalesce(renew_lease.rent, x."rent"),
"renewed_at" = now(), "updated_at" = now()
where x."id" = v_row."id"
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'partyType', v_row."party_type", 'partyId', v_row."party_id", 'title', v_row."title", 'status', v_row."status", 'startsOn', v_row."starts_on", 'endsOn', v_row."ends_on", 'rent', v_row."rent"::text, 'currency', v_row."currency", 'frequency', v_row."frequency", 'deposit', v_row."deposit"::text, 'depositHoldId', v_row."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text, 'renewedAt', v_row."renewed_at", 'terminatedOn', v_row."terminated_on", 'terminationReason', v_row."termination_reason", 'metadata', v_row."metadata", 'createdBy', v_row."created_by", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."renew_lease"(uuid, date, bigint) from public, anon;
grant execute on function "better_supabase"."renew_lease"(uuid, date, bigint) to authenticated, service_role;
-- Ends a lease on terminates_on (default today): ended on or after its end,
-- terminated before. No rent posts for periods from that day. The deposit
-- hold is captured for deposit_deduction (into rent income) and the rest
-- goes back to the party. A draft is terminated without touching the ledger.
create or replace function "better_supabase"."terminate_lease"(lease_id uuid, terminates_on date default null, reason text default null, deposit_deduction bigint default 0)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."lease_agreements"%rowtype;
v_on date := coalesce(terminate_lease.terminates_on, current_date);
v_deduction bigint := coalesce(terminate_lease.deposit_deduction, 0);
v_held bigint;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = terminate_lease.lease_id for update;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.manage'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
if v_row."status" not in ('draft', 'active') then
raise exception 'this lease has already ended' using errcode = '55000', hint = 'LEASE_STATUS_INVALID';
end if;
if v_on < v_row."starts_on" and v_row."status" = 'active' then
raise exception 'a lease can''t end before it starts' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
if exists (select 1 from "better_supabase"."lease_charges" y where y."lease_id" = v_row."id" and y."period_start" >= v_on) then
raise exception 'rent is already posted for a period from this date; pick a later date' using errcode = '22023', hint = 'LEASE_TERMS_INVALID';
end if;
v_held := coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0);
if v_deduction < 0 or v_deduction > v_held then
raise exception 'a deduction is zero or more and at most the deposit held' using errcode = '22023', hint = 'LEASE_DEPOSIT_INVALID';
end if;
if v_held > 0 then
if v_deduction > 0 then
perform "better_supabase"."ledger_capture"(v_row."deposit_hold_id", "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_income', 'leases.income:' || v_row."currency", 'Rent income ' || v_row."currency",
v_row."currency"::text, 0::smallint, null, null, null, true, 'leases.read', 'leases', null), v_deduction, 'lease.deposit_deduction', 'Deposit deduction');
else
perform "better_supabase"."ledger_release"(v_row."deposit_hold_id");
end if;
if v_held - v_deduction > 0 then
perform "better_supabase"."ledger_post"(v_row."tenant_id", 'lease.deposit_refund',
jsonb_build_array(
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_deposit', 'leases.deposit:' || v_row."id"::text, 'Deposit ' || v_row."title",
v_row."currency"::text, 0::smallint, 'lease', v_row."id"::text, null, false, 'leases.read', 'leases',
jsonb_build_object('leaseId', v_row."id")), 'amount', -(v_held - v_deduction)),
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_cash', 'leases.cash:' || v_row."currency", 'Rent received ' || v_row."currency",
v_row."currency"::text, 0::smallint, null, null, null, true, 'leases.read', 'leases', null), 'amount', v_held - v_deduction)),
'leases', v_row."id"::text, 'Deposit refunded', 'leases.deposit_refund:' || v_row."id"::text, null,
jsonb_build_object('leaseId', v_row."id"));
end if;
end if;
update "better_supabase"."lease_agreements" x set
"status" = case when x."status" = 'active' and x."ends_on" is not null and v_on >= x."ends_on" then 'ended' else 'terminated' end,
"terminated_on" = v_on, "termination_reason" = nullif(trim(terminate_lease.reason), ''), "updated_at" = now()
where x."id" = v_row."id"
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'partyType', v_row."party_type", 'partyId', v_row."party_id", 'title', v_row."title", 'status', v_row."status", 'startsOn', v_row."starts_on", 'endsOn', v_row."ends_on", 'rent', v_row."rent"::text, 'currency', v_row."currency", 'frequency', v_row."frequency", 'deposit', v_row."deposit"::text, 'depositHoldId', v_row."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text, 'renewedAt', v_row."renewed_at", 'terminatedOn', v_row."terminated_on", 'terminationReason', v_row."termination_reason", 'metadata', v_row."metadata", 'createdBy', v_row."created_by", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."terminate_lease"(uuid, date, text, bigint) from public, anon;
grant execute on function "better_supabase"."terminate_lease"(uuid, date, text, bigint) to authenticated, service_role;
create or replace function "better_supabase"."get_lease"(lease_id uuid)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_row "better_supabase"."lease_agreements"%rowtype;
begin
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = get_lease.lease_id;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.read'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'partyType', v_row."party_type", 'partyId', v_row."party_id", 'title', v_row."title", 'status', v_row."status", 'startsOn', v_row."starts_on", 'endsOn', v_row."ends_on", 'rent', v_row."rent"::text, 'currency', v_row."currency", 'frequency', v_row."frequency", 'deposit', v_row."deposit"::text, 'depositHoldId', v_row."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = v_row."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text, 'renewedAt', v_row."renewed_at", 'terminatedOn', v_row."terminated_on", 'terminationReason', v_row."termination_reason", 'metadata', v_row."metadata", 'createdBy', v_row."created_by", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."get_lease"(uuid) from public, anon;
grant execute on function "better_supabase"."get_lease"(uuid) to authenticated, service_role;
create or replace function "better_supabase"."list_leases"(tenant uuid, status text default null, unit_id uuid default null, party_type text default null, party_id text default null, page_size integer default 100)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
#variable_conflict use_column
begin
if not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', list_leases.tenant, 'leases.read'), false)) then
raise exception 'not allowed to read leases' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
return (
select coalesce(jsonb_agg(jsonb_build_object('id', q."id", 'organizationId', q."tenant_id", 'unitId', q."unit_id", 'partyType', q."party_type", 'partyId', q."party_id", 'title', q."title", 'status', q."status", 'startsOn', q."starts_on", 'endsOn', q."ends_on", 'rent', q."rent"::text, 'currency', q."currency", 'frequency', q."frequency", 'deposit', q."deposit"::text, 'depositHoldId', q."deposit_hold_id", 'depositHeld', coalesce((select h."amount" from "better_supabase"."ledger_holds" h where h."id" = q."deposit_hold_id" and h."status" = 'active'), 0)::text, 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = q."tenant_id" and x."code" = 'leases.rent:' || q."id"::text), 0)::text, 'renewedAt', q."renewed_at", 'terminatedOn', q."terminated_on", 'terminationReason', q."termination_reason", 'metadata', q."metadata", 'createdBy', q."created_by", 'createdAt', q."created_at", 'updatedAt', q."updated_at") order by q."starts_on" desc, q."id"), '[]'::jsonb)
from (
select * from "better_supabase"."lease_agreements" x
where x."tenant_id" = list_leases.tenant
and (list_leases.status is null or x."status" = list_leases.status)
and (list_leases.unit_id is null or x."unit_id" = list_leases.unit_id)
and (list_leases.party_type is null or (x."party_type" = list_leases.party_type and x."party_id" = list_leases.party_id))
order by x."starts_on" desc, x."id"
limit least(greatest(coalesce(list_leases.page_size, 100), 1), 1000)
) q
);
end;
$$;
revoke execute on function "better_supabase"."list_leases"(uuid, text, uuid, text, text, integer) from public, anon;
grant execute on function "better_supabase"."list_leases"(uuid, text, uuid, text, text, integer) to authenticated, service_role;
-- Posts the rent of every period of an active lease that starts on or
-- before through (default today), up to max_rows charges. Each period posts
-- once: the charge row is unique per lease and period, and its ledger
-- transaction carries the key lease:<id>:rent:<period start>. Without a
-- tenant it runs for every organization and needs the service role, so a
-- scheduled job can call it.
create or replace function "better_supabase"."post_due_rent"(tenant uuid default null, through date default null, max_rows integer default 500)
returns integer
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_through date := coalesce(post_due_rent.through, current_date);
v_max integer := least(greatest(coalesce(post_due_rent.max_rows, 500), 1), 5000);
v_count integer := 0;
v_row "better_supabase"."lease_agreements"%rowtype;
v_charge "better_supabase"."lease_charges"%rowtype;
v_n integer;
v_start date;
v_end date;
v_tx uuid;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
if post_due_rent.tenant is null then
if not coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') then
raise exception 'posting rent for every organization needs the service role' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
elsif not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', post_due_rent.tenant, 'leases.manage'), false)) then
raise exception 'not allowed to manage leases' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
<<periods>>
for v_row in
select * from "better_supabase"."lease_agreements" x
where x."status" = 'active' and x."starts_on" <= v_through
and (post_due_rent.tenant is null or x."tenant_id" = post_due_rent.tenant)
order by x."id"
for update skip locked
loop
v_n := 0;
loop
v_start := (v_row."starts_on" + v_n * (case v_row."frequency" when 'weekly' then interval '1 week' when 'monthly' then interval '1 month' when 'quarterly' then interval '3 months' else interval '1 year' end))::date;
exit when v_start > v_through or (v_row."ends_on" is not null and v_start >= v_row."ends_on");
if not exists (select 1 from "better_supabase"."lease_charges" y where y."lease_id" = v_row."id" and y."period_start" = v_start) then
exit periods when v_count >= v_max;
v_end := least((v_row."starts_on" + (v_n + 1) * (case v_row."frequency" when 'weekly' then interval '1 week' when 'monthly' then interval '1 month' when 'quarterly' then interval '3 months' else interval '1 year' end))::date, coalesce(v_row."ends_on", 'infinity'::date));
v_tx := "better_supabase"."ledger_post"(v_row."tenant_id", 'lease.rent',
jsonb_build_array(
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_receivable', 'leases.rent:' || v_row."id"::text, 'Rent ' || v_row."title",
v_row."currency"::text, 0::smallint, 'lease', v_row."id"::text, null, true, 'leases.read', 'leases',
jsonb_build_object('leaseId', v_row."id")), 'amount', v_row."rent"),
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_income', 'leases.income:' || v_row."currency", 'Rent income ' || v_row."currency",
v_row."currency"::text, 0::smallint, null, null, null, true, 'leases.read', 'leases', null), 'amount', -v_row."rent")),
'leases', v_row."id"::text, 'Rent from ' || v_start::text,
'leases.rent:' || v_row."id"::text || ':' || v_start::text, v_start::timestamptz,
jsonb_build_object('leaseId', v_row."id", 'periodStart', v_start, 'periodEnd', v_end));
insert into "better_supabase"."lease_charges" ("tenant_id", "lease_id", "period_start", "period_end", "amount", "currency", "transaction_id")
values (v_row."tenant_id", v_row."id", v_start, v_end, v_row."rent", v_row."currency", v_tx)
returning * into v_charge;
null;
v_count := v_count + 1;
end if;
v_n := v_n + 1;
end loop;
end loop;
return v_count;
end;
$$;
revoke execute on function "better_supabase"."post_due_rent"(uuid, date, integer) from public, anon;
grant execute on function "better_supabase"."post_due_rent"(uuid, date, integer) to authenticated, service_role;
-- Records a rent payment: it moves amount from the lease's receivable to
-- rent received. With a reference a second call returns the first payment.
create or replace function "better_supabase"."record_rent_payment"(lease_id uuid, amount bigint, paid_at timestamptz default null, reference text default null)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."lease_agreements"%rowtype;
v_key text;
v_tx uuid;
begin
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = record_rent_payment.lease_id;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.manage'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
if v_row."status" = 'draft' then
raise exception 'a draft lease takes no payments' using errcode = '55000', hint = 'LEASE_STATUS_INVALID';
end if;
if record_rent_payment.amount is null or record_rent_payment.amount <= 0 then
raise exception 'a payment is more than zero minor units' using errcode = '22023', hint = 'LEASE_PAYMENT_INVALID';
end if;
v_key := case when nullif(trim(record_rent_payment.reference), '') is not null
then 'leases.payment:' || v_row."id"::text || ':' || trim(record_rent_payment.reference) end;
select x."id" into v_tx from "better_supabase"."ledger_transactions" x
where x."tenant_id" = v_row."tenant_id" and x."idempotency_key" = v_key;
if v_tx is null then
v_tx := "better_supabase"."ledger_post"(v_row."tenant_id", 'lease.payment',
jsonb_build_array(
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_receivable', 'leases.rent:' || v_row."id"::text, 'Rent ' || v_row."title",
v_row."currency"::text, 0::smallint, 'lease', v_row."id"::text, null, true, 'leases.read', 'leases',
jsonb_build_object('leaseId', v_row."id")), 'amount', -record_rent_payment.amount),
jsonb_build_object('account', "better_supabase"."ledger_account_for"(v_row."tenant_id", 'rent_cash', 'leases.cash:' || v_row."currency", 'Rent received ' || v_row."currency",
v_row."currency"::text, 0::smallint, null, null, null, true, 'leases.read', 'leases', null), 'amount', record_rent_payment.amount)),
'leases', v_row."id"::text, 'Rent payment', v_key, record_rent_payment.paid_at,
jsonb_strip_nulls(jsonb_build_object('leaseId', v_row."id", 'reference', nullif(trim(record_rent_payment.reference), ''))));
null;
end if;
return jsonb_build_object('transactionId', v_tx, 'leaseId', v_row."id", 'amount', record_rent_payment.amount::text,
'currency', v_row."currency", 'balanceDue', coalesce((select y."balance" from "better_supabase"."ledger_accounts" x join "better_supabase"."ledger_balances" y on y."account_id" = x."id"
where x."tenant_id" = v_row."tenant_id" and x."code" = 'leases.rent:' || v_row."id"::text), 0)::text);
end;
$$;
revoke execute on function "better_supabase"."record_rent_payment"(uuid, bigint, timestamptz, text) from public, anon;
grant execute on function "better_supabase"."record_rent_payment"(uuid, bigint, timestamptz, text) to authenticated, service_role;
create or replace function "better_supabase"."list_lease_charges"(lease_id uuid, page_size integer default 200)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
declare
v_row "better_supabase"."lease_agreements"%rowtype;
begin
select * into v_row from "better_supabase"."lease_agreements" x where x."id" = list_lease_charges.lease_id;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.read'), false)) then
raise exception 'no such lease you can read' using errcode = 'P0002', hint = 'LEASE_NOT_FOUND';
end if;
return (
select coalesce(jsonb_agg(jsonb_build_object('id', q."id", 'organizationId', q."tenant_id", 'leaseId', q."lease_id", 'periodStart', q."period_start", 'periodEnd', q."period_end", 'amount', q."amount"::text, 'currency', q."currency", 'transactionId', q."transaction_id", 'createdAt', q."created_at") order by q."period_start" desc), '[]'::jsonb)
from (
select * from "better_supabase"."lease_charges" y where y."lease_id" = v_row."id"
order by y."period_start" desc
limit least(greatest(coalesce(list_lease_charges.page_size, 200), 1), 2000)
) q
);
end;
$$;
revoke execute on function "better_supabase"."list_lease_charges"(uuid, integer) from public, anon;
grant execute on function "better_supabase"."list_lease_charges"(uuid, integer) to authenticated, service_role;
-- Files a maintenance request on a unit, optionally for one of its leases.
create or replace function "better_supabase"."file_maintenance_request"(tenant uuid, unit_id uuid, title text, description text default null, priority text default 'normal', lease_id uuid default null)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."maintenance_requests"%rowtype;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
if not ((coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', file_maintenance_request.tenant, 'leases.maintain'), false)) or coalesce(better_supabase.can('tenant', file_maintenance_request.tenant, 'leases.manage'), false)) then
raise exception 'not allowed to file maintenance requests' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
if not exists (select 1 from "better_supabase"."assets" u where u."id" = file_maintenance_request.unit_id and u."tenant_id" = file_maintenance_request.tenant) then
raise exception 'the unit is unknown or not in this organization' using errcode = '22023', hint = 'LEASE_UNIT_INVALID';
end if;
if file_maintenance_request.lease_id is not null and not exists (
select 1 from "better_supabase"."lease_agreements" x where x."id" = file_maintenance_request.lease_id
and x."tenant_id" = file_maintenance_request.tenant and x."unit_id" = file_maintenance_request.unit_id
) then
raise exception 'the lease is unknown or not a lease of this unit' using errcode = '22023', hint = 'LEASE_NOT_FOUND';
end if;
if coalesce(file_maintenance_request.priority, 'normal') not in ('low', 'normal', 'high', 'urgent') then
raise exception 'priority is low, normal, high or urgent' using errcode = '22023', hint = 'MAINTENANCE_INVALID';
end if;
if nullif(trim(file_maintenance_request.title), '') is null then
raise exception 'a maintenance request needs a title' using errcode = '22023', hint = 'MAINTENANCE_INVALID';
end if;
insert into "better_supabase"."maintenance_requests" ("tenant_id", "unit_id", "lease_id", "title", "description", "priority", "reported_by")
values (file_maintenance_request.tenant, file_maintenance_request.unit_id, file_maintenance_request.lease_id, trim(file_maintenance_request.title),
nullif(trim(file_maintenance_request.description), ''), coalesce(file_maintenance_request.priority, 'normal'), auth.uid())
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'leaseId', v_row."lease_id", 'title', v_row."title", 'description', v_row."description", 'priority', v_row."priority", 'status', v_row."status", 'reportedBy', v_row."reported_by", 'assigneeId', v_row."assignee_id", 'resolvedAt', v_row."resolved_at", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."file_maintenance_request"(uuid, uuid, text, text, text, uuid) from public, anon;
grant execute on function "better_supabase"."file_maintenance_request"(uuid, uuid, text, text, text, uuid) to authenticated, service_role;
-- Moves a maintenance request along (open, in_progress, resolved,
-- cancelled), reprioritizes it or assigns it to a member. Null keeps a value.
create or replace function "better_supabase"."update_maintenance_request"(request_id uuid, status text default null, priority text default null, assignee_id uuid default null)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_row "better_supabase"."maintenance_requests"%rowtype;
begin
if not (better_supabase.actor_may_write()) then
raise exception 'This session is read-only' using errcode = '42501', hint = 'ACTOR_READ_ONLY';
end if;
select * into v_row from "better_supabase"."maintenance_requests" x where x."id" = update_maintenance_request.request_id for update;
if not found or not (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', v_row."tenant_id", 'leases.manage'), false)) then
raise exception 'no such maintenance request you can change' using errcode = 'P0002', hint = 'MAINTENANCE_NOT_FOUND';
end if;
if update_maintenance_request.status is not null and update_maintenance_request.status not in ('open', 'in_progress', 'resolved', 'cancelled') then
raise exception 'status is open, in_progress, resolved or cancelled' using errcode = '22023', hint = 'MAINTENANCE_INVALID';
end if;
if update_maintenance_request.priority is not null and update_maintenance_request.priority not in ('low', 'normal', 'high', 'urgent') then
raise exception 'priority is low, normal, high or urgent' using errcode = '22023', hint = 'MAINTENANCE_INVALID';
end if;
if update_maintenance_request.assignee_id is not null and not exists (
select 1 from "better_supabase"."memberships" y
where y."organization_id" = v_row."tenant_id" and y."user_id" = update_maintenance_request.assignee_id
) then
raise exception 'the assignee is not a member of this organization' using errcode = '22023', hint = 'MAINTENANCE_INVALID';
end if;
update "better_supabase"."maintenance_requests" x set
"status" = coalesce(update_maintenance_request.status, x."status"),
"priority" = coalesce(update_maintenance_request.priority, x."priority"),
"assignee_id" = coalesce(update_maintenance_request.assignee_id, x."assignee_id"),
"resolved_at" = case when coalesce(update_maintenance_request.status, x."status") = 'resolved' then coalesce(x."resolved_at", now()) end,
"updated_at" = now()
where x."id" = v_row."id"
returning * into v_row;
null;
return jsonb_build_object('id', v_row."id", 'organizationId', v_row."tenant_id", 'unitId', v_row."unit_id", 'leaseId', v_row."lease_id", 'title', v_row."title", 'description', v_row."description", 'priority', v_row."priority", 'status', v_row."status", 'reportedBy', v_row."reported_by", 'assigneeId', v_row."assignee_id", 'resolvedAt', v_row."resolved_at", 'createdAt', v_row."created_at", 'updatedAt', v_row."updated_at");
end;
$$;
revoke execute on function "better_supabase"."update_maintenance_request"(uuid, text, text, uuid) from public, anon;
grant execute on function "better_supabase"."update_maintenance_request"(uuid, text, text, uuid) to authenticated, service_role;
-- Maintenance requests, newest first: every one with leases.read, else the
-- ones the caller filed.
create or replace function "better_supabase"."list_maintenance_requests"(tenant uuid, unit_id uuid default null, lease_id uuid default null, status text default null, page_size integer default 100)
returns jsonb
language plpgsql
stable
security definer
set search_path = ''
as $$
#variable_conflict use_column
declare
v_all boolean := (coalesce(nullif((select auth.jwt()) ->> 'role', ''), session_user::text) in ('service_role', 'postgres', 'supabase_admin') or coalesce(better_supabase.can('tenant', list_maintenance_requests.tenant, 'leases.read'), false));
begin
if not (v_all or coalesce(better_supabase.can('tenant', list_maintenance_requests.tenant, 'leases.maintain'), false)) then
raise exception 'not allowed to read maintenance requests' using errcode = '42501', hint = 'LEASES_FORBIDDEN';
end if;
return (
select coalesce(jsonb_agg(jsonb_build_object('id', q."id", 'organizationId', q."tenant_id", 'unitId', q."unit_id", 'leaseId', q."lease_id", 'title', q."title", 'description', q."description", 'priority', q."priority", 'status', q."status", 'reportedBy', q."reported_by", 'assigneeId', q."assignee_id", 'resolvedAt', q."resolved_at", 'createdAt', q."created_at", 'updatedAt', q."updated_at") order by q."created_at" desc, q."id"), '[]'::jsonb)
from (
select * from "better_supabase"."maintenance_requests" x
where x."tenant_id" = list_maintenance_requests.tenant
and (v_all or x."reported_by" = auth.uid())
and (list_maintenance_requests.unit_id is null or x."unit_id" = list_maintenance_requests.unit_id)
and (list_maintenance_requests.lease_id is null or x."lease_id" = list_maintenance_requests.lease_id)
and (list_maintenance_requests.status is null or x."status" = list_maintenance_requests.status)
order by x."created_at" desc, x."id"
limit least(greatest(coalesce(list_maintenance_requests.page_size, 100), 1), 1000)
) q
);
end;
$$;
revoke execute on function "better_supabase"."list_maintenance_requests"(uuid, uuid, uuid, text, integer) from public, anon;
grant execute on function "better_supabase"."list_maintenance_requests"(uuid, uuid, uuid, text, integer) to authenticated, service_role;
create schema if not exists better_supabase;
create table if not exists better_supabase.modules (
name text primary key,
version integer not null,
mode text not null,
installed_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table better_supabase.modules enable row level security;
revoke all on better_supabase.modules from anon, authenticated;
grant select on better_supabase.modules to service_role;
Client
This block has no React entry yet. Its TypeScript side is in better-supabase/blocks/leases, which runs on the server.
Events
With the outbox installed, the module writes these in the same transaction as the change.
- lease.activated
- lease.charged
- lease.created
- lease.paid
- lease.renewed
- lease.terminated
- maintenance_request.filed
- maintenance_request.updated
Dependencies
Recipes
No UI recipe uses it yet. See all recipes.
More in Billing & commerce
Stripe entitlementsentitlements Active Stripe entitlements per tenant from the Stripe Sync Engine, feature_claims() for the access token hook, and has_entitlement() and tenant_ids_with_entitlement() for RLS.React hooks Usage metering and quotasusage Usage counters per tenant, meter and day with idempotent increments, quotas per tenant or plan, within_quota() for policies and consume_quota(), which raises quota_exceeded. unreported_usage() feeds reportUsageToStripe.React hooks Stripe billingbilling Each tenant's Stripe customer, seat counts (options.seatRoles) and the active subscription item from the Stripe Sync Engine, for createBilling(): checkout, the customer portal, seat sync and Stripe webhook handling.React hooks Catalogcatalog Products with options, variants and prices in minor units, price lists, a category tree, collections and an optional public storefront. Inventoryinventory Stock per catalog variant and location: movements posted to the ledger block, reservations with expiry, transfers and purchase orders with partial receiving. Ordersorders Carts on anonymous or signed-in users, checkout priced from the catalog, gapless order and invoice numbers, payments, fulfillments, returns and refunds, with an optional kitchen display.