@vrplatform/kysely 1.3.45-stage.6033 → 1.3.45-stage.604

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/build/.data.tar +0 -0
  2. package/build/main/control-plane/index.d.ts +314 -0
  3. package/build/main/control-plane/index.js.map +1 -1
  4. package/build/main/index.d.ts +13 -2
  5. package/build/main/index.js +217 -23
  6. package/build/main/index.js.map +1 -1
  7. package/build/main/local/generate.js +2 -0
  8. package/build/main/local/generate.js.map +1 -1
  9. package/build/main/local/index.js +110 -8
  10. package/build/main/local/index.js.map +1 -1
  11. package/build/main/postgres-error.d.ts +3 -3
  12. package/build/main/postgres-error.js +4 -3
  13. package/build/main/postgres-error.js.map +1 -1
  14. package/build/main/query/listing/status.js +2 -6
  15. package/build/main/query/listing/status.js.map +1 -1
  16. package/build/main/query/tenant/status.d.ts +4 -6
  17. package/build/main/query/tenant/status.js +17 -14
  18. package/build/main/query/tenant/status.js.map +1 -1
  19. package/build/main/regional.d.ts +1 -0
  20. package/build/main/regional.js +1 -0
  21. package/build/main/regional.js.map +1 -1
  22. package/build/main/test/index.d.ts +23 -3
  23. package/build/main/test/index.js +35 -13
  24. package/build/main/test/index.js.map +1 -1
  25. package/build/main/v1.generated.d.ts +560 -7
  26. package/build/main/v1.generated.js.map +1 -1
  27. package/build/migrations-v2/0000000000013-ui-permission-bundles.ts +45 -0
  28. package/build/migrations-v2/0000000000014-ui-permission-bundle-top-up.ts +32 -0
  29. package/build/migrations-v2/0000000000015-durable-flow-events.ts +163 -0
  30. package/build/migrations-v2/0000000000016-billing-listing-facts.ts +539 -0
  31. package/build/migrations-v2/0000000000016-managed-team-access.ts +34 -0
  32. package/build/migrations-v2/0000000000017-drop-trial-until.ts +42 -0
  33. package/build/migrations-v2/0000000000017-webhook-secret-reencryption-failures.ts +18 -0
  34. package/build/migrations-v2/0000000000018-accounting-integrity-monitoring.ts +276 -0
  35. package/build/migrations-v2/0000000000018-durable-flow-event-sensitive-input.ts +28 -0
  36. package/build/migrations-v2/0000000000019-accounting-integrity-run-shards.ts +26 -0
  37. package/build/migrations-v2/0000000000019-asynchronous-operations.ts +736 -0
  38. package/build/migrations-v2/0000000000020-accounting-integrity-case-classification.ts +52 -0
  39. package/build/migrations-v2/0000000000021-payment-line-classification-monitor-indexes.ts +31 -0
  40. package/build/migrations-v2/0000000000022-accounting-integrity-controlled-repairs.ts +281 -0
  41. package/build/migrations-v2/0000000000023-line-classification-usage.ts +325 -0
  42. package/build/migrations-v2/0000000000024-tenant-region-migration.ts +108 -0
  43. package/build/migrations-v2/0000000000025-partner-ach-denial-default.ts +30 -0
  44. package/build/migrations-v2/0000000000026-line-classification-usage-capture.ts +399 -0
  45. package/build/migrations-v2/0000000000027-payout-replacement-chain.ts +27 -0
  46. package/build/migrations-v2/0000000000028-accounting-integrity-operator-workflow.ts +108 -0
  47. package/build/migrations-v2/0000000000029-accounting-integrity-definition-artifacts.ts +40 -0
  48. package/build/migrations-v2/0000000000030-accounting-integrity-review-metadata-compatibility.ts +49 -0
  49. package/build/migrations-v2/0000000000031-ui-permission-role-simplification.ts +70 -0
  50. package/build/migrations-v2/0000000000032-tenant-delete-ownership-cascades.ts +44 -0
  51. package/build/migrations-v2/0000000000033-reservation-issue-seed-bucket-index.ts +19 -0
  52. package/build/migrations-v2/0000000000034-retire-recent-classification-indexes.ts +29 -0
  53. package/build/migrations-v2/0000000000035-core-change-autovacuum.ts +24 -0
  54. package/build/migrations-v2/0000000000036-journal-entry-inactive-reason-constraint.ts +33 -0
  55. package/build/migrations-v2/0000000000037-journal-entry-inactive-reasons.ts +19 -0
  56. package/build/migrations-v2/0000000000038-journal-entry-group-inactive-reason.ts +14 -0
  57. package/build/migrations-v2/0000000000039-reservation-guest-breakdown.ts +19 -0
  58. package/build/migrations-v2/0000000000040-csv-import-source-identity-index.ts +23 -0
  59. package/build/migrations-v2/0000000000041-payment-returns.ts +38 -0
  60. package/build/migrations-v2/0000000000042-operation-resource-types.ts +609 -0
  61. package/build/migrations-v2/0000000000043-reservation-journal-freshness.ts +149 -0
  62. package/build/migrations-v2/0000000000044-reservation-journal-freshness-backfill.ts +254 -0
  63. package/build/migrations-v2/0000000000045-reservation-journal-freshness-revision-repair.ts +83 -0
  64. package/build/migrations-v2/0000000000046-materialized-reservation-journal-operations.ts +709 -0
  65. package/build/migrations-v2/0000000000047-public-audit-completion.ts +77 -0
  66. package/build/migrations-v2/0000000000048-reservation-journal-full-refresh-repair.ts +136 -0
  67. package/build/migrations-v2/0000000000049-queued-journal-operation-failures.ts +60 -0
  68. package/build/migrations-v2/0000000000050-connection-oauth-state.ts +43 -0
  69. package/build/migrations-v2/0000000000051-owner-statement-email-delivery.ts +134 -0
  70. package/build/migrations-v2/0000000000052-fee-journal-lock-skip-operations.ts +59 -0
  71. package/build/migrations-v2/0000000000053-fee-journal-lock-skip-null-attempt.ts +44 -0
  72. package/build/migrations-v2/0000000000054-journal-refresh-issue-codes.ts +16 -0
  73. package/build/module/control-plane/index.d.ts +314 -0
  74. package/build/module/control-plane/index.js.map +1 -1
  75. package/build/module/index.d.ts +13 -2
  76. package/build/module/index.js +219 -24
  77. package/build/module/index.js.map +1 -1
  78. package/build/module/local/generate.js +2 -0
  79. package/build/module/local/generate.js.map +1 -1
  80. package/build/module/local/index.js +110 -8
  81. package/build/module/local/index.js.map +1 -1
  82. package/build/module/postgres-error.d.ts +3 -3
  83. package/build/module/postgres-error.js +4 -3
  84. package/build/module/postgres-error.js.map +1 -1
  85. package/build/module/query/listing/status.js +2 -6
  86. package/build/module/query/listing/status.js.map +1 -1
  87. package/build/module/query/tenant/status.d.ts +4 -6
  88. package/build/module/query/tenant/status.js +17 -14
  89. package/build/module/query/tenant/status.js.map +1 -1
  90. package/build/module/regional.d.ts +1 -0
  91. package/build/module/regional.js +1 -0
  92. package/build/module/regional.js.map +1 -1
  93. package/build/module/test/index.d.ts +23 -3
  94. package/build/module/test/index.js +34 -13
  95. package/build/module/test/index.js.map +1 -1
  96. package/build/module/v1.generated.d.ts +560 -7
  97. package/build/module/v1.generated.js.map +1 -1
  98. package/package.json +4 -2
@@ -0,0 +1,163 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`
6
+ create type core.flow_event_status as enum (
7
+ 'pending', 'processing', 'succeeded', 'dead', 'canceled'
8
+ )
9
+ `.execute(db);
10
+ await sql`
11
+ create type core.flow_event_operation_action as enum (
12
+ 'replay', 'abandon'
13
+ )
14
+ `.execute(db);
15
+
16
+ await sql`
17
+ create table core.flow_event (
18
+ id uuid primary key default gen_random_uuid(),
19
+ tenant_id uuid not null,
20
+ event_type text not null,
21
+ entity_type text not null,
22
+ entity_id uuid not null,
23
+ data jsonb not null default '{}'::jsonb,
24
+ sync_id uuid,
25
+ target_connection_id uuid,
26
+ target_flow_id uuid,
27
+ status core.flow_event_status not null default 'pending',
28
+ available_at timestamptz not null default now(),
29
+ claimed_at timestamptz,
30
+ claimed_by text,
31
+ attempts integer not null default 0,
32
+ max_attempts integer not null default 10,
33
+ completed_at timestamptz,
34
+ last_error_code text,
35
+ last_error_details jsonb,
36
+ created_at timestamptz not null default now(),
37
+ updated_at timestamptz not null default now(),
38
+ constraint flow_event_attempts_check check (attempts >= 0),
39
+ constraint flow_event_max_attempts_check check (max_attempts > 0),
40
+ constraint flow_event_target_check check (
41
+ target_connection_id is not null
42
+ or target_flow_id is null
43
+ )
44
+ )
45
+ `.execute(db);
46
+
47
+ await sql`
48
+ create table core.flow_event_delivery (
49
+ id uuid primary key default gen_random_uuid(),
50
+ event_id uuid not null
51
+ references core.flow_event(id) on delete cascade,
52
+ tenant_id uuid not null,
53
+ connection_id uuid not null,
54
+ flow_id uuid not null,
55
+ status core.flow_event_status not null default 'pending',
56
+ available_at timestamptz not null default now(),
57
+ claimed_at timestamptz,
58
+ claimed_by text,
59
+ attempts integer not null default 0,
60
+ max_attempts integer not null default 10,
61
+ trigger_run_id text,
62
+ completed_at timestamptz,
63
+ last_error_code text,
64
+ last_error_details jsonb,
65
+ created_at timestamptz not null default now(),
66
+ updated_at timestamptz not null default now(),
67
+ constraint flow_event_delivery_attempts_check check (attempts >= 0),
68
+ constraint flow_event_delivery_max_attempts_check
69
+ check (max_attempts > 0),
70
+ constraint flow_event_delivery_listener_key
71
+ unique (event_id, connection_id, flow_id)
72
+ )
73
+ `.execute(db);
74
+
75
+ await sql`
76
+ create table core.flow_event_secret (
77
+ event_id uuid primary key
78
+ references core.flow_event(id) on delete cascade,
79
+ encryption_key_id text not null,
80
+ encrypted_payload text not null,
81
+ expires_at timestamptz not null,
82
+ created_at timestamptz not null default now()
83
+ )
84
+ `.execute(db);
85
+ await sql`
86
+ create table core.flow_event_operation (
87
+ id uuid primary key default gen_random_uuid(),
88
+ event_id uuid not null references core.flow_event(id) on delete cascade,
89
+ action core.flow_event_operation_action not null,
90
+ actor text not null,
91
+ reason_code text not null,
92
+ previous_status core.flow_event_status not null,
93
+ created_at timestamptz not null default now()
94
+ )
95
+ `.execute(db);
96
+
97
+ await sql`
98
+ create index flow_event_pending_idx
99
+ on core.flow_event (available_at, created_at, id)
100
+ where status = 'pending'
101
+ `.execute(db);
102
+ await sql`
103
+ create index flow_event_stale_claim_idx
104
+ on core.flow_event (claimed_at, id)
105
+ where status = 'processing'
106
+ `.execute(db);
107
+ await sql`
108
+ create index flow_event_tenant_history_idx
109
+ on core.flow_event (tenant_id, created_at desc, id desc)
110
+ `.execute(db);
111
+ await sql`
112
+ create index flow_event_sync_idx
113
+ on core.flow_event (tenant_id, sync_id)
114
+ where sync_id is not null
115
+ `.execute(db);
116
+ await sql`
117
+ create index flow_event_delivery_pending_idx
118
+ on core.flow_event_delivery (available_at, created_at, id)
119
+ where status = 'pending'
120
+ `.execute(db);
121
+ await sql`
122
+ create index flow_event_delivery_stale_claim_idx
123
+ on core.flow_event_delivery (claimed_at, id)
124
+ where status = 'processing'
125
+ `.execute(db);
126
+ await sql`
127
+ create index flow_event_delivery_event_idx
128
+ on core.flow_event_delivery (event_id, status, id)
129
+ `.execute(db);
130
+ await sql`
131
+ create index flow_event_secret_expiry_idx
132
+ on core.flow_event_secret (expires_at, event_id)
133
+ `.execute(db);
134
+ await sql`
135
+ create index flow_event_operation_history_idx
136
+ on core.flow_event_operation (event_id, created_at desc, id desc)
137
+ `.execute(db);
138
+
139
+ await sql`
140
+ do $$
141
+ begin
142
+ if exists (select 1 from pg_roles where rolname = 'application') then
143
+ grant usage on schema core to application;
144
+ grant select, insert, update, delete
145
+ on core.flow_event,
146
+ core.flow_event_delivery,
147
+ core.flow_event_operation,
148
+ core.flow_event_secret
149
+ to application;
150
+ end if;
151
+ end
152
+ $$
153
+ `.execute(db);
154
+ }
155
+
156
+ export async function down(db: Kysely<unknown>): Promise<void> {
157
+ await sql`drop table if exists core.flow_event_operation`.execute(db);
158
+ await sql`drop table if exists core.flow_event_secret`.execute(db);
159
+ await sql`drop table if exists core.flow_event_delivery`.execute(db);
160
+ await sql`drop table if exists core.flow_event`.execute(db);
161
+ await sql`drop type if exists core.flow_event_operation_action`.execute(db);
162
+ await sql`drop type if exists core.flow_event_status`.execute(db);
163
+ }
@@ -0,0 +1,539 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ export async function up(db: Kysely<unknown>): Promise<void> {
5
+ await sql`create schema if not exists billing`.execute(db);
6
+ await sql`
7
+ create type billing.listing_fact_source_type as enum (
8
+ 'listing_insert',
9
+ 'listing_update',
10
+ 'listing_delete',
11
+ 'ownership_insert',
12
+ 'ownership_update',
13
+ 'ownership_delete',
14
+ 'tenant_insert',
15
+ 'tenant_update',
16
+ 'tenant_delete',
17
+ 'daily_reconciliation',
18
+ 'legacy_tracking_import',
19
+ 'baseline_backfill'
20
+ )
21
+ `.execute(db);
22
+ await sql`
23
+ create table billing.listing_fact (
24
+ id uuid primary key default gen_random_uuid(),
25
+ tenant_id uuid not null,
26
+ billing_partner_id uuid,
27
+ active_listings integer not null,
28
+ tenant_name text not null,
29
+ data_region text not null,
30
+ effective_at timestamptz not null,
31
+ recorded_at timestamptz not null default now(),
32
+ source_type billing.listing_fact_source_type not null,
33
+ source_id uuid not null,
34
+ source_revision bigint,
35
+ reconciliation boolean not null default false,
36
+ constraint listing_fact_active_listings_check
37
+ check (active_listings >= 0),
38
+ constraint listing_fact_source_revision_check
39
+ check (source_revision is null or source_revision > 0),
40
+ constraint listing_fact_source_key
41
+ unique (tenant_id, source_type, source_id)
42
+ )
43
+ `.execute(db);
44
+ await sql`
45
+ create table billing.listing_fact_readiness (
46
+ singleton boolean primary key default true,
47
+ ready_at timestamptz,
48
+ available_from timestamptz,
49
+ verified_month timestamptz,
50
+ legacy_imported_rows integer,
51
+ legacy_gap_count integer,
52
+ parity_confirmed boolean not null default false,
53
+ constraint listing_fact_readiness_singleton_check check (singleton),
54
+ constraint listing_fact_readiness_count_check check (
55
+ legacy_imported_rows is null or legacy_imported_rows >= 0
56
+ ),
57
+ constraint listing_fact_readiness_gap_count_check check (
58
+ legacy_gap_count is null or legacy_gap_count >= 0
59
+ ),
60
+ constraint listing_fact_readiness_evidence_check check (
61
+ (
62
+ ready_at is null
63
+ and available_from is null
64
+ and verified_month is null
65
+ and legacy_imported_rows is null
66
+ and legacy_gap_count is null
67
+ and parity_confirmed = false
68
+ ) or (
69
+ ready_at is not null
70
+ and available_from is not null
71
+ and verified_month is not null
72
+ and legacy_imported_rows is not null
73
+ and legacy_gap_count is not null
74
+ and parity_confirmed = true
75
+ )
76
+ )
77
+ )
78
+ `.execute(db);
79
+ await sql`
80
+ insert into billing.listing_fact_readiness (singleton) values (true)
81
+ `.execute(db);
82
+ await sql`
83
+ create index listing_fact_tenant_effective_idx
84
+ on billing.listing_fact (tenant_id, effective_at desc, id desc)
85
+ `.execute(db);
86
+ await sql`
87
+ create index listing_fact_partner_effective_idx
88
+ on billing.listing_fact (billing_partner_id, effective_at, tenant_id)
89
+ `.execute(db);
90
+ await sql`
91
+ create index listing_fact_region_recorded_idx
92
+ on billing.listing_fact (data_region, recorded_at, id)
93
+ `.execute(db);
94
+
95
+ await sql`
96
+ create function billing.active_listing_count(
97
+ selected_tenant_id uuid
98
+ ) returns integer
99
+ language sql
100
+ stable
101
+ as $$
102
+ select count(*)::integer
103
+ from public.listing as listing
104
+ inner join public.tenant as tenant
105
+ on tenant.id = listing.tenant_id
106
+ left join public.tenant as billing_partner
107
+ on billing_partner.id = tenant.calculated_billing_partner_id
108
+ where listing.tenant_id = selected_tenant_id
109
+ and tenant.calculated_status = 'active'
110
+ and (
111
+ tenant.calculated_billing_partner_id is null
112
+ or billing_partner.calculated_status = 'active'
113
+ )
114
+ and (
115
+ (
116
+ tenant.is_general_ledger = false
117
+ and listing.calculated_status = 'active'
118
+ ) or (
119
+ tenant.is_general_ledger = true
120
+ and not exists (
121
+ select 1
122
+ from public.listing_ownership_period as ownership
123
+ where ownership.listing_id = coalesce(
124
+ listing.parent_id,
125
+ listing.id
126
+ )
127
+ and ownership.tenant_id = listing.tenant_id
128
+ and ownership.set_listing_inactive = true
129
+ and ownership.start_at <= current_date
130
+ and (
131
+ ownership.end_at is null
132
+ or ownership.end_at >= current_date
133
+ )
134
+ )
135
+ )
136
+ )
137
+ $$
138
+ `.execute(db);
139
+ await sql`
140
+ create function billing.record_listing_facts(
141
+ selected_tenant_ids uuid[],
142
+ selected_source_type billing.listing_fact_source_type,
143
+ selected_source_id uuid,
144
+ selected_effective_at timestamptz,
145
+ selected_reconciliation boolean
146
+ ) returns void
147
+ language sql
148
+ as $$
149
+ with calculated as materialized (
150
+ select tenant.id as tenant_id,
151
+ tenant.calculated_billing_partner_id as billing_partner_id,
152
+ billing.active_listing_count(tenant.id) as active_listings,
153
+ tenant.name as tenant_name,
154
+ tenant.data_region as data_region
155
+ from public.tenant as tenant
156
+ where tenant.id = any(selected_tenant_ids)
157
+ and tenant.type = 'propertyManager'
158
+ ), changed as (
159
+ select calculated.*
160
+ from calculated
161
+ left join lateral (
162
+ select fact.id,
163
+ fact.billing_partner_id,
164
+ fact.active_listings,
165
+ fact.tenant_name,
166
+ fact.data_region
167
+ from billing.listing_fact as fact
168
+ where fact.tenant_id = calculated.tenant_id
169
+ order by fact.effective_at desc, fact.recorded_at desc, fact.id desc
170
+ limit 1
171
+ ) as latest on true
172
+ where latest.id is null
173
+ or (
174
+ latest.billing_partner_id,
175
+ latest.active_listings,
176
+ latest.tenant_name,
177
+ latest.data_region
178
+ ) is distinct from (
179
+ calculated.billing_partner_id,
180
+ calculated.active_listings,
181
+ calculated.tenant_name,
182
+ calculated.data_region
183
+ )
184
+ )
185
+ insert into billing.listing_fact (
186
+ tenant_id,
187
+ billing_partner_id,
188
+ active_listings,
189
+ tenant_name,
190
+ data_region,
191
+ effective_at,
192
+ source_type,
193
+ source_id,
194
+ reconciliation
195
+ )
196
+ select tenant_id,
197
+ billing_partner_id,
198
+ active_listings,
199
+ tenant_name,
200
+ data_region,
201
+ selected_effective_at,
202
+ selected_source_type,
203
+ selected_source_id,
204
+ selected_reconciliation
205
+ from changed
206
+ on conflict (tenant_id, source_type, source_id) do nothing
207
+ $$
208
+ `.execute(db);
209
+ await sql`
210
+ create function billing.capture_listing_insert() returns trigger
211
+ language plpgsql as $$
212
+ begin
213
+ perform billing.record_listing_facts(
214
+ array(select distinct tenant_id from new_rows),
215
+ 'listing_insert',
216
+ gen_random_uuid(),
217
+ now(),
218
+ false
219
+ );
220
+ return null;
221
+ end
222
+ $$
223
+ `.execute(db);
224
+ await sql`
225
+ create function billing.capture_listing_update() returns trigger
226
+ language plpgsql as $$
227
+ begin
228
+ perform billing.record_listing_facts(
229
+ array(
230
+ select new_row.tenant_id
231
+ from new_rows as new_row
232
+ inner join old_rows as old_row on old_row.id = new_row.id
233
+ where (
234
+ new_row.tenant_id,
235
+ new_row.parent_id,
236
+ new_row.calculated_status
237
+ ) is distinct from (
238
+ old_row.tenant_id,
239
+ old_row.parent_id,
240
+ old_row.calculated_status
241
+ )
242
+ union
243
+ select old_row.tenant_id
244
+ from new_rows as new_row
245
+ inner join old_rows as old_row on old_row.id = new_row.id
246
+ where (
247
+ new_row.tenant_id,
248
+ new_row.parent_id,
249
+ new_row.calculated_status
250
+ ) is distinct from (
251
+ old_row.tenant_id,
252
+ old_row.parent_id,
253
+ old_row.calculated_status
254
+ )
255
+ ),
256
+ 'listing_update',
257
+ gen_random_uuid(),
258
+ now(),
259
+ false
260
+ );
261
+ return null;
262
+ end
263
+ $$
264
+ `.execute(db);
265
+ await sql`
266
+ create function billing.capture_listing_delete() returns trigger
267
+ language plpgsql as $$
268
+ begin
269
+ perform billing.record_listing_facts(
270
+ array(select distinct tenant_id from old_rows),
271
+ 'listing_delete',
272
+ gen_random_uuid(),
273
+ now(),
274
+ false
275
+ );
276
+ return null;
277
+ end
278
+ $$
279
+ `.execute(db);
280
+ await sql`
281
+ create function billing.capture_ownership_insert() returns trigger
282
+ language plpgsql as $$
283
+ begin
284
+ perform billing.record_listing_facts(
285
+ array(select distinct tenant_id from new_rows),
286
+ 'ownership_insert',
287
+ gen_random_uuid(),
288
+ now(),
289
+ false
290
+ );
291
+ return null;
292
+ end
293
+ $$
294
+ `.execute(db);
295
+ await sql`
296
+ create function billing.capture_ownership_update() returns trigger
297
+ language plpgsql as $$
298
+ begin
299
+ perform billing.record_listing_facts(
300
+ array(
301
+ select new_row.tenant_id
302
+ from new_rows as new_row
303
+ inner join old_rows as old_row on old_row.id = new_row.id
304
+ where (
305
+ new_row.tenant_id,
306
+ new_row.listing_id,
307
+ new_row.set_listing_inactive,
308
+ new_row.start_at,
309
+ new_row.end_at
310
+ ) is distinct from (
311
+ old_row.tenant_id,
312
+ old_row.listing_id,
313
+ old_row.set_listing_inactive,
314
+ old_row.start_at,
315
+ old_row.end_at
316
+ )
317
+ union
318
+ select old_row.tenant_id
319
+ from new_rows as new_row
320
+ inner join old_rows as old_row on old_row.id = new_row.id
321
+ where (
322
+ new_row.tenant_id,
323
+ new_row.listing_id,
324
+ new_row.set_listing_inactive,
325
+ new_row.start_at,
326
+ new_row.end_at
327
+ ) is distinct from (
328
+ old_row.tenant_id,
329
+ old_row.listing_id,
330
+ old_row.set_listing_inactive,
331
+ old_row.start_at,
332
+ old_row.end_at
333
+ )
334
+ ),
335
+ 'ownership_update',
336
+ gen_random_uuid(),
337
+ now(),
338
+ false
339
+ );
340
+ return null;
341
+ end
342
+ $$
343
+ `.execute(db);
344
+ await sql`
345
+ create function billing.capture_ownership_delete() returns trigger
346
+ language plpgsql as $$
347
+ begin
348
+ perform billing.record_listing_facts(
349
+ array(select distinct tenant_id from old_rows),
350
+ 'ownership_delete',
351
+ gen_random_uuid(),
352
+ now(),
353
+ false
354
+ );
355
+ return null;
356
+ end
357
+ $$
358
+ `.execute(db);
359
+ await sql`
360
+ create function billing.capture_tenant_insert() returns trigger
361
+ language plpgsql as $$
362
+ begin
363
+ perform billing.record_listing_facts(
364
+ array(select distinct id from new_rows),
365
+ 'tenant_insert',
366
+ gen_random_uuid(),
367
+ now(),
368
+ false
369
+ );
370
+ return null;
371
+ end
372
+ $$
373
+ `.execute(db);
374
+ await sql`
375
+ create function billing.capture_tenant_update() returns trigger
376
+ language plpgsql as $$
377
+ begin
378
+ perform billing.record_listing_facts(
379
+ array(
380
+ select new_row.id
381
+ from new_rows as new_row
382
+ inner join old_rows as old_row on old_row.id = new_row.id
383
+ where (
384
+ new_row.type,
385
+ new_row.name,
386
+ new_row.data_region,
387
+ new_row.calculated_billing_partner_id,
388
+ new_row.calculated_status,
389
+ new_row.is_general_ledger
390
+ ) is distinct from (
391
+ old_row.type,
392
+ old_row.name,
393
+ old_row.data_region,
394
+ old_row.calculated_billing_partner_id,
395
+ old_row.calculated_status,
396
+ old_row.is_general_ledger
397
+ )
398
+ ),
399
+ 'tenant_update',
400
+ gen_random_uuid(),
401
+ now(),
402
+ false
403
+ );
404
+ return null;
405
+ end
406
+ $$
407
+ `.execute(db);
408
+ await sql`
409
+ create function billing.capture_tenant_delete() returns trigger
410
+ language plpgsql as $$
411
+ begin
412
+ if old.type = 'propertyManager' then
413
+ insert into billing.listing_fact (
414
+ tenant_id,
415
+ billing_partner_id,
416
+ active_listings,
417
+ tenant_name,
418
+ data_region,
419
+ effective_at,
420
+ source_type,
421
+ source_id
422
+ ) values (
423
+ old.id,
424
+ old.calculated_billing_partner_id,
425
+ 0,
426
+ old.name,
427
+ old.data_region,
428
+ now(),
429
+ 'tenant_delete',
430
+ gen_random_uuid()
431
+ );
432
+ end if;
433
+ return old;
434
+ end
435
+ $$
436
+ `.execute(db);
437
+
438
+ await sql`
439
+ create trigger billing_listing_insert
440
+ after insert on public.listing
441
+ referencing new table as new_rows
442
+ for each statement execute function billing.capture_listing_insert()
443
+ `.execute(db);
444
+ await sql`
445
+ create trigger billing_listing_update
446
+ after update on public.listing
447
+ referencing old table as old_rows new table as new_rows
448
+ for each statement execute function billing.capture_listing_update()
449
+ `.execute(db);
450
+ await sql`
451
+ create trigger billing_listing_delete
452
+ after delete on public.listing
453
+ referencing old table as old_rows
454
+ for each statement execute function billing.capture_listing_delete()
455
+ `.execute(db);
456
+ await sql`
457
+ create trigger billing_ownership_insert
458
+ after insert on public.listing_ownership_period
459
+ referencing new table as new_rows
460
+ for each statement execute function billing.capture_ownership_insert()
461
+ `.execute(db);
462
+ await sql`
463
+ create trigger billing_ownership_update
464
+ after update on public.listing_ownership_period
465
+ referencing old table as old_rows new table as new_rows
466
+ for each statement execute function billing.capture_ownership_update()
467
+ `.execute(db);
468
+ await sql`
469
+ create trigger billing_ownership_delete
470
+ after delete on public.listing_ownership_period
471
+ referencing old table as old_rows
472
+ for each statement execute function billing.capture_ownership_delete()
473
+ `.execute(db);
474
+ await sql`
475
+ create trigger billing_tenant_insert
476
+ after insert on public.tenant
477
+ referencing new table as new_rows
478
+ for each statement execute function billing.capture_tenant_insert()
479
+ `.execute(db);
480
+ await sql`
481
+ create trigger billing_tenant_update
482
+ after update on public.tenant
483
+ referencing old table as old_rows new table as new_rows
484
+ for each statement execute function billing.capture_tenant_update()
485
+ `.execute(db);
486
+ await sql`
487
+ create trigger billing_tenant_delete
488
+ before delete on public.tenant
489
+ for each row execute function billing.capture_tenant_delete()
490
+ `.execute(db);
491
+
492
+ await sql`
493
+ do $$
494
+ begin
495
+ if exists (select 1 from pg_roles where rolname = 'application') then
496
+ grant usage on schema billing to application;
497
+ grant select, insert on billing.listing_fact to application;
498
+ grant select on billing.listing_fact_readiness to application;
499
+ grant execute on function billing.active_listing_count(uuid)
500
+ to application;
501
+ grant execute on function billing.record_listing_facts(
502
+ uuid[], billing.listing_fact_source_type, uuid, timestamptz, boolean
503
+ ) to application;
504
+ end if;
505
+ end
506
+ $$
507
+ `.execute(db);
508
+ }
509
+
510
+ export async function down(db: Kysely<unknown>): Promise<void> {
511
+ await sql`drop trigger if exists billing_tenant_delete on public.tenant`.execute(
512
+ db
513
+ );
514
+ await sql`drop trigger if exists billing_tenant_update on public.tenant`.execute(
515
+ db
516
+ );
517
+ await sql`drop trigger if exists billing_tenant_insert on public.tenant`.execute(
518
+ db
519
+ );
520
+ await sql`drop trigger if exists billing_ownership_delete on public.listing_ownership_period`.execute(
521
+ db
522
+ );
523
+ await sql`drop trigger if exists billing_ownership_update on public.listing_ownership_period`.execute(
524
+ db
525
+ );
526
+ await sql`drop trigger if exists billing_ownership_insert on public.listing_ownership_period`.execute(
527
+ db
528
+ );
529
+ await sql`drop trigger if exists billing_listing_delete on public.listing`.execute(
530
+ db
531
+ );
532
+ await sql`drop trigger if exists billing_listing_update on public.listing`.execute(
533
+ db
534
+ );
535
+ await sql`drop trigger if exists billing_listing_insert on public.listing`.execute(
536
+ db
537
+ );
538
+ await sql`drop schema if exists billing cascade`.execute(db);
539
+ }