From 53da66c3bd60c07c273eb5b0db1a8e745a0cecaf Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 18:58:15 +0800 Subject: [PATCH 01/29] fix(scheduling): derive scheduled_start_ms on reschedule when the row has none MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fulfillBooking was the only writer, so every manually-created inspection kept a NULL instant forever and conflict detection stayed on the sameDayHour bucket for them — 09:59 and 10:01 collide under that rule. The wall-clock now comes from the date suffix when there is no prior instant. Rows whose date carries no time stay NULL: inventing midnight would be less honest than the bucket fallback. Also retires scripts/backfill-scheduled-start.mjs. It matched zero production rows (0 of 19 inspections carry a non-NULL scheduled_start_ms, verified against the remote database), and the hole it patched — a reschedule that moved date without moving the instant — was closed by the dual-write in #289, so the drift cannot recur. Git history is the archive. --- scripts/backfill-scheduled-start.mjs | 134 ------------------ server/services/inspection/reschedule-date.ts | 35 +++-- .../inspections/reschedule-dual-write.spec.ts | 17 ++- 3 files changed, 43 insertions(+), 143 deletions(-) delete mode 100644 scripts/backfill-scheduled-start.mjs diff --git a/scripts/backfill-scheduled-start.mjs b/scripts/backfill-scheduled-start.mjs deleted file mode 100644 index ae63ca15e..000000000 --- a/scripts/backfill-scheduled-start.mjs +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node -/** - * Roadmap §7.5 item 3 — one-time backfill for `scheduled_start_ms` drift. - * - * Before the PATCH handler dual-wrote the scheduled instant, every calendar - * drag (and settings-sheet date change) moved `inspections.date` while - * `scheduled_start_ms` kept the OLD instant. This script finds rows whose - * instant's civil date (in the TENANT timezone) no longer matches the row's - * own `date`, and shifts start+end to the row's date preserving wall-clock - * time-of-day — the same rule the handler now applies. - * - * DRY RUN by default: prints the planned updates and exits. Pass --apply to - * write. Pass --remote to target remote D1 — read - * docs/saas-ops/d1-migration-sop.md FIRST and take a `d1 export --remote` - * backup + time-travel bookmark before applying remotely. - * - * node scripts/backfill-scheduled-start.mjs # local, dry run - * node scripts/backfill-scheduled-start.mjs --apply # local, write - * node scripts/backfill-scheduled-start.mjs --remote # remote, dry run - * node scripts/backfill-scheduled-start.mjs --remote --apply - * - * Timezone math duplicates server/lib/tz.ts (an .mjs script cannot import the - * TS module). Both sides delegate DST to Intl; keep them behaviorally in sync. - */ -import { execSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; - -const REMOTE = process.argv.includes('--remote'); -const APPLY = process.argv.includes('--apply'); - -const cfg = - process.env.WRANGLER_CONFIG || - (existsSync('wrangler.local.jsonc') ? 'wrangler.local.jsonc' : 'wrangler.jsonc'); - -// Windows-safe: a spawned arg list under shell:true loses its quoting, so the -// command line is assembled as one string with the SQL double-quoted. -// Backslash FIRST, then quote — escaping the quote first would re-escape the -// backslashes this step introduces. Without the backslash rule a value ending -// in `\` escapes the closing quote and the rest of the command line becomes -// argument data (CodeQL js/incomplete-sanitization). -const q = (s) => '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; - -function d1(command, { json = true } = {}) { - const cmd = [ - 'npx', 'wrangler', 'd1', 'execute', 'DB', - REMOTE ? '--remote' : '--local', - '--command', q(command), - '-c', cfg, - ...(json ? ['--json'] : []), - ].join(' '); - const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); - if (!json) return out; - const parsed = JSON.parse(out); - // wrangler --json returns [{ results: [...], success, meta }] per statement. - return parsed.flatMap((s) => s.results ?? []); -} - -// ── tz helpers (mirrors server/lib/tz.ts; Intl owns DST) ───────────────────── -function isValidTimeZone(tz) { - if (!tz) return false; - if (tz !== 'UTC' && !tz.includes('/')) return false; - try { new Intl.DateTimeFormat('en-US', { timeZone: tz }); return true; } catch { return false; } -} -const resolveTz = (raw) => (raw && isValidTimeZone(raw) ? raw : 'UTC'); - -function offsetMinutes(ms, tz) { - const dtf = new Intl.DateTimeFormat('en-US', { - timeZone: tz, hourCycle: 'h23', - year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', - }); - const p = Object.fromEntries(dtf.formatToParts(new Date(ms)).map((x) => [x.type, x.value])); - const asUtc = Date.UTC(+p.year, +p.month - 1, +p.day, +p.hour, +p.minute, +p.second); - return Math.round((asUtc - ms) / 60000); -} -const pad = (n) => String(n).padStart(2, '0'); -function epochMsToWallClock(ms, tz) { - const local = new Date(ms + offsetMinutes(ms, tz) * 60000); - return { - civil: `${local.getUTCFullYear()}-${pad(local.getUTCMonth() + 1)}-${pad(local.getUTCDate())}`, - hm: `${pad(local.getUTCHours())}:${pad(local.getUTCMinutes())}`, - }; -} -function wallClockToEpochMs(dateYmd, timeHm, tz) { - const [y, mo, d] = dateYmd.split('-').map(Number); - const [h, mi] = timeHm.split(':').map(Number); - const guess = Date.UTC(y, mo - 1, d, h, mi, 0); - return guess - offsetMinutes(guess, tz) * 60000; -} - -// ── scan ───────────────────────────────────────────────────────────────────── -const rows = d1( - 'SELECT i.id, i.tenant_id, i.date, i.scheduled_start_ms, i.scheduled_end_ms, c.default_timezone ' + - 'FROM inspections i LEFT JOIN tenant_configs c ON c.tenant_id = i.tenant_id ' + - 'WHERE i.scheduled_start_ms IS NOT NULL', -); - -const updates = []; -for (const r of rows) { - const civilTarget = String(r.date ?? '').slice(0, 10); - if (!/^\d{4}-\d{2}-\d{2}$/.test(civilTarget)) continue; // unparseable date — leave it - const tz = resolveTz(r.default_timezone); - const startMs = Number(r.scheduled_start_ms); - const wall = epochMsToWallClock(startMs, tz); - if (wall.civil === civilTarget) continue; // instant agrees with the row's date - const newStartMs = wallClockToEpochMs(civilTarget, wall.hm, tz); - const delta = newStartMs - startMs; - const endMs = r.scheduled_end_ms == null ? null : Number(r.scheduled_end_ms) + delta; - updates.push({ id: String(r.id), tz, from: `${wall.civil} ${wall.hm}`, to: `${civilTarget} ${wall.hm}`, newStartMs, endMs }); -} - -console.log(`${rows.length} scheduled rows scanned, ${updates.length} diverged.`); -for (const u of updates) { - console.log(` ${u.id}: ${u.from} -> ${u.to} (${u.tz})`); -} - -if (updates.length === 0) process.exit(0); -if (!APPLY) { - console.log('\nDry run — nothing written. Re-run with --apply to write.' + (REMOTE ? ' (REMOTE: back up per docs/saas-ops/d1-migration-sop.md first.)' : '')); - process.exit(0); -} - -// Ids come from the SELECT above, not user input; still keep them quoted and -// batched well under D1's statement limits. -const BATCH = 40; -for (let i = 0; i < updates.length; i += BATCH) { - const stmts = updates.slice(i, i + BATCH).map((u) => - `UPDATE inspections SET scheduled_start_ms = ${u.newStartMs}` + - (u.endMs == null ? '' : `, scheduled_end_ms = ${u.endMs}`) + - ` WHERE id = '${u.id.replace(/'/g, "''")}'`, - ); - d1(stmts.join('; '), { json: false }); -} -console.log(`Applied ${updates.length} update(s).`); diff --git a/server/services/inspection/reschedule-date.ts b/server/services/inspection/reschedule-date.ts index 034d9ef85..f68013643 100644 --- a/server/services/inspection/reschedule-date.ts +++ b/server/services/inspection/reschedule-date.ts @@ -6,8 +6,9 @@ * forever — every downstream reader saw a timestamp diverging from the row's * own date. * - * The one-time cleanup for rows that diverged before this shipped is - * `scripts/backfill-scheduled-start.mjs` (same shift rule, dry-run first). + * A row that never had an instant at all — anything created outside the booking + * path — derives one from the time suffix on `date`, so the column stops + * depending solely on `fulfillBooking` being the writer. */ import { and, eq } from 'drizzle-orm'; import { inspections, tenantConfigs } from '../../lib/db/schema'; @@ -52,17 +53,35 @@ export async function datePatchValues( : body.date; const values: Record = { ...body, date: dateValue }; - const oldStart = toMs(row?.scheduledStartMs); - if (oldStart != null) { + let tzCache: string | undefined; + const tenantTz = async (): Promise => { + if (tzCache) return tzCache; const tzRow = await db.select({ defaultTimezone: tenantConfigs.defaultTimezone }) .from(tenantConfigs).where(eq(tenantConfigs.tenantId, tenantId)).get(); - const tz = resolveTenantTimeZone(tzRow?.defaultTimezone); - const newStartMs = wallClockToEpochMs(newCivil, epochMsToWallClockHm(oldStart, tz), tz); + tzCache = resolveTenantTimeZone(tzRow?.defaultTimezone); + return tzCache; + }; + + const oldStart = toMs(row?.scheduledStartMs); + // Two sources for the wall-clock to preserve: the existing instant when the + // row has one, otherwise the time suffix on `date`. Rows created outside the + // booking path only ever have the latter, and without this they stay NULL + // forever, permanently degrading conflict detection to the hour bucket. + const oldHm = oldStart != null + ? epochMsToWallClockHm(oldStart, await tenantTz()) + : (oldDate.length > 10 ? oldDate.slice(11, 16) : null); + + if (oldHm) { + const tz = await tenantTz(); + const newStartMs = wallClockToEpochMs(newCivil, oldHm, tz); values.scheduledStartMs = new Date(newStartMs); const oldEnd = toMs(row?.scheduledEndMs); // End shifts by the same delta, so the booked duration survives even - // across a DST boundary. - if (oldEnd != null) values.scheduledEndMs = new Date(oldEnd + (newStartMs - oldStart)); + // across a DST boundary. A row with no prior instant has no prior end + // either; leave it null rather than inventing a duration. + if (oldEnd != null && oldStart != null) { + values.scheduledEndMs = new Date(oldEnd + (newStartMs - oldStart)); + } } return values; } diff --git a/tests/unit/inspections/reschedule-dual-write.spec.ts b/tests/unit/inspections/reschedule-dual-write.spec.ts index 0f2c69986..d847c3d66 100644 --- a/tests/unit/inspections/reschedule-dual-write.spec.ts +++ b/tests/unit/inspections/reschedule-dual-write.spec.ts @@ -133,7 +133,10 @@ describe('PATCH /api/inspections/:id — reschedule dual-write (§7.5 item 3)', ); }); - it('a legacy row with no scheduled instant updates date only, ms stays NULL', async () => { + it('a row whose date carries no time keeps a NULL instant', async () => { + // Nothing to derive a wall-clock from. Inventing midnight would be worse + // than NULL: the sameDayHour bucket is at least honest about being + // imprecise, whereas a fabricated 00:00 reads as a real appointment. await seed({ date: '2026-01-15', startMs: null, endMs: null }); expect(await patchDate('2026-01-20')).toBe(200); const row = await readRow(); @@ -142,6 +145,18 @@ describe('PATCH /api/inspections/:id — reschedule dual-write (§7.5 item 3)', expect(row?.scheduledEndMs).toBeNull(); }); + it('derives the instant for a row that never had one, from the date suffix', async () => { + // Manually-created inspections never went through fulfillBooking, so they + // carry no instant at all — and without this they never would, leaving + // conflict detection on the hour bucket for them forever. + await seed({ date: '2026-01-15T10:00:00Z', startMs: null, endMs: null }); + expect(await patchDate('2026-01-20')).toBe(200); + const row = await readRow(); + expect(toMs(row?.scheduledStartMs)).toBe(wallClockToEpochMs('2026-01-20', '10:00', TZ)); + // No prior instant means no prior end either — a duration would be invented. + expect(row?.scheduledEndMs).toBeNull(); + }); + it('the settings-sheet ISO payload also dual-writes', async () => { await seed(); expect(await patchDate('2026-02-03T09:00:00.000Z')).toBe(200); From cfd8e2cad27968c7b8f787129ca4aeeb395f4431 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 19:01:21 +0800 Subject: [PATCH 02/29] feat(library): delete canned comments, one row or many (#291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The library had create, read, update and a per-user touch counter, but no delete at any surface — so 2,774 of the 3,654 production rows could only ever accumulate. `/library/repair-items` deletes through RecommendationService, which filters on `isNotNull(repair_summary)` and therefore only ever reached the other 880. `DELETE /api/admin/comments/{id}` already existed, was on the OpenAPI surface and RBAC-gated, and had zero callers. This adds the caller rather than a second endpoint. Bulk delete loops the same route: the concrete case is the 80 seeded rows per tenant that a tenant may decline wholesale, and a second delete path on one table is how two paths end up behaving differently. Both confirmations identify what they are about to remove — a library page is dozens of near-identical rows, so "delete this comment?" cannot tell you whether the right one is selected. One selected row is named rather than counted, which also keeps the count message honestly plural. The design rests on a claim nothing enforces: an inspection snapshots the comment TEXT and holds no reference to the library row. comment-delete-isolation.spec.ts pins it — the defect narrative and the published version's snapshot, content hash and signature all survive the delete — so a future change to that relationship fails there instead of in a delivered report. --- app/routes/comments-delete.test.tsx | 133 ++++++++++++++ app/routes/comments.tsx | 170 ++++++++++++++++-- messages/en/library.json | 9 + .../comments/comment-delete-isolation.spec.ts | 149 +++++++++++++++ 4 files changed, 451 insertions(+), 10 deletions(-) create mode 100644 app/routes/comments-delete.test.tsx create mode 100644 tests/unit/comments/comment-delete-isolation.spec.ts diff --git a/app/routes/comments-delete.test.tsx b/app/routes/comments-delete.test.tsx new file mode 100644 index 000000000..ae1f32a97 --- /dev/null +++ b/app/routes/comments-delete.test.tsx @@ -0,0 +1,133 @@ +/** + * OI #291 — `/library/comments` could create, edit and touch a canned comment + * but never delete one, so 2,774 rows had no delete path at any surface. The + * `DELETE /api/admin/comments/{id}` route already existed with zero callers. + * + * These tests pin the two things that make a destructive control safe: it never + * fires without confirmation, and the confirmation says WHICH row (or how many) + * — a library page is dozens of near-identical entries, and "delete this + * comment?" cannot tell you whether the right one is selected. + */ +import { describe, it, expect } from "vitest"; +import { render, screen, within, fireEvent, waitFor } from "@testing-library/react"; +import { createRoutesStub } from "react-router"; + +import CommentsPage from "~/routes/comments"; + +const A = { id: "c1", text: "Cracked flue tile observed at the chimney crown.", severity: "significant" as const, section: "Chimney" }; +const B = { id: "c2", text: "Downspout discharges against the foundation.", severity: "marginal" as const, section: "Exterior" }; + +function renderPage({ comments = [A, B], ok = true } = {}) { + const calls: Record[] = []; + const Stub = createRoutesStub([ + { + path: "/library/comments", + Component: CommentsPage, + action: async ({ request }) => { + const form = await request.formData(); + calls.push(Object.fromEntries(form) as Record); + return { ok, intent: String(form.get("intent")), deleted: ok ? 1 : 0 }; + }, + loader: () => ({ + comments, + meta: { total: comments.length, page: 1, pageSize: 50, totalPages: 1 }, + contractorTypes: [], + loadFailed: false, + }), + }, + ]); + render(); + return { calls }; +} + +const rowDeleteButtons = () => + screen.getAllByRole("button", { name: /^delete$/i }) + .filter((b) => !b.closest('[role="dialog"]')); + +async function confirm() { + const dialog = await screen.findByRole("dialog"); + fireEvent.click(within(dialog).getByRole("button", { name: /^delete$/i })); +} + +describe("/library/comments — delete (#291)", () => { + it("offers a delete control on every row", async () => { + renderPage(); + await screen.findByText(A.text); + expect(rowDeleteButtons()).toHaveLength(2); + }); + + it("does not delete without confirmation", async () => { + const { calls } = renderPage(); + await screen.findByText(A.text); + + fireEvent.click(rowDeleteButtons()[0]); + expect(calls).toHaveLength(0); + + // Backing out of the dialog is still not a delete. + const dialog = await screen.findByRole("dialog"); + fireEvent.click(within(dialog).getByRole("button", { name: /cancel/i })); + expect(calls).toHaveLength(0); + }); + + it("names the comment in the confirmation, not just 'this comment'", async () => { + renderPage(); + await screen.findByText(A.text); + fireEvent.click(rowDeleteButtons()[0]); + + const dialog = await screen.findByRole("dialog"); + expect(dialog.textContent).toContain("Cracked flue tile"); + }); + + it("submits the delete intent for the row that was clicked", async () => { + const { calls } = renderPage(); + await screen.findByText(A.text); + fireEvent.click(rowDeleteButtons()[1]); + await confirm(); + + await waitFor(() => expect(calls).toEqual([{ intent: "delete", id: "c2" }])); + }); + + it("states how many will be deleted when several are selected", async () => { + renderPage(); + await screen.findByText(A.text); + + fireEvent.click(screen.getByLabelText(/select all on this page/i)); + fireEvent.click(screen.getByRole("button", { name: /delete selected \(2\)/i })); + + const dialog = await screen.findByRole("dialog"); + expect(dialog.textContent).toContain("2"); + }); + + it("names the single selected row instead of saying '1 comments'", async () => { + renderPage(); + await screen.findByText(A.text); + + fireEvent.click(screen.getAllByLabelText(/select this comment/i)[0]); + fireEvent.click(screen.getByRole("button", { name: /delete selected \(1\)/i })); + + const dialog = await screen.findByRole("dialog"); + expect(dialog.textContent).toContain("Cracked flue tile"); + expect(dialog.textContent).not.toMatch(/1 comments/); + }); + + it("bulk delete sends exactly the selected ids", async () => { + const { calls } = renderPage(); + await screen.findByText(A.text); + + fireEvent.click(screen.getAllByLabelText(/select this comment/i)[0]); + fireEvent.click(screen.getByRole("button", { name: /delete selected \(1\)/i })); + await confirm(); + + await waitFor(() => expect(calls).toEqual([{ intent: "delete-many", ids: "c1" }])); + }); + + it("says nothing was removed when the delete fails", async () => { + renderPage({ comments: [A], ok: false }); + await screen.findByText(A.text); + + fireEvent.click(rowDeleteButtons()[0]); + await confirm(); + + expect(await screen.findByRole("alert")).toHaveTextContent(/nothing was removed/i); + }); +}); diff --git a/app/routes/comments.tsx b/app/routes/comments.tsx index e5732db7b..bb6f922cb 100644 --- a/app/routes/comments.tsx +++ b/app/routes/comments.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; -import { useLoaderData, useSearchParams } from "react-router"; +import { useLoaderData, useSearchParams, useFetcher } from "react-router"; import type { Route } from "./+types/comments"; import { useDisplayTimeZone } from "~/hooks/useSessionContext"; import { requireToken } from "~/lib/session.server"; import { createApi } from "~/lib/api-client.server"; -import { PageHeader, TabStrip, Card, Pill, Button, EmptyState, Pagination } from "@core/shared-ui"; +import { PageHeader, TabStrip, Card, Pill, Button, EmptyState, Pagination, Checkbox } from "@core/shared-ui"; import { Breadcrumb } from "~/components/Breadcrumb"; +import { ConfirmDialog } from "~/components/ConfirmDialog"; import { usePagination } from "~/hooks/usePagination"; import { CommentEditor } from "~/components/CommentEditor"; import { EntityAuditTrail } from "~/components/audit/EntityAuditTrail"; @@ -61,6 +62,48 @@ export async function loader({ request, context }: Route.LoaderArgs) { } } +/** + * #291 — the library had create, read and update but no delete at any surface, + * so 2,774 rows could only ever accumulate. The endpoint already existed and had + * zero callers; this is the caller. + * + * Delete is safe to offer because an inspection snapshots the comment TEXT and + * holds no reference to the library row — pinned by + * `tests/unit/comments/comment-delete-isolation.spec.ts`, which is what stops a + * future change to that relationship from reaching a published report. + */ +export async function action({ request, context }: Route.ActionArgs) { + const token = await requireToken(context, request); + const api = createApi(context, { token }); + const form = await request.formData(); + const intent = String(form.get("intent") ?? ""); + + const del = async (id: string) => { + const res = await api.admin.comments[":id"].$delete({ param: { id } }); + return res.ok; + }; + + try { + if (intent === "delete") { + const id = String(form.get("id") ?? ""); + if (!id) return { ok: false, intent, deleted: 0 }; + return { ok: await del(id), intent, deleted: 1 }; + } + if (intent === "delete-many") { + // No bulk endpoint exists and adding one would leave two delete paths on + // one table. Sequential keeps the audit rows in a readable order and the + // page sizes here are bounded by the paginator. + const ids = String(form.get("ids") ?? "").split(",").map((s) => s.trim()).filter(Boolean); + let deleted = 0; + for (const id of ids) if (await del(id)) deleted += 1; + return { ok: deleted === ids.length, intent, deleted }; + } + } catch { + return { ok: false, intent, deleted: 0 }; + } + return { ok: false, intent, deleted: 0 }; +} + // Module D — severity tabs (single canonical vocabulary shared with rating // levels, module F). The "all" tab clears the filter; the rest map straight // onto the `severity` query param the loader forwards to the API. @@ -73,6 +116,9 @@ function getTabs() { ]; } +/** Enough of the wording to recognise the row, short enough to read in a dialog. */ +const preview = (text: string) => (text.length > 80 ? `${text.slice(0, 80)}…` : text); + const SEVERITY_TONE: Record = { good: "sat", marginal: "monitor", @@ -88,6 +134,24 @@ export default function CommentsPage() { const { setPage, setPageSize } = usePagination(); const [editorOpen, setEditorOpen] = useState(false); const [editing, setEditing] = useState(null); + const deleteFetcher = useFetcher(); + const [pendingDelete, setPendingDelete] = useState(null); + const [bulkConfirm, setBulkConfirm] = useState(false); + const [selected, setSelected] = useState([]); + + // A selection means ids on THIS page of THIS filter. Everything downstream + // reads `selectedComments`, which intersects with what is currently listed — + // so a page change, a filter change or a completed delete drops those rows + // from the selection by construction. Clearing the array in an effect instead + // would race the first click after mount. + const selectedComments = comments.filter((c) => selected.includes(c.id)); + const soleSelected = selectedComments.length === 1 ? selectedComments[0] : null; + const busy = deleteFetcher.state !== "idle"; + const deleteFailed = deleteFetcher.state === "idle" && deleteFetcher.data?.ok === false; + + function toggle(id: string) { + setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])); + } function setActiveTab(id: string) { setSearchParams((prev) => { @@ -114,6 +178,40 @@ export default function CommentsPage() { + {deleteFailed && ( +

{m.comments_delete_failed()}

+ )} + + {comments.length > 0 && ( +
+
+ 0} + onChange={(e) => setSelected(e.target.checked ? comments.map((c) => c.id) : [])} + /> + +
+ {selectedComments.length > 0 && ( + <> + + + + )} +
+ )} + {comments.length === 0 ? ( {comments.map((c) => ( -

{c.text}

+
+ toggle(c.id)} + /> +

{c.text}

+
{c.severity && ( @@ -134,13 +241,22 @@ export default function CommentsPage() { )} {c.section && {c.section}}
- +
+ + +
@@ -164,6 +280,40 @@ export default function CommentsPage() { comment={editing} contractorTypes={contractorTypes} /> + + {/* A library page is dozens of near-identical rows, so the confirmation + quotes the wording rather than asking "delete this comment?". */} + { + if (pendingDelete) deleteFetcher.submit({ intent: "delete", id: pendingDelete.id }, { method: "POST" }); + setPendingDelete(null); + }} + onCancel={() => setPendingDelete(null)} + /> + + {/* One selected row is not a bulk operation: name it, the way the row + control does. It also keeps the count message honestly plural. */} + { + deleteFetcher.submit( + { intent: "delete-many", ids: selectedComments.map((c) => c.id).join(",") }, + { method: "POST" }, + ); + setBulkConfirm(false); + setSelected([]); + }} + onCancel={() => setBulkConfirm(false)} + /> ); } diff --git a/messages/en/library.json b/messages/en/library.json index 8628599c5..4a3af73c2 100644 --- a/messages/en/library.json +++ b/messages/en/library.json @@ -91,6 +91,15 @@ "comments_tab_all": "All", "comments_empty_title": "No comments yet", "comments_empty_desc": "Click \"+ Add comment\" above to create your first comment snippet.", + "comments_delete_title": "Delete comment", + "comments_delete_message": "Delete \"{text}\"? This can't be undone. Reports that already use this wording keep it.", + "comments_delete_many_title": "Delete comments", + "comments_delete_many_message": "Delete {count} comments? This can't be undone. Reports that already use their wording keep it.", + "comments_delete_selected": "Delete selected ({count})", + "comments_delete_failed": "Couldn't delete. Nothing was removed - try again.", + "comments_select_label": "Select this comment", + "comments_select_all": "Select all on this page", + "comments_clear_selection": "Clear selection", "repair_items_meta_title": "Repair Items - OpenInspection", "repair_items_heading": "Repair Items", "repair_items_meta": "{count} in library", diff --git a/tests/unit/comments/comment-delete-isolation.spec.ts b/tests/unit/comments/comment-delete-isolation.spec.ts new file mode 100644 index 000000000..f291abe1c --- /dev/null +++ b/tests/unit/comments/comment-delete-isolation.spec.ts @@ -0,0 +1,149 @@ +/** + * Characterisation spec for OI #291 — deleting a library comment must not reach + * delivered work. + * + * The library delete path is about to gain a UI, so 2,774 rows that have never + * been deletable are about to become deletable. The whole design rests on one + * claim: an inspection snapshots the comment TEXT, it does not reference the + * library row. Nothing enforces that — `inspection_results` has no comment + * column and no FK — so it is an invariant held only by convention, and this + * spec is what makes breaking it fail here instead of in a delivered report. + * + * These tests are expected to pass on first run. That is the point: they pin + * behaviour that already holds, so a future change to how defects reference the + * library breaks a spec rather than a signature. + */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { createHash } from 'node:crypto'; +import { eq } from 'drizzle-orm'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; +import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { OpenAPIHono } from '@hono/zod-openapi'; +import adminRoutes from '../../../server/api/admin'; +import { AppError } from '../../../server/lib/errors'; +import type { HonoConfig } from '../../../server/types/hono'; + +const TENANT = '00000000-0000-0000-0000-000000000001'; +const INSP_ID = '550e8400-e29b-41d4-a716-446655440091'; +const COMMENT_ID = 'c-flue-tile'; +const NARRATIVE = 'Cracked flue tile observed at the chimney crown.'; + +let db: BetterSQLite3Database; + +/** Mirrors `server/index.ts`'s onError so AppError surfaces as its own status. */ +function buildApp(tenantId = TENANT, userId = 'u1') { + const app = new OpenAPIHono(); + app.use('*', async (c, next) => { + c.set('userRole', 'owner' as never); + c.set('tenantId', tenantId); + c.set('user', { sub: userId } as never); + await next(); + }); + app.route('/api/admin', adminRoutes); + app.onError((err, c) => { + if (err instanceof AppError) { + return c.json({ success: false, error: { code: err.code, message: err.message } }, err.status); + } + return c.json({ success: false, error: { code: 'internal_error', message: String(err) } }, 500); + }); + return app; +} + +async function deleteComment(id: string): Promise { + const res = await buildApp().request( + `/api/admin/comments/${id}`, + { method: 'DELETE' }, + { DB: {} }, + ); + return res.status; +} + +const sha256 = (s: string) => createHash('sha256').update(s).digest('hex'); + +/** The defect as the inspector left it — narrative copied out of the library. */ +const resultsData = { + sections: [{ + id: 'chimney', + items: [{ id: 'flue', rating: 'significant', comment: NARRATIVE }], + }], +}; + +async function seed() { + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + await db.insert(schema.comments).values({ + id: COMMENT_ID, tenantId: TENANT, text: NARRATIVE, + severity: 'significant', section: 'Chimney', category: null, createdAt: new Date(), + }); + await db.insert(schema.inspections).values({ + id: INSP_ID, tenantId: TENANT, propertyAddress: '1 Main St', + date: '2026-01-15', status: 'completed', paymentStatus: 'unpaid', + price: 50000, createdAt: new Date(), + }); + await db.insert(schema.inspectionResults).values({ + id: 'r1', tenantId: TENANT, inspectionId: INSP_ID, + data: resultsData, lastSyncedAt: new Date(), + }); + const snapshotJson = JSON.stringify({ inspection: { id: INSP_ID }, results: resultsData }); + await db.insert(schema.reportVersions).values({ + id: 'v1', tenantId: TENANT, inspectionId: INSP_ID, versionNumber: 1, + snapshotJson, + contentHash: sha256(snapshotJson), + signature: 'sig-over-content-hash', + publishedAt: new Date(), publishedBy: 'u1', createdAt: new Date(), + }); +} + +describe('DELETE /api/admin/comments/:id — isolation from delivered work (#291)', () => { + beforeEach(async () => { + const fixture = createTestDb(); + db = fixture.db; + await setupSchema(fixture.sqlite); + (mockDrizzle as unknown as ReturnType).mockReturnValue(db); + await seed(); + }); + + it('leaves the defect narrative intact after its source comment is deleted', async () => { + expect(await deleteComment(COMMENT_ID)).toBe(200); + + const gone = await db.select().from(schema.comments) + .where(eq(schema.comments.id, COMMENT_ID)).get(); + expect(gone).toBeUndefined(); + + const results = await db.select().from(schema.inspectionResults) + .where(eq(schema.inspectionResults.inspectionId, INSP_ID)).get(); + expect(results?.data).toEqual(resultsData); + }); + + it('leaves a published version verifiable — snapshot and hash both unchanged', async () => { + // report_versions holds a snapshot with a content hash and a signature + // over it. If a delete could reach the snapshot, every prior version + // would fail verification and nothing would say so. + const before = await db.select().from(schema.reportVersions) + .where(eq(schema.reportVersions.id, 'v1')).get(); + + expect(await deleteComment(COMMENT_ID)).toBe(200); + + const after = await db.select().from(schema.reportVersions) + .where(eq(schema.reportVersions.id, 'v1')).get(); + expect(after).toEqual(before); + // And the hash still describes the snapshot it is stored beside. + expect(after?.contentHash).toBe(sha256(after!.snapshotJson)); + }); + + it('refuses to delete a comment belonging to another tenant', async () => { + const other = buildApp('00000000-0000-0000-0000-0000000000ff', 'u2'); + const res = await other.request( + `/api/admin/comments/${COMMENT_ID}`, { method: 'DELETE' }, { DB: {} }, + ); + expect(res.status).toBe(404); + const still = await db.select().from(schema.comments) + .where(eq(schema.comments.id, COMMENT_ID)).get(); + expect(still?.text).toBe(NARRATIVE); + }); +}); From 1f2d4f23058f8fac07cd574dccc9c089640697ab Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 19:26:00 +0800 Subject: [PATCH 03/29] perf(gates): stop pre-commit paying for work the staged change cannot affect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects, both measured on this repo rather than reasoned about. 1. The i18n guard's stamp lived only inside `app/paraglide/`, which the paraglide compiler CLEANS on every run — and `paraglideVitePlugin` in vite.config.ts writes to that same outdir with the same options. So every `npm run dev` and every `npm run build` silently deleted the stamp, and the next commit staging `messages/**` recompiled 4,249 keys from inputs that had not moved: 66s. The hash is now mirrored to `node_modules/.cache/`, either copy is enough to skip, and a skip re-writes whichever copy went missing. Verified: 806ms after deleting the in-output stamp, and still "inputs changed" when a message file actually moves. 2. `needs_typegen` fired on any added or deleted path under `app/routes/`, including colocated specs. `app/routes/foo.test.tsx` never enters routes.ts, so typegen's output cannot differ because one arrived — but the Test Layout REQUIRES frontend specs to sit beside what they test, so this fired on ordinary work and cost ~60s each time. Worth recording alongside these: a running dev server makes the hook far more expensive than either fix saves. It rewrites `app/paraglide/**`, which is inside the app tsconfig program, so tsc's incremental build-info goes cold — measured 11.5s vs 295.5s for type-check:app, and 4.1s vs 37.6s for eslint. Stop the dev server before committing. --- .githooks/pre-commit | 7 ++- scripts/i18n-compile-if-changed.mjs | 68 +++++++++++++++++++---------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 73a459026..1b8d3dc1c 100644 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -83,7 +83,12 @@ needs_typegen() { # Editing a route module's body does not change its generated types — those # infer from `typeof loader` when tsc runs. A route file APPEARING or # DISAPPEARING changes the tree, and that does. - echo "$STAGED_TREE" | grep -qE '^app/routes/' + # + # A colocated SPEC is not a route: `app/routes/foo.test.tsx` never enters + # routes.ts, so typegen's output cannot differ because one arrived. The Test + # Layout in CLAUDE.md REQUIRES frontend specs to sit beside what they test, + # so this fires on ordinary work — and typegen costs ~60s measured here. + echo "$STAGED_TREE" | grep -E '^app/routes/' | grep -qvE '\.(test|spec)\.(ts|tsx)$' } step "Type check" diff --git a/scripts/i18n-compile-if-changed.mjs b/scripts/i18n-compile-if-changed.mjs index cca2e0860..6d3c4f9d1 100644 --- a/scripts/i18n-compile-if-changed.mjs +++ b/scripts/i18n-compile-if-changed.mjs @@ -21,16 +21,25 @@ */ import { createHash } from 'node:crypto'; import { execSync } from 'node:child_process'; -import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; +import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; const OUT_DIR = 'app/paraglide'; -// The stamp lives INSIDE the output directory on purpose: anything that caches -// or restores `app/paraglide` (CI does) carries the record of which inputs -// produced it, so a restored directory is trusted only for the exact inputs -// that built it. Keeping the stamp elsewhere would make every fresh checkout -// recompile even with the output already in hand. -const STAMP = 'app/paraglide/.inputs-hash'; +// The stamp is written to TWO places, and either one is enough to skip. +// +// app/paraglide/.inputs-hash — inside the output, so anything that caches or +// restores `app/paraglide` (CI does) carries the record of which inputs +// produced it. A restored directory is then trusted for exactly those inputs +// instead of recompiling on every fresh checkout. +// +// node_modules/.cache/… — outside it, because the compiler CLEANS its +// outdir, and `paraglideVitePlugin` in vite.config.ts writes to the same +// `./app/paraglide` with the same options. So every `npm run dev` and every +// `npm run build` silently deleted the in-output stamp, and the next commit +// that staged `messages/**` paid the full 66s for inputs that had not moved. +// Vite's output is byte-equivalent (same project, outdir, strategy, +// outputStructure and emitTsDeclarations), so trusting this copy is sound. +const STAMPS = ['app/paraglide/.inputs-hash', 'node_modules/.cache/i18n-inputs-hash']; const INPUT_DIRS = ['messages', 'project.inlang']; const force = process.argv.includes('--force'); @@ -58,6 +67,24 @@ function compile() { execSync('npm run i18n:compile', { stdio: 'inherit' }); } +/** + * Called after a successful compile AND after a skip. The skip case matters: + * when only one of the two locations still holds the hash, this is what + * restores the other — otherwise a wiped in-output stamp would never come back + * and every fresh clone would be one full compile away from a warm cache. + */ +function writeStamps(value) { + for (const p of STAMPS) { + try { + mkdirSync(dirname(p), { recursive: true }); + writeFileSync(p, value); + } catch { + // A missing stamp only costs a redundant compile next time — and the + // other copy still votes, so one unwritable location is not fatal. + } + } +} + const hash = inputHash(); if (force) { @@ -67,25 +94,22 @@ if (force) { console.log(`[i18n] ${OUT_DIR} missing — compiling.`); compile(); } else { - let previous = null; - try { - previous = readFileSync(STAMP, 'utf8').trim(); - } catch { - // No stamp yet, or unreadable — fall through and compile. - } - if (previous === hash) { + const seen = STAMPS.map((p) => { + try { + return readFileSync(p, 'utf8').trim(); + } catch { + return null; // No stamp yet, or unreadable — that one does not vote. + } + }); + if (seen.includes(hash)) { console.log('[i18n] inputs unchanged — skipped.'); + writeStamps(hash); process.exit(0); } - console.log(previous ? '[i18n] inputs changed — compiling.' : '[i18n] no stamp — compiling.'); + console.log(seen.some(Boolean) ? '[i18n] inputs changed — compiling.' : '[i18n] no stamp — compiling.'); compile(); } // Stamp only AFTER a compile that did not throw: recording the hash for a failed // run would make the next one skip and leave app/paraglide half-written. -try { - execSync('node -e "require(\'node:fs\').mkdirSync(\'node_modules/.cache\',{recursive:true})"'); - writeFileSync(STAMP, hash); -} catch { - // A missing stamp only costs a redundant compile next time. -} +writeStamps(hash); From 9f8cf55c563456d4ca5fd2b5bae8b21b8e3d8768 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 19:42:23 +0800 Subject: [PATCH 04/29] =?UTF-8?q?chore:=20delete=20the=20one-shot=20data?= =?UTF-8?q?=20backfills=20=E2=80=94=20there=20is=20nothing=20left=20to=20m?= =?UTF-8?q?igrate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These exist to repair or populate data in an install that predates a change. There are no open-source users yet, so there is no such install: every one of them can only ever run against our own production, and each has already been checked there. - backfill-notice-headers.mjs — dry run against production returned "0 headers to create; 9 rows have no recipient id". All 9 candidate rows carry neither a recipient_contact_id nor a recipient_role_key, so no header can be derived from them by this or any script. Zero unstamped rows postdate the split, so there is no live defect feeding it either. - backfill-rich-templates.mjs — seeds rich templates into tenants that predate them; POST /api/admin/backfill-default-templates covers the same ground for a live tenant and stays. - migrate-rating-levels.mjs and migrations/data/2026-07-04-severity-normalization.sql — a matched pair normalising the retired {abbr,bucket} rating shape. The SQL file was never journalled and nothing read it. The `noticeId` schema comment cited the deleted script as the reason the column is nullable. It now states the invariant instead: a row whose recipient resolves to neither a contact nor a user keeps NULL, the Outbox falls back to the interim key, and pre-split rows stay NULL permanently because they recorded no recipient at all. Kept deliberately: backfill-route-metadata.ts and backfill-zod-descriptions.ts are idempotent SOURCE codemods, not data migrations — they are still how a new route gets its MCP metadata and field descriptions. verify-migration-equivalence.mjs is db:check itself. Verified after deletion: db:check green (86 tables, no drift), knip clean (2 baselined, 0 new), no references left anywhere in the repo. --- .../2026-07-04-severity-normalization.sql | 16 -- scripts/backfill-notice-headers.mjs | 138 ------------------ scripts/backfill-rich-templates.mjs | 136 ----------------- scripts/migrate-rating-levels.mjs | 116 --------------- server/lib/db/schema/inspection/automation.ts | 10 +- 5 files changed, 6 insertions(+), 410 deletions(-) delete mode 100644 migrations/data/2026-07-04-severity-normalization.sql delete mode 100644 scripts/backfill-notice-headers.mjs delete mode 100644 scripts/backfill-rich-templates.mjs delete mode 100644 scripts/migrate-rating-levels.mjs diff --git a/migrations/data/2026-07-04-severity-normalization.sql b/migrations/data/2026-07-04-severity-normalization.sql deleted file mode 100644 index 9f1196d90..000000000 --- a/migrations/data/2026-07-04-severity-normalization.sql +++ /dev/null @@ -1,16 +0,0 @@ --- Pre-launch one-shot: fold the retired rating_bucket vocabulary into the --- canonical severity column on comments. rating_bucket stays FROZEN (DEAD) — --- see server/lib/db/schema/inspection/comments.ts. This is a DATA migration, --- run manually pre-launch against local/remote D1 per the D1 migration SOP --- (docs/saas-ops/d1-migration-sop.md) — it is NOT tracked by drizzle-kit and --- must NOT be added to migrations/0000_baseline.sql or a numbered migration. --- --- Also normalizes any legacy severity values recommendation.service wrote in --- the old satisfactory/monitor/defect words before module F. -UPDATE comments SET severity = 'good' WHERE severity IS NULL AND rating_bucket = 'satisfactory'; -UPDATE comments SET severity = 'marginal' WHERE severity IS NULL AND rating_bucket = 'monitor'; -UPDATE comments SET severity = 'significant' WHERE severity IS NULL AND rating_bucket = 'defect'; -UPDATE comments SET severity = 'minor' WHERE severity IS NULL AND rating_bucket = 'na'; -UPDATE comments SET severity = 'good' WHERE severity = 'satisfactory'; -UPDATE comments SET severity = 'marginal' WHERE severity = 'monitor'; -UPDATE comments SET severity = 'significant' WHERE severity = 'defect'; diff --git a/scripts/backfill-notice-headers.mjs b/scripts/backfill-notice-headers.mjs deleted file mode 100644 index 392db9b00..000000000 --- a/scripts/backfill-notice-headers.mjs +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env node -/** - * Communication C1 (design §3.13) — one-time notice-header backfill. - * - * Rows in `automation_logs` written before C1 have no header: their recipient - * cannot see them in a Notices inbox and the Outbox groups them by the - * interim (automation_id, send_at) key. This script creates one - * `notifications` header per (automation_id, inspection_id, send_at, - * recipient) group and stamps `notice_id` on the group's rows. - * - * Recipient mapping mirrors the live path (trigger.ts): - * - recipient_role_key = 'inspector' -> header user_id (the resolver - * stuffs the user id into recipient_contact_id for staff) - * - anything else with a contact id -> header contact_id - * - no contact id at all -> SKIPPED (notice_id stays NULL and - * the Outbox fallback grouping keeps covering the row) - * - * Header titles are synthesized from the automation name (or 'Manual send') — - * the same hardcoded-English debt as titleFor (IA-115); Track B templates it. - * - * DRY RUN by default; --apply writes; --remote targets remote D1 — read - * docs/saas-ops/d1-migration-sop.md FIRST and take a backup + time-travel - * bookmark before applying remotely. - */ -import { execSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; -import { randomUUID } from 'node:crypto'; - -const REMOTE = process.argv.includes('--remote'); -const APPLY = process.argv.includes('--apply'); - -const cfg = - process.env.WRANGLER_CONFIG || - (existsSync('wrangler.local.jsonc') ? 'wrangler.local.jsonc' : 'wrangler.jsonc'); - -// Backslash FIRST, then quote — escaping the quote first would re-escape the -// backslashes this step introduces. Without the backslash rule a value ending -// in `\` escapes the closing quote and the rest of the command line becomes -// argument data (CodeQL js/incomplete-sanitization). Titles and property -// addresses reach this from the database, so it is reachable, not theoretical. -const q = (s) => '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; - -function d1(command, { json = true } = {}) { - const cmd = [ - 'npx', 'wrangler', 'd1', 'execute', 'DB', - REMOTE ? '--remote' : '--local', - '--command', q(command), - '-c', cfg, - ...(json ? ['--json'] : []), - ].join(' '); - const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); - if (!json) return out; - return JSON.parse(out).flatMap((s) => s.results ?? []); -} - -const sq = (s) => "'" + String(s).replace(/'/g, "''") + "'"; - -const rows = d1( - 'SELECT l.id, l.tenant_id, l.automation_id, l.inspection_id, l.send_at, ' + - 'l.recipient_contact_id, l.recipient_role_key, a.name AS automation_name, a.trigger AS trigger_event ' + - 'FROM automation_logs l LEFT JOIN automations a ON a.id = l.automation_id ' + - 'WHERE l.notice_id IS NULL AND l.recipient_contact_id IS NOT NULL', -); - -// Group by (automation_id, inspection_id, send_at, recipient side+id). -const groups = new Map(); -for (const r of rows) { - const isStaff = r.recipient_role_key === 'inspector'; - const key = [r.automation_id ?? '', r.inspection_id, r.send_at, isStaff ? 'u' : 'c', r.recipient_contact_id].join('|'); - const g = groups.get(key) ?? { - tenantId: r.tenant_id, - inspectionId: r.inspection_id, - userId: isStaff ? r.recipient_contact_id : null, - contactId: isStaff ? null : r.recipient_contact_id, - type: r.automation_id == null ? 'manual.send' : (r.trigger_event ?? 'notice'), - title: r.automation_id == null ? 'Manual send' : (r.automation_name ?? 'Notice'), - createdAtMs: Number(r.send_at), - logIds: [], - }; - g.logIds.push(r.id); - groups.set(key, g); -} - -// `notifications.user_id` carries a frozen legacy FK to `users` — a staff -// header for a since-deleted user would violate it. Pre-check existence and -// skip those groups (their rows stay on the fallback grouping). `contact_id` -// has no FK (added post-freeze per Schema Rules), so no check there. -const staffIds = [...new Set([...groups.values()].filter((g) => g.userId).map((g) => g.userId))]; -if (staffIds.length > 0) { - const found = new Set( - d1(`SELECT id FROM users WHERE id IN (${staffIds.map(sq).join(', ')})`).map((r) => String(r.id)), - ); - let dropped = 0; - for (const [key, g] of [...groups.entries()]) { - if (g.userId && !found.has(g.userId)) { groups.delete(key); dropped++; } - } - if (dropped > 0) console.log(`${dropped} staff group(s) skipped — user no longer exists (rows stay on fallback grouping).`); -} - -console.log(`${rows.length} unstamped log rows scanned -> ${groups.size} headers to create.`); -let skippedNoRecipient = 0; -const skipped = d1( - 'SELECT COUNT(*) AS n FROM automation_logs WHERE notice_id IS NULL AND recipient_contact_id IS NULL', -); -skippedNoRecipient = Number(skipped[0]?.n ?? 0); -if (skippedNoRecipient > 0) { - console.log(`${skippedNoRecipient} rows have no recipient id and stay on the fallback grouping (not covered).`); -} - -if (groups.size === 0) process.exit(0); -if (!APPLY) { - for (const g of [...groups.values()].slice(0, 10)) { - console.log(` ${g.contactId ? 'contact ' + g.contactId : 'user ' + g.userId}: "${g.title}" x${g.logIds.length} rows (inspection ${g.inspectionId})`); - } - if (groups.size > 10) console.log(` … and ${groups.size - 10} more.`); - console.log('\nDry run — nothing written. Re-run with --apply to write.' + (REMOTE ? ' (REMOTE: back up per docs/saas-ops/d1-migration-sop.md first.)' : '')); - process.exit(0); -} - -const BATCH = 20; -const entries = [...groups.values()]; -for (let i = 0; i < entries.length; i += BATCH) { - const stmts = []; - for (const g of entries.slice(i, i + BATCH)) { - const noticeId = randomUUID(); - stmts.push( - 'INSERT INTO notifications (id, tenant_id, user_id, contact_id, type, title, body, entity_type, entity_id, inspection_id, metadata, read_at, archived_at, created_at) VALUES (' + - [sq(noticeId), sq(g.tenantId), g.userId ? sq(g.userId) : 'NULL', g.contactId ? sq(g.contactId) : 'NULL', - sq(g.type), sq(g.title), 'NULL', sq('inspection'), sq(g.inspectionId), sq(g.inspectionId), - 'NULL', 'NULL', 'NULL', String(g.createdAtMs)].join(', ') + ')', - ); - stmts.push( - `UPDATE automation_logs SET notice_id = ${sq(noticeId)} WHERE id IN (${g.logIds.map(sq).join(', ')})`, - ); - } - d1(stmts.join('; '), { json: false }); -} -console.log(`Created ${entries.length} headers and stamped their rows.`); diff --git a/scripts/backfill-rich-templates.mjs b/scripts/backfill-rich-templates.mjs deleted file mode 100644 index f9b0fc8d5..000000000 --- a/scripts/backfill-rich-templates.mjs +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env node -/** - * One-shot backfill: add the rich seed-templates to every existing SaaS - * tenant. Equivalent to looping POST /api/admin/backfill-default-templates - * once per tenant — but works against any environment without needing the - * M2M secret in plaintext (uses wrangler's authenticated D1 connection). - * - * Idempotent: for each (tenant, template-name), insert only if the same - * (tenantId, name) row doesn't already exist. Doesn't touch any rows we - * didn't add, so the bare-fixture templates (e.g. "Residential" without - * the "Inspection" suffix) are left alone for the user to clean up. - * - * Schemas can be ~40KB so we batch SQL into a tempfile and feed it to - * `wrangler d1 execute DB --file ...` — passing on the command line - * tripped Windows' ENAMETOOLONG. - * - * Usage: node scripts/backfill-rich-templates.mjs [--config wrangler.saas.toml] [--local] - */ - -import { execSync } from 'child_process'; -import { randomUUID } from 'crypto'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; -import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'fs'; -import { tmpdir } from 'os'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const ROOT = join(__dirname, '..'); - -const args = new Set(process.argv.slice(2)); -const configIdx = process.argv.indexOf('--config'); -const configFlag = configIdx >= 0 ? process.argv[configIdx + 1] : null; -const local = args.has('--local'); - -const SEED_FILES = [ - 'residential.json', - 'pre-listing.json', - 'new-construction.json', - 'new-construction-final.json', - 'sewer-scope.json', - 'radon.json', - 'mold-inspection.json', -]; - -const seeds = SEED_FILES.map((f) => { - const raw = JSON.parse(readFileSync(join(ROOT, 'server/data/seed-templates', f), 'utf8')); - return { name: raw.name, schema: JSON.stringify(raw.schema) }; -}); - -const remoteFlag = local ? '--local' : '--remote'; -const configArg = configFlag ? `-c "${configFlag}"` : ''; -const tmp = mkdtempSync(join(tmpdir(), 'oi-backfill-')); - -// `--file` mode is fast for huge INSERTs but returns only a summary (no -// query results). `--command` returns rows but is limited by the OS arg -// length. Use whichever fits the use case. -function wranglerD1FromFile(sql) { - const file = join(tmp, `q-${randomUUID()}.sql`); - writeFileSync(file, sql, 'utf8'); - const cmd = `npx wrangler d1 execute DB ${remoteFlag} ${configArg} --json --file "${file}"`; - const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); - rmSync(file); - return out; -} - -function wranglerD1Query(sql) { - const cmd = `npx wrangler d1 execute DB ${remoteFlag} ${configArg} --json --command ${JSON.stringify(sql)}`; - const out = execSync(cmd, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); - const start = out.indexOf('['); - const end = out.lastIndexOf(']'); - if (start < 0 || end < 0) return []; - try { return JSON.parse(out.slice(start, end + 1)); } catch { return []; } -} - -function sqlEscape(s) { - return String(s).replace(/'/g, "''"); -} - -console.log(`[backfill] target: ${configFlag || 'wrangler.local.jsonc'} (${local ? 'local' : 'remote'})`); - -// Step 1 — list every tenant. -const tenantsRows = wranglerD1Query('SELECT id, name FROM tenants ORDER BY id'); -const tenants = tenantsRows[0]?.results || []; -console.log(`[backfill] ${tenants.length} tenants`); - -let totalInserted = 0; -let totalSkipped = 0; - -for (const t of tenants) { - const tenantId = String(t.id); - const tenantName = String(t.name || tenantId.slice(0, 8)); - - const existsRows = wranglerD1Query( - `SELECT name FROM templates WHERE tenant_id = '${sqlEscape(tenantId)}'` - ); - const existingNames = new Set((existsRows[0]?.results || []).map((r) => r.name)); - - const missing = seeds.filter((s) => !existingNames.has(s.name)); - if (missing.length === 0) { - console.log(`[backfill] ${tenantName.padEnd(40)} 0 inserted, ${seeds.length} already present`); - totalSkipped += seeds.length; - continue; - } - - // Batch all missing inserts into a single file so we make one wrangler - // call per tenant rather than 7. SQLite supports multiple statements per - // execute; wrangler runs them as a single batch. - // templates.created_at is `INTEGER NOT NULL` (Drizzle mode: 'timestamp', - // serialized as ms). One INSERT at a time so a failure pinpoints the - // specific (tenant, template) pair rather than aborting the batch. - const nowMs = Date.now(); - let inserted = 0; - let failed = 0; - for (const seed of missing) { - const id = randomUUID(); - const sql = - `INSERT INTO templates (id, tenant_id, name, version, schema, created_at) VALUES ` + - `('${id}', '${sqlEscape(tenantId)}', '${sqlEscape(seed.name)}', 1, '${sqlEscape(seed.schema)}', ${nowMs});`; - try { - wranglerD1FromFile(sql); - inserted++; - } catch (err) { - failed++; - const msg = err instanceof Error ? err.message : String(err); - console.log(`[backfill] ${tenantName} :: ${seed.name} FAILED — ${msg.split('\n')[0].slice(0, 200)}`); - } - } - totalInserted += inserted; - totalSkipped += seeds.length - missing.length; - const failedStr = failed > 0 ? `, ${failed} FAILED` : ''; - console.log(`[backfill] ${tenantName.padEnd(40)} +${inserted} inserted, ${seeds.length - missing.length} already present${failedStr}`); -} - -rmSync(tmp, { recursive: true, force: true }); -console.log(`---`); -console.log(`[backfill] done. inserted=${totalInserted}, skipped=${totalSkipped}`); diff --git a/scripts/migrate-rating-levels.mjs b/scripts/migrate-rating-levels.mjs deleted file mode 100644 index d23404b70..000000000 --- a/scripts/migrate-rating-levels.mjs +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env node -/** - * Pre-launch one-shot: normalize `rating_systems.levels` JSON from the - * retired `{ abbr, bucket }` shape to the canonical - * `{ abbreviation, severity, isDefect, pausesAdvance? }` shape (module F — - * see app/lib/severity.ts / server/lib/validations/rating-system.schema.ts). - * - * Companion to migrations/data/2026-07-04-severity-normalization.sql (which - * normalizes `comments.severity`). This script handles `rating_systems` - * because its levels are JSON-encoded, not a flat column the SQL file's - * UPDATE statements can reach directly. - * - * NOT a drizzle migration — this is a DATA migration, run manually - * pre-launch. Local rows only have a handful of seeded/cloned systems, so a - * per-row Node normalizer is simpler than hand-writing JSON1 SQL. - * - * node scripts/migrate-rating-levels.mjs --local - * node scripts/migrate-rating-levels.mjs --remote - * - * Per the D1 migration SOP (docs/saas-ops/d1-migration-sop.md), take a - * `d1 export --remote` backup before running with --remote. - */ -import { execFileSync } from 'node:child_process'; -import { writeFileSync, rmSync } from 'node:fs'; -import { join } from 'node:path'; - -const args = process.argv.slice(2); -const isRemote = args.includes('--remote'); -const isLocal = args.includes('--local'); -if (isRemote === isLocal) { - console.error('Specify exactly one of --local or --remote.'); - process.exit(1); -} -const scope = isRemote ? '--remote' : '--local'; -const wranglerShim = join(import.meta.dirname, 'wrangler.mjs'); - -function wrangler(extraArgs) { - return execFileSync(process.execPath, [wranglerShim, ...extraArgs], { encoding: 'utf8' }); -} - -/** bucket -> severity (module F's single canonical vocabulary). */ -const BUCKET_TO_SEVERITY = { - satisfactory: 'good', - monitor: 'marginal', - defect: 'significant', - na: 'minor', -}; - -/** Normalize one level: legacy {abbr,bucket} -> canonical {abbreviation,severity,isDefect}. */ -function normalizeLevel(lvl) { - if (lvl && typeof lvl === 'object' && 'severity' in lvl && 'abbreviation' in lvl) { - // Already canonical (e.g. re-run, or a system created after module F shipped). - return lvl; - } - const bucket = lvl?.bucket; - const severity = BUCKET_TO_SEVERITY[bucket] ?? 'minor'; - const normalized = { - ...(lvl?.id ? { id: lvl.id } : {}), - label: lvl?.label ?? '', - abbreviation: lvl?.abbr ?? lvl?.label ?? '', - color: lvl?.color ?? '#9ca3af', - severity, - isDefect: bucket === 'defect', - ...(typeof lvl?.pausesAdvance === 'boolean' ? { pausesAdvance: lvl.pausesAdvance } : {}), - ...(lvl?.hotkey ? { hotkey: lvl.hotkey } : {}), - ...(typeof lvl?.order === 'number' ? { order: lvl.order } : {}), - }; - return normalized; -} - -// 1. Read every rating_systems row. -const listSql = 'SELECT id, levels FROM rating_systems;'; -const parsed = JSON.parse(wrangler(['d1', 'execute', 'DB', scope, '--json', '--yes', '--command', listSql])); -const rows = Array.isArray(parsed) ? (parsed[0]?.results ?? []) : (parsed?.results ?? []); - -if (!rows.length) { - console.log(`No rating_systems rows on ${scope} — nothing to normalize.`); - process.exit(0); -} - -// 2. Normalize + build one UPDATE per row (levels JSON differs per row, so a -// single templated UPDATE can't cover them all). -const statements = []; -let changed = 0; -for (const row of rows) { - const raw = row.levels; - let levels; - try { - levels = typeof raw === 'string' ? JSON.parse(raw) : raw; - } catch { - console.warn(`Skipping rating_systems.id=${row.id} — unparseable levels JSON.`); - continue; - } - if (!Array.isArray(levels)) continue; - const alreadyCanonical = levels.every((l) => l && typeof l === 'object' && 'severity' in l && 'abbreviation' in l); - if (alreadyCanonical) continue; - const normalized = levels.map(normalizeLevel); - const json = JSON.stringify(normalized).replace(/'/g, "''"); - statements.push(`UPDATE rating_systems SET levels = '${json}' WHERE id = '${String(row.id).replace(/'/g, "''")}';`); - changed++; -} - -if (!statements.length) { - console.log(`All ${rows.length} rating_systems row(s) on ${scope} already canonical — nothing to do.`); - process.exit(0); -} - -const tmp = join(process.cwd(), `.migrate-rating-levels.${isRemote ? 'remote' : 'local'}.sql`); -writeFileSync(tmp, statements.join('\n') + '\n', 'utf8'); -try { - console.log(`Normalizing ${changed} of ${rows.length} rating_systems row(s) on ${scope}…`); - wrangler(['d1', 'execute', 'DB', scope, '--file', tmp, '--yes']); - console.log('✓ rating_systems.levels normalized to the canonical abbreviation/severity shape.'); -} finally { - rmSync(tmp, { force: true }); -} diff --git a/server/lib/db/schema/inspection/automation.ts b/server/lib/db/schema/inspection/automation.ts index e091dc395..926c5d59b 100644 --- a/server/lib/db/schema/inspection/automation.ts +++ b/server/lib/db/schema/inspection/automation.ts @@ -131,10 +131,12 @@ export const automationLogs = sqliteTable('automation_logs', { // Communication C1 (design §3.13) — the NOTICE HEADER this channel-attempt // belongs to (notifications.id). The header carries the recipient and read // state; this row carries one channel's delivery outcome. App-layer soft - // reference, no .references() per Schema Rules. Nullable: legacy rows are - // stamped by scripts/backfill-notice-headers.mjs, and a row whose - // recipient resolves to neither a contact nor a user keeps NULL (the - // Outbox grouping falls back to the interim (automation_id, send_at) key). + // reference, no .references() per Schema Rules. Nullable, and the NULL is + // load-bearing in two ways: a row whose recipient resolves to neither a + // contact nor a user keeps NULL, and the Outbox grouping falls back to the + // interim (automation_id, send_at) key. Rows written before the split keep + // NULL permanently — they recorded no recipient at all, so no header can be + // derived for them and no backfill could ever have helped. // Appended at table end for D1 rebuild safety. noticeId: text('notice_id'), }, (t) => [ From 8acc808330b579392e289bc235ec6a816e1c1a51 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 20:26:46 +0800 Subject: [PATCH 05/29] chore(i18n): let server code read compiled messages, through one guarded seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First Paraglide usage in server/. Four things had to agree on where the compiled catalogue lives, and only one of them did: - tsconfig.api.json mapped @core/api-types alone, so `~/paraglide/messages` did not resolve in the api program. Adding `~/*` keeps `include` at server/**; tsc follows the import out of the entry set without widening the program to all of app/. - vitest.api.config.ts had no `~` alias either, so an api spec reaching a server-side message failed to IMPORT rather than to assert. - vite.config.ts already aliases `~`, so the worker build was never a problem. - eslint forbids server/ importing app/ at all — the BFF boundary. That rule is right, and app/paraglide/ is the case it did not anticipate: generated data, compiled from messages/**, living under app/ only because that is where the outdir points. Relocating it to a shared package would rewrite the import in 392 files for no behavioural gain. The exception is an inline disable on the seam module, not an eslint.config.js entry, and that choice is the point: a config entry would exempt all of server/, while one line exempts one file. Verified both directions — the seam lints clean, and a probe file importing the catalogue directly still fails the rule. So "the one place server code reads messages" is enforced, not just documented. Same shape as server/lib/jwt-keyring.ts, the sanctioned wrapper for hono/jwt. --- eslint.config.js | 9 +++++++++ server/lib/i18n/messages.ts | 27 +++++++++++++++++++++++++++ tsconfig.api.json | 3 ++- vitest.api.config.ts | 7 +++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 server/lib/i18n/messages.ts diff --git a/eslint.config.js b/eslint.config.js index 9d8d3e066..12e4af740 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -517,6 +517,15 @@ export default tseslint.config( // and the sole hono/jwt import (server/lib/jwt-keyring.ts) is a // verify-only import carrying an inline disable with a reason (the // keyring IS the sanctioned wrapper). + // + // There is now a second sanctioned wrapper on the same pattern: + // server/lib/i18n/messages.ts re-exports the compiled Paraglide + // catalogue, which lives at app/paraglide/ because that is where the + // outdir points — generated data, not app code. Its disable is inline + // for the same reason the keyring's is: scoped to one file, so every + // other server module still fails the rule and has to go through the + // wrapper. Keep new exceptions inline; an entry here would apply to all + // of server/ and is how this list would start growing. files: ['server/**/*.ts'], rules: { 'no-restricted-imports': ['error', { diff --git a/server/lib/i18n/messages.ts b/server/lib/i18n/messages.ts new file mode 100644 index 000000000..2a2761b64 --- /dev/null +++ b/server/lib/i18n/messages.ts @@ -0,0 +1,27 @@ +/** + * The one place server code reads compiled Paraglide messages. + * + * Server-side i18n has a constraint the client does not: a notification is + * rendered FOR a recipient, whose locale is not the request's locale, and in a + * cron or queue context there is no request at all — `getLocale()` returns + * `baseLocale`. Recipient-locale resolution is a separate piece of work. Until + * it lands, every message read through here resolves in the ambient locale, + * which for notifications means English. Routing this through one module means + * that change touches one file rather than every call site. + * + * Why the disable below, and why it does not weaken the BFF boundary: the rule + * stops `server/` depending on `app/` because `app/` is loaders, components and + * browser-only helpers. `app/paraglide/` is none of those — it is GENERATED + * data, compiled from `messages/**` by a build step, and it sits under `app/` + * only because that is where the paraglide outdir points (vite.config.ts and + * the `i18n:compile` script). Relocating it to a genuinely shared package would + * rewrite the import in 392 files for no behavioural gain. + * + * The exception is kept inline rather than in eslint.config.js on purpose: as a + * one-line disable it covers exactly this file, so every OTHER server module + * still fails the rule if it reaches for the catalogue directly. That makes + * "the one place" an enforced property rather than a comment. Same shape as + * `server/lib/jwt-keyring.ts`, the sanctioned wrapper for `hono/jwt`. + */ +// eslint-disable-next-line no-restricted-imports -- this IS the sanctioned message wrapper; app/paraglide is generated data, not app code, and every other server module must still go through here. +export { m } from '~/paraglide/messages'; diff --git a/tsconfig.api.json b/tsconfig.api.json index 176545270..dcbbe17a3 100644 --- a/tsconfig.api.json +++ b/tsconfig.api.json @@ -23,7 +23,8 @@ "incremental": true, "tsBuildInfoFile": "./.tsbuildinfo.api", "paths": { - "@core/api-types": ["./packages/api-types/index.ts"] + "@core/api-types": ["./packages/api-types/index.ts"], + "~/*": ["./app/*"] } }, "include": ["server/**/*.ts", "server/**/*.tsx", "worker-configuration.d.ts"], diff --git a/vitest.api.config.ts b/vitest.api.config.ts index e14e26061..fa9b60874 100644 --- a/vitest.api.config.ts +++ b/vitest.api.config.ts @@ -18,6 +18,13 @@ export default defineConfig({ // Workers-runtime tests (tests/workers/mcp/*) and production. '@cloudflare/workers-oauth-provider': path.resolve(__dirname, 'tests/unit/stubs/workers-oauth-provider.ts'), 'agents/mcp': path.resolve(__dirname, 'tests/unit/stubs/agents-mcp.ts'), + // `server/lib/i18n/messages.ts` re-exports the compiled Paraglide + // catalogue, which lives under `app/`. The worker build resolves this + // through vite.config.ts's own `~` alias and the api tsc pass through + // tsconfig.api.json's `paths`; this is the third resolver that has to + // agree, and without it every api spec that reaches a server-side + // message fails to import rather than to assert. + '~': path.resolve(__dirname, 'app'), }, }, test: { From aeab1f5ca77fa1b59df82cecf342fbf3f922e910 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 20:27:25 +0800 Subject: [PATCH 06/29] i18n(notices): move the seven stored notice titles into the catalogue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T6 moved the notification CALL SITES onto templates but left titleFor as the fallback, so six hard-coded English cases plus a default survived where no translator could reach them. The switch stays — a tenant can have no template, and a trigger can exist in the enum before a template does — but its literals are now message keys. They are `comm_` prefixed, not `notice_`, and that is not arbitrary. The catalogue already holds a `notice_title_*` family, and at a glance these look like duplicates of it. They are not: `notice_title_report_published` is recipient voice with no address ("Your inspection report is ready") and is what app/lib/notice-view.ts renders for types it recognises, while these are staff/ledger voice with the address ("Report published — 12 Oak St") and are what gets STORED on the row and shown in the Outbox. The same split already distinguishes comm_reason_sms_opt_out from notice_reason_sms_opt_out. English output is byte-identical — the spec asserts exact strings, em dash and all, rather than "contains". es-419 keys are deliberately absent so the catalogue gate's English-fallback path applies; an empty target key would render blank, which is a hard failure. This does NOT make titles render in the recipient's language. Nothing resolves a recipient locale, and in a cron or queue context there is no request locale at all. It makes them reachable by a translator, which they were not before. The spec carries a guard against the literals returning. Narrow by choice: a general "no English literals in server/" lint would need an allowlist for logs, error codes and SQL, and the allowlist is what would quietly grow. It was seen red first — it listed all seven literals before the rewrite. --- messages/en/communication.json | 7 +++ server/services/automation/trigger.ts | 33 ++++++++++--- tests/unit/automations/notice-title.spec.ts | 55 +++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 tests/unit/automations/notice-title.spec.ts diff --git a/messages/en/communication.json b/messages/en/communication.json index 696f5baa6..1c72c453e 100644 --- a/messages/en/communication.json +++ b/messages/en/communication.json @@ -93,6 +93,13 @@ "notice_title_inspection_reminder": "Your inspection is coming up", "notice_title_agreement_signed": "Your inspection agreement is signed", "notice_title_manual_send": "A message from your inspector", + "comm_notice_title_inspection_created": "New inspection scheduled — {address}", + "comm_notice_title_inspection_confirmed": "Inspection confirmed — {address}", + "comm_notice_title_inspection_cancelled": "Inspection cancelled — {address}", + "comm_notice_title_report_published": "Report published — {address}", + "comm_notice_title_invoice_created": "Invoice created — {address}", + "comm_notice_title_payment_received": "Payment received — {address}", + "comm_notice_title_generic": "{event} — {address}", "notice_empty_body_staff": "When a booking comes in, an agreement is signed, or a payment lands, it shows up here.", "comm_reason_sms_opt_out": "Skipped — this person replied STOP. They can text START to opt back in.", "notice_reason_sms_opt_out": "You asked us to stop texting you. Text START to any of our messages to turn texts back on." diff --git a/server/services/automation/trigger.ts b/server/services/automation/trigger.ts index 17d82128f..ca8f13e06 100644 --- a/server/services/automation/trigger.ts +++ b/server/services/automation/trigger.ts @@ -11,6 +11,7 @@ import { interpolate } from './shared'; import type { AutomationChannel, RecipientKind, Constructor, TriggerContext } from './shared'; import type { AutomationBase, HasEnsureSeeds, HasParseChannels } from './shared'; import { PRIMARY_CLIENT_KEY } from '../../lib/people/default-role-profiles'; +import { m } from '../../lib/i18n/messages'; /** * Trigger mixin: fan out pending automation_log rows when a domain event fires, @@ -351,16 +352,32 @@ export function AutomationTrigger
" + // beats an empty notice title. It is now translatable too. + default: return m.comm_notice_title_generic({ event, address }); } } diff --git a/tests/unit/automations/notice-title.spec.ts b/tests/unit/automations/notice-title.spec.ts new file mode 100644 index 000000000..20e8dfac8 --- /dev/null +++ b/tests/unit/automations/notice-title.spec.ts @@ -0,0 +1,55 @@ +/** + * The stored notice titles are the last user-facing English literals in + * `server/`. This spec is what stops them being re-hardcoded: it asserts the + * CATALOG holds them, so a future edit that inlines a string again fails here + * rather than silently removing a translatable key. + * + * Note the two families in `messages/en/communication.json`, which look like + * duplicates and are not: + * `notice_title_*` — recipient voice, no address ("Your inspection + * report is ready"). Rendered by `noticeTitle()` in + * `app/lib/notice-view.ts` for types it recognises. + * `comm_notice_title_*` — staff/ledger voice, address included ("Report + * published — 12 Oak St"). What `titleFor` STORES on + * the row, and what the Outbox shows. + * The same `comm_` / `notice_` split already distinguishes + * `comm_reason_sms_opt_out` from `notice_reason_sms_opt_out`. + */ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { m } from '../../../server/lib/i18n/messages'; + +describe('notice titles come from the message catalog', () => { + const address = '12 Oak St'; + + it('renders each event title from a message key', () => { + expect(m.comm_notice_title_inspection_created({ address })) + .toBe(`New inspection scheduled — ${address}`); + expect(m.comm_notice_title_inspection_confirmed({ address })) + .toBe(`Inspection confirmed — ${address}`); + expect(m.comm_notice_title_inspection_cancelled({ address })) + .toBe(`Inspection cancelled — ${address}`); + expect(m.comm_notice_title_report_published({ address })) + .toBe(`Report published — ${address}`); + expect(m.comm_notice_title_invoice_created({ address })) + .toBe(`Invoice created — ${address}`); + expect(m.comm_notice_title_payment_received({ address })) + .toBe(`Payment received — ${address}`); + }); + + it('renders the unknown-event fallback from a message key', () => { + expect(m.comm_notice_title_generic({ event: 'report.amended', address })) + .toBe(`report.amended — ${address}`); + }); + + it('trigger.ts holds no bare notice-title literal', () => { + const src = readFileSync( + new URL('../../../server/services/automation/trigger.ts', import.meta.url), + 'utf8', + ); + // The em dash is the tell: every stored notice title is + // "
". + const bare = src.match(/return\s+`[^`]*—[^`]*`/g) ?? []; + expect(bare).toEqual([]); + }); +}); From 8592dcca8dd22d3f9aabb671a19951e0f087bf33 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 20:43:52 +0800 Subject: [PATCH 07/29] fix(qbo): push card payments to QuickBooks, and make the push idempotent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects of the same shape — the function was written and one line was never connected. recordPayment had exactly ONE caller, the manual "mark as paid" route. A client paid by card, Stripe confirmed, markPaid ran, and QuickBooks never learned. A tenant reconciling their books found every online payment missing. Verified against the code rather than assumed: `git grep recordPayment` returns the one call site, and stripe-webhook.ts settled an invoice with no QBO call at all. The push also carried no idempotency key, and Stripe redelivers webhooks. QBO is the tenant's book of record, so a duplicate Payment overstates their revenue and their tax position — the kind of error found at tax time rather than in a test. The push now carries QBO's `requestid`, whose contract is that a repeated key returns the original response instead of performing the operation again. The key identifies the FACT, not the attempt: a per-attempt uuid is unique every time and therefore protects nothing. Both push sites derive it from one function so they agree — an invoice settled online and then also marked paid by hand is one payment, and the second push collapses. That single derivation point is also what makes the ledger migration safe later: when the fact becomes a ledger row rather than an invoice, every caller moves at once, because re-pushing under a new key would duplicate. Also adds InvoiceService.findById. The manual route was reading one invoice's amount out of a full listInvoices scan; the webhook runs on every card settlement and must not do that. Out of scope, and paired with the payment ledger in a later batch: pushing the amount actually paid rather than the invoice total (correct today only because payment is all-or-nothing), TxnDate being the push date rather than when money moved, and refunds — createCreditMemo is still implemented with zero callers. --- server/api/invoices.ts | 7 +- server/api/stripe-webhook.ts | 25 ++++ server/lib/qbo-payment-key.ts | 20 +++ server/services/invoice.service.ts | 17 +++ server/services/qbo/invoice-sync.ts | 20 ++- tests/unit/qbo/payment-push.spec.ts | 195 ++++++++++++++++++++++++++++ 6 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 server/lib/qbo-payment-key.ts create mode 100644 tests/unit/qbo/payment-push.spec.ts diff --git a/server/api/invoices.ts b/server/api/invoices.ts index 17fc8d64e..a0fde3dab 100644 --- a/server/api/invoices.ts +++ b/server/api/invoices.ts @@ -20,6 +20,7 @@ import { resolveSignatureInspector } from '../lib/signature-helpers'; import { getTenantId, getDrizzle } from '../lib/route-helpers'; import { resolveLocale } from '../lib/locale'; import { formatCurrency } from '../lib/format'; +import { qboPaymentKey } from '../lib/qbo-payment-key'; /** * `invoices.id` is an opaque TEXT id, so the route must not demand a UUID @@ -192,7 +193,11 @@ const invoiceRoutes = createApiRouter() } if (c.env.QBO_CLIENT_ID && inv) { c.executionCtx.waitUntil( - c.var.services.qbo.recordPayment(tenantId, id, inv.amountCents / 100), + // Same key the Stripe webhook uses, deliberately: an invoice + // settled online and then also marked paid by hand is ONE + // payment, and QBO collapses the second push on the repeated + // requestid instead of booking it twice. + c.var.services.qbo.recordPayment(tenantId, id, inv.amountCents / 100, qboPaymentKey(id)), ); } return c.json({ success: true }, 200); diff --git a/server/api/stripe-webhook.ts b/server/api/stripe-webhook.ts index bb6f361c9..f61de5b32 100644 --- a/server/api/stripe-webhook.ts +++ b/server/api/stripe-webhook.ts @@ -4,6 +4,7 @@ import { logger } from '../lib/logger'; import { extractSettledPayment } from '../lib/stripe-helpers'; import { appendWebhookLogEntry } from '../lib/stripe-webhook-log'; import { AppError } from '../lib/errors'; +import { qboPaymentKey } from '../lib/qbo-payment-key'; /** * Stripe webhook (bring-your-own-keys). Excluded from JWT middleware (see @@ -96,6 +97,30 @@ api.post('/', async (c) => { return c.json({ success: false, error: { message: 'Processing failed' } }, 500); } + // QuickBooks learns about card payments here or not at all. `recordPayment` + // had exactly one caller — the manual "mark as paid" route — so a tenant + // reconciling their books found every online payment missing. + // + // In waitUntil, and deliberately after the ACK path is settled: the customer + // has already paid, and a QuickBooks outage must not turn a successful + // payment into a 500 that Stripe redelivers forever. `markPaid` above is + // idempotent on our side; the requestid is what keeps their side clean when + // Stripe redelivers anyway. + if (c.env.QBO_CLIENT_ID) { + c.executionCtx.waitUntil((async () => { + try { + const inv = await c.var.services.invoice.findById(tenantId, settled.invoiceId); + if (!inv) return; + await c.var.services.qbo.recordPayment( + tenantId, settled.invoiceId, inv.amountCents / 100, qboPaymentKey(settled.invoiceId), + ); + } catch (e) { + logger.error('Stripe webhook: QBO payment push failed', + { invoiceId: settled.invoiceId.slice(0, 8) }, e instanceof Error ? e : undefined); + } + })()); + } + await appendWebhookLogEntry(c.env.TENANT_CACHE, tenantId, { eventType: event.type, result: 'processed', }); diff --git a/server/lib/qbo-payment-key.ts b/server/lib/qbo-payment-key.ts new file mode 100644 index 000000000..a81e0daf8 --- /dev/null +++ b/server/lib/qbo-payment-key.ts @@ -0,0 +1,20 @@ +/** + * The idempotency key for a QuickBooks Payment push. + * + * QBO's `requestid` returns the ORIGINAL response for a repeated key rather + * than performing the operation again, and keys are unique per company + * FOREVER. So the key has to identify the fact — the thing that happened once — + * and never the attempt. Both push sites (the manual "mark as paid" route and + * the Stripe webhook) derive it from here so they agree: one invoice settled + * online and then also marked paid by hand is one payment, not two. + * + * Today the fact is the invoice, because payment is all-or-nothing. When a + * payment ledger exists the fact becomes the ledger ROW — a $90 deposit and a + * $360 balance are two payments against one invoice — and this function changes + * to take the row id. Every caller must move in that same change: an invoice + * pushed under the old key and re-pushed under a new one is a duplicate in + * someone's books, and they would find it at tax time rather than in a test. + */ +export function qboPaymentKey(invoiceId: string): string { + return `pay-${invoiceId}`; +} diff --git a/server/services/invoice.service.ts b/server/services/invoice.service.ts index cce738a5a..2145ea264 100644 --- a/server/services/invoice.service.ts +++ b/server/services/invoice.service.ts @@ -47,6 +47,23 @@ export class InvoiceService { })); } + /** + * One invoice by id, tenant-scoped. Exists because both QuickBooks push + * sites need the amount and nothing else: the manual route was reading it + * out of a full `listInvoices` scan, and the Stripe webhook — which runs on + * every card settlement — must not do that. + * + * Returns `null` rather than throwing: a caller that cannot find the + * invoice has nothing to push, which is not an error. + */ + async findById(tenantId: string, id: string) { + const db = this.getDrizzle(); + const row = await db.select().from(invoices) + .where(and(eq(invoices.id, id), eq(invoices.tenantId, tenantId))) + .get(); + return row ?? null; + } + /** * iter-2 production bug #10 — given an inspection id, return its most * recent invoice (if any) within the given tenant. Used by the public diff --git a/server/services/qbo/invoice-sync.ts b/server/services/qbo/invoice-sync.ts index 4c0c05c75..7dad89ef8 100644 --- a/server/services/qbo/invoice-sync.ts +++ b/server/services/qbo/invoice-sync.ts @@ -191,7 +191,23 @@ export function withInvoiceSync>(Base: } } - async recordPayment(tenantId: string, invoiceId: string, amountPaid: number): Promise { + /** + * QuickBooks is the tenant's book of record, so a duplicate Payment + * overstates their revenue and their tax position. `idempotencyKey` + * becomes QBO's `requestid`: Intuit's contract is that a repeated key + * returns the ORIGINAL response instead of performing the operation + * again, which is what stops a Stripe webhook redelivery — or the + * manual route and the webhook both firing for one invoice — becoming a + * second Payment. + * + * It must identify the FACT, not the attempt. A per-attempt uuid is + * unique every time and therefore protects nothing. Keys are unique per + * company forever, so derive them from a row id and never from a + * counter. + */ + async recordPayment( + tenantId: string, invoiceId: string, amountPaid: number, idempotencyKey: string, + ): Promise { const db = this.getDrizzle(); const invoiceMap = await db.select().from(qboEntityMap).where( and(eq(qboEntityMap.tenantId, tenantId), eq(qboEntityMap.oiType, 'invoice'), eq(qboEntityMap.oiId, invoiceId)), @@ -205,7 +221,7 @@ export function withInvoiceSync>(Base: } try { - await this.apiCall(tenantId, 'POST', 'payment', { + await this.apiCall(tenantId, 'POST', `payment?requestid=${encodeURIComponent(idempotencyKey)}`, { CustomerRef: { value: qboCustomerId }, TotalAmt: amountPaid, TxnDate: new Date().toISOString().slice(0, 10), diff --git a/tests/unit/qbo/payment-push.spec.ts b/tests/unit/qbo/payment-push.spec.ts new file mode 100644 index 000000000..adf390cff --- /dev/null +++ b/tests/unit/qbo/payment-push.spec.ts @@ -0,0 +1,195 @@ +/** + * QuickBooks payment push — two defects, both "the function was written and one + * line was never connected": + * + * 1. `recordPayment` had exactly ONE caller, the manual "mark as paid" route. + * A client paying by card settled through Stripe, `markPaid` ran, and + * QuickBooks never learned. Every online payment was missing from the + * tenant's books. + * 2. The push carried no idempotency key, and Stripe redelivers webhooks. A + * duplicate Payment overstates a tenant's revenue and their tax position. + * + * QBO's contract for `requestid` is that a repeated key returns the ORIGINAL + * response rather than performing the operation again. What we can verify + * locally is that we send a key derived from the OI record — unique per fact — + * rather than per attempt. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { Hono } from 'hono'; + +import { QBOServiceBase } from '../../../server/services/qbo/api-base'; +import { withInvoiceSync } from '../../../server/services/qbo/invoice-sync'; + +const verifyWebhook = vi.fn(); +vi.mock('../../../server/services/stripe.service', () => ({ + StripeService: class { constructor(_k: string) { void _k; } verifyWebhook = verifyWebhook; }, +})); + +import stripeWebhookApi from '../../../server/api/stripe-webhook'; + +// --- recordPayment: what actually goes on the wire ------------------------ + +interface Call { method: string; path: string; body: unknown } + +/** + * The invoice→QBO mapping lookup is one `select().from().where().get()`; stub + * that rather than standing up sqlite, because what this spec is about is the + * request we build, not the join that finds the id. + */ +function stubDb(qboId: string | null) { + const chain = { + select: () => chain, + from: () => chain, + where: () => chain, + get: async () => (qboId == null ? undefined : { qboId }), + }; + return chain; +} + +class ProbeQbo extends withInvoiceSync(QBOServiceBase) { + calls: Call[] = []; + constructor(private readonly mappedQboId: string | null = 'QBO-INV-1') { + super({} as never, 'cid', 'secret', 'whsec', 'jwt'); + } + protected override getDrizzle() { return stubDb(this.mappedQboId) as never; } + protected override async getQBOCustomerIdForInvoice(): Promise { return 'QBO-CUST-9'; } + protected override async apiCall( + _tenantId: string, method: 'GET' | 'POST' | 'PUT', path: string, body?: unknown, + ): Promise { + this.calls.push({ method, path, body }); + return {} as T; + } +} + +const requestIdOf = (path: string) => + new URLSearchParams(path.slice(path.indexOf('?') + 1)).get('requestid'); + +describe('recordPayment → QuickBooks', () => { + it('carries a requestid derived from the OI record, not a random uuid', async () => { + const qbo = new ProbeQbo(); + await qbo.recordPayment('t1', 'inv-abc12345', 450, 'pay-inv-abc12345'); + + expect(qbo.calls).toHaveLength(1); + expect(qbo.calls[0].path.startsWith('payment')).toBe(true); + expect(requestIdOf(qbo.calls[0].path)).toBe('pay-inv-abc12345'); + }); + + it('sends the same key twice for the same fact — QBO collapses the second', async () => { + const qbo = new ProbeQbo(); + await qbo.recordPayment('t1', 'inv-abc12345', 450, 'pay-inv-abc12345'); + await qbo.recordPayment('t1', 'inv-abc12345', 450, 'pay-inv-abc12345'); + + expect(qbo.calls).toHaveLength(2); // both attempted + expect(new Set(qbo.calls.map((c) => requestIdOf(c.path))).size).toBe(1); // one key + }); + + it('still posts the amount and the invoice link', async () => { + const qbo = new ProbeQbo(); + await qbo.recordPayment('t1', 'inv-1', 450, 'pay-inv-1'); + + const body = qbo.calls[0].body as { + TotalAmt: number; CustomerRef: { value: string }; + Line: Array<{ Amount: number; LinkedTxn: Array<{ TxnId: string; TxnType: string }> }>; + }; + expect(body.TotalAmt).toBe(450); + expect(body.CustomerRef.value).toBe('QBO-CUST-9'); + expect(body.Line[0].LinkedTxn[0]).toEqual({ TxnId: 'QBO-INV-1', TxnType: 'Invoice' }); + }); + + it('pushes nothing when the invoice has no QBO mapping', async () => { + const qbo = new ProbeQbo(null); + await qbo.recordPayment('t1', 'inv-1', 450, 'pay-inv-1'); + expect(qbo.calls).toHaveLength(0); + }); +}); + +// --- the Stripe webhook must reach it at all ------------------------------ + +const SIG = { 'stripe-signature': 't=1,v1=x' }; +const KEYS = { STRIPE_SECRET_KEY: 'sk_test_1', STRIPE_WEBHOOK_SECRET: 'whsec_1' }; +const SETTLED = { + type: 'payment_intent.succeeded', + data: { object: { metadata: { invoiceId: 'inv-1', tenantId: 'tA', inspectionId: 'insp1' } } }, +}; + +function makeApp(opts: { + env?: Record; + recordPayment?: ReturnType; + findById?: ReturnType; +} = {}) { + const kv = { get: vi.fn().mockResolvedValue(null), put: vi.fn() }; + const settled: Promise[] = []; + const app = new Hono(); + app.use('*', async (c, next) => { + c.set('tenantId' as never, 'tA' as never); + (c as { env: Record }).env = { TENANT_CACHE: kv, ...KEYS, ...(opts.env ?? {}) }; + c.set('services' as never, { + invoice: { + markPaid: vi.fn().mockResolvedValue(undefined), + findById: opts.findById ?? vi.fn().mockResolvedValue({ id: 'inv-1', amountCents: 45000 }), + }, + inspection: { markPaymentReceived: vi.fn().mockResolvedValue(undefined) }, + qbo: { recordPayment: opts.recordPayment ?? vi.fn().mockResolvedValue(undefined) }, + } as never); + Object.defineProperty(c, 'executionCtx', { + value: { waitUntil: (p: Promise) => { settled.push(p); } }, + configurable: true, + }); + await next(); + }); + app.route('/', stripeWebhookApi); + return { app, settled }; +} + +beforeEach(() => { verifyWebhook.mockReset(); }); + +describe('a card payment reaches QuickBooks', () => { + it('pushes the payment when Stripe settles an invoice', async () => { + verifyWebhook.mockResolvedValue(SETTLED); + const recordPayment = vi.fn().mockResolvedValue(undefined); + const { app, settled } = makeApp({ env: { QBO_CLIENT_ID: 'qbo-client' }, recordPayment }); + + const res = await app.request('/', { method: 'POST', headers: SIG, body: '{}' }); + await Promise.all(settled); + + expect(res.status).toBe(200); + expect(recordPayment).toHaveBeenCalledWith('tA', 'inv-1', 450, 'pay-inv-1'); + }); + + it('does not push when QuickBooks is not connected', async () => { + verifyWebhook.mockResolvedValue(SETTLED); + const recordPayment = vi.fn(); + const { app, settled } = makeApp({ recordPayment }); + + await app.request('/', { method: 'POST', headers: SIG, body: '{}' }); + await Promise.all(settled); + + expect(recordPayment).not.toHaveBeenCalled(); + }); + + it('never fails the webhook when QuickBooks is down', async () => { + // The customer has already paid. A QuickBooks outage returning 500 here + // would make Stripe redeliver forever against a payment that succeeded. + verifyWebhook.mockResolvedValue(SETTLED); + const recordPayment = vi.fn().mockRejectedValue(new Error('QBO 503')); + const { app, settled } = makeApp({ env: { QBO_CLIENT_ID: 'qbo-client' }, recordPayment }); + + const res = await app.request('/', { method: 'POST', headers: SIG, body: '{}' }); + await Promise.allSettled(settled); + + expect(res.status).toBe(200); + }); + + it('pushes nothing when the invoice cannot be read', async () => { + verifyWebhook.mockResolvedValue(SETTLED); + const recordPayment = vi.fn(); + const findById = vi.fn().mockResolvedValue(null); + const { app, settled } = makeApp({ env: { QBO_CLIENT_ID: 'qbo-client' }, recordPayment, findById }); + + const res = await app.request('/', { method: 'POST', headers: SIG, body: '{}' }); + await Promise.all(settled); + + expect(res.status).toBe(200); + expect(recordPayment).not.toHaveBeenCalled(); + }); +}); From 53a1d40112dd6bf0aecabbdea3e8fb02e9650a5a Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 22:41:50 +0800 Subject: [PATCH 08/29] feat(services): seed the service catalogue, and give a service its visits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production had 43 event types, 7 templates and ZERO services. Service lines can only be attached from a catalogue, so an empty catalogue is why no inspection had ever carried one — a product gap, not slow adoption. A tenant had a "Sewer Scope" template and a sewer scope event type and no sewer scope they could sell. Three corrections to the plan, all found by checking code before writing any: 1. `services.price` is NOT NULL, and `inspection_services.price_snapshot` copies it. "Seed the entry and leave price null" was never available. These are real starting prices the tenant is expected to edit, which is a deliberate trade against the alternative — making the column nullable would have put a table rebuild and a null path through getEffectivePriceCents, the booking page and the order picker on the critical path of three dependent plans. 2. There were TWO event-type seed lists and they disagreed. This one seeded three `starter_*` types at provisioning; server/data/event-type-seeds.ts held five more behind a manual endpoint, despite its own header claiming they were "seeded on every new tenant" — bulkSeed had exactly one caller and it was a button. `starter_sewer_scope` and `sewer_scope` were one real-world thing under two slugs, and a tenant who provisioned and then pressed the button got both. Merged into one list; bulkSeed now reads it too and is a gap-filling repair tool rather than a second source of truth. 3. Radon's two event types were not seeded at provisioning at all, so a service referencing them by id would have pointed at rows that did not exist. Hence slugs, not ids, for `services.default_event_type_slugs`: they do not depend on seed ordering inside one run, they survive a tenant deleting and re-creating a type, and an unmatched slug degrades to a shorter proposal instead of dangling. `proposeEventsForService` returns visits in the service's declared order — a radon pickup proposed before its drop-off is a nonsense sequence, and the order is the only thing carrying that meaning. Also guards deleteTemplate against `services.template_id`, the second foreign key to that table, which was unchecked. Latent only while the catalogue was empty; seeding it ends that. The error names the service, because "Conflict" with no subject sends a tenant hunting through their inspections for a reference that is in their catalogue. A soft-deleted service still blocks — deleteService leaves template_id in place, so the FK outlives the delete the tenant thinks they did, and a guard that filtered on `active` would hand them the raw FK error instead. The seeding and the batchInsert helper move into their own modules to stay under the file-size ratchet; extracting them was preferable to bumping the baseline, and seedServices reads better as a named unit than as an inline block anyway. Migration 0025 is a single appended ADD COLUMN, no table rebuild. db:check green, 86 tables. Two pre-existing specs updated for the merged event-type list. --- migrations/0025_hot_venom.sql | 1 + migrations/meta/0025_snapshot.json | 10126 ++++++++++++++++ migrations/meta/_journal.json | 7 + server/data/event-type-seeds.ts | 10 - server/lib/db/schema/inspection/services.ts | 13 + server/services/event.service.ts | 7 +- server/services/service.service.ts | 30 +- server/services/starter-content.service.ts | 46 +- .../services/starter-content/batch-insert.ts | 32 + .../starter-content/fixtures/event-types.ts | 59 +- .../starter-content/fixtures/services.ts | 104 + .../services/starter-content/seed-services.ts | 59 + server/services/template.service.ts | 21 +- tests/unit/calendar/event.service.spec.ts | 20 +- .../service-visit-proposal.spec.ts | 104 + .../starter-content-services.spec.ts | 125 + .../starter-content.service.spec.ts | 5 +- .../templates/template-delete-guard.spec.ts | 85 + 18 files changed, 10795 insertions(+), 59 deletions(-) create mode 100644 migrations/0025_hot_venom.sql create mode 100644 migrations/meta/0025_snapshot.json delete mode 100644 server/data/event-type-seeds.ts create mode 100644 server/services/starter-content/batch-insert.ts create mode 100644 server/services/starter-content/fixtures/services.ts create mode 100644 server/services/starter-content/seed-services.ts create mode 100644 tests/unit/inspections/service-visit-proposal.spec.ts create mode 100644 tests/unit/integrations/starter-content-services.spec.ts create mode 100644 tests/unit/templates/template-delete-guard.spec.ts diff --git a/migrations/0025_hot_venom.sql b/migrations/0025_hot_venom.sql new file mode 100644 index 000000000..e732368cf --- /dev/null +++ b/migrations/0025_hot_venom.sql @@ -0,0 +1 @@ +ALTER TABLE `services` ADD `default_event_type_slugs` text; \ No newline at end of file diff --git a/migrations/meta/0025_snapshot.json b/migrations/meta/0025_snapshot.json new file mode 100644 index 000000000..c1557aaf2 --- /dev/null +++ b/migrations/meta/0025_snapshot.json @@ -0,0 +1,10126 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "8ea55fc4-03d1-47a8-8359-46a911957fb9", + "prevId": "f4a25042-4ded-529b-8ffc-ae3883d3ac0b", + "tables": { + "agreement_requests": { + "name": "agreement_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signature_base64": { + "name": "inspector_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signed_at": { + "name": "inspector_signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_user_id": { + "name": "inspector_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_snapshot": { + "name": "content_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agreement_requests_token_unique": { + "name": "agreement_requests_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_agreement_requests_verify_token": { + "name": "idx_agreement_requests_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + }, + "idx_agreement_requests_tenant": { + "name": "idx_agreement_requests_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_agreement_requests_inspection": { + "name": "idx_agreement_requests_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_agreement_requests_token_hash": { + "name": "idx_agreement_requests_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agreement_requests_tenant_id_tenants_id_fk": { + "name": "agreement_requests_tenant_id_tenants_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspection_id_inspections_id_fk": { + "name": "agreement_requests_inspection_id_inspections_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_agreement_id_agreements_id_fk": { + "name": "agreement_requests_agreement_id_agreements_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspector_user_id_users_id_fk": { + "name": "agreement_requests_inspector_user_id_users_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "users", + "columnsFrom": [ + "inspector_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreement_signers": { + "name": "agreement_signers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_of": { + "name": "on_behalf_of", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_disclaimer": { + "name": "on_behalf_disclaimer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reminded_at": { + "name": "last_reminded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_agreement_signers_tenant_request": { + "name": "idx_agreement_signers_tenant_request", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": false + }, + "idx_agreement_signers_request_email": { + "name": "idx_agreement_signers_request_email", + "columns": [ + "request_id", + "email" + ], + "isUnique": true + }, + "idx_agreement_signers_token_hash": { + "name": "idx_agreement_signers_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreements": { + "name": "agreements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agreements_tenant": { + "name": "idx_agreements_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agreements_tenant_id_tenants_id_fk": { + "name": "agreements_tenant_id_tenants_id_fk", + "tableFrom": "agreements", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_logs": { + "name": "automation_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_key": { + "name": "recipient_role_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_contact_id": { + "name": "recipient_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notice_id": { + "name": "notice_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_logs_pending": { + "name": "idx_automation_logs_pending", + "columns": [ + "tenant_id", + "status", + "send_at" + ], + "isUnique": false + }, + "idx_automation_logs_insp": { + "name": "idx_automation_logs_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_automation_logs_event": { + "name": "uq_automation_logs_event", + "columns": [ + "automation_id", + "inspection_id", + "event_id", + "channel", + "recipient" + ], + "isUnique": true, + "where": "event_id IS NOT NULL" + } + }, + "foreignKeys": { + "automation_logs_tenant_id_tenants_id_fk": { + "name": "automation_logs_tenant_id_tenants_id_fk", + "tableFrom": "automation_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_kind": { + "name": "recipient_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_profile_id": { + "name": "recipient_role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delay_minutes": { + "name": "delay_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conditions": { + "name": "conditions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"email\"]'" + }, + "sms_body": { + "name": "sms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "in_app_template_id": { + "name": "in_app_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automations_tenant": { + "name": "idx_automations_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_tenant_id_tenants_id_fk": { + "name": "automations_tenant_id_tenants_id_fk", + "tableFrom": "automations", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability": { + "name": "availability", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "day_of_week": { + "name": "day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_availability_inspector": { + "name": "idx_availability_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_availability_window_unique": { + "name": "idx_availability_window_unique", + "columns": [ + "inspector_id", + "day_of_week", + "start_time" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_tenant_id_tenants_id_fk": { + "name": "availability_tenant_id_tenants_id_fk", + "tableFrom": "availability", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_inspector_id_users_id_fk": { + "name": "availability_inspector_id_users_id_fk", + "tableFrom": "availability", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability_overrides": { + "name": "availability_overrides", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_available": { + "name": "is_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transparency": { + "name": "transparency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_avail_overrides_insp": { + "name": "idx_avail_overrides_insp", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_avail_overrides_block_unique": { + "name": "idx_avail_overrides_block_unique", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": true, + "where": "is_available = 0 AND source IS NULL" + }, + "uq_avail_overrides_external": { + "name": "uq_avail_overrides_external", + "columns": [ + "inspector_id", + "source", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_overrides_tenant_id_tenants_id_fk": { + "name": "availability_overrides_tenant_id_tenants_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_overrides_inspector_id_users_id_fk": { + "name": "availability_overrides_inspector_id_users_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_blocks": { + "name": "calendar_blocks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_all_day": { + "name": "is_all_day", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_calendar_blocks_tenant_user_date": { + "name": "idx_calendar_blocks_tenant_user_date", + "columns": [ + "tenant_id", + "user_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connection_read_calendars": { + "name": "calendar_connection_read_calendars", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_calendar_id": { + "name": "external_calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_role": { + "name": "access_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_conn_read_cal": { + "name": "uq_conn_read_cal", + "columns": [ + "connection_id", + "external_calendar_id" + ], + "isUnique": true + }, + "idx_conn_read_cal_tenant": { + "name": "idx_conn_read_cal_tenant", + "columns": [ + "tenant_id", + "connection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connections": { + "name": "calendar_connections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_dek_enc": { + "name": "credentials_dek_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_calendar_connections_user_provider": { + "name": "uq_calendar_connections_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": true + }, + "idx_calendar_connections_tenant_user": { + "name": "idx_calendar_connections_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comment_usage": { + "name": "comment_usage", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_id": { + "name": "comment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_comment_usage_user_last_used": { + "name": "idx_comment_usage_user_last_used", + "columns": [ + "tenant_id", + "user_id", + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comment_usage_comment_id_comments_id_fk": { + "name": "comment_usage_comment_id_comments_id_fk", + "tableFrom": "comment_usage", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_usage_tenant_id_user_id_comment_id_pk": { + "columns": [ + "tenant_id", + "user_id", + "comment_id" + ], + "name": "comment_usage_tenant_id_user_id_comment_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_bucket": { + "name": "rating_bucket", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section": { + "name": "section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_ids": { + "name": "section_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_labels": { + "name": "item_labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_code": { + "name": "trigger_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_keywords": { + "name": "search_keywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repair_summary": { + "name": "repair_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_min_cents": { + "name": "estimate_min_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_max_cents": { + "name": "estimate_max_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recommended_contractor_type_id": { + "name": "recommended_contractor_type_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_comments_tenant": { + "name": "idx_comments_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_comments_rating_bucket": { + "name": "idx_comments_rating_bucket", + "columns": [ + "tenant_id", + "rating_bucket" + ], + "isUnique": false + }, + "idx_comments_library_id": { + "name": "idx_comments_library_id", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comments_tenant_id_tenants_id_fk": { + "name": "comments_tenant_id_tenants_id_fk", + "tableFrom": "comments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "commercial_subtypes": { + "name": "commercial_subtypes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_disabled": { + "name": "is_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_commercial_subtypes_tenant_name": { + "name": "idx_commercial_subtypes_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "commercial_subtypes_tenant_id_tenants_id_fk": { + "name": "commercial_subtypes_tenant_id_tenants_id_fk", + "tableFrom": "commercial_subtypes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "concierge_confirm_tokens": { + "name": "concierge_confirm_tokens", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_concierge_tokens_expiry": { + "name": "idx_concierge_tokens_expiry", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_concierge_confirm_token_hash": { + "name": "idx_concierge_confirm_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "concierge_confirm_tokens_inspection_id_inspections_id_fk": { + "name": "concierge_confirm_tokens_inspection_id_inspections_id_fk", + "tableFrom": "concierge_confirm_tokens", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_role_profiles": { + "name": "contact_role_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability_overrides": { + "name": "capability_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_crp_tenant": { + "name": "idx_crp_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_crp_tenant_key": { + "name": "uq_crp_tenant_key", + "columns": [ + "tenant_id", + "key" + ], + "isUnique": true, + "where": "is_active = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agency": { + "name": "agency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_user_id": { + "name": "agent_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_linked_at": { + "name": "agent_linked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_revoked_at": { + "name": "agent_revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_contacts_type": { + "name": "idx_contacts_type", + "columns": [ + "tenant_id", + "type" + ], + "isUnique": false + }, + "idx_contacts_tenant": { + "name": "idx_contacts_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_contacts_tenant_email": { + "name": "uq_contacts_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "email IS NOT NULL AND archived_at IS NULL" + }, + "uq_contacts_tenant_agent_user": { + "name": "uq_contacts_tenant_agent_user", + "columns": [ + "tenant_id", + "agent_user_id" + ], + "isUnique": true, + "where": "agent_user_id IS NOT NULL AND archived_at IS NULL" + }, + "idx_contacts_agent_user": { + "name": "idx_contacts_agent_user", + "columns": [ + "agent_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_tenant_id_tenants_id_fk": { + "name": "contacts_tenant_id_tenants_id_fk", + "tableFrom": "contacts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contractor_types": { + "name": "contractor_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_contractor_types_tenant": { + "name": "idx_contractor_types_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_items": { + "name": "cost_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "building_id": { + "name": "building_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_index": { + "name": "instance_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_id": { + "name": "unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_method": { + "name": "cost_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uom": { + "name": "uom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_cost_cents": { + "name": "unit_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lump_sum_cents": { + "name": "lump_sum_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eul": { + "name": "eul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_age": { + "name": "eff_age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rul": { + "name": "rul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suggested_remedy": { + "name": "suggested_remedy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_ref": { + "name": "section_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_ref": { + "name": "photo_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_cost_items_tenant_inspection": { + "name": "idx_cost_items_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_cost_items_finding_key": { + "name": "idx_cost_items_finding_key", + "columns": [ + "finding_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "defect_categories": { + "name": "defect_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6b7280'" + }, + "is_summary_driver": { + "name": "is_summary_driver", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_defect_categories_tenant": { + "name": "idx_defect_categories_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "discount_codes": { + "name": "discount_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uses_count": { + "name": "uses_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_discount_codes_tenant": { + "name": "idx_discount_codes_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_discount_codes_code_tenant": { + "name": "uq_discount_codes_code_tenant", + "columns": [ + "upper(code)", + "tenant_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "discount_codes_tenant_id_tenants_id_fk": { + "name": "discount_codes_tenant_id_tenants_id_fk", + "tableFrom": "discount_codes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "document_review_items": { + "name": "document_review_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_requested": { + "name": "is_requested", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_received": { + "name": "is_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_reviewed": { + "name": "is_reviewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_na": { + "name": "is_na", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_doc_review_inspection": { + "name": "idx_doc_review_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_doc_review_item": { + "name": "uq_doc_review_item", + "columns": [ + "inspection_id", + "document_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_suppressions": { + "name": "email_suppressions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_provider": { + "name": "source_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_email_suppressions_email": { + "name": "idx_email_suppressions_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "erasure_log": { + "name": "erasure_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_email": { + "name": "subject_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_basis": { + "name": "identity_basis", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "decisions_json": { + "name": "decisions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retained_count": { + "name": "retained_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "anonymized_count": { + "name": "anonymized_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_count": { + "name": "deleted_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "response_note": { + "name": "response_note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_erasure_log_tenant": { + "name": "idx_erasure_log_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "esign_audit_logs": { + "name": "esign_audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_esign_audit_logs_request": { + "name": "idx_esign_audit_logs_request", + "columns": [ + "tenant_id", + "request_id", + "created_at" + ], + "isUnique": false + }, + "idx_esign_audit_logs_event_dedup": { + "name": "idx_esign_audit_logs_event_dedup", + "columns": [ + "tenant_id", + "request_id", + "event" + ], + "isUnique": true, + "where": "event NOT LIKE 'signer.%'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "event_types": { + "name": "event_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_duration_min": { + "name": "default_duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "default_price_cents": { + "name": "default_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6366f1'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_event_types_tenant_slug": { + "name": "uq_event_types_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "event_types_tenant_id_tenants_id_fk": { + "name": "event_types_tenant_id_tenants_id_fk", + "tableFrom": "event_types", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_access_tokens": { + "name": "inspection_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_iat_token": { + "name": "idx_iat_token", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_iat_inspection": { + "name": "idx_iat_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_iat_recipient": { + "name": "idx_iat_recipient", + "columns": [ + "inspection_id", + "recipient_email" + ], + "isUnique": true + }, + "idx_iat_token_hash": { + "name": "idx_iat_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_access_tokens_tenant_id_tenants_id_fk": { + "name": "inspection_access_tokens_tenant_id_tenants_id_fk", + "tableFrom": "inspection_access_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_events": { + "name": "inspection_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type_id": { + "name": "event_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'scheduled'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "results_received_at": { + "name": "results_received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gcal_event_id": { + "name": "gcal_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_events_scheduled": { + "name": "idx_inspection_events_scheduled", + "columns": [ + "tenant_id", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_events_inspection": { + "name": "idx_inspection_events_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_events_tenant_id_tenants_id_fk": { + "name": "inspection_events_tenant_id_tenants_id_fk", + "tableFrom": "inspection_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspection_id_inspections_id_fk": { + "name": "inspection_events_inspection_id_inspections_id_fk", + "tableFrom": "inspection_events", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_events_event_type_id_event_types_id_fk": { + "name": "inspection_events_event_type_id_event_types_id_fk", + "tableFrom": "inspection_events", + "tableTo": "event_types", + "columnsFrom": [ + "event_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspector_id_users_id_fk": { + "name": "inspection_events_inspector_id_users_id_fk", + "tableFrom": "inspection_events", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_inspectors": { + "name": "inspection_inspectors", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lead'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_inspectors_tenant_user": { + "name": "idx_insp_inspectors_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "idx_insp_inspectors_user": { + "name": "idx_insp_inspectors_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_inspectors_inspection_id_user_id_pk": { + "columns": [ + "inspection_id", + "user_id" + ], + "name": "inspection_inspectors_inspection_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_item_tag_links": { + "name": "inspection_item_tag_links", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_links_tenant": { + "name": "idx_tag_links_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_tag_links_tag": { + "name": "idx_tag_links_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + }, + "idx_tag_links_inspection_item": { + "name": "idx_tag_links_inspection_item", + "columns": [ + "inspection_id", + "item_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_item_tag_links_inspection_id_item_id_tag_id_pk": { + "columns": [ + "inspection_id", + "item_id", + "tag_id" + ], + "name": "inspection_item_tag_links_inspection_id_item_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_media_pool": { + "name": "inspection_media_pool", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exif_data": { + "name": "exif_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "caption": { + "name": "caption", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'photo'" + }, + "stream_uid": { + "name": "stream_uid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "poster_pct": { + "name": "poster_pct", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_sec": { + "name": "duration_sec", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'stream'" + }, + "poster_key": { + "name": "poster_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_media_pool_tenant": { + "name": "idx_media_pool_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_media_pool_inspection": { + "name": "idx_media_pool_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_messages": { + "name": "inspection_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_role": { + "name": "from_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_user_id": { + "name": "from_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_msg_inspection": { + "name": "idx_msg_inspection", + "columns": [ + "inspection_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_contact": { + "name": "idx_msg_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_unread": { + "name": "idx_msg_unread", + "columns": [ + "tenant_id", + "contact_id", + "from_role" + ], + "isUnique": false, + "where": "\"inspection_messages\".\"read_at\" IS NULL" + } + }, + "foreignKeys": { + "inspection_messages_tenant_id_tenants_id_fk": { + "name": "inspection_messages_tenant_id_tenants_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_messages_inspection_id_inspections_id_fk": { + "name": "inspection_messages_inspection_id_inspections_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_people": { + "name": "inspection_people", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_profile_id": { + "name": "role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_ip_inspection": { + "name": "idx_ip_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_ip_tenant": { + "name": "idx_ip_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_ip_insp_contact_role": { + "name": "uq_ip_insp_contact_role", + "columns": [ + "inspection_id", + "contact_id", + "role_profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_requests": { + "name": "inspection_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_phone": { + "name": "client_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "property_city": { + "name": "property_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_state": { + "name": "property_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_zip": { + "name": "property_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_requests_tenant": { + "name": "idx_inspection_requests_tenant", + "columns": [ + "tenant_id", + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_requests_email": { + "name": "idx_inspection_requests_email", + "columns": [ + "tenant_id", + "client_email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_requests_tenant_id_tenants_id_fk": { + "name": "inspection_requests_tenant_id_tenants_id_fk", + "tableFrom": "inspection_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_results": { + "name": "inspection_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ydoc_state": { + "name": "ydoc_state", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_system_snapshot": { + "name": "rating_system_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_results_tenant": { + "name": "idx_results_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_results_inspection": { + "name": "idx_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_results_inspection": { + "name": "uq_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_results_tenant_id_tenants_id_fk": { + "name": "inspection_results_tenant_id_tenants_id_fk", + "tableFrom": "inspection_results", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_results_inspection_id_inspections_id_fk": { + "name": "inspection_results_inspection_id_inspections_id_fk", + "tableFrom": "inspection_results", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_services": { + "name": "inspection_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_override_cents": { + "name": "price_override_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_snapshot": { + "name": "name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_snapshot_cents": { + "name": "price_snapshot_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_services_tenant": { + "name": "idx_insp_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_insp_services_insp": { + "name": "idx_insp_services_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_services_tenant_id_tenants_id_fk": { + "name": "inspection_services_tenant_id_tenants_id_fk", + "tableFrom": "inspection_services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_services_inspection_id_inspections_id_fk": { + "name": "inspection_services_inspection_id_inspections_id_fk", + "tableFrom": "inspection_services", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_services_service_id_services_id_fk": { + "name": "inspection_services_service_id_services_id_fk", + "tableFrom": "inspection_services", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_types": { + "name": "inspection_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_types_tenant_name": { + "name": "idx_inspection_types_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_units": { + "name": "inspection_units", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_unit_id": { + "name": "parent_unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unit'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "attrs": { + "name": "attrs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_units_tenant_inspection": { + "name": "idx_inspection_units_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_inspection_units_parent": { + "name": "idx_inspection_units_parent", + "columns": [ + "parent_unit_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspections": { + "name": "inspections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_place_id": { + "name": "address_place_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_county": { + "name": "address_county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lat": { + "name": "address_lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lng": { + "name": "address_lng", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_geocoded_at": { + "name": "address_geocoded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'requested'" + }, + "report_status": { + "name": "report_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_notes": { + "name": "cancel_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_payment_required": { + "name": "is_payment_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_agreement_required": { + "name": "is_agreement_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_auto_sign_on_publish": { + "name": "is_auto_sign_on_publish", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "discount_code_id": { + "name": "discount_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discount_amount_cents": { + "name": "discount_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closing_date": { + "name": "closing_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referral_source": { + "name": "referral_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_notes": { + "name": "internal_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sqft": { + "name": "sqft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "foundation_type": { + "name": "foundation_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrooms": { + "name": "bedrooms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bathrooms": { + "name": "bathrooms", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lot_size": { + "name": "lot_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_facts": { + "name": "property_facts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_photo_id": { + "name": "cover_photo_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_crop": { + "name": "cover_crop", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_image_key": { + "name": "cover_image_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_tier": { + "name": "report_tier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_automations_disabled": { + "name": "is_automations_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "template_snapshot": { + "name": "template_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_snapshot_version": { + "name": "template_snapshot_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "profile_override": { + "name": "profile_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_defect_fields_override": { + "name": "require_defect_fields_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "concierge_status": { + "name": "concierge_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_team_mode": { + "name": "is_team_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "lead_inspector_id": { + "name": "lead_inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "helper_inspector_ids": { + "name": "helper_inspector_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "data_version": { + "name": "data_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source_inspection_id": { + "name": "source_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_inspection_id": { + "name": "root_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reinspection_round": { + "name": "reinspection_round", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_inspection_mode": { + "name": "unit_inspection_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'tagged'" + }, + "location_options": { + "name": "location_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sampling_declaration": { + "name": "sampling_declaration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pca_narrative": { + "name": "pca_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviations": { + "name": "deviations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_mode": { + "name": "report_photo_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_start_ms": { + "name": "scheduled_start_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_end_ms": { + "name": "scheduled_end_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "badge_layout_override": { + "name": "badge_layout_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_columns": { + "name": "report_photo_columns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referred_by_contact_id": { + "name": "referred_by_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspections_tenant": { + "name": "idx_inspections_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspections_request": { + "name": "idx_inspections_request", + "columns": [ + "request_id" + ], + "isUnique": false + }, + "idx_inspections_inspector": { + "name": "idx_inspections_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_inspections_tenant_status": { + "name": "idx_inspections_tenant_status", + "columns": [ + "tenant_id", + "status" + ], + "isUnique": false + }, + "idx_inspections_tenant_date": { + "name": "idx_inspections_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_inspector_date": { + "name": "idx_inspections_inspector_date", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_root": { + "name": "idx_inspections_root", + "columns": [ + "root_inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspections_tenant_id_tenants_id_fk": { + "name": "inspections_tenant_id_tenants_id_fk", + "tableFrom": "inspections", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_inspector_id_users_id_fk": { + "name": "inspections_inspector_id_users_id_fk", + "tableFrom": "inspections", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_template_id_templates_id_fk": { + "name": "inspections_template_id_templates_id_fk", + "tableFrom": "inspections", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_discount_code_id_discount_codes_id_fk": { + "name": "inspections_discount_code_id_discount_codes_id_fk", + "tableFrom": "inspections", + "tableTo": "discount_codes", + "columnsFrom": [ + "discount_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_request_id_inspection_requests_id_fk": { + "name": "inspections_request_id_inspection_requests_id_fk", + "tableFrom": "inspections", + "tableTo": "inspection_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspector_credentials": { + "name": "inspector_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_r2_key": { + "name": "image_r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspector_credentials_tenant": { + "name": "idx_inspector_credentials_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspector_credentials_user": { + "name": "idx_inspector_credentials_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "partial_paid_at": { + "name": "partial_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voided_at": { + "name": "voided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qbo_sync_status": { + "name": "qbo_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + } + }, + "indexes": { + "idx_invoices_tenant": { + "name": "idx_invoices_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_invoices_inspection": { + "name": "idx_invoices_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_invoices_contact": { + "name": "idx_invoices_contact", + "columns": [ + "tenant_id", + "contact_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_tenant_id_tenants_id_fk": { + "name": "invoices_tenant_id_tenants_id_fk", + "tableFrom": "invoices", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_inspection_id_inspections_id_fk": { + "name": "invoices_inspection_id_inspections_id_fk", + "tableFrom": "invoices", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_contact_id_contacts_id_fk": { + "name": "invoices_contact_id_contacts_id_fk", + "tableFrom": "invoices", + "tableTo": "contacts", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_libraries": { + "name": "marketplace_libraries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_libraries_kind_featured": { + "name": "idx_marketplace_libraries_kind_featured", + "columns": [ + "kind", + "is_featured" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_templates": { + "name": "marketplace_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_templates": { + "name": "message_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "variables": { + "name": "variables", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seeded": { + "name": "is_seeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_templates_tenant_channel": { + "name": "idx_message_templates_tenant_channel", + "columns": [ + "tenant_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messaging_compliance": { + "name": "messaging_compliance", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'own'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subaccount_sid": { + "name": "subaccount_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_sid": { + "name": "customer_profile_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_status": { + "name": "customer_profile_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_sid": { + "name": "brand_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_status": { + "name": "brand_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_sid": { + "name": "campaign_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_status": { + "name": "campaign_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_sid": { + "name": "tfv_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_status": { + "name": "tfv_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "messaging_resource_sid": { + "name": "messaging_resource_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_meta": { + "name": "provider_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number": { + "name": "provisioned_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number_sid": { + "name": "provisioned_number_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_sender_attached": { + "name": "has_sender_attached", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "compliance_status": { + "name": "compliance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_preferences": { + "name": "notification_preferences", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "class_id": { + "name": "class_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notification_prefs_unique": { + "name": "idx_notification_prefs_unique", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "class_id", + "channel" + ], + "isUnique": true + }, + "idx_notification_prefs_subject": { + "name": "idx_notification_prefs_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orphaned_media": { + "name": "orphaned_media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_orphaned_media_key": { + "name": "idx_orphaned_media_key", + "columns": [ + "tenant_id", + "r2_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_webhook_events": { + "name": "processed_webhook_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "psq_responses": { + "name": "psq_responses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sent'" + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_psq_inspection": { + "name": "uq_psq_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": true + }, + "idx_psq_share_token": { + "name": "idx_psq_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_connections": { + "name": "qbo_connections", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "realm_id": { + "name": "realm_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_enc": { + "name": "access_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_enc": { + "name": "refresh_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "default_item_id": { + "name": "default_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_entity_map": { + "name": "qbo_entity_map", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_type": { + "name": "qbo_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_id": { + "name": "qbo_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_sync_token": { + "name": "qbo_sync_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_qbo_entity_map_qbo": { + "name": "idx_qbo_entity_map_qbo", + "columns": [ + "tenant_id", + "qbo_type", + "qbo_id" + ], + "isUnique": true + }, + "idx_qbo_entity_map_oi": { + "name": "idx_qbo_entity_map_oi", + "columns": [ + "tenant_id", + "oi_type", + "oi_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_sync_errors": { + "name": "qbo_sync_errors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_resolved": { + "name": "is_resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rating_systems": { + "name": "rating_systems", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "levels": { + "name": "levels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_rating_systems_tenant_slug": { + "name": "idx_rating_systems_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_rating_systems_tenant": { + "name": "idx_rating_systems_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rating_systems_tenant_id_tenants_id_fk": { + "name": "rating_systems_tenant_id_tenants_id_fk", + "tableFrom": "rating_systems", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_request_items": { + "name": "repair_request_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repair_request_id": { + "name": "repair_request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_title": { + "name": "section_title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_snapshot": { + "name": "comment_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_credit_cents": { + "name": "requested_credit_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "defect_title_snapshot": { + "name": "defect_title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_snapshot": { + "name": "location_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_snapshot": { + "name": "category_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_snapshot": { + "name": "trade_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_request_items_rr": { + "name": "idx_repair_request_items_rr", + "columns": [ + "repair_request_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_requests": { + "name": "repair_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_ref": { + "name": "created_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_intro": { + "name": "custom_intro", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_requests_inspection": { + "name": "idx_repair_requests_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_repair_requests_share_token": { + "name": "idx_repair_requests_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_exports": { + "name": "report_exports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_report_exports_inspection": { + "name": "idx_report_exports_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_pdfs": { + "name": "report_pdfs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rendered_at": { + "name": "rendered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ready'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_report_pdfs_inspection_type": { + "name": "uq_report_pdfs_inspection_type", + "columns": [ + "inspection_id", + "type", + "version_number" + ], + "isUnique": true + }, + "idx_report_pdfs_tenant": { + "name": "idx_report_pdfs_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_report_pdfs_status": { + "name": "idx_report_pdfs_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_report_pdfs_content_hash": { + "name": "idx_report_pdfs_content_hash", + "columns": [ + "inspection_id", + "type", + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": { + "report_pdfs_tenant_id_tenants_id_fk": { + "name": "report_pdfs_tenant_id_tenants_id_fk", + "tableFrom": "report_pdfs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_signoff": { + "name": "report_signoff", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualifications_ref": { + "name": "qualifications_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_ref": { + "name": "signature_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dual_role": { + "name": "is_dual_role", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_report_signoff_inspection": { + "name": "idx_report_signoff_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_report_signoff_role": { + "name": "uq_report_signoff_role", + "columns": [ + "inspection_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_versions": { + "name": "report_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_json": { + "name": "snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_amendment": { + "name": "is_amendment", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "idx_report_versions_inspection": { + "name": "idx_report_versions_inspection", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": false + }, + "uq_report_versions_inspection_version": { + "name": "uq_report_versions_inspection_version", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": true + }, + "idx_report_versions_verify_token": { + "name": "idx_report_versions_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_inspectors": { + "name": "service_inspectors", + "columns": { + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_service_inspectors_tenant": { + "name": "idx_service_inspectors_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "service_inspectors_service_id_user_id_pk": { + "columns": [ + "service_id", + "user_id" + ], + "name": "service_inspectors_service_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_event_type_slugs": { + "name": "default_event_type_slugs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_services_tenant": { + "name": "idx_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "services_tenant_id_tenants_id_fk": { + "name": "services_tenant_id_tenants_id_fk", + "tableFrom": "services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_template_id_templates_id_fk": { + "name": "services_template_id_templates_id_fk", + "tableFrom": "services", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_agreement_id_agreements_id_fk": { + "name": "services_agreement_id_agreements_id_fk", + "tableFrom": "services", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_enc": { + "name": "private_key_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Ed25519'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_consent_log": { + "name": "sms_consent_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disclosure_version": { + "name": "disclosure_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "captured_via": { + "name": "captured_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'contact'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": { + "idx_sms_consent_contact": { + "name": "idx_sms_consent_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_sms_consent_subject": { + "name": "idx_sms_consent_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_delivery_status": { + "name": "sms_delivery_status", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sms_delivery_status_msg": { + "name": "idx_sms_delivery_status_msg", + "columns": [ + "tenant_id", + "provider_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_disclosure_versions": { + "name": "sms_disclosure_versions", + "columns": { + "version": { + "name": "version", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tags_tenant_name": { + "name": "idx_tags_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + }, + "idx_tags_tenant": { + "name": "idx_tags_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_templates_tenant": { + "name": "idx_templates_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_templates_rating_system": { + "name": "idx_templates_rating_system", + "columns": [ + "rating_system_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "templates_tenant_id_tenants_id_fk": { + "name": "templates_tenant_id_tenants_id_fk", + "tableFrom": "templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_custom_holidays": { + "name": "tenant_custom_holidays", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_tenant_custom_holidays_tenant_date": { + "name": "uq_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": true + }, + "idx_tenant_custom_holidays_tenant_date": { + "name": "idx_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_library_imports": { + "name": "tenant_library_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "uq_tenant_library_import": { + "name": "uq_tenant_library_import", + "columns": [ + "tenant_id", + "library_id" + ], + "isUnique": true + }, + "idx_tenant_library_imports_tenant": { + "name": "idx_tenant_library_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_import_history": { + "name": "tenant_marketplace_import_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_version": { + "name": "target_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_affected": { + "name": "rows_affected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_history_tenant": { + "name": "idx_marketplace_history_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_marketplace_history_template": { + "name": "idx_marketplace_history_template", + "columns": [ + "template_id" + ], + "isUnique": false + }, + "idx_marketplace_history_library": { + "name": "idx_marketplace_history_library", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_imports": { + "name": "tenant_marketplace_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "marketplace_template_id": { + "name": "marketplace_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_template_id": { + "name": "local_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_mkt_imports_tmpl": { + "name": "idx_mkt_imports_tmpl", + "columns": [ + "marketplace_template_id" + ], + "isUnique": false + }, + "idx_mkt_imports_tenant": { + "name": "idx_mkt_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk": { + "name": "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "marketplace_templates", + "columnsFrom": [ + "marketplace_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tenant_marketplace_imports_local_template_id_templates_id_fk": { + "name": "tenant_marketplace_imports_local_template_id_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "templates", + "columnsFrom": [ + "local_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "usage_counters": { + "name": "usage_counters", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_usage_counters_tenant": { + "name": "idx_usage_counters_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_counters_tenant_id_metric_period_key_pk": { + "columns": [ + "tenant_id", + "metric", + "period_key" + ], + "name": "usage_counters_tenant_id_metric_period_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_templates": { + "name": "email_templates", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocks": { + "name": "blocks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_templates_tenant_id_tenants_id_fk": { + "name": "email_templates_tenant_id_tenants_id_fk", + "tableFrom": "email_templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_templates_tenant_id_trigger_pk": { + "columns": [ + "tenant_id", + "trigger" + ], + "name": "email_templates_tenant_id_trigger_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_configs": { + "name": "tenant_configs", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "support_email": { + "name": "support_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_address": { + "name": "company_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pdf_footer_shown": { + "name": "is_pdf_footer_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_page_numbers_shown": { + "name": "is_pdf_page_numbers_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_license_shown": { + "name": "is_pdf_license_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reply_to": { + "name": "reply_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_mode": { + "name": "email_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "video_mode": { + "name": "video_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'r2'" + }, + "sms_mode": { + "name": "sms_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "sender_display_name": { + "name": "sender_display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "point_of_contact": { + "name": "point_of_contact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company'" + }, + "billing_url": { + "name": "billing_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_url": { + "name": "review_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_phone": { + "name": "company_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "integration_config": { + "name": "integration_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secrets_enc": { + "name": "secrets_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dek_enc": { + "name": "dek_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ics_token": { + "name": "ics_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widget_allowed_origins": { + "name": "widget_allowed_origins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'signature'" + }, + "attention_thresholds": { + "name": "attention_thresholds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"agreement_unsigned_h\":72,\"invoice_overdue_h\":72,\"report_unpublished_h\":72}'" + }, + "inspection_prefs": { + "name": "inspection_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_estimates_shown": { + "name": "is_estimates_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_repair_list_enabled": { + "name": "is_repair_list_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_customer_repair_export_enabled": { + "name": "is_customer_repair_export_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unpaid_blocked": { + "name": "is_unpaid_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unsigned_agreement_blocked": { + "name": "is_unsigned_agreement_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "custom_referral_sources": { + "name": "custom_referral_sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dashboard_column_prefs": { + "name": "dashboard_column_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_concierge_review_required": { + "name": "is_concierge_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_inspector_choice_allowed": { + "name": "is_inspector_choice_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_pdf_pipeline_enabled": { + "name": "is_pdf_pipeline_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_team_mode_default": { + "name": "is_team_mode_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_apprentice_review_required": { + "name": "is_apprentice_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_guest_invites_enabled": { + "name": "is_guest_invites_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "require_defect_fields": { + "name": "require_defect_fields", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "agreement_retention_years": { + "name": "agreement_retention_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "reinspection_statuses": { + "name": "reinspection_statuses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_collab_editing_enabled": { + "name": "is_collab_editing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sms_byo_provider": { + "name": "sms_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_byo_provider": { + "name": "email_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "is_managed_eligible": { + "name": "is_managed_eligible", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "managed_provider": { + "name": "managed_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'twilio'" + }, + "is_reserve_schedule_enabled": { + "name": "is_reserve_schedule_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reserve_term_years": { + "name": "reserve_term_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "inflation_rate_bps": { + "name": "inflation_rate_bps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'UTC'" + }, + "booking_slot_mode": { + "name": "booking_slot_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "booking_slot_interval_min": { + "name": "booking_slot_interval_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "holiday_region": { + "name": "holiday_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "holiday_public_policy": { + "name": "holiday_public_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "holiday_internal_policy": { + "name": "holiday_internal_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'advisory'" + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en-US'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "is_archive_revoking_access": { + "name": "is_archive_revoking_access", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "legal_mode": { + "name": "legal_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'hosted'" + }, + "custom_privacy_url": { + "name": "custom_privacy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_terms_url": { + "name": "custom_terms_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privacy_body": { + "name": "privacy_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_body": { + "name": "terms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_configs_tenant_id_tenants_id_fk": { + "name": "tenant_configs_tenant_id_tenants_id_fk", + "tableFrom": "tenant_configs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "stripe_connect_account_id": { + "name": "stripe_connect_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "max_users": { + "name": "max_users", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'shared'" + }, + "applied_cmd_seq": { + "name": "applied_cmd_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_cred_seq": { + "name": "applied_cred_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_invites": { + "name": "tenant_invites", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inspector'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_invites_tenant": { + "name": "idx_invites_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_tenant_invites_pending_email": { + "name": "uq_tenant_invites_pending_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "status = 'pending'" + } + }, + "foreignKeys": { + "tenant_invites_tenant_id_tenants_id_fk": { + "name": "tenant_invites_tenant_id_tenants_id_fk", + "tableFrom": "tenant_invites", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_signature_base64": { + "name": "default_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_signature_enabled": { + "name": "is_signature_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manager'" + }, + "onboarding_state": { + "name": "onboarding_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_totp_enabled": { + "name": "is_totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_recovery_codes": { + "name": "totp_recovery_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_verified_at": { + "name": "totp_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_accepted": { + "name": "terms_accepted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + "deleted_at" + ], + "isUnique": false + }, + "uq_users_tenant_email": { + "name": "uq_users_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "deleted_at IS NULL" + }, + "idx_users_tenant": { + "name": "idx_users_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_users_slug_per_tenant": { + "name": "idx_users_slug_per_tenant", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_users_email": { + "name": "idx_users_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_slug": { + "name": "inspector_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_audit_tenant_created": { + "name": "idx_audit_tenant_created", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_audit_entity": { + "name": "idx_audit_entity", + "columns": [ + "entity_type", + "entity_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_tenant_id_tenants_id_fk": { + "name": "audit_logs_tenant_id_tenants_id_fk", + "tableFrom": "audit_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration_test_results": { + "name": "integration_test_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_ok": { + "name": "is_ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_by_user_id": { + "name": "tested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_at": { + "name": "tested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_integration_test_tenant_target": { + "name": "idx_integration_test_tenant_target", + "columns": [ + "tenant_id", + "target", + "tested_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_tenant_user_created": { + "name": "idx_notifications_tenant_user_created", + "columns": [ + "tenant_id", + "user_id", + "created_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_user_unread": { + "name": "idx_notifications_tenant_user_unread", + "columns": [ + "tenant_id", + "user_id", + "read_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_contact_created": { + "name": "idx_notifications_tenant_contact_created", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_tenant_id_tenants_id_fk": { + "name": "notifications_tenant_id_tenants_id_fk", + "tableFrom": "notifications", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parked_cmd_events": { + "name": "parked_cmd_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "envelope": { + "name": "envelope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_parked_cmd_events_received_at": { + "name": "idx_parked_cmd_events_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_cmd_events": { + "name": "processed_cmd_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cmd_type": { + "name": "cmd_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slug_reservations": { + "name": "slug_reservations", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_outbox": { + "name": "sync_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_tried_at": { + "name": "last_tried_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_sync_outbox_status_created": { + "name": "idx_sync_outbox_status_created", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_destruction_records": { + "name": "tenant_destruction_records", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_slug": { + "name": "tenant_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_deleted": { + "name": "rows_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_objects": { + "name": "r2_objects", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_bytes": { + "name": "r2_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "kv_keys": { + "name": "kv_keys", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_destruction_tenant": { + "name": "idx_destruction_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_destruction_destroyed_at": { + "name": "idx_destruction_destroyed_at", + "columns": [ + "destroyed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_legal_versions": { + "name": "tenant_legal_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "doc": { + "name": "doc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_material": { + "name": "is_material", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_tenant_legal_versions_doc_version": { + "name": "idx_tenant_legal_versions_doc_version", + "columns": [ + "tenant_id", + "doc", + "version" + ], + "isUnique": true + }, + "idx_tenant_legal_versions_latest": { + "name": "idx_tenant_legal_versions_latest", + "columns": [ + "tenant_id", + "doc", + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_uploads": { + "name": "client_uploads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_kind": { + "name": "uploaded_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_ref": { + "name": "uploaded_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_name": { + "name": "uploaded_by_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_client_uploads_inspection": { + "name": "idx_client_uploads_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uq_discount_codes_code_tenant": { + "columns": { + "upper(code)": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index db1383189..f8d0211c5 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -176,6 +176,13 @@ "when": 1785515515925, "tag": "0024_drop_users_license_number", "breakpoints": true + }, + { + "idx": 25, + "version": "6", + "when": 1785680855335, + "tag": "0025_hot_venom", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/data/event-type-seeds.ts b/server/data/event-type-seeds.ts deleted file mode 100644 index a453a2739..000000000 --- a/server/data/event-type-seeds.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Spec 4D — default event types seeded on every new tenant. -// Slugs are stable identifiers — never rename. Names are display-only and may be customized per tenant. - -export const EVENT_TYPE_SEEDS = [ - { slug: 'radon_dropoff', name: 'Radon Drop-off', defaultDurationMin: 15, defaultPriceCents: 0, color: '#10b981', sortOrder: 10 }, - { slug: 'radon_pickup', name: 'Radon Pickup', defaultDurationMin: 15, defaultPriceCents: 0, color: '#10b981', sortOrder: 20 }, - { slug: 'mold_test', name: 'Mold Test', defaultDurationMin: 30, defaultPriceCents: 15000, color: '#a855f7', sortOrder: 30 }, - { slug: 'water_test', name: 'Water Test', defaultDurationMin: 20, defaultPriceCents: 12500, color: '#0ea5e9', sortOrder: 40 }, - { slug: 'sewer_scope', name: 'Sewer Scope', defaultDurationMin: 60, defaultPriceCents: 25000, color: '#f59e0b', sortOrder: 50 }, -] as const; diff --git a/server/lib/db/schema/inspection/services.ts b/server/lib/db/schema/inspection/services.ts index 5cb026445..64b78ef1b 100644 --- a/server/lib/db/schema/inspection/services.ts +++ b/server/lib/db/schema/inspection/services.ts @@ -17,6 +17,19 @@ export const services = sqliteTable('services', { active: integer('is_active', { mode: 'boolean' }).notNull().default(true), sortOrder: integer('sort_order').notNull().default(0), createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), + // The visits this service implies. Radon needs two — a drop-off and a + // pickup at least 48h later — while a sewer scope needs none beyond the + // main visit. + // + // Stored as event_type SLUGS, not ids, and that is the load-bearing choice: + // slugs are stable identifiers that survive a tenant deleting and + // re-creating a type, they do not depend on seed ordering (services and + // event types are seeded in the same run), and an unmatched slug degrades + // to "propose nothing" instead of dangling. A hard FK would additionally + // block deleting an event type, which is not a trade worth making for a + // proposal. + // Appended at table end for D1 rebuild safety. + defaultEventTypeSlugs: text('default_event_type_slugs', { mode: 'json' }).$type(), }, (t) => [ index('idx_services_tenant').on(t.tenantId), ]); diff --git a/server/services/event.service.ts b/server/services/event.service.ts index 8c3425946..c64db9308 100644 --- a/server/services/event.service.ts +++ b/server/services/event.service.ts @@ -1,7 +1,7 @@ import { drizzle } from 'drizzle-orm/d1'; import { eq, and, gte, lte, asc } from 'drizzle-orm'; import { eventTypes, inspectionEvents, automations, automationLogs } from '../lib/db/schema'; -import { EVENT_TYPE_SEEDS } from '../data/event-type-seeds'; +import { EVENT_TYPES } from './starter-content/fixtures/event-types'; import { logger } from '../lib/logger'; import { PeopleService } from './people.service'; @@ -59,7 +59,10 @@ export class EventService { .where(eq(eventTypes.tenantId, tenantId)).all(); const existingSlugs = new Set(existing.map(e => e.slug as string)); let seeded = 0, skipped = 0; - for (const seed of EVENT_TYPE_SEEDS) { + // Same list provisioning uses. This endpoint is now a repair tool — + // idempotent on slug, so it fills gaps for a tenant provisioned before a + // type existed and does nothing for anyone else. + for (const seed of EVENT_TYPES) { if (existingSlugs.has(seed.slug)) { skipped++; continue; } await d.insert(eventTypes).values({ id: crypto.randomUUID(), diff --git a/server/services/service.service.ts b/server/services/service.service.ts index cab979006..515c4169a 100644 --- a/server/services/service.service.ts +++ b/server/services/service.service.ts @@ -1,6 +1,6 @@ import { drizzle } from 'drizzle-orm/d1'; import { eq, and, asc, isNull, ne, inArray, sql } from 'drizzle-orm'; -import { services, inspectionServices, discountCodes, serviceInspectors, users, inspections } from '../lib/db/schema'; +import { services, inspectionServices, discountCodes, serviceInspectors, users, inspections, eventTypes } from '../lib/db/schema'; import { Errors } from '../lib/errors'; import { nanoid } from 'nanoid'; import type { z } from 'zod'; @@ -22,6 +22,34 @@ export class ServiceService { .orderBy(asc(services.sortOrder), asc(services.name)); } + /** + * The visits a service implies, resolved from its stored event-type slugs + * and returned in the order the fixture declares them — for radon that is + * drop-off before pickup, which is the whole point. + * + * A slug with no matching event type is SKIPPED rather than erroring. That + * is the deliberate consequence of storing slugs instead of a hard + * reference: a tenant who tidies up their event types gets a shorter + * proposal, not a 500 on the order screen. Returning [] for a service with + * no defaults is the same rule with an empty list. + */ + async proposeEventsForService(tenantId: string, serviceId: string) { + const db = this.getDrizzle(); + const svc = await db.select({ slugs: services.defaultEventTypeSlugs }).from(services) + .where(and(eq(services.id, serviceId), eq(services.tenantId, tenantId))) + .get(); + const slugs = svc?.slugs ?? []; + if (slugs.length === 0) return []; + + const rows = await db.select().from(eventTypes) + .where(and(eq(eventTypes.tenantId, tenantId), inArray(eventTypes.slug, slugs))) + .all(); + const bySlug = new Map(rows.map(r => [r.slug as string, r])); + // Order comes from the service's own list, not from the query — a + // pickup proposed before its drop-off is a nonsense visit sequence. + return slugs.map(s => bySlug.get(s)).filter((r): r is NonNullable => r != null); + } + async createService(tenantId: string, data: CreateServiceData) { const db = this.getDrizzle(); const id = nanoid(); diff --git a/server/services/starter-content.service.ts b/server/services/starter-content.service.ts index b4655cca4..0324b9a6c 100644 --- a/server/services/starter-content.service.ts +++ b/server/services/starter-content.service.ts @@ -8,11 +8,12 @@ * - 3 inspection templates (Residential / Pre-Listing / Sewer Scope) * - 1 agreement template (generic pre-inspection w/ disclaimer) * - 250 canned comments (from starter-content/fixtures/canned-comments) - * - 3 event_types (Standard / Pre-Listing / Sewer Scope) + * - 7 event_types (main visits + the radon pair, mold and water tests) * - 4 tags (Safety concern / Needs maintenance / Cosmetic / Follow-up needed) * - 80 recommendations (from server/data/recommendation-seeds.ts) * - 4 rating systems (from server/data/rating-system-seeds.ts) * - 10 contractor types (standard repair-contractor taxonomy) + * - 7 services (the catalogue a tenant can actually sell from) * - N marketplace libraries (global; idempotent at the libraries table) * * "Idempotent" means: safe to call twice — the second call inserts 0 rows @@ -26,6 +27,7 @@ * recommendations → (tenantId, category, name) * rating_systems → (tenantId, slug) (enforced by uniqueIndex) * contractor_types → (tenantId, name) + * services → (tenantId, name) * marketplace_libraries → (name) (global table — name unique) * * The function never throws on individual-row insert failure unless the @@ -75,6 +77,8 @@ const INSPECTION_TEMPLATES = [ import { AGREEMENT_TEMPLATE } from './starter-content/fixtures/agreement-template'; import { CANNED_COMMENTS } from './starter-content/fixtures/canned-comments'; import { EVENT_TYPES } from './starter-content/fixtures/event-types'; +import { seedServices } from './starter-content/seed-services'; +import { batchInsert } from './starter-content/batch-insert'; import { TAGS } from './starter-content/fixtures/tags'; import { RECOMMENDATIONS } from './starter-content/fixtures/recommendations'; import { RATING_SYSTEMS } from './starter-content/fixtures/rating-systems'; @@ -97,39 +101,6 @@ const CONTRACTOR_TYPES: ReadonlyArray<{ name: string; sortOrder: number }> = [ { name: 'Grading/Drainage', sortOrder: 10 }, ]; -/** - * Insert many rows in as few D1 round-trips as possible: multi-row INSERTs - * chunked to stay under D1's 100-bound-parameter-per-statement limit, all sent - * in a single db.batch(). No-op for an empty array. This turns hundreds of - * sequential awaited inserts (slow, and a long window during which a closed - * setup tab leaves partial data) into one batched round-trip per table. - */ -async function batchInsert( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - d: any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - table: any, - rows: Record[], -): Promise { - if (rows.length === 0) return; - - // Drivers without batch support (e.g. unit-test mocks): sequential inserts. - if (typeof d.batch !== 'function') { - for (const row of rows) await d.insert(table).values(row).run(); - return; - } - - const colsPerRow = Object.keys(rows[0]!).length || 1; - const maxRowsPerStmt = Math.max(1, Math.floor(100 / colsPerRow)); - const stmts = []; - for (let i = 0; i < rows.length; i += maxRowsPerStmt) { - stmts.push(d.insert(table).values(rows.slice(i, i + maxRowsPerStmt))); - } - // d.batch wants a non-empty tuple; stmts is guaranteed non-empty here. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await d.batch(stmts as [any, ...any[]]); -} - export interface StarterContentResult { inspectionTemplatesSeeded: number; agreementTemplatesSeeded: number; @@ -140,6 +111,7 @@ export interface StarterContentResult { ratingSystemsSeeded: number; marketplaceLibrariesSeeded: number; contractorTypesSeeded: number; + servicesSeeded: number; } /** @@ -247,6 +219,11 @@ export async function seedStarterContent( eventTypesSeeded = rows.length; } + // ── service catalogue ─────────────────────────────────────────────── + // Deliberately AFTER templates: each entry resolves its template by name. + // Extracted to keep this file under the size ratchet. + const servicesSeeded = await seedServices(d, tenantId); + // ── tags ──────────────────────────────────────────────────────────── let tagsSeeded = 0; { @@ -388,6 +365,7 @@ export async function seedStarterContent( ratingSystemsSeeded, marketplaceLibrariesSeeded, contractorTypesSeeded, + servicesSeeded, }; logger.info('starter-content.seeded', { tenantId, ...result }); return result; diff --git a/server/services/starter-content/batch-insert.ts b/server/services/starter-content/batch-insert.ts new file mode 100644 index 000000000..4f0c100c5 --- /dev/null +++ b/server/services/starter-content/batch-insert.ts @@ -0,0 +1,32 @@ +/** + * Insert many rows in as few D1 round-trips as possible: multi-row INSERTs + * chunked to stay under D1's 100-bound-parameter-per-statement limit, all sent + * in a single db.batch(). No-op for an empty array. This turns hundreds of + * sequential awaited inserts (slow, and a long window during which a closed + * setup tab leaves partial data) into one batched round-trip per table. + */ +export async function batchInsert( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + d: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + table: any, + rows: Record[], +): Promise { + if (rows.length === 0) return; + + // Drivers without batch support (e.g. unit-test mocks): sequential inserts. + if (typeof d.batch !== 'function') { + for (const row of rows) await d.insert(table).values(row).run(); + return; + } + + const colsPerRow = Object.keys(rows[0]!).length || 1; + const maxRowsPerStmt = Math.max(1, Math.floor(100 / colsPerRow)); + const stmts = []; + for (let i = 0; i < rows.length; i += maxRowsPerStmt) { + stmts.push(d.insert(table).values(rows.slice(i, i + maxRowsPerStmt))); + } + // d.batch wants a non-empty tuple; stmts is guaranteed non-empty here. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await d.batch(stmts as [any, ...any[]]); +} diff --git a/server/services/starter-content/fixtures/event-types.ts b/server/services/starter-content/fixtures/event-types.ts index dc3d86c8c..47bf8d0f2 100644 --- a/server/services/starter-content/fixtures/event-types.ts +++ b/server/services/starter-content/fixtures/event-types.ts @@ -1,6 +1,22 @@ /** - * Three booking event_types aligned with the seeded inspection templates. - * Price + duration are placeholders — the user edits each row before + * The ONE list of booking event_types, aligned with the seeded inspection + * templates and the seeded service catalogue. + * + * There used to be two lists. This one seeded three `starter_*` types at + * provisioning; `server/data/event-type-seeds.ts` held five more behind the + * manual bulk-seed endpoint, despite its own header claiming they were "seeded + * on every new tenant" — `bulkSeed` had exactly one caller and it was a button. + * The two disagreed: `starter_sewer_scope` and `sewer_scope` were one + * real-world thing under two slugs, and a tenant who provisioned and then + * pressed the button got both. + * + * They are merged here, keeping the bare `sewer_scope` slug because the other + * four bare slugs are what the services catalogue and the settings UI already + * reference. `starter_standard_home` and `starter_pre_listing` keep their names + * — they had no counterpart, so renaming them would be churn, and slugs are + * stable identifiers. + * + * Price + duration are starting points; the user edits each row before * publishing the public booking page. * * Slugs are stable identifiers — never rename; names are display-only and @@ -35,10 +51,45 @@ export const EVENT_TYPES: ReadonlyArray = [ }, { name: 'Sewer Scope', - slug: 'starter_sewer_scope', + slug: 'sewer_scope', defaultDurationMin: 60, - defaultPriceCents: 0, + defaultPriceCents: 25000, color: '#f59e0b', sortOrder: 30, }, + // The radon pair is why services carry default event-type slugs at all: a + // radon test is two visits, and the second one — the pickup, ≥48h later — + // is the thing that otherwise lives only in the inspector's head. + { + name: 'Radon Drop-off', + slug: 'radon_dropoff', + defaultDurationMin: 15, + defaultPriceCents: 0, + color: '#10b981', + sortOrder: 40, + }, + { + name: 'Radon Pickup', + slug: 'radon_pickup', + defaultDurationMin: 15, + defaultPriceCents: 0, + color: '#10b981', + sortOrder: 50, + }, + { + name: 'Mold Test', + slug: 'mold_test', + defaultDurationMin: 30, + defaultPriceCents: 15000, + color: '#a855f7', + sortOrder: 60, + }, + { + name: 'Water Test', + slug: 'water_test', + defaultDurationMin: 20, + defaultPriceCents: 12500, + color: '#0ea5e9', + sortOrder: 70, + }, ]; diff --git a/server/services/starter-content/fixtures/services.ts b/server/services/starter-content/fixtures/services.ts new file mode 100644 index 000000000..b96d09cb4 --- /dev/null +++ b/server/services/starter-content/fixtures/services.ts @@ -0,0 +1,104 @@ +/** + * The starter service catalogue — what a new tenant can actually sell. + * + * Production had 43 event types, 7 templates and ZERO services, because + * starter content seeded templates and event types but never the catalogue. + * Service lines can only be attached at inspection-create time from a + * catalogue, so an empty catalogue is why no inspection had ever had one. A + * product gap, not slow adoption. + * + * Three artefacts describing the same thing now line up by construction: each + * entry names the template it produces and the event-type slugs it implies. + * + * **Prices are real starting numbers, not placeholders.** `services.price` is + * NOT NULL and `inspection_services.price_snapshot` copies it, so "seed the + * entry with no price" was never available. These are plausible US market + * rates that a tenant is expected to edit — the settings UI is where they do + * that, and it should make them easy to find. + * + * Event types are referenced by SLUG, not id. Slugs survive a tenant deleting + * and re-creating a type, they do not depend on seed ordering inside + * `seedStarterContent`, and an unmatched slug degrades to "propose nothing" + * rather than to a dangling id. + */ + +export interface StarterServiceFixture { + name: string; + description: string; + /** Exact `templates.name` this service produces a report from. */ + templateName: string; + priceCents: number; + durationMinutes: number; + /** `event_types.slug` values this service implies, in visit order. */ + defaultEventTypeSlugs: string[]; + sortOrder: number; +} + +export const STARTER_SERVICES: ReadonlyArray = [ + { + name: 'Standard Home Inspection', + description: 'Full residential inspection of the home’s systems and structure.', + templateName: 'Standard Residential Inspection', + priceCents: 45000, + durationMinutes: 180, + defaultEventTypeSlugs: ['starter_standard_home'], + sortOrder: 10, + }, + { + name: 'Pre-Listing Inspection', + description: 'Inspection for a seller preparing to put the home on the market.', + templateName: 'Pre-Listing Inspection', + priceCents: 40000, + durationMinutes: 120, + defaultEventTypeSlugs: ['starter_pre_listing'], + sortOrder: 20, + }, + { + name: 'New Construction Pre-Drywall', + description: 'Inspection before drywall closes the framing, wiring and plumbing in.', + templateName: 'New Construction Pre-Drywall Inspection', + priceCents: 50000, + durationMinutes: 150, + defaultEventTypeSlugs: [], + sortOrder: 30, + }, + { + name: 'New Construction Final Walkthrough', + description: 'Final walkthrough before closing on a newly built home.', + templateName: 'New Construction Final Walkthrough', + priceCents: 35000, + durationMinutes: 120, + defaultEventTypeSlugs: [], + sortOrder: 40, + }, + { + name: 'Sewer Scope', + description: 'Camera inspection of the sewer lateral from the home to the main.', + templateName: 'Sewer Scope Inspection', + priceCents: 25000, + durationMinutes: 60, + defaultEventTypeSlugs: ['sewer_scope'], + sortOrder: 50, + }, + { + // The reason services carry visits at all. A radon test is a drop-off + // and a pickup at least 48 hours apart, and the pickup is the one that + // otherwise lives only in the inspector's head. + name: 'Radon Testing', + description: 'Continuous radon monitor placed and collected at least 48 hours later.', + templateName: 'Radon Measurement Report', + priceCents: 15000, + durationMinutes: 15, + defaultEventTypeSlugs: ['radon_dropoff', 'radon_pickup'], + sortOrder: 60, + }, + { + name: 'Mold Inspection', + description: 'Visual mold assessment with sampling where conditions warrant it.', + templateName: 'Mold Inspection', + priceCents: 35000, + durationMinutes: 90, + defaultEventTypeSlugs: ['mold_test'], + sortOrder: 70, + }, +]; diff --git a/server/services/starter-content/seed-services.ts b/server/services/starter-content/seed-services.ts new file mode 100644 index 000000000..fcbcfcde6 --- /dev/null +++ b/server/services/starter-content/seed-services.ts @@ -0,0 +1,59 @@ +/** + * Seed the service catalogue — what a new tenant can actually sell. + * + * Extracted from `starter-content.service.ts` because it is the one block that + * has to read another block's output: each entry resolves its template by NAME, + * so it must run after templates are seeded. Event types are referenced by slug + * and so do not care about ordering, which is half the reason slugs were chosen + * over ids. + */ +import { eq } from 'drizzle-orm'; +import { services, templates } from '../../lib/db/schema'; +import { STARTER_SERVICES } from './fixtures/services'; +import { batchInsert } from './batch-insert'; + +/** + * Idempotent on `(tenantId, name)`, matching every other block in starter + * content. A tenant who renamed a seeded service keeps their name — re-running + * must not hand them a second copy under the original one. + * + * @returns the number of NEW rows inserted (zero on an idempotent re-run). + */ +export async function seedServices( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + d: any, + tenantId: string, +): Promise { + const existingServices = await d.select({ name: services.name }).from(services) + .where(eq(services.tenantId, tenantId)).all(); + const existingNames = new Set(existingServices.map((r: { name: string }) => r.name)); + + const templateRows = await d.select({ id: templates.id, name: templates.name }) + .from(templates).where(eq(templates.tenantId, tenantId)).all(); + const templateIdByName = new Map( + templateRows.map((r: { id: string; name: string }) => [r.name, r.id]), + ); + + const rows = STARTER_SERVICES + .filter(s => !existingNames.has(s.name)) + .map(s => ({ + id: crypto.randomUUID(), + tenantId, + name: s.name, + description: s.description, + price: s.priceCents, + durationMinutes: s.durationMinutes, + // A tenant who already had a template of this name keeps THEIR + // template, and a missing one leaves the link null rather than + // failing the whole provisioning run. + templateId: templateIdByName.get(s.templateName) ?? null, + agreementId: null, + active: true, + sortOrder: s.sortOrder, + createdAt: new Date(), + defaultEventTypeSlugs: s.defaultEventTypeSlugs, + })); + + await batchInsert(d, services, rows); + return rows.length; +} diff --git a/server/services/template.service.ts b/server/services/template.service.ts index e2f7e326f..ddfa0da35 100644 --- a/server/services/template.service.ts +++ b/server/services/template.service.ts @@ -1,6 +1,6 @@ import { drizzle } from 'drizzle-orm/d1'; import { eq, and, desc, sql, like } from 'drizzle-orm'; -import { templates, inspections } from '../lib/db/schema'; +import { templates, inspections, services } from '../lib/db/schema'; import { Errors } from '../lib/errors'; import { TemplateSchemaV2Schema } from '../lib/validations/template.schema'; @@ -272,6 +272,25 @@ export class TemplateService { throw Errors.Conflict('Cannot delete a template that is referenced by existing inspections'); } + // `services.template_id` is the SECOND foreign key to this table, and it + // was unchecked. Latent only while the services catalogue was empty — + // seeding it ends that, and without this the delete fails at the FK with + // a message naming neither the table nor the row. + // + // Name the service. "Conflict" with no subject sends a tenant hunting + // through their inspections for a reference that is in their catalogue. + const usedByService = await db.select({ name: services.name }) + .from(services) + .where(and(eq(services.templateId, id), eq(services.tenantId, tenantId))) + .limit(1) + .get(); + + if (usedByService) { + throw Errors.Conflict( + `Cannot delete a template that is the default for the service "${usedByService.name}"`, + ); + } + await db.delete(templates).where(eq(templates.id, id)); } } diff --git a/tests/unit/calendar/event.service.spec.ts b/tests/unit/calendar/event.service.spec.ts index 35fbde8d5..54ea7c180 100644 --- a/tests/unit/calendar/event.service.spec.ts +++ b/tests/unit/calendar/event.service.spec.ts @@ -30,20 +30,28 @@ describe('EventService', () => { }); describe('bulkSeed', () => { - it('seeds 5 default event types on first run', async () => { + // 7, not 5: this endpoint and provisioning used to seed from two + // different lists that disagreed with each other (`starter_sewer_scope` + // vs `sewer_scope` was one real thing under two slugs). They are now + // one list, so this is a repair tool — it fills gaps for a tenant + // provisioned before a type existed, and no-ops for everyone else. + it('seeds every default event type on first run', async () => { const r = await svc.bulkSeed(TENANT); - expect(r.seeded).toBe(5); + expect(r.seeded).toBe(7); expect(r.skipped).toBe(0); const types = await svc.listEventTypes(TENANT); - expect(types).toHaveLength(5); - expect(types.map(t => t.slug).sort()).toEqual(['mold_test', 'radon_dropoff', 'radon_pickup', 'sewer_scope', 'water_test']); + expect(types).toHaveLength(7); + expect(types.map(t => t.slug).sort()).toEqual([ + 'mold_test', 'radon_dropoff', 'radon_pickup', 'sewer_scope', + 'starter_pre_listing', 'starter_standard_home', 'water_test', + ]); }); - it('is idempotent — second run skips all 5', async () => { + it('is idempotent — second run skips them all', async () => { await svc.bulkSeed(TENANT); const r = await svc.bulkSeed(TENANT); expect(r.seeded).toBe(0); - expect(r.skipped).toBe(5); + expect(r.skipped).toBe(7); }); it('respects tenant scoping — seeds only for given tenant', async () => { diff --git a/tests/unit/inspections/service-visit-proposal.spec.ts b/tests/unit/inspections/service-visit-proposal.spec.ts new file mode 100644 index 000000000..16c6e6703 --- /dev/null +++ b/tests/unit/inspections/service-visit-proposal.spec.ts @@ -0,0 +1,104 @@ +/** + * A service knows which visits it implies. + * + * Radon is the case that motivates the whole feature: it is two visits, a + * drop-off and a pickup at least 48h later, and the pickup is the one that + * otherwise lives only in the inspector's head. + * + * The reference is by SLUG rather than event-type id, and these tests pin the + * consequence of that choice — a slug with no matching row degrades to a + * shorter proposal instead of a dangling id or a 500 on the order screen. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { and, eq } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { ServiceService } from '../../../server/services/service.service'; + +const TENANT = 'tenant-visits-1'; + +describe('ServiceService.proposeEventsForService', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + let svc: ServiceService; + + async function addEventType(slug: string, name: string) { + await db.insert(schema.eventTypes).values({ + id: `et-${slug}`, tenantId: TENANT, name, slug, + defaultDurationMin: 15, defaultPriceCents: 0, color: '#000', + sortOrder: 1, active: true, createdAt: new Date(), + } as never); + } + + async function addService(id: string, name: string, slugs: string[] | null) { + await db.insert(schema.services).values({ + id, tenantId: TENANT, name, description: null, price: 15000, + durationMinutes: 15, templateId: null, agreementId: null, + active: true, sortOrder: 1, createdAt: new Date(), + defaultEventTypeSlugs: slugs, + } as never); + } + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockDrizzle as any).mockReturnValue(db); + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + await addEventType('radon_dropoff', 'Radon Drop-off'); + await addEventType('radon_pickup', 'Radon Pickup'); + await addService('svc-radon', 'Radon Testing', ['radon_dropoff', 'radon_pickup']); + await addService('svc-sewer', 'Sewer Scope', []); + svc = new ServiceService({} as never); + }); + + afterEach(() => { sqlite.close(); }); + + it('proposes the two radon visits, drop-off first', async () => { + const proposed = await svc.proposeEventsForService(TENANT, 'svc-radon'); + expect(proposed.map(e => e.slug)).toEqual(['radon_dropoff', 'radon_pickup']); + }); + + it('proposes nothing for a service with no default visits', async () => { + expect(await svc.proposeEventsForService(TENANT, 'svc-sewer')).toEqual([]); + }); + + it('degrades to what remains when a referenced event type was deleted', async () => { + // Soft references are chosen deliberately. Assert the degradation rather + // than discovering it as a 500 the first time a tenant tidies up. + await db.delete(schema.eventTypes) + .where(and(eq(schema.eventTypes.tenantId, TENANT), eq(schema.eventTypes.slug, 'radon_pickup'))); + + const proposed = await svc.proposeEventsForService(TENANT, 'svc-radon'); + expect(proposed.map(e => e.slug)).toEqual(['radon_dropoff']); + }); + + it('never proposes another tenant\'s event type of the same slug', async () => { + await db.insert(schema.tenants).values({ + id: 'other', name: 'O', slug: 'o', createdAt: new Date(), + }); + await db.insert(schema.eventTypes).values({ + id: 'et-other', tenantId: 'other', name: 'Radon Pickup', slug: 'radon_pickup', + defaultDurationMin: 15, defaultPriceCents: 0, color: '#000', + sortOrder: 1, active: true, createdAt: new Date(), + } as never); + await db.delete(schema.eventTypes) + .where(and(eq(schema.eventTypes.tenantId, TENANT), eq(schema.eventTypes.slug, 'radon_pickup'))); + + const proposed = await svc.proposeEventsForService(TENANT, 'svc-radon'); + expect(proposed.map(e => e.id)).toEqual(['et-radon_dropoff']); + }); + + it('proposes nothing for a service that is not this tenant\'s', async () => { + expect(await svc.proposeEventsForService('other', 'svc-radon')).toEqual([]); + }); +}); diff --git a/tests/unit/integrations/starter-content-services.spec.ts b/tests/unit/integrations/starter-content-services.spec.ts new file mode 100644 index 000000000..c44bdbfed --- /dev/null +++ b/tests/unit/integrations/starter-content-services.spec.ts @@ -0,0 +1,125 @@ +/** + * Starter content seeds a SERVICE CATALOGUE. + * + * Production had 43 event types, 7 templates and zero services. Service lines + * can only be attached from a catalogue, so an empty catalogue is why no + * inspection had ever carried one — a product gap, not slow adoption. + * + * The alignment is the point: every seeded service names the template it + * produces and the event-type slugs it implies, so three artefacts that used to + * describe the same thing separately now line up by construction. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { and, eq } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { seedStarterContent } from '../../../server/services/starter-content.service'; +import { STARTER_SERVICES } from '../../../server/services/starter-content/fixtures/services'; +import { EVENT_TYPES } from '../../../server/services/starter-content/fixtures/event-types'; + +describe('seedStarterContent — service catalogue', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + const tenantId = 'tenant-services-1'; + + const listServices = () => + db.select().from(schema.services).where(eq(schema.services.tenantId, tenantId)).all(); + const findService = (name: string) => + db.select().from(schema.services) + .where(and(eq(schema.services.tenantId, tenantId), eq(schema.services.name, name))).get(); + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockDrizzle as any).mockReturnValue(db); + await db.insert(schema.tenants).values({ + id: tenantId, name: 'T', slug: 't', createdAt: new Date(), + }); + }); + + afterEach(() => { sqlite.close(); }); + + it('seeds a catalogue where there was none', async () => { + expect(await listServices()).toHaveLength(0); + const result = await seedStarterContent({} as never, tenantId); + expect(result.servicesSeeded).toBe(STARTER_SERVICES.length); + expect(await listServices()).toHaveLength(STARTER_SERVICES.length); + }); + + it('links every seeded service to a template that exists', async () => { + await seedStarterContent({} as never, tenantId); + const templateIds = new Set( + (await db.select().from(schema.templates).where(eq(schema.templates.tenantId, tenantId)).all()) + .map(t => t.id), + ); + for (const s of await listServices()) { + expect(s.templateId).not.toBeNull(); + expect(templateIds.has(s.templateId as string)).toBe(true); + } + }); + + it('links radon to both of its visits, in order', async () => { + await seedStarterContent({} as never, tenantId); + const radon = await findService('Radon Testing'); + // Drop-off before pickup: a pickup proposed first is a nonsense visit + // sequence, and the order is the only thing carrying that meaning. + expect(radon?.defaultEventTypeSlugs).toEqual(['radon_dropoff', 'radon_pickup']); + }); + + it('every referenced event-type slug is actually seeded', async () => { + // The failure this catches: a service pointing at a slug that only + // existed in the other, manually-triggered seed list. + await seedStarterContent({} as never, tenantId); + const seededSlugs = new Set( + (await db.select().from(schema.eventTypes).where(eq(schema.eventTypes.tenantId, tenantId)).all()) + .map(e => e.slug), + ); + const referenced = STARTER_SERVICES.flatMap(s => s.defaultEventTypeSlugs); + expect(referenced.length).toBeGreaterThan(0); + for (const slug of referenced) expect(seededSlugs.has(slug)).toBe(true); + }); + + it('gives every service a price, because the column cannot hold none', async () => { + // services.price is NOT NULL and inspection_services.price_snapshot + // copies it, so "seed the entry without a price" was never available. + // These are editable starting numbers, not placeholders. + for (const s of await (async () => { await seedStarterContent({} as never, tenantId); return listServices(); })()) { + expect(typeof s.price).toBe('number'); + expect(s.price).toBeGreaterThan(0); + } + }); + + it('is idempotent and never resurrects a service the tenant renamed', async () => { + await seedStarterContent({} as never, tenantId); + await db.update(schema.services).set({ name: 'Our Sewer Scope' }) + .where(and(eq(schema.services.tenantId, tenantId), eq(schema.services.name, 'Sewer Scope'))); + + const second = await seedStarterContent({} as never, tenantId); + + // Re-seeding must not hand them back a second "Sewer Scope" beside the + // one they renamed. + expect(second.servicesSeeded).toBe(1); + expect(await findService('Our Sewer Scope')).toBeTruthy(); + expect(await listServices()).toHaveLength(STARTER_SERVICES.length + 1); + }); + + it('seeds one event type per fixture entry and no duplicate sewer scope', async () => { + await seedStarterContent({} as never, tenantId); + const slugs = (await db.select().from(schema.eventTypes) + .where(eq(schema.eventTypes.tenantId, tenantId)).all()).map(e => e.slug).sort(); + + expect(slugs).toEqual(EVENT_TYPES.map(e => e.slug).sort()); + // The two seed lists used to disagree here: one real-world thing under + // `starter_sewer_scope` and `sewer_scope` both. + expect(slugs).toContain('sewer_scope'); + expect(slugs).not.toContain('starter_sewer_scope'); + }); +}); diff --git a/tests/unit/integrations/starter-content.service.spec.ts b/tests/unit/integrations/starter-content.service.spec.ts index aa34b1544..76d63205d 100644 --- a/tests/unit/integrations/starter-content.service.spec.ts +++ b/tests/unit/integrations/starter-content.service.spec.ts @@ -52,7 +52,10 @@ describe('seedStarterContent', () => { expect(result.inspectionTemplatesSeeded).toBe(7); expect(result.agreementTemplatesSeeded).toBe(1); expect(result.cannedCommentsSeeded).toBe(254); - expect(result.eventTypesSeeded).toBe(3); + // 7 since the two event-type seed lists were merged: the three main + // visits plus the radon pair, mold and water tests that the seeded + // service catalogue references. + expect(result.eventTypesSeeded).toBe(7); expect(result.tagsSeeded).toBe(4); expect(result.recommendationsSeeded).toBeGreaterThan(0); expect(result.ratingSystemsSeeded).toBeGreaterThan(0); diff --git a/tests/unit/templates/template-delete-guard.spec.ts b/tests/unit/templates/template-delete-guard.spec.ts new file mode 100644 index 000000000..4794cf293 --- /dev/null +++ b/tests/unit/templates/template-delete-guard.spec.ts @@ -0,0 +1,85 @@ +/** + * `services.template_id` is the SECOND foreign key to `templates`, and + * `deleteTemplate` only ever checked the first one (`inspections.template_id`). + * + * Latent while the services catalogue was empty. Seeding it ends that: from now + * on a fresh tenant has seven services each defaulting to a template, so the + * unguarded path would fail at the foreign key with a message naming neither + * the table nor the row. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { TemplateService } from '../../../server/services/template.service'; + +const TENANT = 'tenant-tpl-guard'; +const TEMPLATE_ID = 'tpl-1'; + +describe('deleteTemplate — the service catalogue reference', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + let svc: TemplateService; + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockDrizzle as any).mockReturnValue(db); + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + await db.insert(schema.templates).values({ + id: TEMPLATE_ID, tenantId: TENANT, name: 'Sewer Scope Inspection', + version: 1, schema: JSON.stringify({ sections: [] }), createdAt: new Date(), + } as never); + svc = new TemplateService({} as never); + }); + + afterEach(() => { sqlite.close(); }); + + async function addService(name: string, templateId: string | null) { + await db.insert(schema.services).values({ + id: `svc-${name}`, tenantId: TENANT, name, description: null, + price: 25000, durationMinutes: 60, templateId, agreementId: null, + active: true, sortOrder: 1, createdAt: new Date(), + defaultEventTypeSlugs: [], + } as never); + } + + it('refuses to delete a template a service defaults to', async () => { + await addService('Sewer Scope', TEMPLATE_ID); + await expect(svc.deleteTemplate(TEMPLATE_ID, TENANT)).rejects.toThrow(/Cannot delete/); + }); + + it('names the service that is blocking the delete', async () => { + // "Conflict" with no subject sends the tenant hunting through their + // inspections for a reference that is in their service catalogue. + await addService('Sewer Scope', TEMPLATE_ID); + await expect(svc.deleteTemplate(TEMPLATE_ID, TENANT)).rejects.toThrow(/Sewer Scope/); + }); + + it('still deletes a template nothing references', async () => { + await addService('Radon Testing', null); + await expect(svc.deleteTemplate(TEMPLATE_ID, TENANT)).resolves.not.toThrow(); + const rows = await db.select().from(schema.templates).all(); + expect(rows).toHaveLength(0); + }); + + it('is still blocked by a service the tenant soft-deleted', async () => { + // `deleteService` sets active:false and leaves template_id in place, so + // the foreign key survives the delete the tenant thinks they did. The + // guard deliberately does not filter on `active`: if it did, this case + // would skip the friendly message and fail at the FK instead. + await addService('Sewer Scope', TEMPLATE_ID); + await db.update(schema.services).set({ active: false }); + + await expect(svc.deleteTemplate(TEMPLATE_ID, TENANT)).rejects.toThrow(/Sewer Scope/); + }); +}); From b783fe0a77d3d44b027a829698d1968df962c5d0 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 23:30:07 +0800 Subject: [PATCH 09/29] feat(services): warn when two services would default to the same template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The competitor keeps an FAQ entry for this exact confusion: "If you're seeing multiple reports generating on your inspections, you may have the same template defaulted for both your primary service and your add-on services." A tenant who defaults the residential template for both Standard Home Inspection and Sewer Scope gets two identical blank reports and no way to guess why. Naming the other service is the whole value — "this template is already in use" is not actionable. It warns rather than blocks: there are legitimate reasons to want it, so the failure mode being prevented is surprise, not invalidity. Two things the Chrome pass caught that the unit tests could not: - The message pushed the Save button down 20px. The component already reserved a fixed-height slot for exactly this reason ("appearing and disappearing moved the form's Save button by two lines, under whatever the cursor was already aiming at") but it was sized for the one-line hint. Measured in the browser at 341px vs 321px, tightened the copy, resized the slot to the tallest message, and re-measured: 341 vs 339, which is line-height rounding rather than movement. - "{name} and 1 others" — the same plural bug as "Delete 1 comments?" earlier in this branch. Rephrased to "{count} more ... too", which reads correctly at 1 and at N. role="status", not "alert": this is advice about a choice the admin just made deliberately, not an error, and assertive would interrupt them on every keystroke through the select. Verified in both themes — amber #fbbf24 on #0f172a is ~9.5:1. --- .../ServiceDuplicateTemplate.test.tsx | 108 ++++++++++++++++++ .../settings/services/ServiceEditForm.tsx | 4 + .../settings/services/ServiceFields.tsx | 33 +++++- app/routes/settings-services.tsx | 3 +- messages/en/settings-components.json | 2 + 5 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 app/components/settings/services/ServiceDuplicateTemplate.test.tsx diff --git a/app/components/settings/services/ServiceDuplicateTemplate.test.tsx b/app/components/settings/services/ServiceDuplicateTemplate.test.tsx new file mode 100644 index 000000000..363422814 --- /dev/null +++ b/app/components/settings/services/ServiceDuplicateTemplate.test.tsx @@ -0,0 +1,108 @@ +/** + * Two services defaulting to the same template is the single most common way a + * tenant ends up with duplicate reports — the competitor keeps an FAQ entry + * answering exactly this: "If you're seeing multiple reports generating on your + * inspections, you may have the same template defaulted for both your primary + * service and your add-on services." + * + * Naming the other service is the whole value. "This template is already in + * use" is not actionable; "Standard Home Inspection already uses this template" + * is. And it warns rather than blocks — there are legitimate reasons to want it, + * so the failure mode being prevented is surprise, not invalidity. + */ +import { describe, it, expect } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { createRoutesStub } from "react-router"; + +import { ServiceFields } from "~/components/settings/services/ServiceFields"; + +const TEMPLATES = [ + { id: "tpl-residential", name: "Standard Residential Inspection" }, + { id: "tpl-sewer", name: "Sewer Scope Inspection" }, +]; + +const OTHERS = [ + { id: "svc-standard", name: "Standard Home Inspection", templateId: "tpl-residential" }, + { id: "svc-prelist", name: "Pre-Listing Inspection", templateId: "tpl-residential" }, + { id: "svc-sewer", name: "Sewer Scope", templateId: "tpl-sewer" }, +]; + +/** conform's field metas, reduced to what ServiceFields actually reads. */ +const field = (name: string) => ({ id: `f-${name}`, name, errors: undefined }); +const FIELDS = { + name: field("name"), description: field("description"), price: field("price"), + durationMinutes: field("durationMinutes"), templateId: field("templateId"), +} as never; + +function renderFields(otherServices = OTHERS, initialTemplateId = "") { + const Stub = createRoutesStub([ + { + path: "/settings/services", + Component: () => ( + + ), + }, + ]); + render(); + return screen.getByLabelText(/template/i) as HTMLSelectElement; +} + +describe("service template picker — the duplicate-report trap", () => { + it("names the service already using the template", async () => { + const select = renderFields([OTHERS[0], OTHERS[2]]); + fireEvent.change(select, { target: { value: "tpl-residential" } }); + + expect(await screen.findByText(/Standard Home Inspection also uses this template/i)) + .toBeTruthy(); + }); + + it("says what it will cost them, in reports rather than in jargon", async () => { + const select = renderFields([OTHERS[0], OTHERS[2]]); + fireEvent.change(select, { target: { value: "tpl-residential" } }); + + expect(await screen.findByText(/two identical reports/i)).toBeTruthy(); + }); + + it("counts the rest when more than one service shares it", async () => { + const select = renderFields(); + fireEvent.change(select, { target: { value: "tpl-residential" } }); + + const msg = await screen.findByText(/use this template too/i); + expect(msg.textContent).toContain("Standard Home Inspection"); + // "and 1 others" was the bug this phrasing avoids — same shape as + // "Delete 1 comments?". "1 more" and "3 more" both read correctly. + expect(msg.textContent).toContain("1 more"); + expect(msg.textContent).not.toMatch(/1 others/); + }); + + it("stays quiet when the template is unique to this service", async () => { + const select = renderFields(); + fireEvent.change(select, { target: { value: "tpl-sewer" } }); + // Sewer Scope is in OTHERS, so pick a template nobody else uses instead. + fireEvent.change(select, { target: { value: "tpl-residential" } }); + fireEvent.change(select, { target: { value: "" } }); + + expect(screen.queryByText(/uses? this template/i)).toBeNull(); + }); + + it("warns without blocking — the select keeps the value", async () => { + const select = renderFields([OTHERS[0]]); + fireEvent.change(select, { target: { value: "tpl-residential" } }); + + await screen.findByText(/also uses this template/i); + expect(select.value).toBe("tpl-residential"); + }); + + it("does not warn about the service being edited itself", async () => { + // The edit form filters the current service out before passing the rest; + // without that a tenant editing a service would be warned about itself. + const select = renderFields([], "tpl-residential"); + expect(select.value).toBe("tpl-residential"); + expect(screen.queryByText(/uses? this template/i)).toBeNull(); + }); +}); diff --git a/app/components/settings/services/ServiceEditForm.tsx b/app/components/settings/services/ServiceEditForm.tsx index 9b6cf254f..5e6adc985 100644 --- a/app/components/settings/services/ServiceEditForm.tsx +++ b/app/components/settings/services/ServiceEditForm.tsx @@ -31,10 +31,13 @@ interface EditableService { export function ServiceEditForm({ service, templates, + otherServices = [], onCancel, }: { service: EditableService; templates: Array<{ id: string; name: string }>; + /** The rest of the catalogue, so the template picker can warn about sharing. */ + otherServices?: Array<{ id: string; name: string; templateId: string | null }>; onCancel: () => void; }) { const [form, fields] = useForm({ @@ -70,6 +73,7 @@ export function ServiceEditForm({ templates={templates} initialPriceCents={service.price} initialTemplateId={service.templateId ?? ""} + otherServices={otherServices.filter((s) => s.id !== service.id)} /> {form.errors && (
diff --git a/app/components/settings/services/ServiceFields.tsx b/app/components/settings/services/ServiceFields.tsx index 3a6d57c98..f934315e0 100644 --- a/app/components/settings/services/ServiceFields.tsx +++ b/app/components/settings/services/ServiceFields.tsx @@ -64,11 +64,14 @@ export function ServiceFields({ templates, initialPriceCents = null, initialTemplateId = "", + otherServices = [], }: { fields: ServiceFieldMetas; templates: Array<{ id: string; name: string }>; initialPriceCents?: number | null; initialTemplateId?: string; + /** Every OTHER service in the catalogue — the one being edited is excluded. */ + otherServices?: Array<{ id: string; name: string; templateId: string | null }>; }) { // Price is held in integer cents; a hidden field carries dollars so the zod // schema (which multiplies by 100) sees the same contract as the API. @@ -76,6 +79,12 @@ export function ServiceFields({ const onlyTemplate = templates.length === 1 ? templates[0].id : ""; const [templateId, setTemplateId] = useState(initialTemplateId || onlyTemplate); + // Two services sharing a default template is the single most common way to + // end up with duplicate reports — the competitor keeps an FAQ entry for it. + // Nothing downstream is invalid, so this warns and does not block: there are + // legitimate reasons to want it, and the failure mode is surprise. + const sharing = templateId ? otherServices.filter((s) => s.templateId === templateId) : []; + return (
@@ -155,10 +164,32 @@ export function ServiceFields({ {/* Reserved, not conditional: appearing and disappearing moved the form's Save button by two lines, under whatever the cursor was already aiming at. */} -
+ {/* Both messages share the reserved slot and are mutually exclusive + — one needs a blank template, the other needs a chosen one — so + the Save button stays put either way. The height is the TALLEST + of them, measured in the browser: at 30px the sharing warning + wrapped to a third line and pushed Save down 20px, which is the + exact thing this slot exists to stop. + + role="status", not "alert": this is advice about a choice the + admin just made deliberately, not an error. Assertive would + interrupt them on every keystroke through the select. */} +
{!templateId && (

{m.settings_services_template_consequence()}

)} + {sharing.length === 1 && ( +

+ {m.settings_services_template_shared_one({ name: sharing[0].name })} +

+ )} + {sharing.length > 1 && ( +

+ {m.settings_services_template_shared_many({ + name: sharing[0].name, count: sharing.length - 1, + })} +

+ )}
)} diff --git a/app/routes/settings-services.tsx b/app/routes/settings-services.tsx index 3a48445f8..025c0c131 100644 --- a/app/routes/settings-services.tsx +++ b/app/routes/settings-services.tsx @@ -290,7 +290,7 @@ export default function SettingsServices() { className="bg-ih-bg-card border border-ih-border rounded-lg p-4 space-y-3" > - + {form.errors && (
{form.errors[0]} @@ -314,6 +314,7 @@ export default function SettingsServices() { setEditingId(null)} /> )} diff --git a/messages/en/settings-components.json b/messages/en/settings-components.json index ce6553de0..bc39c6a1b 100644 --- a/messages/en/settings-components.json +++ b/messages/en/settings-components.json @@ -469,6 +469,8 @@ "settings_mcw_submitting": "Submitting…", "settings_mcw_start_provisioning": "Start provisioning", "settings_services_template_consequence": "Without a template, a customer who picks this service cannot book online.", + "settings_services_template_shared_one": "{name} also uses this template — an inspection with both generates two identical reports.", + "settings_services_template_shared_many": "{name} and {count} more use this template too — an inspection with several generates duplicate reports.", "settings_services_template_none_available": "No templates yet.", "settings_services_template_create_link": "Create a template", "settings_services_edit_heading": "Edit {name}", From bb957bbdbe195de578a8006dcef160dd72568487 Mon Sep 17 00:00:00 2001 From: important-new Date: Sun, 2 Aug 2026 23:41:26 +0800 Subject: [PATCH 10/29] feat(roster): one accessor for "who worked this inspection" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 2 of the roster-convergence plan, and on the measured evidence it is the task that carries the plan's actual value: a pay split is money attributed to a named person that nobody re-derives later, so it must read one function rather than whichever table its author reached for. Reads inspection_inspectors and never inspections.inspector_id. That is pinned by asserting on the EXECUTED SQL rather than on the result, and the assertion was proven red first: an implementation that also selected from `inspections` returned an identical roster and passed all four result-based tests, which is exactly the failure mode a result-only test cannot see. Batch-first — getInspectionRosters takes a list and getInspectionRoster delegates to it, because the calendar and the inspections list render many rows and a per-row accessor turns one query into N. A test asserts the query count is one, and it also went red against the sabotaged version. Tenant scope is on the LINK row, not on the joined user: an agent user carries a null tenant_id, so scoping on the join would silently drop rows rather than protect them. inspection_events.inspector_id is deliberately out of scope and the module says so — it answers who performed THAT visit, and a radon pickup may legitimately be a different person from the lead. --- server/lib/inspection/roster.ts | 100 +++++++++++++++++++++ tests/unit/inspections/roster.spec.ts | 124 ++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 server/lib/inspection/roster.ts create mode 100644 tests/unit/inspections/roster.spec.ts diff --git a/server/lib/inspection/roster.ts b/server/lib/inspection/roster.ts new file mode 100644 index 000000000..92e54837c --- /dev/null +++ b/server/lib/inspection/roster.ts @@ -0,0 +1,100 @@ +/** + * One answer to "who worked this inspection", read through one function. + * + * The question has more than one representation in the schema, and the point of + * this module is that consumers stop choosing between them. It reads + * `inspection_inspectors` — the link table — and never + * `inspections.inspector_id`. That is deliberate and it is what the tests pin: + * a version that falls back to the column would reintroduce the ability for two + * callers to get two different answers. + * + * On the current design (see the schema comment on `inspectionInspectors`), + * `inspections.inspector_id` is canonical and this table is its mirror, kept in + * step by `syncInspectionAssignments` from every assignment write. Reading the + * mirror is still the right call here: it carries a ROLE per person and it can + * express more than one of them, which the column cannot, and anything that + * attributes work or money to a named person needs both. + * + * `inspection_events.inspector_id` is NOT part of this. It answers a narrower + * question — who performed THAT visit — and a radon pickup may legitimately be + * a different person from the lead. Do not sweep it in. + */ +import { and, eq, inArray } from 'drizzle-orm'; +import type { DrizzleD1Database } from 'drizzle-orm/d1'; +import { inspectionInspectors } from '../db/schema'; +import { users } from '../db/schema'; + +export interface RosterMember { + id: string; + name: string | null; + email: string; +} + +export interface InspectionRoster { + /** Null when nobody is assigned. Callers render "unassigned", never throw. */ + lead: RosterMember | null; + helpers: RosterMember[]; +} + +const EMPTY: InspectionRoster = { lead: null, helpers: [] }; + +/** + * Rosters for many inspections in ONE query. + * + * The single-inspection accessor delegates here rather than the other way + * round: the calendar and the inspections list render many rows at a time, and + * a per-row accessor would turn one query into N. + * + * Returns a Map keyed by inspection id. Ids with no roster are simply absent — + * callers use `rosterOf` below, which supplies the empty roster. + */ +export async function getInspectionRosters( + db: DrizzleD1Database, + tenantId: string, + inspectionIds: string[], +): Promise> { + const out = new Map(); + if (inspectionIds.length === 0) return out; + + const rows = await db.select({ + inspectionId: inspectionInspectors.inspectionId, + role: inspectionInspectors.role, + userId: users.id, + name: users.name, + email: users.email, + }) + .from(inspectionInspectors) + .innerJoin(users, eq(users.id, inspectionInspectors.userId)) + .where(and( + // Tenant scope is on the LINK row, not on the user: an agent user + // carries a null tenant_id, and scoping on the join would drop + // rows rather than protect them. + eq(inspectionInspectors.tenantId, tenantId), + inArray(inspectionInspectors.inspectionId, inspectionIds), + )) + .all(); + + for (const r of rows) { + const entry = out.get(r.inspectionId) ?? { lead: null, helpers: [] }; + const member: RosterMember = { id: r.userId, name: r.name, email: r.email }; + if (r.role === 'lead') entry.lead = member; + else entry.helpers.push(member); + out.set(r.inspectionId, entry); + } + return out; +} + +/** The roster for one inspection. Empty rather than absent when unassigned. */ +export async function getInspectionRoster( + db: DrizzleD1Database, + tenantId: string, + inspectionId: string, +): Promise { + const map = await getInspectionRosters(db, tenantId, [inspectionId]); + return map.get(inspectionId) ?? EMPTY; +} + +/** Reading helper for batch callers — never returns undefined. */ +export function rosterOf(map: Map, inspectionId: string): InspectionRoster { + return map.get(inspectionId) ?? EMPTY; +} diff --git a/tests/unit/inspections/roster.spec.ts b/tests/unit/inspections/roster.spec.ts new file mode 100644 index 000000000..7e7a98681 --- /dev/null +++ b/tests/unit/inspections/roster.spec.ts @@ -0,0 +1,124 @@ +/** + * The roster accessor — one answer to "who worked this inspection". + * + * The fourth test is the point of the module: it asserts on the SQL, not on the + * result, because an implementation that fell back to `inspections.inspector_id` + * would pass a result-only test while reintroducing the ability for two callers + * to get two different answers. It was proven to go red against a deliberately + * wrong implementation that reads the column (see the comment on that test). + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { getInspectionRoster, getInspectionRosters } from '../../../server/lib/inspection/roster'; + +const TENANT = 'tenant-roster-1'; +const OTHER = 'tenant-roster-2'; +const INSP = 'insp-1'; +const UNASSIGNED = 'insp-2'; +const LEAD = 'user-lead'; +const HELPER = 'user-helper'; + +describe('getInspectionRoster', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + + for (const id of [TENANT, OTHER]) { + await db.insert(schema.tenants).values({ + id, name: id, slug: id, createdAt: new Date(), + }); + } + for (const [id, name] of [[LEAD, 'Dana Okoye'], [HELPER, 'Sam Reyes']] as const) { + await db.insert(schema.users).values({ + id, tenantId: TENANT, email: `${id}@example.com`, passwordHash: 'x', + name, role: 'inspector', createdAt: new Date(), + } as never); + } + await db.insert(schema.inspectionInspectors).values([ + { inspectionId: INSP, userId: LEAD, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + { inspectionId: INSP, userId: HELPER, tenantId: TENANT, role: 'helper', createdAt: new Date() }, + ]); + }); + + afterEach(() => { sqlite.close(); }); + + it('returns the lead and helpers from the link table', async () => { + const roster = await getInspectionRoster(db as never, TENANT, INSP); + expect(roster.lead?.id).toBe(LEAD); + expect(roster.lead?.name).toBe('Dana Okoye'); + expect(roster.helpers.map(h => h.id)).toEqual([HELPER]); + }); + + it('is tenant-scoped', async () => { + // A roster that leaks across tenants leaks who works for whom. + const roster = await getInspectionRoster(db as never, OTHER, INSP); + expect(roster.lead).toBeNull(); + expect(roster.helpers).toEqual([]); + }); + + it('returns a null lead rather than throwing when the roster is empty', async () => { + // Pre-backfill rows exist in production. Callers must render + // "unassigned" rather than crash. + const roster = await getInspectionRoster(db as never, TENANT, UNASSIGNED); + expect(roster.lead).toBeNull(); + expect(roster.helpers).toEqual([]); + }); + + it('does not read inspections.inspector_id', async () => { + // Asserted on the executed SQL, not the result. Proven red first: an + // implementation that resolved the lead from `inspections` returned the + // same roster and passed every other test in this file, which is + // exactly the failure mode this test exists for. + const statements: string[] = []; + const spy = sqlite.prepare.bind(sqlite); + sqlite.prepare = (sql: string) => { statements.push(sql); return spy(sql); }; + + await getInspectionRoster(db as never, TENANT, INSP); + sqlite.prepare = spy; + + expect(statements.length).toBeGreaterThan(0); + expect(statements.join(' ')).not.toMatch(/\binspections\b/); + expect(statements.join(' ')).toMatch(/inspection_inspectors/); + }); + + it('batches many inspections into one query', async () => { + // The calendar and the inspections list render many rows; a per-row + // accessor would turn one query into N. + await db.insert(schema.inspectionInspectors).values([ + { inspectionId: 'insp-3', userId: LEAD, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + ]); + const statements: string[] = []; + const spy = sqlite.prepare.bind(sqlite); + sqlite.prepare = (sql: string) => { statements.push(sql); return spy(sql); }; + + const map = await getInspectionRosters(db as never, TENANT, [INSP, 'insp-3', UNASSIGNED]); + sqlite.prepare = spy; + + expect(statements).toHaveLength(1); + expect(map.get(INSP)?.lead?.id).toBe(LEAD); + expect(map.get('insp-3')?.lead?.id).toBe(LEAD); + expect(map.has(UNASSIGNED)).toBe(false); + }); + + it('asks for nothing when given no ids', async () => { + const statements: string[] = []; + const spy = sqlite.prepare.bind(sqlite); + sqlite.prepare = (sql: string) => { statements.push(sql); return spy(sql); }; + + const map = await getInspectionRosters(db as never, TENANT, []); + sqlite.prepare = spy; + + expect(statements).toHaveLength(0); + expect(map.size).toBe(0); + }); +}); From b7f8564489354bfe407a270f2730ca058d0066c4 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 07:30:16 +0800 Subject: [PATCH 11/29] fix(roster): one authority for "who worked this inspection", and freeze the dead columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three answers to that question were live in code and agreed only by accident: inspections.inspector_id (17 refs) coalesce(lead_inspector_id, inspector_id) (metrics) inspection_inspectors (39 refs) lead_inspector_id is NULL on all 19 production rows and helper_inspector_ids is '[]' on all of them, so the second collapsed to the first and nothing disagreed. The first write of a lead or a helper would have ended that — in metrics, and in collab access control, which decides who may EDIT an inspection. That is the worst place to find out, so this closes it before anything writes one. metrics.ts now attributes work to the roster's lead row. Joined on role = 'lead' deliberately, NOT the whole roster: grouping over every link row would count an inspection once per assigned person and double its revenue the moment a job has a helper. One inspection is attributed to one person here; this moves the SOURCE, not the meaning. can-access.ts now takes the roster instead of three columns, which also makes it fail CLOSED — an inspection with no roster grants access to nobody outside the admin roles. Its malformed-JSON and null-helpers tests are gone along with the column they guarded; a role lives in a column of its own now, so there is no string left to fail to parse. The presence badge label moved too, so it cannot say "Helper" about the person the roster calls the lead. Both collab routes still call getInspection, now for the guard rather than the row: it throws when the inspection is missing or belongs to another tenant, which is the 404. Who may edit comes from the roster. Both dual-write feed sites stop reading the dead columns. bulk.ts and core.ts selected them back out and re-supplied them to syncInspectionAssignments to avoid wiping "team mode" rows — there was never anything to preserve. The columns are frozen, not dropped (D1 cannot rebuild an FK-referenced table), with the evidence in the schema comment and the same treatment as comments.rating_bucket. Neither name is ever reused. Production was reconciled first, since metrics filters out rows with no lead and three inspections had none: 3 rows backfilled under the D1 SOP with a fresh export and bookmark 0000489a-00000000-000050bb-b61cbfa911c7445cd70556d1137c2a73. Applied changes: 3, exactly the predicted count. After: 19 inspections, 19 with a roster, 0 missing, 0 disagreeing. --- scripts/file-size-baseline.json | 2 +- server/api/inspections/bulk.ts | 24 ++------ server/api/inspections/collab.ts | 19 +++--- server/api/inspections/core.ts | 29 +++++---- server/api/metrics.ts | 21 +++++-- server/lib/collab/can-access.ts | 28 ++++++--- server/lib/db/schema/inspection/core.ts | 18 +++++- tests/unit/collab/can-access.spec.ts | 61 ++++++++++++++----- tests/unit/collab/collab-route-auth.spec.ts | 36 ++++++++++- .../unit/metrics/metrics-by-inspector.spec.ts | 14 +++++ 10 files changed, 177 insertions(+), 75 deletions(-) diff --git a/scripts/file-size-baseline.json b/scripts/file-size-baseline.json index e17fefd5b..81cb6e588 100644 --- a/scripts/file-size-baseline.json +++ b/scripts/file-size-baseline.json @@ -30,8 +30,8 @@ "server/api/public-report.ts": 567, "server/api/auth.ts": 564, "server/services/agent/referral.ts": 564, - "server/api/inspections/core.ts": 561, "app/lib/collab/results-binding.ts": 560, + "server/api/inspections/core.ts": 560, "server/api/calendar.ts": 547, "server/services/inspection/inspection-photo.service.ts": 531, "app/components/NewInspectionWizard.tsx": 530, diff --git a/server/api/inspections/bulk.ts b/server/api/inspections/bulk.ts index e7167acb5..493b7ca61 100644 --- a/server/api/inspections/bulk.ts +++ b/server/api/inspections/bulk.ts @@ -252,31 +252,19 @@ const bulkRoutes = createApiRouter() if (body.action === 'assignInspector') { if (!body.inspectorId) throw Errors.BadRequest('inspectorId is required for assignInspector.'); // DB-8: fetch team fields BEFORE the update so the link-table mirror - // carries ALL canonical assignment columns (preserves team-mode lead/ - // helpers that bulk-assign cannot change). + // carries the ids being reassigned. const affected = await db.select({ - id: inspectionTable.id, - leadInspectorId: inspectionTable.leadInspectorId, - helperInspectorIds: inspectionTable.helperInspectorIds, + id: inspectionTable.id, }).from(inspectionTable) .where(and(inArray(inspectionTable.id, body.ids), eq(inspectionTable.tenantId, tenantId))) .all(); await db.update(inspectionTable).set({ inspectorId: body.inspectorId }) .where(and(inArray(inspectionTable.id, body.ids), eq(inspectionTable.tenantId, tenantId))); - // DB-8: re-sync the link table for each reassigned inspection, preserving - // team-mode rows that this bulk operation cannot change. B-29: one - // db.batch round trip for all N resyncs (was a 2N-statement loop). + // DB-8: re-sync the link table for each reassigned inspection. B-29: + // one db.batch round trip for all N resyncs (was a 2N-statement loop). const inspectorId = body.inspectorId; - await syncInspectionAssignmentsBatch(db, tenantId, affected.map(row => { - let helpers: string[] = []; - try { helpers = JSON.parse(row.helperInspectorIds ?? '[]'); } catch { /* malformed legacy JSON */ } - return { - inspectionId: row.id, - inspectorId, - leadInspectorId: row.leadInspectorId, - helperInspectorIds: helpers, - }; - })); + await syncInspectionAssignmentsBatch(db, tenantId, + affected.map(row => ({ inspectionId: row.id, inspectorId }))); auditFromContext(c, 'inspection.bulk_assign', 'inspection', { metadata: { ids: body.ids, inspectorId: body.inspectorId }, diff --git a/server/api/inspections/collab.ts b/server/api/inspections/collab.ts index a074827ff..646dcb349 100644 --- a/server/api/inspections/collab.ts +++ b/server/api/inspections/collab.ts @@ -12,7 +12,7 @@ * 4. Load the inspection via `services.inspection.getInspection(id, tenantId)` * (tenant-scoped service call) — 404 if not found. * 5. Check that the caller is on the inspection (inspectorId / - * leadInspectorId / helperInspectorIds) — 403 otherwise. + * the inspection's roster) — 403 otherwise. * * The WS upgrade route adds the `Upgrade: websocket` (426) check on top. All * routes forward to the INSPECTION_DOC DO keyed by `${tenantId}:${id}`, @@ -30,6 +30,8 @@ import { logger } from '../../lib/logger'; import { CollabRestoreRequestSchema, CollabSnapshotParamSchema } from '../../lib/validations/collab.schema'; import type { HonoConfig } from '../../types/hono'; import { canAccessInspectionCollab } from '../../lib/collab/can-access'; +import { getInspectionRoster } from '../../lib/inspection/roster'; +import { getDrizzle } from '../../lib/route-helpers'; /** * Result of the shared fail-closed auth: either an early `Response` (the caller @@ -63,15 +65,11 @@ async function authorizeCollab(c: Context): Promise { } // ── (4) Inspection lookup — tenant-scoped ───────────────────────────────── - let inspection: { - id: string; - inspectorId: string | null; - leadInspectorId: string | null; - helperInspectorIds: string; - }; + // Called for the guard, not the row: it throws when the inspection does not + // exist OR belongs to another tenant, which is the 404 below. Who may edit + // comes from the roster in step 5. try { - const out = await c.var.services.inspection.getInspection(id, tenantId); - inspection = out.inspection; + await c.var.services.inspection.getInspection(id, tenantId); } catch (err) { logger.error( 'collab: inspection lookup failed', @@ -83,7 +81,8 @@ async function authorizeCollab(c: Context): Promise { // ── (5) Edit-permission check (mirrors presence route) ──────────────────── const userRole = c.get('userRole') as string | undefined; - const allowed = canAccessInspectionCollab(inspection, { id: userId, role: userRole ?? '' }); + const roster = await getInspectionRoster(getDrizzle(c), tenantId, id); + const allowed = canAccessInspectionCollab(roster, { id: userId, role: userRole ?? '' }); if (!allowed) { return { ok: false, response: new Response('forbidden', { status: 403 }) }; diff --git a/server/api/inspections/core.ts b/server/api/inspections/core.ts index bfbe71e3a..00dbca608 100644 --- a/server/api/inspections/core.ts +++ b/server/api/inspections/core.ts @@ -12,6 +12,7 @@ import { requireRole } from '../../lib/middleware/rbac'; import { auditFromContext } from '../../lib/audit'; import { Errors } from '../../lib/errors'; import { canAccessInspectionCollab } from '../../lib/collab/can-access'; +import { getInspectionRoster } from '../../lib/inspection/roster'; import { createApiResponseSchema, SuccessResponseSchema } from '../../lib/validations/shared.schema'; import { InspectionSchema, CreateInspectionSchema, UpdateInspectionSchema } from '../../lib/validations/inspection.schema'; import { CreateInspectionFromWizardSchema } from '../../lib/validations/wizard.schema'; @@ -358,18 +359,13 @@ const coreRoutes = createApiRouter() await db.update(inspectionTable).set(updateValues).where(and(eq(inspectionTable.id, id), eq(inspectionTable.tenantId, tenantId))); } - // DB-8: re-sync link table when inspectorId is explicitly updated. - // DB-8: mirror ALL canonical assignment columns — PATCH can only change - // inspectorId, so preserve the pre-patch team-mode fields (leadInspectorId, - // helperInspectorIds) from the fetched row so the link table stays a faithful - // mirror of post-patch canonical state and team-mode rows are not wiped. + // DB-8: re-sync the link table when inspectorId is explicitly updated. + // The pre-patch lead/helper columns used to be read back and re-supplied + // here to avoid wiping "team mode" rows. They are frozen dead and were + // NULL / '[]' on every row, so there was never anything to preserve. if ('inspectorId' in body) { - let helpers: string[] = []; - try { helpers = JSON.parse(inspection.helperInspectorIds ?? '[]'); } catch { /* malformed legacy JSON -> no helpers */ } await syncInspectionAssignments(db, tenantId, id, { - inspectorId: body.inspectorId ?? null, - leadInspectorId: inspection.leadInspectorId, - helperInspectorIds: helpers, + inspectorId: body.inspectorId ?? null, }); } @@ -525,21 +521,24 @@ const coreRoutes = createApiRouter() // with its landing page, so there is no second identity to try.) if (!userId || !tenantId) return new Response('unauthorized', { status: 401 }); - let ins; + // For the guard, not the row: throws when the inspection is missing or + // belongs to another tenant. Who may edit comes from the roster below. try { - const out = await c.var.services.inspection.getInspection(id, tenantId); - ins = out.inspection; + await c.var.services.inspection.getInspection(id, tenantId); } catch { return new Response('not found', { status: 404 }); } const userRole = c.get('userRole') as string | undefined; - if (!canAccessInspectionCollab(ins, { id: userId, role: userRole ?? '' })) { + const roster = await getInspectionRoster(getDrizzle(c), tenantId, id); + if (!canAccessInspectionCollab(roster, { id: userId, role: userRole ?? '' })) { return new Response('forbidden', { status: 403 }); } const attachUserId = userId; - const attachName = ins.inspectorId === userId ? 'Inspector' : 'Helper'; + // Label from the roster too, so the presence badge cannot say "Helper" + // about the person the roster calls the lead. + const attachName = roster.lead?.id === userId ? 'Inspector' : 'Helper'; const doId = c.env.INSPECTION_PRESENCE.idFromName(id); const stub = c.env.INSPECTION_PRESENCE.get(doId); diff --git a/server/api/metrics.ts b/server/api/metrics.ts index 3b912465b..d9f4ac4af 100644 --- a/server/api/metrics.ts +++ b/server/api/metrics.ts @@ -2,7 +2,7 @@ import { createRoute } from '@hono/zod-openapi'; import { createApiRouter } from '../lib/openapi-router'; import { requireRole } from '../lib/middleware/rbac'; import { MetricsQuerySchema, MetricsApiResponseSchema } from '../lib/validations/metrics.schema'; -import { inspections, inspectionServices, contacts, users, reportVersions } from '../lib/db/schema'; +import { inspections, inspectionServices, contacts, users, reportVersions, inspectionInspectors } from '../lib/db/schema'; import { eq, and, gte, lte, sql } from 'drizzle-orm'; import { withMcpMetadata } from "../lib/route-metadata-standards"; import { sumEffectivePriceCentsSql } from '../lib/effective-price.sql'; @@ -107,13 +107,21 @@ const metricsRoutes = createApiRouter() // Per-inspector productivity (IA-63) — multi-inspector companies need count, // revenue, and turnaround per inspector for team management + commission. - // "Who did this inspection" authority: lead_inspector_id, falling back to - // inspector_id (schema: lead is primary, NULL ⇒ inspector_id). Turnaround = + // "Who did this inspection" authority: the ROSTER's lead row. It used to be + // coalesce(lead_inspector_id, inspector_id), which agreed with everything + // else only because lead_inspector_id is NULL on every row — the first write + // of a lead would have made these numbers disagree with the rest of the app. + // + // Joined on role = 'lead' deliberately, NOT the whole roster: grouping over + // every link row would count an inspection once per assigned person and + // double its revenue the moment a job has a helper. One inspection is + // attributed to one person here; this change moves the SOURCE, not the + // meaning. Turnaround = // first publish (report_versions v1 published_at) − inspection date, in days; // the LEFT JOIN keeps unpublished inspections in the count while avg() skips // their NULL turnaround (so an all-unpublished inspector reports null, not 0). // Explicit column projection keeps well under D1's 100-column result cap. - const inspectorKey = sql`coalesce(${inspections.leadInspectorId}, ${inspections.inspectorId})`; + const inspectorKey = inspectionInspectors.userId; const byInspector = await db.select({ inspectorId: inspectorKey, inspectorName: users.name, @@ -127,6 +135,11 @@ const metricsRoutes = createApiRouter() eq(reportVersions.tenantId, inspections.tenantId), eq(reportVersions.versionNumber, 1), )) + .leftJoin(inspectionInspectors, and( + eq(inspectionInspectors.inspectionId, inspections.id), + eq(inspectionInspectors.tenantId, inspections.tenantId), + eq(inspectionInspectors.role, 'lead'), + )) .leftJoin(users, eq(users.id, inspectorKey)) .where(and( eq(inspections.tenantId, tenantId), diff --git a/server/lib/collab/can-access.ts b/server/lib/collab/can-access.ts index 6c1e33add..02fe3eebc 100644 --- a/server/lib/collab/can-access.ts +++ b/server/lib/collab/can-access.ts @@ -1,18 +1,28 @@ +import type { InspectionRoster } from '../inspection/roster'; + // Roles that can edit any inspection in the tenant (mirror the editor loader's // tenant-scoped authorization). Inspector-class users still need assignment. const ADMIN_ROLES = new Set(['admin', 'manager']); +/** + * May this user edit this inspection collaboratively? + * + * Reads the ROSTER, not `inspections.lead_inspector_id` / `helper_inspector_ids`. + * Those two columns are NULL and `'[]'` on every production row, so this check + * was effectively "are you inspections.inspector_id" while claiming to honour a + * lead and a helper list. The first write of either would have made collab + * access disagree with every other answer to "who works this inspection" — and + * disagreeing about who may EDIT is the worst place to discover that. + * + * Fails closed by construction: an inspection with no roster grants access to + * nobody outside the admin roles above, which is the right direction for an + * authorization check. + */ export function canAccessInspectionCollab( - inspection: { inspectorId: string | null; leadInspectorId: string | null; helperInspectorIds: string | null }, + roster: InspectionRoster, user: { id: string; role: string }, ): boolean { if (ADMIN_ROLES.has(user.role)) return true; - let helpers: string[] = []; - try { - const p = JSON.parse(inspection.helperInspectorIds ?? '[]'); - if (Array.isArray(p)) helpers = p as string[]; - } catch { /* malformed — treat as no helpers */ } - return inspection.inspectorId === user.id - || inspection.leadInspectorId === user.id - || helpers.includes(user.id); + return roster.lead?.id === user.id + || roster.helpers.some((h) => h.id === user.id); } diff --git a/server/lib/db/schema/inspection/core.ts b/server/lib/db/schema/inspection/core.ts index f0f398c55..2c4b0dc3d 100644 --- a/server/lib/db/schema/inspection/core.ts +++ b/server/lib/db/schema/inspection/core.ts @@ -115,13 +115,27 @@ export const inspections = sqliteTable('inspections', { conciergeStatus: text('concierge_status'), // Design System 0520 M3 — TeamMode + multi-inspector (subsystem B, phase 1). // teamMode = boolean flag enabling team UI (TeamBanner / RosterPopover). - // leadInspectorId = primary inspector. NULL ⇒ falls back to inspectorId above. - // helperInspectorIds = JSON array of additional inspectors with edit access. + // leadInspectorId = -- DEAD (2026-08-03): replaced by inspection_inspectors. + // helperInspectorIds = -- DEAD (2026-08-03): replaced by inspection_inspectors. // dataVersion = monotonic counter; bumped on every successful field write // (see InspectionService.patchItem) for offline-queue staleness checks. // Superseded by the Yjs state vector under collab editing (#181); // column frozen — stop writes once the DO is the authority. teamMode: integer('is_team_mode', { mode: 'boolean' }).notNull().default(false), + // -- DEAD (2026-08-03): replaced by inspection_inspectors. + // + // Zero production rows ever carried either: lead_inspector_id was NULL on all + // 19 inspections and helper_inspector_ids was '[]' on all of them. They were + // still READ — metrics attributed work via coalesce(lead_inspector_id, + // inspector_id) and collab access granted editing to the lead and the helpers + // JSON — so the two agreed with the rest of the app only by being empty. The + // first write of either would have made "who worked this" and, worse, "who may + // EDIT this" answer differently from everywhere else. + // + // A JSON array of ids cannot be indexed, joined, or carry a role beyond its + // position, which is what the link table exists to fix. No reads, no writes. + // Columns frozen — D1 cannot rebuild an FK-referenced table — and neither name + // is ever reused. Same treatment as `comments.rating_bucket`. leadInspectorId: text('lead_inspector_id'), helperInspectorIds: text('helper_inspector_ids').notNull().default('[]'), dataVersion: integer('data_version').notNull().default(0), diff --git a/tests/unit/collab/can-access.spec.ts b/tests/unit/collab/can-access.spec.ts index 945d0c3b2..20fee1f2b 100644 --- a/tests/unit/collab/can-access.spec.ts +++ b/tests/unit/collab/can-access.spec.ts @@ -1,23 +1,54 @@ +/** + * Collab edit permission, now decided from the ROSTER. + * + * It used to read `inspections.inspector_id`, `lead_inspector_id` and the + * `helper_inspector_ids` JSON. The latter two are NULL and '[]' on every + * production row, so the check was effectively "are you inspector_id" while + * claiming to honour a lead and a helper list — and the first write of either + * would have made who-may-EDIT disagree with every other answer to "who works + * this inspection". + * + * The malformed-JSON and null-helpers cases that used to live here are gone + * along with the column they guarded: a role now lives in a column of its own, + * so there is no string left to fail to parse. + */ import { describe, it, expect } from 'vitest'; import { canAccessInspectionCollab } from '../../../server/lib/collab/can-access'; +import type { InspectionRoster } from '../../../server/lib/inspection/roster'; -const base = { inspectorId: 'u-insp', leadInspectorId: null, helperInspectorIds: '[]' }; +const member = (id: string) => ({ id, name: id, email: `${id}@example.com` }); +const roster = (lead: string | null, helpers: string[] = []): InspectionRoster => ({ + lead: lead ? member(lead) : null, + helpers: helpers.map(member), +}); + +const LED_BY_INSP = roster('u-insp'); describe('canAccessInspectionCollab', () => { it('admin not assigned is allowed', () => - expect(canAccessInspectionCollab(base, { id: 'u-admin', role: 'admin' })).toBe(true)); + expect(canAccessInspectionCollab(LED_BY_INSP, { id: 'u-admin', role: 'admin' })).toBe(true)); + it('manager not assigned is allowed', () => - expect(canAccessInspectionCollab(base, { id: 'u-mgr', role: 'manager' })).toBe(true)); - it('assigned inspector allowed', () => - expect(canAccessInspectionCollab(base, { id: 'u-insp', role: 'inspector' })).toBe(true)); - it('lead inspector allowed', () => - expect(canAccessInspectionCollab({ ...base, leadInspectorId: 'u-lead' }, { id: 'u-lead', role: 'inspector' })).toBe(true)); - it('unassigned inspector denied', () => - expect(canAccessInspectionCollab(base, { id: 'u-other', role: 'inspector' })).toBe(false)); - it('helper allowed', () => - expect(canAccessInspectionCollab({ ...base, helperInspectorIds: '["u-h"]' }, { id: 'u-h', role: 'inspector' })).toBe(true)); - it('malformed helpers JSON = no helpers', () => - expect(canAccessInspectionCollab({ ...base, helperInspectorIds: 'not-json' }, { id: 'u-other', role: 'inspector' })).toBe(false)); - it('null helpers = no helpers', () => - expect(canAccessInspectionCollab({ ...base, helperInspectorIds: null }, { id: 'u-other', role: 'inspector' })).toBe(false)); + expect(canAccessInspectionCollab(LED_BY_INSP, { id: 'u-mgr', role: 'manager' })).toBe(true)); + + it('the lead is allowed', () => + expect(canAccessInspectionCollab(LED_BY_INSP, { id: 'u-insp', role: 'inspector' })).toBe(true)); + + it('a helper is allowed', () => + expect(canAccessInspectionCollab(roster('u-lead', ['u-h']), { id: 'u-h', role: 'inspector' })).toBe(true)); + + it('an unassigned inspector is denied', () => + expect(canAccessInspectionCollab(LED_BY_INSP, { id: 'u-other', role: 'inspector' })).toBe(false)); + + it('an empty roster grants nobody outside the admin roles', () => { + // Fails CLOSED. An inspection whose roster has not been written must not + // become editable by whoever asks — the safe direction for an auth check. + expect(canAccessInspectionCollab(roster(null), { id: 'u-insp', role: 'inspector' })).toBe(false); + expect(canAccessInspectionCollab(roster(null), { id: 'u-admin', role: 'admin' })).toBe(true); + }); + + it('a helper on ANOTHER inspection is denied', () => { + // The roster is fetched per inspection, so membership is never global. + expect(canAccessInspectionCollab(roster('u-lead', ['u-h']), { id: 'u-h2', role: 'inspector' })).toBe(false); + }); }); diff --git a/tests/unit/collab/collab-route-auth.spec.ts b/tests/unit/collab/collab-route-auth.spec.ts index d197f7a2d..a9f805414 100644 --- a/tests/unit/collab/collab-route-auth.spec.ts +++ b/tests/unit/collab/collab-route-auth.spec.ts @@ -34,6 +34,21 @@ function makeMockDoNamespace(doFetchResponse?: Response) { // ── App builder ─────────────────────────────────────────────────────────────── +// The route now resolves who may edit from the ROSTER rather than from the +// inspection row's lead/helper columns. Mock that one module: this spec's +// subject is the route's 403/200 behaviour, not the SQL that fetches a roster +// (which tests/unit/inspections/roster.spec.ts pins, including on the SQL). +const getInspectionRoster = vi.fn(); +vi.mock('../../../server/lib/inspection/roster', () => ({ + getInspectionRoster: (...args: unknown[]) => getInspectionRoster(...args), +})); + +const member = (id: string) => ({ id, name: id, email: `${id}@example.com` }); +const rosterOf = (lead: string | null, helpers: string[] = []) => ({ + lead: lead ? member(lead) : null, + helpers: helpers.map(member), +}); + interface BuildAppOptions { /** If null, simulate no JWT (middleware never ran). */ tenantId?: string | null; @@ -47,6 +62,8 @@ interface BuildAppOptions { tenantId?: string; } | 'not_found'; doNamespace?: ReturnType; + /** Who the roster says works this inspection. */ + roster?: { lead: string | null; helpers?: string[] }; } function buildApp(opts: BuildAppOptions = {}) { @@ -61,10 +78,27 @@ function buildApp(opts: BuildAppOptions = {}) { tenantId: 't1', }, doNamespace, + roster, } = opts; const mockDo = doNamespace ?? makeMockDoNamespace(); + // Default the roster from the legacy lead/helper override so each existing + // case keeps asserting the same thing it always did. + const legacyHelpers: string[] = (() => { + if (inspectionOverride === 'not_found') return []; + try { return JSON.parse(inspectionOverride.helperInspectorIds ?? '[]') as string[]; } + catch { return []; } + })(); + const effectiveRoster = roster ?? { + lead: inspectionOverride === 'not_found' + ? null + : (inspectionOverride.leadInspectorId ?? inspectionOverride.inspectorId ?? null), + helpers: legacyHelpers, + }; + getInspectionRoster.mockReset(); + getInspectionRoster.mockResolvedValue(rosterOf(effectiveRoster.lead, effectiveRoster.helpers ?? [])); + // Build a fake inspection object from the override. const fakeInspection = inspectionOverride === 'not_found' @@ -93,7 +127,7 @@ function buildApp(opts: BuildAppOptions = {}) { // Simulate what the global JWT middleware + DI middleware set. app.use('*', async (c, next) => { // Set env bindings before any handler sees it (mirrors repair-builder harness). - c.env = { INSPECTION_DOC: mockDo } as unknown as HonoConfig['Bindings']; + c.env = { INSPECTION_DOC: mockDo, DB: {} } as unknown as HonoConfig['Bindings']; if (tenantId !== null) c.set('tenantId', tenantId); if (userId !== null) c.set('user', { sub: userId } as never); if (userRole) c.set('userRole', userRole as never); diff --git a/tests/unit/metrics/metrics-by-inspector.spec.ts b/tests/unit/metrics/metrics-by-inspector.spec.ts index 0079bc0b3..7ec6c9df5 100644 --- a/tests/unit/metrics/metrics-by-inspector.spec.ts +++ b/tests/unit/metrics/metrics-by-inspector.spec.ts @@ -65,6 +65,16 @@ describe('GET /api/metrics — byInspector (IA-63)', () => { // Bob — attributed via lead_inspector_id, which must win over inspector_id. { id: 'i-b1', tenantId: TENANT, propertyAddress: '3 Oak', date: today, status: 'completed', paymentStatus: 'paid', price: 20000, inspectorId: U1, leadInspectorId: U2, createdAt: new Date() }, { id: 'i-b2', tenantId: TENANT, propertyAddress: '4 Oak', date: today, status: 'completed', paymentStatus: 'paid', price: 5000, inspectorId: U1, leadInspectorId: U2, createdAt: new Date() }, + ]); + // Attribution now comes from the roster's lead row, not from + // coalesce(lead_inspector_id, inspector_id). Same intent as the columns + // above: a1/a2 are Alice's, b1/b2 are Bob's even though Alice is the + // inspector_id on them. + await db.insert(schema.inspectionInspectors).values([ + { inspectionId: 'i-a1', userId: U1, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + { inspectionId: 'i-a2', userId: U1, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + { inspectionId: 'i-b1', userId: U2, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + { inspectionId: 'i-b2', userId: U2, tenantId: TENANT, role: 'lead', createdAt: new Date() }, ] as never); const res = await buildApp().request('/api/metrics?from=2024-01-01&to=2028-12-31', {}, ENV, CTX); @@ -88,6 +98,10 @@ describe('GET /api/metrics — byInspector (IA-63)', () => { await db.insert(schema.inspections).values([ { id: 'pub', tenantId: TENANT, propertyAddress: '1 Main', date, status: 'delivered', paymentStatus: 'paid', price: 10000, inspectorId: U1, createdAt: new Date() }, { id: 'unpub', tenantId: TENANT, propertyAddress: '2 Oak', date, status: 'completed', paymentStatus: 'unpaid', price: 10000, inspectorId: U2, createdAt: new Date() }, + ]); + await db.insert(schema.inspectionInspectors).values([ + { inspectionId: 'pub', userId: U1, tenantId: TENANT, role: 'lead', createdAt: new Date() }, + { inspectionId: 'unpub', userId: U2, tenantId: TENANT, role: 'lead', createdAt: new Date() }, ] as never); // Alice's inspection published 3 days after the inspection date. await db.insert(schema.reportVersions).values({ From 4c76ee5c213fb8face8c6823f533d87594cdbdb6 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 08:10:50 +0800 Subject: [PATCH 12/29] feat(services): soft-delete inspection service lines before anything points at them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 0 of the per-deliverable-reports plan, numbered 0 because order matters: it has to land before the first `reports` row points at a billing line. Reversed, there is a window in which a client changing scope at the door silently orphans a report. A scope change at the door is routine — add a sewer scope, drop the pool inspection, decline the radon — and it was a hard delete. Harmless only while nothing hung off a line. Once a `reports` row or a pay split does, Schema Rules forbid the foreign key that would catch it, so the delete leaves dangling rows and nothing surfaces them. The invoice disagrees too: invoices.amountCents outranks the line sum, so removing a line does not change what was billed while a split keeps paying against it. The column is the easy half. The half that gets forgotten is that EVERY reader must filter on it, and getEffectivePriceCents is a pure function over already-fetched lines — so the filter belongs at each fetch site, not in the helper. Grepped first, then filtered, all of them: effective-price.sql.ts the money chain itself api/invoices.ts a declined line must not appear on the invoice api/metrics.ts revenue by service inspection-analytics feeds getEffectivePriceCents per inspection automation/conditions or an automation keeps firing for a declined service service.service.ts the live list every surface reads Re-adding a declined service REACTIVATES the original row rather than inserting a second one. A reports row or a pay split may already point at that id, and a fresh row would strand them while the client is billed for the line they see. The REFUSAL half of the guard is deliberately absent. Neither `reports` nor `inspection_service_pay_splits` exists yet, so a check against them would be a function that always returns "nothing blocks this" — a gate that passes vacuously, which this repo has shipped before. Each of those tasks adds its own clause, with a test, when it creates the table. IA-26's inspector-qualification pair moves to service/qualification.ts: the file crossed the 400-line ratchet, and which inspectors MAY perform a catalogue service is a separate concern from what it costs. Thin delegates keep every caller unchanged. Migration 0026 is a single appended ADD COLUMN, no table rebuild. db:check clean. One pre-existing spec updated: removal is a soft delete now, so it asserts the row survives and leaves the live list rather than disappearing. --- migrations/0026_charming_earthquake.sql | 1 + migrations/meta/0026_snapshot.json | 10134 ++++++++++++++++ migrations/meta/_journal.json | 7 + scripts/file-size-baseline.json | 2 +- server/api/invoices.ts | 2 + server/api/metrics.ts | 3 +- server/lib/db/schema/inspection/services.ts | 15 + server/lib/effective-price.sql.ts | 6 +- server/services/automation/conditions.ts | 7 +- .../inspection-analytics.service.ts | 4 +- server/services/service.service.ts | 134 +- server/services/service/qualification.ts | 94 + .../inspection-service-lines.spec.ts | 10 +- .../service-line-lifecycle.spec.ts | 127 + 14 files changed, 10458 insertions(+), 88 deletions(-) create mode 100644 migrations/0026_charming_earthquake.sql create mode 100644 migrations/meta/0026_snapshot.json create mode 100644 server/services/service/qualification.ts create mode 100644 tests/unit/inspections/service-line-lifecycle.spec.ts diff --git a/migrations/0026_charming_earthquake.sql b/migrations/0026_charming_earthquake.sql new file mode 100644 index 000000000..0962a113d --- /dev/null +++ b/migrations/0026_charming_earthquake.sql @@ -0,0 +1 @@ +ALTER TABLE `inspection_services` ADD `is_active` integer DEFAULT true NOT NULL; \ No newline at end of file diff --git a/migrations/meta/0026_snapshot.json b/migrations/meta/0026_snapshot.json new file mode 100644 index 000000000..d55513d10 --- /dev/null +++ b/migrations/meta/0026_snapshot.json @@ -0,0 +1,10134 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a34d771b-dbd8-4196-9ebb-b27025a12a02", + "prevId": "8ea55fc4-03d1-47a8-8359-46a911957fb9", + "tables": { + "agreement_requests": { + "name": "agreement_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signature_base64": { + "name": "inspector_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signed_at": { + "name": "inspector_signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_user_id": { + "name": "inspector_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_snapshot": { + "name": "content_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agreement_requests_token_unique": { + "name": "agreement_requests_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_agreement_requests_verify_token": { + "name": "idx_agreement_requests_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + }, + "idx_agreement_requests_tenant": { + "name": "idx_agreement_requests_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_agreement_requests_inspection": { + "name": "idx_agreement_requests_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_agreement_requests_token_hash": { + "name": "idx_agreement_requests_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agreement_requests_tenant_id_tenants_id_fk": { + "name": "agreement_requests_tenant_id_tenants_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspection_id_inspections_id_fk": { + "name": "agreement_requests_inspection_id_inspections_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_agreement_id_agreements_id_fk": { + "name": "agreement_requests_agreement_id_agreements_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspector_user_id_users_id_fk": { + "name": "agreement_requests_inspector_user_id_users_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "users", + "columnsFrom": [ + "inspector_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreement_signers": { + "name": "agreement_signers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_of": { + "name": "on_behalf_of", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_disclaimer": { + "name": "on_behalf_disclaimer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reminded_at": { + "name": "last_reminded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_agreement_signers_tenant_request": { + "name": "idx_agreement_signers_tenant_request", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": false + }, + "idx_agreement_signers_request_email": { + "name": "idx_agreement_signers_request_email", + "columns": [ + "request_id", + "email" + ], + "isUnique": true + }, + "idx_agreement_signers_token_hash": { + "name": "idx_agreement_signers_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreements": { + "name": "agreements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agreements_tenant": { + "name": "idx_agreements_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agreements_tenant_id_tenants_id_fk": { + "name": "agreements_tenant_id_tenants_id_fk", + "tableFrom": "agreements", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_logs": { + "name": "automation_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_key": { + "name": "recipient_role_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_contact_id": { + "name": "recipient_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notice_id": { + "name": "notice_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_logs_pending": { + "name": "idx_automation_logs_pending", + "columns": [ + "tenant_id", + "status", + "send_at" + ], + "isUnique": false + }, + "idx_automation_logs_insp": { + "name": "idx_automation_logs_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_automation_logs_event": { + "name": "uq_automation_logs_event", + "columns": [ + "automation_id", + "inspection_id", + "event_id", + "channel", + "recipient" + ], + "isUnique": true, + "where": "event_id IS NOT NULL" + } + }, + "foreignKeys": { + "automation_logs_tenant_id_tenants_id_fk": { + "name": "automation_logs_tenant_id_tenants_id_fk", + "tableFrom": "automation_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_kind": { + "name": "recipient_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_profile_id": { + "name": "recipient_role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delay_minutes": { + "name": "delay_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conditions": { + "name": "conditions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"email\"]'" + }, + "sms_body": { + "name": "sms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "in_app_template_id": { + "name": "in_app_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automations_tenant": { + "name": "idx_automations_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_tenant_id_tenants_id_fk": { + "name": "automations_tenant_id_tenants_id_fk", + "tableFrom": "automations", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability": { + "name": "availability", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "day_of_week": { + "name": "day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_availability_inspector": { + "name": "idx_availability_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_availability_window_unique": { + "name": "idx_availability_window_unique", + "columns": [ + "inspector_id", + "day_of_week", + "start_time" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_tenant_id_tenants_id_fk": { + "name": "availability_tenant_id_tenants_id_fk", + "tableFrom": "availability", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_inspector_id_users_id_fk": { + "name": "availability_inspector_id_users_id_fk", + "tableFrom": "availability", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability_overrides": { + "name": "availability_overrides", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_available": { + "name": "is_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transparency": { + "name": "transparency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_avail_overrides_insp": { + "name": "idx_avail_overrides_insp", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_avail_overrides_block_unique": { + "name": "idx_avail_overrides_block_unique", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": true, + "where": "is_available = 0 AND source IS NULL" + }, + "uq_avail_overrides_external": { + "name": "uq_avail_overrides_external", + "columns": [ + "inspector_id", + "source", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_overrides_tenant_id_tenants_id_fk": { + "name": "availability_overrides_tenant_id_tenants_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_overrides_inspector_id_users_id_fk": { + "name": "availability_overrides_inspector_id_users_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_blocks": { + "name": "calendar_blocks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_all_day": { + "name": "is_all_day", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_calendar_blocks_tenant_user_date": { + "name": "idx_calendar_blocks_tenant_user_date", + "columns": [ + "tenant_id", + "user_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connection_read_calendars": { + "name": "calendar_connection_read_calendars", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_calendar_id": { + "name": "external_calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_role": { + "name": "access_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_conn_read_cal": { + "name": "uq_conn_read_cal", + "columns": [ + "connection_id", + "external_calendar_id" + ], + "isUnique": true + }, + "idx_conn_read_cal_tenant": { + "name": "idx_conn_read_cal_tenant", + "columns": [ + "tenant_id", + "connection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connections": { + "name": "calendar_connections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_dek_enc": { + "name": "credentials_dek_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_calendar_connections_user_provider": { + "name": "uq_calendar_connections_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": true + }, + "idx_calendar_connections_tenant_user": { + "name": "idx_calendar_connections_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comment_usage": { + "name": "comment_usage", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_id": { + "name": "comment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_comment_usage_user_last_used": { + "name": "idx_comment_usage_user_last_used", + "columns": [ + "tenant_id", + "user_id", + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comment_usage_comment_id_comments_id_fk": { + "name": "comment_usage_comment_id_comments_id_fk", + "tableFrom": "comment_usage", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_usage_tenant_id_user_id_comment_id_pk": { + "columns": [ + "tenant_id", + "user_id", + "comment_id" + ], + "name": "comment_usage_tenant_id_user_id_comment_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_bucket": { + "name": "rating_bucket", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section": { + "name": "section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_ids": { + "name": "section_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_labels": { + "name": "item_labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_code": { + "name": "trigger_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_keywords": { + "name": "search_keywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repair_summary": { + "name": "repair_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_min_cents": { + "name": "estimate_min_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_max_cents": { + "name": "estimate_max_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recommended_contractor_type_id": { + "name": "recommended_contractor_type_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_comments_tenant": { + "name": "idx_comments_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_comments_rating_bucket": { + "name": "idx_comments_rating_bucket", + "columns": [ + "tenant_id", + "rating_bucket" + ], + "isUnique": false + }, + "idx_comments_library_id": { + "name": "idx_comments_library_id", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comments_tenant_id_tenants_id_fk": { + "name": "comments_tenant_id_tenants_id_fk", + "tableFrom": "comments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "commercial_subtypes": { + "name": "commercial_subtypes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_disabled": { + "name": "is_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_commercial_subtypes_tenant_name": { + "name": "idx_commercial_subtypes_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "commercial_subtypes_tenant_id_tenants_id_fk": { + "name": "commercial_subtypes_tenant_id_tenants_id_fk", + "tableFrom": "commercial_subtypes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "concierge_confirm_tokens": { + "name": "concierge_confirm_tokens", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_concierge_tokens_expiry": { + "name": "idx_concierge_tokens_expiry", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_concierge_confirm_token_hash": { + "name": "idx_concierge_confirm_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "concierge_confirm_tokens_inspection_id_inspections_id_fk": { + "name": "concierge_confirm_tokens_inspection_id_inspections_id_fk", + "tableFrom": "concierge_confirm_tokens", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_role_profiles": { + "name": "contact_role_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability_overrides": { + "name": "capability_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_crp_tenant": { + "name": "idx_crp_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_crp_tenant_key": { + "name": "uq_crp_tenant_key", + "columns": [ + "tenant_id", + "key" + ], + "isUnique": true, + "where": "is_active = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agency": { + "name": "agency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_user_id": { + "name": "agent_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_linked_at": { + "name": "agent_linked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_revoked_at": { + "name": "agent_revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_contacts_type": { + "name": "idx_contacts_type", + "columns": [ + "tenant_id", + "type" + ], + "isUnique": false + }, + "idx_contacts_tenant": { + "name": "idx_contacts_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_contacts_tenant_email": { + "name": "uq_contacts_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "email IS NOT NULL AND archived_at IS NULL" + }, + "uq_contacts_tenant_agent_user": { + "name": "uq_contacts_tenant_agent_user", + "columns": [ + "tenant_id", + "agent_user_id" + ], + "isUnique": true, + "where": "agent_user_id IS NOT NULL AND archived_at IS NULL" + }, + "idx_contacts_agent_user": { + "name": "idx_contacts_agent_user", + "columns": [ + "agent_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_tenant_id_tenants_id_fk": { + "name": "contacts_tenant_id_tenants_id_fk", + "tableFrom": "contacts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contractor_types": { + "name": "contractor_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_contractor_types_tenant": { + "name": "idx_contractor_types_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_items": { + "name": "cost_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "building_id": { + "name": "building_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_index": { + "name": "instance_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_id": { + "name": "unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_method": { + "name": "cost_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uom": { + "name": "uom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_cost_cents": { + "name": "unit_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lump_sum_cents": { + "name": "lump_sum_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eul": { + "name": "eul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_age": { + "name": "eff_age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rul": { + "name": "rul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suggested_remedy": { + "name": "suggested_remedy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_ref": { + "name": "section_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_ref": { + "name": "photo_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_cost_items_tenant_inspection": { + "name": "idx_cost_items_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_cost_items_finding_key": { + "name": "idx_cost_items_finding_key", + "columns": [ + "finding_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "defect_categories": { + "name": "defect_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6b7280'" + }, + "is_summary_driver": { + "name": "is_summary_driver", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_defect_categories_tenant": { + "name": "idx_defect_categories_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "discount_codes": { + "name": "discount_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uses_count": { + "name": "uses_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_discount_codes_tenant": { + "name": "idx_discount_codes_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_discount_codes_code_tenant": { + "name": "uq_discount_codes_code_tenant", + "columns": [ + "upper(code)", + "tenant_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "discount_codes_tenant_id_tenants_id_fk": { + "name": "discount_codes_tenant_id_tenants_id_fk", + "tableFrom": "discount_codes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "document_review_items": { + "name": "document_review_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_requested": { + "name": "is_requested", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_received": { + "name": "is_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_reviewed": { + "name": "is_reviewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_na": { + "name": "is_na", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_doc_review_inspection": { + "name": "idx_doc_review_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_doc_review_item": { + "name": "uq_doc_review_item", + "columns": [ + "inspection_id", + "document_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_suppressions": { + "name": "email_suppressions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_provider": { + "name": "source_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_email_suppressions_email": { + "name": "idx_email_suppressions_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "erasure_log": { + "name": "erasure_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_email": { + "name": "subject_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_basis": { + "name": "identity_basis", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "decisions_json": { + "name": "decisions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retained_count": { + "name": "retained_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "anonymized_count": { + "name": "anonymized_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_count": { + "name": "deleted_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "response_note": { + "name": "response_note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_erasure_log_tenant": { + "name": "idx_erasure_log_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "esign_audit_logs": { + "name": "esign_audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_esign_audit_logs_request": { + "name": "idx_esign_audit_logs_request", + "columns": [ + "tenant_id", + "request_id", + "created_at" + ], + "isUnique": false + }, + "idx_esign_audit_logs_event_dedup": { + "name": "idx_esign_audit_logs_event_dedup", + "columns": [ + "tenant_id", + "request_id", + "event" + ], + "isUnique": true, + "where": "event NOT LIKE 'signer.%'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "event_types": { + "name": "event_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_duration_min": { + "name": "default_duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "default_price_cents": { + "name": "default_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6366f1'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_event_types_tenant_slug": { + "name": "uq_event_types_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "event_types_tenant_id_tenants_id_fk": { + "name": "event_types_tenant_id_tenants_id_fk", + "tableFrom": "event_types", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_access_tokens": { + "name": "inspection_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_iat_token": { + "name": "idx_iat_token", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_iat_inspection": { + "name": "idx_iat_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_iat_recipient": { + "name": "idx_iat_recipient", + "columns": [ + "inspection_id", + "recipient_email" + ], + "isUnique": true + }, + "idx_iat_token_hash": { + "name": "idx_iat_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_access_tokens_tenant_id_tenants_id_fk": { + "name": "inspection_access_tokens_tenant_id_tenants_id_fk", + "tableFrom": "inspection_access_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_events": { + "name": "inspection_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type_id": { + "name": "event_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'scheduled'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "results_received_at": { + "name": "results_received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gcal_event_id": { + "name": "gcal_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_events_scheduled": { + "name": "idx_inspection_events_scheduled", + "columns": [ + "tenant_id", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_events_inspection": { + "name": "idx_inspection_events_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_events_tenant_id_tenants_id_fk": { + "name": "inspection_events_tenant_id_tenants_id_fk", + "tableFrom": "inspection_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspection_id_inspections_id_fk": { + "name": "inspection_events_inspection_id_inspections_id_fk", + "tableFrom": "inspection_events", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_events_event_type_id_event_types_id_fk": { + "name": "inspection_events_event_type_id_event_types_id_fk", + "tableFrom": "inspection_events", + "tableTo": "event_types", + "columnsFrom": [ + "event_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspector_id_users_id_fk": { + "name": "inspection_events_inspector_id_users_id_fk", + "tableFrom": "inspection_events", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_inspectors": { + "name": "inspection_inspectors", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lead'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_inspectors_tenant_user": { + "name": "idx_insp_inspectors_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "idx_insp_inspectors_user": { + "name": "idx_insp_inspectors_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_inspectors_inspection_id_user_id_pk": { + "columns": [ + "inspection_id", + "user_id" + ], + "name": "inspection_inspectors_inspection_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_item_tag_links": { + "name": "inspection_item_tag_links", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_links_tenant": { + "name": "idx_tag_links_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_tag_links_tag": { + "name": "idx_tag_links_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + }, + "idx_tag_links_inspection_item": { + "name": "idx_tag_links_inspection_item", + "columns": [ + "inspection_id", + "item_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_item_tag_links_inspection_id_item_id_tag_id_pk": { + "columns": [ + "inspection_id", + "item_id", + "tag_id" + ], + "name": "inspection_item_tag_links_inspection_id_item_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_media_pool": { + "name": "inspection_media_pool", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exif_data": { + "name": "exif_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "caption": { + "name": "caption", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'photo'" + }, + "stream_uid": { + "name": "stream_uid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "poster_pct": { + "name": "poster_pct", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_sec": { + "name": "duration_sec", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'stream'" + }, + "poster_key": { + "name": "poster_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_media_pool_tenant": { + "name": "idx_media_pool_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_media_pool_inspection": { + "name": "idx_media_pool_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_messages": { + "name": "inspection_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_role": { + "name": "from_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_user_id": { + "name": "from_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_msg_inspection": { + "name": "idx_msg_inspection", + "columns": [ + "inspection_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_contact": { + "name": "idx_msg_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_unread": { + "name": "idx_msg_unread", + "columns": [ + "tenant_id", + "contact_id", + "from_role" + ], + "isUnique": false, + "where": "\"inspection_messages\".\"read_at\" IS NULL" + } + }, + "foreignKeys": { + "inspection_messages_tenant_id_tenants_id_fk": { + "name": "inspection_messages_tenant_id_tenants_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_messages_inspection_id_inspections_id_fk": { + "name": "inspection_messages_inspection_id_inspections_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_people": { + "name": "inspection_people", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_profile_id": { + "name": "role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_ip_inspection": { + "name": "idx_ip_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_ip_tenant": { + "name": "idx_ip_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_ip_insp_contact_role": { + "name": "uq_ip_insp_contact_role", + "columns": [ + "inspection_id", + "contact_id", + "role_profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_requests": { + "name": "inspection_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_phone": { + "name": "client_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "property_city": { + "name": "property_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_state": { + "name": "property_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_zip": { + "name": "property_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_requests_tenant": { + "name": "idx_inspection_requests_tenant", + "columns": [ + "tenant_id", + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_requests_email": { + "name": "idx_inspection_requests_email", + "columns": [ + "tenant_id", + "client_email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_requests_tenant_id_tenants_id_fk": { + "name": "inspection_requests_tenant_id_tenants_id_fk", + "tableFrom": "inspection_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_results": { + "name": "inspection_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ydoc_state": { + "name": "ydoc_state", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_system_snapshot": { + "name": "rating_system_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_results_tenant": { + "name": "idx_results_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_results_inspection": { + "name": "idx_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_results_inspection": { + "name": "uq_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_results_tenant_id_tenants_id_fk": { + "name": "inspection_results_tenant_id_tenants_id_fk", + "tableFrom": "inspection_results", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_results_inspection_id_inspections_id_fk": { + "name": "inspection_results_inspection_id_inspections_id_fk", + "tableFrom": "inspection_results", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_services": { + "name": "inspection_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_override_cents": { + "name": "price_override_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_snapshot": { + "name": "name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_snapshot_cents": { + "name": "price_snapshot_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_insp_services_tenant": { + "name": "idx_insp_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_insp_services_insp": { + "name": "idx_insp_services_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_services_tenant_id_tenants_id_fk": { + "name": "inspection_services_tenant_id_tenants_id_fk", + "tableFrom": "inspection_services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_services_inspection_id_inspections_id_fk": { + "name": "inspection_services_inspection_id_inspections_id_fk", + "tableFrom": "inspection_services", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_services_service_id_services_id_fk": { + "name": "inspection_services_service_id_services_id_fk", + "tableFrom": "inspection_services", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_types": { + "name": "inspection_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_types_tenant_name": { + "name": "idx_inspection_types_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_units": { + "name": "inspection_units", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_unit_id": { + "name": "parent_unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unit'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "attrs": { + "name": "attrs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_units_tenant_inspection": { + "name": "idx_inspection_units_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_inspection_units_parent": { + "name": "idx_inspection_units_parent", + "columns": [ + "parent_unit_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspections": { + "name": "inspections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_place_id": { + "name": "address_place_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_county": { + "name": "address_county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lat": { + "name": "address_lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lng": { + "name": "address_lng", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_geocoded_at": { + "name": "address_geocoded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'requested'" + }, + "report_status": { + "name": "report_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_notes": { + "name": "cancel_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_payment_required": { + "name": "is_payment_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_agreement_required": { + "name": "is_agreement_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_auto_sign_on_publish": { + "name": "is_auto_sign_on_publish", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "discount_code_id": { + "name": "discount_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discount_amount_cents": { + "name": "discount_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closing_date": { + "name": "closing_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referral_source": { + "name": "referral_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_notes": { + "name": "internal_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sqft": { + "name": "sqft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "foundation_type": { + "name": "foundation_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrooms": { + "name": "bedrooms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bathrooms": { + "name": "bathrooms", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lot_size": { + "name": "lot_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_facts": { + "name": "property_facts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_photo_id": { + "name": "cover_photo_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_crop": { + "name": "cover_crop", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_image_key": { + "name": "cover_image_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_tier": { + "name": "report_tier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_automations_disabled": { + "name": "is_automations_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "template_snapshot": { + "name": "template_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_snapshot_version": { + "name": "template_snapshot_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "profile_override": { + "name": "profile_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_defect_fields_override": { + "name": "require_defect_fields_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "concierge_status": { + "name": "concierge_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_team_mode": { + "name": "is_team_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "lead_inspector_id": { + "name": "lead_inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "helper_inspector_ids": { + "name": "helper_inspector_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "data_version": { + "name": "data_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source_inspection_id": { + "name": "source_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_inspection_id": { + "name": "root_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reinspection_round": { + "name": "reinspection_round", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_inspection_mode": { + "name": "unit_inspection_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'tagged'" + }, + "location_options": { + "name": "location_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sampling_declaration": { + "name": "sampling_declaration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pca_narrative": { + "name": "pca_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviations": { + "name": "deviations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_mode": { + "name": "report_photo_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_start_ms": { + "name": "scheduled_start_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_end_ms": { + "name": "scheduled_end_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "badge_layout_override": { + "name": "badge_layout_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_columns": { + "name": "report_photo_columns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referred_by_contact_id": { + "name": "referred_by_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspections_tenant": { + "name": "idx_inspections_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspections_request": { + "name": "idx_inspections_request", + "columns": [ + "request_id" + ], + "isUnique": false + }, + "idx_inspections_inspector": { + "name": "idx_inspections_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_inspections_tenant_status": { + "name": "idx_inspections_tenant_status", + "columns": [ + "tenant_id", + "status" + ], + "isUnique": false + }, + "idx_inspections_tenant_date": { + "name": "idx_inspections_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_inspector_date": { + "name": "idx_inspections_inspector_date", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_root": { + "name": "idx_inspections_root", + "columns": [ + "root_inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspections_tenant_id_tenants_id_fk": { + "name": "inspections_tenant_id_tenants_id_fk", + "tableFrom": "inspections", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_inspector_id_users_id_fk": { + "name": "inspections_inspector_id_users_id_fk", + "tableFrom": "inspections", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_template_id_templates_id_fk": { + "name": "inspections_template_id_templates_id_fk", + "tableFrom": "inspections", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_discount_code_id_discount_codes_id_fk": { + "name": "inspections_discount_code_id_discount_codes_id_fk", + "tableFrom": "inspections", + "tableTo": "discount_codes", + "columnsFrom": [ + "discount_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_request_id_inspection_requests_id_fk": { + "name": "inspections_request_id_inspection_requests_id_fk", + "tableFrom": "inspections", + "tableTo": "inspection_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspector_credentials": { + "name": "inspector_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_r2_key": { + "name": "image_r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspector_credentials_tenant": { + "name": "idx_inspector_credentials_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspector_credentials_user": { + "name": "idx_inspector_credentials_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "partial_paid_at": { + "name": "partial_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voided_at": { + "name": "voided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qbo_sync_status": { + "name": "qbo_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + } + }, + "indexes": { + "idx_invoices_tenant": { + "name": "idx_invoices_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_invoices_inspection": { + "name": "idx_invoices_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_invoices_contact": { + "name": "idx_invoices_contact", + "columns": [ + "tenant_id", + "contact_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_tenant_id_tenants_id_fk": { + "name": "invoices_tenant_id_tenants_id_fk", + "tableFrom": "invoices", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_inspection_id_inspections_id_fk": { + "name": "invoices_inspection_id_inspections_id_fk", + "tableFrom": "invoices", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_contact_id_contacts_id_fk": { + "name": "invoices_contact_id_contacts_id_fk", + "tableFrom": "invoices", + "tableTo": "contacts", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_libraries": { + "name": "marketplace_libraries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_libraries_kind_featured": { + "name": "idx_marketplace_libraries_kind_featured", + "columns": [ + "kind", + "is_featured" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_templates": { + "name": "marketplace_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_templates": { + "name": "message_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "variables": { + "name": "variables", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seeded": { + "name": "is_seeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_templates_tenant_channel": { + "name": "idx_message_templates_tenant_channel", + "columns": [ + "tenant_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messaging_compliance": { + "name": "messaging_compliance", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'own'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subaccount_sid": { + "name": "subaccount_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_sid": { + "name": "customer_profile_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_status": { + "name": "customer_profile_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_sid": { + "name": "brand_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_status": { + "name": "brand_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_sid": { + "name": "campaign_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_status": { + "name": "campaign_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_sid": { + "name": "tfv_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_status": { + "name": "tfv_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "messaging_resource_sid": { + "name": "messaging_resource_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_meta": { + "name": "provider_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number": { + "name": "provisioned_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number_sid": { + "name": "provisioned_number_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_sender_attached": { + "name": "has_sender_attached", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "compliance_status": { + "name": "compliance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_preferences": { + "name": "notification_preferences", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "class_id": { + "name": "class_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notification_prefs_unique": { + "name": "idx_notification_prefs_unique", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "class_id", + "channel" + ], + "isUnique": true + }, + "idx_notification_prefs_subject": { + "name": "idx_notification_prefs_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orphaned_media": { + "name": "orphaned_media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_orphaned_media_key": { + "name": "idx_orphaned_media_key", + "columns": [ + "tenant_id", + "r2_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_webhook_events": { + "name": "processed_webhook_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "psq_responses": { + "name": "psq_responses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sent'" + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_psq_inspection": { + "name": "uq_psq_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": true + }, + "idx_psq_share_token": { + "name": "idx_psq_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_connections": { + "name": "qbo_connections", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "realm_id": { + "name": "realm_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_enc": { + "name": "access_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_enc": { + "name": "refresh_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "default_item_id": { + "name": "default_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_entity_map": { + "name": "qbo_entity_map", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_type": { + "name": "qbo_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_id": { + "name": "qbo_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_sync_token": { + "name": "qbo_sync_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_qbo_entity_map_qbo": { + "name": "idx_qbo_entity_map_qbo", + "columns": [ + "tenant_id", + "qbo_type", + "qbo_id" + ], + "isUnique": true + }, + "idx_qbo_entity_map_oi": { + "name": "idx_qbo_entity_map_oi", + "columns": [ + "tenant_id", + "oi_type", + "oi_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_sync_errors": { + "name": "qbo_sync_errors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_resolved": { + "name": "is_resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rating_systems": { + "name": "rating_systems", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "levels": { + "name": "levels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_rating_systems_tenant_slug": { + "name": "idx_rating_systems_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_rating_systems_tenant": { + "name": "idx_rating_systems_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rating_systems_tenant_id_tenants_id_fk": { + "name": "rating_systems_tenant_id_tenants_id_fk", + "tableFrom": "rating_systems", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_request_items": { + "name": "repair_request_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repair_request_id": { + "name": "repair_request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_title": { + "name": "section_title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_snapshot": { + "name": "comment_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_credit_cents": { + "name": "requested_credit_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "defect_title_snapshot": { + "name": "defect_title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_snapshot": { + "name": "location_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_snapshot": { + "name": "category_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_snapshot": { + "name": "trade_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_request_items_rr": { + "name": "idx_repair_request_items_rr", + "columns": [ + "repair_request_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_requests": { + "name": "repair_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_ref": { + "name": "created_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_intro": { + "name": "custom_intro", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_requests_inspection": { + "name": "idx_repair_requests_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_repair_requests_share_token": { + "name": "idx_repair_requests_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_exports": { + "name": "report_exports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_report_exports_inspection": { + "name": "idx_report_exports_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_pdfs": { + "name": "report_pdfs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rendered_at": { + "name": "rendered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ready'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_report_pdfs_inspection_type": { + "name": "uq_report_pdfs_inspection_type", + "columns": [ + "inspection_id", + "type", + "version_number" + ], + "isUnique": true + }, + "idx_report_pdfs_tenant": { + "name": "idx_report_pdfs_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_report_pdfs_status": { + "name": "idx_report_pdfs_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_report_pdfs_content_hash": { + "name": "idx_report_pdfs_content_hash", + "columns": [ + "inspection_id", + "type", + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": { + "report_pdfs_tenant_id_tenants_id_fk": { + "name": "report_pdfs_tenant_id_tenants_id_fk", + "tableFrom": "report_pdfs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_signoff": { + "name": "report_signoff", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualifications_ref": { + "name": "qualifications_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_ref": { + "name": "signature_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dual_role": { + "name": "is_dual_role", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_report_signoff_inspection": { + "name": "idx_report_signoff_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_report_signoff_role": { + "name": "uq_report_signoff_role", + "columns": [ + "inspection_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_versions": { + "name": "report_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_json": { + "name": "snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_amendment": { + "name": "is_amendment", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "idx_report_versions_inspection": { + "name": "idx_report_versions_inspection", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": false + }, + "uq_report_versions_inspection_version": { + "name": "uq_report_versions_inspection_version", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": true + }, + "idx_report_versions_verify_token": { + "name": "idx_report_versions_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_inspectors": { + "name": "service_inspectors", + "columns": { + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_service_inspectors_tenant": { + "name": "idx_service_inspectors_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "service_inspectors_service_id_user_id_pk": { + "columns": [ + "service_id", + "user_id" + ], + "name": "service_inspectors_service_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_event_type_slugs": { + "name": "default_event_type_slugs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_services_tenant": { + "name": "idx_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "services_tenant_id_tenants_id_fk": { + "name": "services_tenant_id_tenants_id_fk", + "tableFrom": "services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_template_id_templates_id_fk": { + "name": "services_template_id_templates_id_fk", + "tableFrom": "services", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_agreement_id_agreements_id_fk": { + "name": "services_agreement_id_agreements_id_fk", + "tableFrom": "services", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_enc": { + "name": "private_key_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Ed25519'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_consent_log": { + "name": "sms_consent_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disclosure_version": { + "name": "disclosure_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "captured_via": { + "name": "captured_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'contact'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": { + "idx_sms_consent_contact": { + "name": "idx_sms_consent_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_sms_consent_subject": { + "name": "idx_sms_consent_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_delivery_status": { + "name": "sms_delivery_status", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sms_delivery_status_msg": { + "name": "idx_sms_delivery_status_msg", + "columns": [ + "tenant_id", + "provider_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_disclosure_versions": { + "name": "sms_disclosure_versions", + "columns": { + "version": { + "name": "version", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tags_tenant_name": { + "name": "idx_tags_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + }, + "idx_tags_tenant": { + "name": "idx_tags_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_templates_tenant": { + "name": "idx_templates_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_templates_rating_system": { + "name": "idx_templates_rating_system", + "columns": [ + "rating_system_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "templates_tenant_id_tenants_id_fk": { + "name": "templates_tenant_id_tenants_id_fk", + "tableFrom": "templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_custom_holidays": { + "name": "tenant_custom_holidays", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_tenant_custom_holidays_tenant_date": { + "name": "uq_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": true + }, + "idx_tenant_custom_holidays_tenant_date": { + "name": "idx_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_library_imports": { + "name": "tenant_library_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "uq_tenant_library_import": { + "name": "uq_tenant_library_import", + "columns": [ + "tenant_id", + "library_id" + ], + "isUnique": true + }, + "idx_tenant_library_imports_tenant": { + "name": "idx_tenant_library_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_import_history": { + "name": "tenant_marketplace_import_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_version": { + "name": "target_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_affected": { + "name": "rows_affected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_history_tenant": { + "name": "idx_marketplace_history_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_marketplace_history_template": { + "name": "idx_marketplace_history_template", + "columns": [ + "template_id" + ], + "isUnique": false + }, + "idx_marketplace_history_library": { + "name": "idx_marketplace_history_library", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_imports": { + "name": "tenant_marketplace_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "marketplace_template_id": { + "name": "marketplace_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_template_id": { + "name": "local_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_mkt_imports_tmpl": { + "name": "idx_mkt_imports_tmpl", + "columns": [ + "marketplace_template_id" + ], + "isUnique": false + }, + "idx_mkt_imports_tenant": { + "name": "idx_mkt_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk": { + "name": "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "marketplace_templates", + "columnsFrom": [ + "marketplace_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tenant_marketplace_imports_local_template_id_templates_id_fk": { + "name": "tenant_marketplace_imports_local_template_id_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "templates", + "columnsFrom": [ + "local_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "usage_counters": { + "name": "usage_counters", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_usage_counters_tenant": { + "name": "idx_usage_counters_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_counters_tenant_id_metric_period_key_pk": { + "columns": [ + "tenant_id", + "metric", + "period_key" + ], + "name": "usage_counters_tenant_id_metric_period_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_templates": { + "name": "email_templates", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocks": { + "name": "blocks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_templates_tenant_id_tenants_id_fk": { + "name": "email_templates_tenant_id_tenants_id_fk", + "tableFrom": "email_templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_templates_tenant_id_trigger_pk": { + "columns": [ + "tenant_id", + "trigger" + ], + "name": "email_templates_tenant_id_trigger_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_configs": { + "name": "tenant_configs", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "support_email": { + "name": "support_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_address": { + "name": "company_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pdf_footer_shown": { + "name": "is_pdf_footer_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_page_numbers_shown": { + "name": "is_pdf_page_numbers_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_license_shown": { + "name": "is_pdf_license_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reply_to": { + "name": "reply_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_mode": { + "name": "email_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "video_mode": { + "name": "video_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'r2'" + }, + "sms_mode": { + "name": "sms_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "sender_display_name": { + "name": "sender_display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "point_of_contact": { + "name": "point_of_contact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company'" + }, + "billing_url": { + "name": "billing_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_url": { + "name": "review_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_phone": { + "name": "company_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "integration_config": { + "name": "integration_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secrets_enc": { + "name": "secrets_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dek_enc": { + "name": "dek_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ics_token": { + "name": "ics_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widget_allowed_origins": { + "name": "widget_allowed_origins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'signature'" + }, + "attention_thresholds": { + "name": "attention_thresholds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"agreement_unsigned_h\":72,\"invoice_overdue_h\":72,\"report_unpublished_h\":72}'" + }, + "inspection_prefs": { + "name": "inspection_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_estimates_shown": { + "name": "is_estimates_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_repair_list_enabled": { + "name": "is_repair_list_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_customer_repair_export_enabled": { + "name": "is_customer_repair_export_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unpaid_blocked": { + "name": "is_unpaid_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unsigned_agreement_blocked": { + "name": "is_unsigned_agreement_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "custom_referral_sources": { + "name": "custom_referral_sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dashboard_column_prefs": { + "name": "dashboard_column_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_concierge_review_required": { + "name": "is_concierge_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_inspector_choice_allowed": { + "name": "is_inspector_choice_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_pdf_pipeline_enabled": { + "name": "is_pdf_pipeline_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_team_mode_default": { + "name": "is_team_mode_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_apprentice_review_required": { + "name": "is_apprentice_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_guest_invites_enabled": { + "name": "is_guest_invites_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "require_defect_fields": { + "name": "require_defect_fields", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "agreement_retention_years": { + "name": "agreement_retention_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "reinspection_statuses": { + "name": "reinspection_statuses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_collab_editing_enabled": { + "name": "is_collab_editing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sms_byo_provider": { + "name": "sms_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_byo_provider": { + "name": "email_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "is_managed_eligible": { + "name": "is_managed_eligible", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "managed_provider": { + "name": "managed_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'twilio'" + }, + "is_reserve_schedule_enabled": { + "name": "is_reserve_schedule_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reserve_term_years": { + "name": "reserve_term_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "inflation_rate_bps": { + "name": "inflation_rate_bps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'UTC'" + }, + "booking_slot_mode": { + "name": "booking_slot_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "booking_slot_interval_min": { + "name": "booking_slot_interval_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "holiday_region": { + "name": "holiday_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "holiday_public_policy": { + "name": "holiday_public_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "holiday_internal_policy": { + "name": "holiday_internal_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'advisory'" + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en-US'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "is_archive_revoking_access": { + "name": "is_archive_revoking_access", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "legal_mode": { + "name": "legal_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'hosted'" + }, + "custom_privacy_url": { + "name": "custom_privacy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_terms_url": { + "name": "custom_terms_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privacy_body": { + "name": "privacy_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_body": { + "name": "terms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_configs_tenant_id_tenants_id_fk": { + "name": "tenant_configs_tenant_id_tenants_id_fk", + "tableFrom": "tenant_configs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "stripe_connect_account_id": { + "name": "stripe_connect_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "max_users": { + "name": "max_users", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'shared'" + }, + "applied_cmd_seq": { + "name": "applied_cmd_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_cred_seq": { + "name": "applied_cred_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_invites": { + "name": "tenant_invites", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inspector'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_invites_tenant": { + "name": "idx_invites_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_tenant_invites_pending_email": { + "name": "uq_tenant_invites_pending_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "status = 'pending'" + } + }, + "foreignKeys": { + "tenant_invites_tenant_id_tenants_id_fk": { + "name": "tenant_invites_tenant_id_tenants_id_fk", + "tableFrom": "tenant_invites", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_signature_base64": { + "name": "default_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_signature_enabled": { + "name": "is_signature_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manager'" + }, + "onboarding_state": { + "name": "onboarding_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_totp_enabled": { + "name": "is_totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_recovery_codes": { + "name": "totp_recovery_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_verified_at": { + "name": "totp_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_accepted": { + "name": "terms_accepted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + "deleted_at" + ], + "isUnique": false + }, + "uq_users_tenant_email": { + "name": "uq_users_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "deleted_at IS NULL" + }, + "idx_users_tenant": { + "name": "idx_users_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_users_slug_per_tenant": { + "name": "idx_users_slug_per_tenant", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_users_email": { + "name": "idx_users_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_slug": { + "name": "inspector_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_audit_tenant_created": { + "name": "idx_audit_tenant_created", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_audit_entity": { + "name": "idx_audit_entity", + "columns": [ + "entity_type", + "entity_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_tenant_id_tenants_id_fk": { + "name": "audit_logs_tenant_id_tenants_id_fk", + "tableFrom": "audit_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration_test_results": { + "name": "integration_test_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_ok": { + "name": "is_ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_by_user_id": { + "name": "tested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_at": { + "name": "tested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_integration_test_tenant_target": { + "name": "idx_integration_test_tenant_target", + "columns": [ + "tenant_id", + "target", + "tested_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_tenant_user_created": { + "name": "idx_notifications_tenant_user_created", + "columns": [ + "tenant_id", + "user_id", + "created_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_user_unread": { + "name": "idx_notifications_tenant_user_unread", + "columns": [ + "tenant_id", + "user_id", + "read_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_contact_created": { + "name": "idx_notifications_tenant_contact_created", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_tenant_id_tenants_id_fk": { + "name": "notifications_tenant_id_tenants_id_fk", + "tableFrom": "notifications", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parked_cmd_events": { + "name": "parked_cmd_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "envelope": { + "name": "envelope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_parked_cmd_events_received_at": { + "name": "idx_parked_cmd_events_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_cmd_events": { + "name": "processed_cmd_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cmd_type": { + "name": "cmd_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slug_reservations": { + "name": "slug_reservations", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_outbox": { + "name": "sync_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_tried_at": { + "name": "last_tried_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_sync_outbox_status_created": { + "name": "idx_sync_outbox_status_created", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_destruction_records": { + "name": "tenant_destruction_records", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_slug": { + "name": "tenant_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_deleted": { + "name": "rows_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_objects": { + "name": "r2_objects", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_bytes": { + "name": "r2_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "kv_keys": { + "name": "kv_keys", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_destruction_tenant": { + "name": "idx_destruction_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_destruction_destroyed_at": { + "name": "idx_destruction_destroyed_at", + "columns": [ + "destroyed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_legal_versions": { + "name": "tenant_legal_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "doc": { + "name": "doc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_material": { + "name": "is_material", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_tenant_legal_versions_doc_version": { + "name": "idx_tenant_legal_versions_doc_version", + "columns": [ + "tenant_id", + "doc", + "version" + ], + "isUnique": true + }, + "idx_tenant_legal_versions_latest": { + "name": "idx_tenant_legal_versions_latest", + "columns": [ + "tenant_id", + "doc", + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_uploads": { + "name": "client_uploads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_kind": { + "name": "uploaded_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_ref": { + "name": "uploaded_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_name": { + "name": "uploaded_by_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_client_uploads_inspection": { + "name": "idx_client_uploads_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uq_discount_codes_code_tenant": { + "columns": { + "upper(code)": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index f8d0211c5..413b4381e 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1785680855335, "tag": "0025_hot_venom", "breakpoints": true + }, + { + "idx": 26, + "version": "6", + "when": 1785714900847, + "tag": "0026_charming_earthquake", + "breakpoints": true } ] } \ No newline at end of file diff --git a/scripts/file-size-baseline.json b/scripts/file-size-baseline.json index 81cb6e588..04f9ef4a4 100644 --- a/scripts/file-size-baseline.json +++ b/scripts/file-size-baseline.json @@ -14,7 +14,7 @@ "server/api/admin/admin-settings.ts": 742, "server/api/inspections/report-delivery.ts": 736, "app/routes/settings-communication-templates.tsx": 731, - "server/services/inspection/inspection-analytics.service.ts": 727, + "server/services/inspection/inspection-analytics.service.ts": 729, "app/routes/template-edit.tsx": 719, "server/index.ts": 693, "app/components/media-studio/PhotoAnnotator.tsx": 692, diff --git a/server/api/invoices.ts b/server/api/invoices.ts index a0fde3dab..25a347b15 100644 --- a/server/api/invoices.ts +++ b/server/api/invoices.ts @@ -261,6 +261,8 @@ const invoiceRoutes = createApiRouter() .where(and( eq(inspectionServices.tenantId, tenantId), eq(inspectionServices.inspectionId, inspectionId), + // A declined line must not appear on the invoice. + eq(inspectionServices.active, true), )) .all(); diff --git a/server/api/metrics.ts b/server/api/metrics.ts index d9f4ac4af..9bf09dc13 100644 --- a/server/api/metrics.ts +++ b/server/api/metrics.ts @@ -88,7 +88,8 @@ const metricsRoutes = createApiRouter() eq(inspections.id, inspectionServices.inspectionId), eq(inspections.tenantId, inspectionServices.tenantId), )) - .where(and(eq(inspectionServices.tenantId, tenantId), inWindow)) + // Soft-deleted lines are history; revenue must not count them. + .where(and(eq(inspectionServices.tenantId, tenantId), eq(inspectionServices.active, true), inWindow)) .groupBy(inspectionServices.nameSnapshot) .orderBy(sql`count(*) desc`) .limit(10); diff --git a/server/lib/db/schema/inspection/services.ts b/server/lib/db/schema/inspection/services.ts index 64b78ef1b..4def59ff7 100644 --- a/server/lib/db/schema/inspection/services.ts +++ b/server/lib/db/schema/inspection/services.ts @@ -45,6 +45,21 @@ export const inspectionServices = sqliteTable('inspection_services', { priceOverride: integer('price_override_cents'), nameSnapshot: text('name_snapshot').notNull(), priceSnapshot: integer('price_snapshot_cents').notNull(), + // A client changing scope at the door is routine — add a sewer scope, drop + // the pool inspection, decline the radon. That used to be a hard delete, + // which was harmless only while nothing hung off a line. + // + // Once a `reports` row or a pay split points here, a delete leaves dangling + // rows and NOTHING surfaces it: Schema Rules forbid new foreign keys, so + // there is no constraint to catch it. The invoice disagrees too — + // `invoices.amountCents` is authoritative over the line sum, so removing a + // line does not change what was billed while a split keeps paying against + // it. + // + // Matches `services` and `discount_codes` in this same file. Appended at + // table end for D1 rebuild safety. EVERY reader must filter on it — the + // money chain in effective-price.sql.ts first. + active: integer('is_active', { mode: 'boolean' }).notNull().default(true), }, (t) => [ index('idx_insp_services_tenant').on(t.tenantId), index('idx_insp_services_insp').on(t.inspectionId), diff --git a/server/lib/effective-price.sql.ts b/server/lib/effective-price.sql.ts index 872c797fc..220966d70 100644 --- a/server/lib/effective-price.sql.ts +++ b/server/lib/effective-price.sql.ts @@ -35,6 +35,9 @@ const SVC_TENANT = qualified(inspectionServices, inspectionServices.tenantId); const SVC_INSPECTION = qualified(inspectionServices, inspectionServices.inspectionId); const SVC_OVERRIDE = qualified(inspectionServices, inspectionServices.priceOverride); const SVC_SNAPSHOT = qualified(inspectionServices, inspectionServices.priceSnapshot); +// Soft-deleted lines are history, not money. Without this the sum keeps +// charging for a service the client declined at the door. +const SVC_ACTIVE = qualified(inspectionServices, inspectionServices.active); const INSP_ID = qualified(inspections, inspections.id); const INSP_TENANT = qualified(inspections, inspections.tenantId); @@ -86,7 +89,8 @@ export const effectivePriceCentsSql: SQL = sql`coalesce( (select sum(coalesce(${SVC_OVERRIDE}, ${SVC_SNAPSHOT})) from ${inspectionServices} where ${SVC_TENANT} = ${INSP_TENANT} - and ${SVC_INSPECTION} = ${INSP_ID}), + and ${SVC_INSPECTION} = ${INSP_ID} + and ${SVC_ACTIVE} = 1), ${INSP_PRICE}, 0 )`; diff --git a/server/services/automation/conditions.ts b/server/services/automation/conditions.ts index cbe18f635..aad52e35f 100644 --- a/server/services/automation/conditions.ts +++ b/server/services/automation/conditions.ts @@ -52,8 +52,13 @@ export function AutomationConditions>( let bookedServiceIds: string[] = []; if (cond?.serviceIds && cond.serviceIds.length > 0) { + // A declined line must not keep firing an automation for a + // service the client is no longer buying. const rows = await db.select({ serviceId: inspectionServices.serviceId }).from(inspectionServices) - .where(eq(inspectionServices.inspectionId, inspection.id)); + .where(and( + eq(inspectionServices.inspectionId, inspection.id), + eq(inspectionServices.active, true), + )); bookedServiceIds = rows.map((r) => r.serviceId); } diff --git a/server/services/inspection/inspection-analytics.service.ts b/server/services/inspection/inspection-analytics.service.ts index 15961bd32..3acba7a02 100644 --- a/server/services/inspection/inspection-analytics.service.ts +++ b/server/services/inspection/inspection-analytics.service.ts @@ -604,7 +604,9 @@ export class InspectionAnalyticsService extends InspectionSubService { priceOverride: inspectionServices.priceOverride, }) .from(inspectionServices) - .where(eq(inspectionServices.tenantId, tenantId)); + // Feeds getEffectivePriceCents — a soft-deleted line is history and + // must not be summed into what an inspection is worth. + .where(and(eq(inspectionServices.tenantId, tenantId), eq(inspectionServices.active, true))); for (const r of serviceRows) { const key = r.inspectionId as string; const list = serviceLinesByInspection.get(key); diff --git a/server/services/service.service.ts b/server/services/service.service.ts index 515c4169a..1781b55e1 100644 --- a/server/services/service.service.ts +++ b/server/services/service.service.ts @@ -1,7 +1,8 @@ import { drizzle } from 'drizzle-orm/d1'; -import { eq, and, asc, isNull, ne, inArray, sql } from 'drizzle-orm'; -import { services, inspectionServices, discountCodes, serviceInspectors, users, inspections, eventTypes } from '../lib/db/schema'; +import { eq, and, asc, inArray, sql } from 'drizzle-orm'; +import { services, inspectionServices, discountCodes, inspections, eventTypes } from '../lib/db/schema'; import { Errors } from '../lib/errors'; +import { getServiceInspectors, setServiceInspectors } from './service/qualification'; import { nanoid } from 'nanoid'; import type { z } from 'zod'; import type { CreateServiceSchema, UpdateServiceSchema, CreateDiscountCodeSchema } from '../lib/validations/service.schema'; @@ -94,12 +95,17 @@ export class ServiceService { await db.update(services).set({ active: false }).where(and(eq(services.id, id), eq(services.tenantId, tenantId))); } + /** + * The live lines on an inspection. Excludes soft-deleted ones: a line the + * client declined at the door is history, not something still billed. + */ async getInspectionServices(tenantId: string, inspectionId: string) { const db = this.getDrizzle(); return db.select().from(inspectionServices) .where(and( eq(inspectionServices.inspectionId, inspectionId), - eq(inspectionServices.tenantId, tenantId) + eq(inspectionServices.tenantId, tenantId), + eq(inspectionServices.active, true), )); } @@ -149,7 +155,19 @@ export class ServiceService { eq(inspectionServices.serviceId, serviceId), )) .limit(1).get(); - if (existing) return existing; + if (existing?.active) return existing; + if (existing) { + // Declined at the door, then wanted after all. REACTIVATE the row + // rather than inserting a second one: a `reports` row or a pay split + // may already point at this id, and a fresh row would strand them + // while the client is billed for the line they can see. + await db.update(inspectionServices).set({ active: true }) + .where(and( + eq(inspectionServices.id, existing.id), + eq(inspectionServices.tenantId, tenantId), + )); + return { ...existing, active: true }; + } const id = nanoid(); await db.insert(inspectionServices).values({ @@ -189,17 +207,39 @@ export class ServiceService { return updated[0]; } - /** IA-87 — drop a service line from an inspection. */ + /** + * IA-87 — drop a service line from an inspection. + * + * Soft delete, so the history of what was sold survives a scope change at + * the door — and so a `reports` row or a pay split that points at this line + * still finds it. + * + * The REFUSAL half of this guard is deliberately not stubbed here. Neither + * `reports` nor `inspection_service_pay_splits` exists yet, so a check + * against them would be a function that always returns "nothing blocks + * this" — a gate that passes vacuously, which this repo has shipped before. + * Each of those tasks adds its own clause, with a test, when it creates the + * table. What lands now is the column and the soft delete, which is what + * has to exist BEFORE the first row points here. + */ async removeInspectionService(tenantId: string, inspectionId: string, lineId: string) { const db = this.getDrizzle(); - const removed = await db.delete(inspectionServices) + const line = await db.select({ id: inspectionServices.id, active: inspectionServices.active }) + .from(inspectionServices) .where(and( eq(inspectionServices.id, lineId), eq(inspectionServices.tenantId, tenantId), eq(inspectionServices.inspectionId, inspectionId), )) - .returning({ id: inspectionServices.id }); - if (removed.length === 0) throw Errors.NotFound('Service line not found'); + .get(); + if (!line || !line.active) throw Errors.NotFound('Service line not found'); + + await db.update(inspectionServices).set({ active: false }) + .where(and( + eq(inspectionServices.id, lineId), + eq(inspectionServices.tenantId, tenantId), + eq(inspectionServices.inspectionId, inspectionId), + )); } async listDiscountCodes(tenantId: string) { @@ -252,84 +292,14 @@ export class ServiceService { return rows[0]; } - // IA-26 — per-service inspector qualification write face - - /** - * Returns the current list of restricted inspector userIds for a service. - * An empty list means all staff are qualified (no rows = open). - * Throws 404 if the service does not belong to the given tenant. - */ + // IA-26 — per-service inspector qualification. The implementation lives in + // service/qualification.ts; these delegates keep every caller unchanged. async getServiceInspectors(tenantId: string, serviceId: string): Promise { - const db = this.getDrizzle(); - const svc = await db.select({ id: services.id }).from(services) - .where(and(eq(services.id, serviceId), eq(services.tenantId, tenantId))) - .limit(1).get(); - if (!svc) throw Errors.NotFound('Service not found'); - - const rows = await db.select({ userId: serviceInspectors.userId }).from(serviceInspectors) - .where(and(eq(serviceInspectors.serviceId, serviceId), eq(serviceInspectors.tenantId, tenantId))) - .all(); - return rows.map(r => r.userId); + return getServiceInspectors(this.getDrizzle(), tenantId, serviceId); } - /** - * Full-replace the inspector restriction list for a service. - * Empty userIds = clear all rows (back to "all staff qualified"). - * Validates that every provided userId is a non-deleted, non-agent tenant member. - * Throws 404 if the service is not found in the tenant; 400 on invalid userIds. - */ async setServiceInspectors(tenantId: string, serviceId: string, userIds: string[]): Promise { - const db = this.getDrizzle(); - - // 404 guard - const svc = await db.select({ id: services.id }).from(services) - .where(and(eq(services.id, serviceId), eq(services.tenantId, tenantId))) - .limit(1).get(); - if (!svc) throw Errors.NotFound('Service not found'); - - if (userIds.length > 0) { - // Validate: every userId must be a non-deleted, non-agent member of the tenant. - const validMembers = await db.select({ id: users.id }).from(users) - .where(and( - eq(users.tenantId, tenantId), - isNull(users.deletedAt), - ne(users.role, 'agent'), - inArray(users.id, userIds), - )) - .all(); - const validSet = new Set(validMembers.map(m => m.id)); - const invalid = userIds.filter(id => !validSet.has(id)); - if (invalid.length > 0) { - throw Errors.BadRequest(`Invalid or ineligible user IDs: ${invalid.join(', ')}`); - } - } - - // Full-replace atomically: delete existing rows then insert new ones in one - // db.batch() so a failed insert can never leave zero rows (fail-open). - // Drivers without batch support (e.g. the better-sqlite3 unit-test mock) - // fall back to sequential statements, matching the pattern in - // starter-content.service.ts:batchInsert. - const deleteStmt = db.delete(serviceInspectors) - .where(and(eq(serviceInspectors.serviceId, serviceId), eq(serviceInspectors.tenantId, tenantId))); - - if (userIds.length > 0) { - const now = new Date(); - const insertStmt = db.insert(serviceInspectors).values( - userIds.map(userId => ({ serviceId, userId, tenantId, createdAt: now })), - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (typeof (db as any).batch === 'function') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (db as any).batch([deleteStmt, insertStmt]); - } else { - await deleteStmt; - await insertStmt; - } - } else { - await deleteStmt; - } - - return userIds.length; + return setServiceInspectors(this.getDrizzle(), tenantId, serviceId, userIds); } async validateDiscountCode(tenantId: string, code: string, subtotal: number): Promise<{ diff --git a/server/services/service/qualification.ts b/server/services/service/qualification.ts new file mode 100644 index 000000000..379255839 --- /dev/null +++ b/server/services/service/qualification.ts @@ -0,0 +1,94 @@ +/** + * IA-26 — per-service inspector qualification. + * + * Extracted from `service.service.ts` to keep it under the file-size ratchet, + * and it is a genuinely separate concern: which inspectors MAY perform a + * catalogue service, as opposed to what that service costs or which lines an + * inspection carries. ZERO rows for a service means every staff member is + * qualified — the MVP default — and adding rows restricts it. + * + * Takes `db` rather than reading it off a class, matching the other helpers + * extracted from that file. + */ +import { and, eq, inArray, ne, isNull } from 'drizzle-orm'; +import { services, serviceInspectors, users } from '../../lib/db/schema'; +import { Errors } from '../../lib/errors'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Db = any; + +/** + * Returns the current list of restricted inspector userIds for a service. + * An empty list means all staff are qualified (no rows = open). + * Throws 404 if the service does not belong to the given tenant. + */ +export async function getServiceInspectors(db: Db, tenantId: string, serviceId: string): Promise { + const svc = await db.select({ id: services.id }).from(services) + .where(and(eq(services.id, serviceId), eq(services.tenantId, tenantId))) + .limit(1).get(); + if (!svc) throw Errors.NotFound('Service not found'); + + const rows = await db.select({ userId: serviceInspectors.userId }).from(serviceInspectors) + .where(and(eq(serviceInspectors.serviceId, serviceId), eq(serviceInspectors.tenantId, tenantId))) + .all(); + return rows.map((r: { userId: string }) => r.userId); + } + + /** + * Full-replace the inspector restriction list for a service. + * Empty userIds = clear all rows (back to "all staff qualified"). + * Validates that every provided userId is a non-deleted, non-agent tenant member. + * Throws 404 if the service is not found in the tenant; 400 on invalid userIds. + */ +export async function setServiceInspectors(db: Db, tenantId: string, serviceId: string, userIds: string[]): Promise { + + // 404 guard + const svc = await db.select({ id: services.id }).from(services) + .where(and(eq(services.id, serviceId), eq(services.tenantId, tenantId))) + .limit(1).get(); + if (!svc) throw Errors.NotFound('Service not found'); + + if (userIds.length > 0) { + // Validate: every userId must be a non-deleted, non-agent member of the tenant. + const validMembers = await db.select({ id: users.id }).from(users) + .where(and( + eq(users.tenantId, tenantId), + isNull(users.deletedAt), + ne(users.role, 'agent'), + inArray(users.id, userIds), + )) + .all(); + const validSet = new Set(validMembers.map((m: { id: string }) => m.id)); + const invalid = userIds.filter(id => !validSet.has(id)); + if (invalid.length > 0) { + throw Errors.BadRequest(`Invalid or ineligible user IDs: ${invalid.join(', ')}`); + } + } + + // Full-replace atomically: delete existing rows then insert new ones in one + // db.batch() so a failed insert can never leave zero rows (fail-open). + // Drivers without batch support (e.g. the better-sqlite3 unit-test mock) + // fall back to sequential statements, matching the pattern in + // starter-content.service.ts:batchInsert. + const deleteStmt = db.delete(serviceInspectors) + .where(and(eq(serviceInspectors.serviceId, serviceId), eq(serviceInspectors.tenantId, tenantId))); + + if (userIds.length > 0) { + const now = new Date(); + const insertStmt = db.insert(serviceInspectors).values( + userIds.map(userId => ({ serviceId, userId, tenantId, createdAt: now })), + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof (db as any).batch === 'function') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (db as any).batch([deleteStmt, insertStmt]); + } else { + await deleteStmt; + await insertStmt; + } + } else { + await deleteStmt; + } + + return userIds.length; + } diff --git a/tests/unit/inspections/inspection-service-lines.spec.ts b/tests/unit/inspections/inspection-service-lines.spec.ts index f9527d5fc..2d92437a3 100644 --- a/tests/unit/inspections/inspection-service-lines.spec.ts +++ b/tests/unit/inspections/inspection-service-lines.spec.ts @@ -129,7 +129,15 @@ describe('ServiceService — service lines on an inspection (IA-87)', () => { const added = await svc.addInspectionService(TENANT, INSP_ID, SVC_ID); await svc.removeInspectionService(TENANT, INSP_ID, added.id); - expect(await linesOf(INSP_ID)).toHaveLength(0); + // Removal is now a SOFT delete — the row survives so a reports row or a + // pay split pointing at it still resolves, and so the history of what + // was sold outlives a scope change at the door. It is gone from the live + // list, which is what every reader consumes. + expect(await svc.getInspectionServices(TENANT, INSP_ID)).toHaveLength(0); + const raw = await linesOf(INSP_ID); + expect(raw).toHaveLength(1); + expect(raw[0].active).toBe(false); + const catalog = await db.select().from(schema.services).where(eq(schema.services.id, SVC_ID)).get(); expect(catalog?.active).toBe(true); }); diff --git a/tests/unit/inspections/service-line-lifecycle.spec.ts b/tests/unit/inspections/service-line-lifecycle.spec.ts new file mode 100644 index 000000000..25447bb65 --- /dev/null +++ b/tests/unit/inspections/service-line-lifecycle.spec.ts @@ -0,0 +1,127 @@ +/** + * A client changing scope at the door is routine — add a sewer scope, drop the + * pool inspection, decline the radon. That used to hard-delete the billing line, + * which was harmless only while nothing hung off one. + * + * The fourth test here is the one the plan predicted would be forgotten: adding + * `is_active` is worthless unless every READER filters on it, and the money + * chain is the reader that matters. A line the client declined must stop being + * summed into what the inspection is worth. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { and, eq } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { ServiceService } from '../../../server/services/service.service'; + +const TENANT = 'tenant-lines-1'; +const INSP = 'insp-lines-1'; +const SVC = 'svc-sewer'; + +describe('inspection service lines — scope changes at the door', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + let svc: ServiceService; + + const lineRows = () => db.select().from(schema.inspectionServices) + .where(eq(schema.inspectionServices.inspectionId, INSP)).all(); + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockDrizzle as any).mockReturnValue(db); + + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + await db.insert(schema.inspections).values({ + id: INSP, tenantId: TENANT, propertyAddress: '1 Main St', date: '2026-09-01', + status: 'confirmed', paymentStatus: 'unpaid', price: 40000, createdAt: new Date(), + }); + await db.insert(schema.services).values({ + id: SVC, tenantId: TENANT, name: 'Sewer Scope', description: null, + price: 25000, durationMinutes: 60, templateId: null, agreementId: null, + active: true, sortOrder: 1, createdAt: new Date(), defaultEventTypeSlugs: [], + } as never); + + svc = new ServiceService({} as never); + }); + + afterEach(() => { sqlite.close(); }); + + it('soft-deletes a line so the history of what was sold survives', async () => { + const line = await svc.addInspectionService(TENANT, INSP, SVC); + await svc.removeInspectionService(TENANT, INSP, line!.id); + + const rows = await lineRows(); + expect(rows).toHaveLength(1); // still present + expect(rows[0].active).toBe(false); // but not live + }); + + it('hides a removed line from the live list', async () => { + const line = await svc.addInspectionService(TENANT, INSP, SVC); + expect(await svc.getInspectionServices(TENANT, INSP)).toHaveLength(1); + + await svc.removeInspectionService(TENANT, INSP, line!.id); + expect(await svc.getInspectionServices(TENANT, INSP)).toHaveLength(0); + }); + + it('refuses to remove the same line twice', async () => { + const line = await svc.addInspectionService(TENANT, INSP, SVC); + await svc.removeInspectionService(TENANT, INSP, line!.id); + + // A silent second success would tell an operator they removed something + // that was already gone. + await expect(svc.removeInspectionService(TENANT, INSP, line!.id)) + .rejects.toThrow(/not found/i); + }); + + it('excludes an inactive line from the effective price sum', async () => { + // THE one that gets forgotten. `getEffectivePriceCents` is a pure + // function over already-fetched lines, so the filter has to live at + // every fetch site — this asserts the one the service layer owns. + const line = await svc.addInspectionService(TENANT, INSP, SVC); + const before = await svc.getInspectionServices(TENANT, INSP); + expect(before.reduce((n, l) => n + (l.priceOverride ?? l.priceSnapshot), 0)).toBe(25000); + + await svc.removeInspectionService(TENANT, INSP, line!.id); + + const after = await svc.getInspectionServices(TENANT, INSP); + expect(after.reduce((n, l) => n + (l.priceOverride ?? l.priceSnapshot), 0)).toBe(0); + }); + + it('reactivates the original row when a declined service is wanted after all', async () => { + // Not a second row: a reports row or a pay split may already point at + // this id, and a fresh one would strand them while the client is billed + // for the line they can see. + const first = await svc.addInspectionService(TENANT, INSP, SVC); + await svc.removeInspectionService(TENANT, INSP, first!.id); + + const again = await svc.addInspectionService(TENANT, INSP, SVC); + + expect(again!.id).toBe(first!.id); + expect(await lineRows()).toHaveLength(1); + expect(await svc.getInspectionServices(TENANT, INSP)).toHaveLength(1); + }); + + it('never touches another tenant\'s line', async () => { + const line = await svc.addInspectionService(TENANT, INSP, SVC); + await expect(svc.removeInspectionService('other-tenant', INSP, line!.id)) + .rejects.toThrow(/not found/i); + + const still = await db.select().from(schema.inspectionServices) + .where(and( + eq(schema.inspectionServices.id, line!.id), + eq(schema.inspectionServices.tenantId, TENANT), + )).get(); + expect(still?.active).toBe(true); + }); +}); From a0b7f7189ec4bbacab9e76d2f919926c763a0621 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 08:29:34 +0800 Subject: [PATCH 13/29] =?UTF-8?q?feat(reports):=20the=20reports=20entity?= =?UTF-8?q?=20=E2=80=94=20one=20order,=20several=20reports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 1 of the per-deliverable-reports plan. A standard inspection publishes today and the radon report publishes on Thursday, each with its own document, signature chain and notification, without the client waiting on the slowest one. `uq_results_inspection` made that impossible; this is the table it becomes. Two naming decisions carry weight and are written into the schema comment: `inspection_service_id`, NOT `service_id`. In this schema `service_id` already means the CATALOGUE entry — both inspection_services.service_id and service_inspectors.service_id are that — so naming this one `service_id` reads as the catalogue to anyone who has seen the other two, and a catalogue id here makes "which report did this billing line produce" unanswerable. That is exactly the grain pay splits are built on. `kind` distinguishes primary from ancillary because primary is not just another one: it must exist, the pay gate keys on it, and it is what a client means by "my report". One primary per inspection is enforced by a PARTIAL unique index rather than by the service layer — which report the client means must not depend on which caller wrote last — while ancillary reports stay unbounded, since a job can deliver radon, sewer and mould. The erasure-manifest entry lands in this task rather than later, because an entity that enters the schema without one is how the manifest drifts from the database. `title` is the only free text a human writes and it routinely carries the address ("123 Oak St — Radon"), so it is anonymised rather than deleted: the row is the spine of a signed, delivered document and removing it would strand the version chain that proves what was delivered. The other eight columns are ids, enums and a timestamp, declared out of scope. lint:erasure passes at 29 rules and 32 declarations. Migration 0027 is CREATE TABLE plus three indexes, no rebuild. db:check clean at 87 tables. `reports` also had to be added to the top-level schema barrel, which uses explicit named exports — without it drizzle saw no schema change at all. --- migrations/0027_sleepy_switch.sql | 15 + migrations/meta/0027_snapshot.json | 10231 +++++++++++++++++++ migrations/meta/_journal.json | 7 + server/lib/compliance/erasure-manifest.ts | 21 + server/lib/db/schema/index.ts | 1 + server/lib/db/schema/inspection/index.ts | 1 + server/lib/db/schema/inspection/reports.ts | 50 + tests/unit/db/reports-schema.spec.ts | 110 + 8 files changed, 10436 insertions(+) create mode 100644 migrations/0027_sleepy_switch.sql create mode 100644 migrations/meta/0027_snapshot.json create mode 100644 server/lib/db/schema/inspection/reports.ts create mode 100644 tests/unit/db/reports-schema.spec.ts diff --git a/migrations/0027_sleepy_switch.sql b/migrations/0027_sleepy_switch.sql new file mode 100644 index 000000000..46f3105ce --- /dev/null +++ b/migrations/0027_sleepy_switch.sql @@ -0,0 +1,15 @@ +CREATE TABLE `reports` ( + `id` text PRIMARY KEY NOT NULL, + `tenant_id` text NOT NULL, + `inspection_id` text NOT NULL, + `kind` text NOT NULL, + `inspection_service_id` text, + `template_id` text, + `title` text NOT NULL, + `status` text DEFAULT 'in_progress' NOT NULL, + `created_at` integer NOT NULL +); +--> statement-breakpoint +CREATE INDEX `idx_reports_inspection` ON `reports` (`inspection_id`);--> statement-breakpoint +CREATE INDEX `idx_reports_tenant` ON `reports` (`tenant_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `uq_reports_primary` ON `reports` (`inspection_id`) WHERE kind = 'primary'; \ No newline at end of file diff --git a/migrations/meta/0027_snapshot.json b/migrations/meta/0027_snapshot.json new file mode 100644 index 000000000..15e94ce22 --- /dev/null +++ b/migrations/meta/0027_snapshot.json @@ -0,0 +1,10231 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1c4758c4-fbeb-413c-a7a0-35d128968933", + "prevId": "a34d771b-dbd8-4196-9ebb-b27025a12a02", + "tables": { + "agreement_requests": { + "name": "agreement_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signature_base64": { + "name": "inspector_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signed_at": { + "name": "inspector_signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_user_id": { + "name": "inspector_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_snapshot": { + "name": "content_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agreement_requests_token_unique": { + "name": "agreement_requests_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_agreement_requests_verify_token": { + "name": "idx_agreement_requests_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + }, + "idx_agreement_requests_tenant": { + "name": "idx_agreement_requests_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_agreement_requests_inspection": { + "name": "idx_agreement_requests_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_agreement_requests_token_hash": { + "name": "idx_agreement_requests_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agreement_requests_tenant_id_tenants_id_fk": { + "name": "agreement_requests_tenant_id_tenants_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspection_id_inspections_id_fk": { + "name": "agreement_requests_inspection_id_inspections_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_agreement_id_agreements_id_fk": { + "name": "agreement_requests_agreement_id_agreements_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspector_user_id_users_id_fk": { + "name": "agreement_requests_inspector_user_id_users_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "users", + "columnsFrom": [ + "inspector_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreement_signers": { + "name": "agreement_signers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_of": { + "name": "on_behalf_of", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_disclaimer": { + "name": "on_behalf_disclaimer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reminded_at": { + "name": "last_reminded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_agreement_signers_tenant_request": { + "name": "idx_agreement_signers_tenant_request", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": false + }, + "idx_agreement_signers_request_email": { + "name": "idx_agreement_signers_request_email", + "columns": [ + "request_id", + "email" + ], + "isUnique": true + }, + "idx_agreement_signers_token_hash": { + "name": "idx_agreement_signers_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreements": { + "name": "agreements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agreements_tenant": { + "name": "idx_agreements_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agreements_tenant_id_tenants_id_fk": { + "name": "agreements_tenant_id_tenants_id_fk", + "tableFrom": "agreements", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_logs": { + "name": "automation_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_key": { + "name": "recipient_role_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_contact_id": { + "name": "recipient_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notice_id": { + "name": "notice_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_logs_pending": { + "name": "idx_automation_logs_pending", + "columns": [ + "tenant_id", + "status", + "send_at" + ], + "isUnique": false + }, + "idx_automation_logs_insp": { + "name": "idx_automation_logs_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_automation_logs_event": { + "name": "uq_automation_logs_event", + "columns": [ + "automation_id", + "inspection_id", + "event_id", + "channel", + "recipient" + ], + "isUnique": true, + "where": "event_id IS NOT NULL" + } + }, + "foreignKeys": { + "automation_logs_tenant_id_tenants_id_fk": { + "name": "automation_logs_tenant_id_tenants_id_fk", + "tableFrom": "automation_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_kind": { + "name": "recipient_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_profile_id": { + "name": "recipient_role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delay_minutes": { + "name": "delay_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conditions": { + "name": "conditions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"email\"]'" + }, + "sms_body": { + "name": "sms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "in_app_template_id": { + "name": "in_app_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automations_tenant": { + "name": "idx_automations_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_tenant_id_tenants_id_fk": { + "name": "automations_tenant_id_tenants_id_fk", + "tableFrom": "automations", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability": { + "name": "availability", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "day_of_week": { + "name": "day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_availability_inspector": { + "name": "idx_availability_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_availability_window_unique": { + "name": "idx_availability_window_unique", + "columns": [ + "inspector_id", + "day_of_week", + "start_time" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_tenant_id_tenants_id_fk": { + "name": "availability_tenant_id_tenants_id_fk", + "tableFrom": "availability", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_inspector_id_users_id_fk": { + "name": "availability_inspector_id_users_id_fk", + "tableFrom": "availability", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability_overrides": { + "name": "availability_overrides", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_available": { + "name": "is_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transparency": { + "name": "transparency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_avail_overrides_insp": { + "name": "idx_avail_overrides_insp", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_avail_overrides_block_unique": { + "name": "idx_avail_overrides_block_unique", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": true, + "where": "is_available = 0 AND source IS NULL" + }, + "uq_avail_overrides_external": { + "name": "uq_avail_overrides_external", + "columns": [ + "inspector_id", + "source", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_overrides_tenant_id_tenants_id_fk": { + "name": "availability_overrides_tenant_id_tenants_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_overrides_inspector_id_users_id_fk": { + "name": "availability_overrides_inspector_id_users_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_blocks": { + "name": "calendar_blocks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_all_day": { + "name": "is_all_day", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_calendar_blocks_tenant_user_date": { + "name": "idx_calendar_blocks_tenant_user_date", + "columns": [ + "tenant_id", + "user_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connection_read_calendars": { + "name": "calendar_connection_read_calendars", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_calendar_id": { + "name": "external_calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_role": { + "name": "access_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_conn_read_cal": { + "name": "uq_conn_read_cal", + "columns": [ + "connection_id", + "external_calendar_id" + ], + "isUnique": true + }, + "idx_conn_read_cal_tenant": { + "name": "idx_conn_read_cal_tenant", + "columns": [ + "tenant_id", + "connection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connections": { + "name": "calendar_connections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_dek_enc": { + "name": "credentials_dek_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_calendar_connections_user_provider": { + "name": "uq_calendar_connections_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": true + }, + "idx_calendar_connections_tenant_user": { + "name": "idx_calendar_connections_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comment_usage": { + "name": "comment_usage", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_id": { + "name": "comment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_comment_usage_user_last_used": { + "name": "idx_comment_usage_user_last_used", + "columns": [ + "tenant_id", + "user_id", + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comment_usage_comment_id_comments_id_fk": { + "name": "comment_usage_comment_id_comments_id_fk", + "tableFrom": "comment_usage", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_usage_tenant_id_user_id_comment_id_pk": { + "columns": [ + "tenant_id", + "user_id", + "comment_id" + ], + "name": "comment_usage_tenant_id_user_id_comment_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_bucket": { + "name": "rating_bucket", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section": { + "name": "section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_ids": { + "name": "section_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_labels": { + "name": "item_labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_code": { + "name": "trigger_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_keywords": { + "name": "search_keywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repair_summary": { + "name": "repair_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_min_cents": { + "name": "estimate_min_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_max_cents": { + "name": "estimate_max_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recommended_contractor_type_id": { + "name": "recommended_contractor_type_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_comments_tenant": { + "name": "idx_comments_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_comments_rating_bucket": { + "name": "idx_comments_rating_bucket", + "columns": [ + "tenant_id", + "rating_bucket" + ], + "isUnique": false + }, + "idx_comments_library_id": { + "name": "idx_comments_library_id", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comments_tenant_id_tenants_id_fk": { + "name": "comments_tenant_id_tenants_id_fk", + "tableFrom": "comments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "commercial_subtypes": { + "name": "commercial_subtypes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_disabled": { + "name": "is_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_commercial_subtypes_tenant_name": { + "name": "idx_commercial_subtypes_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "commercial_subtypes_tenant_id_tenants_id_fk": { + "name": "commercial_subtypes_tenant_id_tenants_id_fk", + "tableFrom": "commercial_subtypes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "concierge_confirm_tokens": { + "name": "concierge_confirm_tokens", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_concierge_tokens_expiry": { + "name": "idx_concierge_tokens_expiry", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_concierge_confirm_token_hash": { + "name": "idx_concierge_confirm_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "concierge_confirm_tokens_inspection_id_inspections_id_fk": { + "name": "concierge_confirm_tokens_inspection_id_inspections_id_fk", + "tableFrom": "concierge_confirm_tokens", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_role_profiles": { + "name": "contact_role_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability_overrides": { + "name": "capability_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_crp_tenant": { + "name": "idx_crp_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_crp_tenant_key": { + "name": "uq_crp_tenant_key", + "columns": [ + "tenant_id", + "key" + ], + "isUnique": true, + "where": "is_active = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agency": { + "name": "agency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_user_id": { + "name": "agent_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_linked_at": { + "name": "agent_linked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_revoked_at": { + "name": "agent_revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_contacts_type": { + "name": "idx_contacts_type", + "columns": [ + "tenant_id", + "type" + ], + "isUnique": false + }, + "idx_contacts_tenant": { + "name": "idx_contacts_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_contacts_tenant_email": { + "name": "uq_contacts_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "email IS NOT NULL AND archived_at IS NULL" + }, + "uq_contacts_tenant_agent_user": { + "name": "uq_contacts_tenant_agent_user", + "columns": [ + "tenant_id", + "agent_user_id" + ], + "isUnique": true, + "where": "agent_user_id IS NOT NULL AND archived_at IS NULL" + }, + "idx_contacts_agent_user": { + "name": "idx_contacts_agent_user", + "columns": [ + "agent_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_tenant_id_tenants_id_fk": { + "name": "contacts_tenant_id_tenants_id_fk", + "tableFrom": "contacts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contractor_types": { + "name": "contractor_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_contractor_types_tenant": { + "name": "idx_contractor_types_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_items": { + "name": "cost_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "building_id": { + "name": "building_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_index": { + "name": "instance_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_id": { + "name": "unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_method": { + "name": "cost_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uom": { + "name": "uom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_cost_cents": { + "name": "unit_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lump_sum_cents": { + "name": "lump_sum_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eul": { + "name": "eul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_age": { + "name": "eff_age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rul": { + "name": "rul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suggested_remedy": { + "name": "suggested_remedy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_ref": { + "name": "section_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_ref": { + "name": "photo_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_cost_items_tenant_inspection": { + "name": "idx_cost_items_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_cost_items_finding_key": { + "name": "idx_cost_items_finding_key", + "columns": [ + "finding_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "defect_categories": { + "name": "defect_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6b7280'" + }, + "is_summary_driver": { + "name": "is_summary_driver", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_defect_categories_tenant": { + "name": "idx_defect_categories_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "discount_codes": { + "name": "discount_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uses_count": { + "name": "uses_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_discount_codes_tenant": { + "name": "idx_discount_codes_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_discount_codes_code_tenant": { + "name": "uq_discount_codes_code_tenant", + "columns": [ + "upper(code)", + "tenant_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "discount_codes_tenant_id_tenants_id_fk": { + "name": "discount_codes_tenant_id_tenants_id_fk", + "tableFrom": "discount_codes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "document_review_items": { + "name": "document_review_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_requested": { + "name": "is_requested", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_received": { + "name": "is_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_reviewed": { + "name": "is_reviewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_na": { + "name": "is_na", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_doc_review_inspection": { + "name": "idx_doc_review_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_doc_review_item": { + "name": "uq_doc_review_item", + "columns": [ + "inspection_id", + "document_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_suppressions": { + "name": "email_suppressions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_provider": { + "name": "source_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_email_suppressions_email": { + "name": "idx_email_suppressions_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "erasure_log": { + "name": "erasure_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_email": { + "name": "subject_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_basis": { + "name": "identity_basis", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "decisions_json": { + "name": "decisions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retained_count": { + "name": "retained_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "anonymized_count": { + "name": "anonymized_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_count": { + "name": "deleted_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "response_note": { + "name": "response_note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_erasure_log_tenant": { + "name": "idx_erasure_log_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "esign_audit_logs": { + "name": "esign_audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_esign_audit_logs_request": { + "name": "idx_esign_audit_logs_request", + "columns": [ + "tenant_id", + "request_id", + "created_at" + ], + "isUnique": false + }, + "idx_esign_audit_logs_event_dedup": { + "name": "idx_esign_audit_logs_event_dedup", + "columns": [ + "tenant_id", + "request_id", + "event" + ], + "isUnique": true, + "where": "event NOT LIKE 'signer.%'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "event_types": { + "name": "event_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_duration_min": { + "name": "default_duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "default_price_cents": { + "name": "default_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6366f1'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_event_types_tenant_slug": { + "name": "uq_event_types_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "event_types_tenant_id_tenants_id_fk": { + "name": "event_types_tenant_id_tenants_id_fk", + "tableFrom": "event_types", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_access_tokens": { + "name": "inspection_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_iat_token": { + "name": "idx_iat_token", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_iat_inspection": { + "name": "idx_iat_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_iat_recipient": { + "name": "idx_iat_recipient", + "columns": [ + "inspection_id", + "recipient_email" + ], + "isUnique": true + }, + "idx_iat_token_hash": { + "name": "idx_iat_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_access_tokens_tenant_id_tenants_id_fk": { + "name": "inspection_access_tokens_tenant_id_tenants_id_fk", + "tableFrom": "inspection_access_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_events": { + "name": "inspection_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type_id": { + "name": "event_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'scheduled'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "results_received_at": { + "name": "results_received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gcal_event_id": { + "name": "gcal_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_events_scheduled": { + "name": "idx_inspection_events_scheduled", + "columns": [ + "tenant_id", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_events_inspection": { + "name": "idx_inspection_events_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_events_tenant_id_tenants_id_fk": { + "name": "inspection_events_tenant_id_tenants_id_fk", + "tableFrom": "inspection_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspection_id_inspections_id_fk": { + "name": "inspection_events_inspection_id_inspections_id_fk", + "tableFrom": "inspection_events", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_events_event_type_id_event_types_id_fk": { + "name": "inspection_events_event_type_id_event_types_id_fk", + "tableFrom": "inspection_events", + "tableTo": "event_types", + "columnsFrom": [ + "event_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspector_id_users_id_fk": { + "name": "inspection_events_inspector_id_users_id_fk", + "tableFrom": "inspection_events", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_inspectors": { + "name": "inspection_inspectors", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lead'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_inspectors_tenant_user": { + "name": "idx_insp_inspectors_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "idx_insp_inspectors_user": { + "name": "idx_insp_inspectors_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_inspectors_inspection_id_user_id_pk": { + "columns": [ + "inspection_id", + "user_id" + ], + "name": "inspection_inspectors_inspection_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_item_tag_links": { + "name": "inspection_item_tag_links", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_links_tenant": { + "name": "idx_tag_links_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_tag_links_tag": { + "name": "idx_tag_links_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + }, + "idx_tag_links_inspection_item": { + "name": "idx_tag_links_inspection_item", + "columns": [ + "inspection_id", + "item_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_item_tag_links_inspection_id_item_id_tag_id_pk": { + "columns": [ + "inspection_id", + "item_id", + "tag_id" + ], + "name": "inspection_item_tag_links_inspection_id_item_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_media_pool": { + "name": "inspection_media_pool", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exif_data": { + "name": "exif_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "caption": { + "name": "caption", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'photo'" + }, + "stream_uid": { + "name": "stream_uid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "poster_pct": { + "name": "poster_pct", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_sec": { + "name": "duration_sec", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'stream'" + }, + "poster_key": { + "name": "poster_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_media_pool_tenant": { + "name": "idx_media_pool_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_media_pool_inspection": { + "name": "idx_media_pool_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_messages": { + "name": "inspection_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_role": { + "name": "from_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_user_id": { + "name": "from_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_msg_inspection": { + "name": "idx_msg_inspection", + "columns": [ + "inspection_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_contact": { + "name": "idx_msg_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_unread": { + "name": "idx_msg_unread", + "columns": [ + "tenant_id", + "contact_id", + "from_role" + ], + "isUnique": false, + "where": "\"inspection_messages\".\"read_at\" IS NULL" + } + }, + "foreignKeys": { + "inspection_messages_tenant_id_tenants_id_fk": { + "name": "inspection_messages_tenant_id_tenants_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_messages_inspection_id_inspections_id_fk": { + "name": "inspection_messages_inspection_id_inspections_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_people": { + "name": "inspection_people", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_profile_id": { + "name": "role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_ip_inspection": { + "name": "idx_ip_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_ip_tenant": { + "name": "idx_ip_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_ip_insp_contact_role": { + "name": "uq_ip_insp_contact_role", + "columns": [ + "inspection_id", + "contact_id", + "role_profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_requests": { + "name": "inspection_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_phone": { + "name": "client_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "property_city": { + "name": "property_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_state": { + "name": "property_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_zip": { + "name": "property_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_requests_tenant": { + "name": "idx_inspection_requests_tenant", + "columns": [ + "tenant_id", + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_requests_email": { + "name": "idx_inspection_requests_email", + "columns": [ + "tenant_id", + "client_email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_requests_tenant_id_tenants_id_fk": { + "name": "inspection_requests_tenant_id_tenants_id_fk", + "tableFrom": "inspection_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_results": { + "name": "inspection_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ydoc_state": { + "name": "ydoc_state", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_system_snapshot": { + "name": "rating_system_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_results_tenant": { + "name": "idx_results_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_results_inspection": { + "name": "idx_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_results_inspection": { + "name": "uq_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_results_tenant_id_tenants_id_fk": { + "name": "inspection_results_tenant_id_tenants_id_fk", + "tableFrom": "inspection_results", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_results_inspection_id_inspections_id_fk": { + "name": "inspection_results_inspection_id_inspections_id_fk", + "tableFrom": "inspection_results", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_services": { + "name": "inspection_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_override_cents": { + "name": "price_override_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_snapshot": { + "name": "name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_snapshot_cents": { + "name": "price_snapshot_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_insp_services_tenant": { + "name": "idx_insp_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_insp_services_insp": { + "name": "idx_insp_services_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_services_tenant_id_tenants_id_fk": { + "name": "inspection_services_tenant_id_tenants_id_fk", + "tableFrom": "inspection_services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_services_inspection_id_inspections_id_fk": { + "name": "inspection_services_inspection_id_inspections_id_fk", + "tableFrom": "inspection_services", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_services_service_id_services_id_fk": { + "name": "inspection_services_service_id_services_id_fk", + "tableFrom": "inspection_services", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_types": { + "name": "inspection_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_types_tenant_name": { + "name": "idx_inspection_types_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_units": { + "name": "inspection_units", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_unit_id": { + "name": "parent_unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unit'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "attrs": { + "name": "attrs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_units_tenant_inspection": { + "name": "idx_inspection_units_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_inspection_units_parent": { + "name": "idx_inspection_units_parent", + "columns": [ + "parent_unit_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspections": { + "name": "inspections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_place_id": { + "name": "address_place_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_county": { + "name": "address_county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lat": { + "name": "address_lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lng": { + "name": "address_lng", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_geocoded_at": { + "name": "address_geocoded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'requested'" + }, + "report_status": { + "name": "report_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_notes": { + "name": "cancel_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_payment_required": { + "name": "is_payment_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_agreement_required": { + "name": "is_agreement_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_auto_sign_on_publish": { + "name": "is_auto_sign_on_publish", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "discount_code_id": { + "name": "discount_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discount_amount_cents": { + "name": "discount_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closing_date": { + "name": "closing_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referral_source": { + "name": "referral_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_notes": { + "name": "internal_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sqft": { + "name": "sqft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "foundation_type": { + "name": "foundation_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrooms": { + "name": "bedrooms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bathrooms": { + "name": "bathrooms", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lot_size": { + "name": "lot_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_facts": { + "name": "property_facts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_photo_id": { + "name": "cover_photo_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_crop": { + "name": "cover_crop", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_image_key": { + "name": "cover_image_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_tier": { + "name": "report_tier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_automations_disabled": { + "name": "is_automations_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "template_snapshot": { + "name": "template_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_snapshot_version": { + "name": "template_snapshot_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "profile_override": { + "name": "profile_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_defect_fields_override": { + "name": "require_defect_fields_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "concierge_status": { + "name": "concierge_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_team_mode": { + "name": "is_team_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "lead_inspector_id": { + "name": "lead_inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "helper_inspector_ids": { + "name": "helper_inspector_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "data_version": { + "name": "data_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source_inspection_id": { + "name": "source_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_inspection_id": { + "name": "root_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reinspection_round": { + "name": "reinspection_round", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_inspection_mode": { + "name": "unit_inspection_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'tagged'" + }, + "location_options": { + "name": "location_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sampling_declaration": { + "name": "sampling_declaration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pca_narrative": { + "name": "pca_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviations": { + "name": "deviations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_mode": { + "name": "report_photo_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_start_ms": { + "name": "scheduled_start_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_end_ms": { + "name": "scheduled_end_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "badge_layout_override": { + "name": "badge_layout_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_columns": { + "name": "report_photo_columns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referred_by_contact_id": { + "name": "referred_by_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspections_tenant": { + "name": "idx_inspections_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspections_request": { + "name": "idx_inspections_request", + "columns": [ + "request_id" + ], + "isUnique": false + }, + "idx_inspections_inspector": { + "name": "idx_inspections_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_inspections_tenant_status": { + "name": "idx_inspections_tenant_status", + "columns": [ + "tenant_id", + "status" + ], + "isUnique": false + }, + "idx_inspections_tenant_date": { + "name": "idx_inspections_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_inspector_date": { + "name": "idx_inspections_inspector_date", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_root": { + "name": "idx_inspections_root", + "columns": [ + "root_inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspections_tenant_id_tenants_id_fk": { + "name": "inspections_tenant_id_tenants_id_fk", + "tableFrom": "inspections", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_inspector_id_users_id_fk": { + "name": "inspections_inspector_id_users_id_fk", + "tableFrom": "inspections", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_template_id_templates_id_fk": { + "name": "inspections_template_id_templates_id_fk", + "tableFrom": "inspections", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_discount_code_id_discount_codes_id_fk": { + "name": "inspections_discount_code_id_discount_codes_id_fk", + "tableFrom": "inspections", + "tableTo": "discount_codes", + "columnsFrom": [ + "discount_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_request_id_inspection_requests_id_fk": { + "name": "inspections_request_id_inspection_requests_id_fk", + "tableFrom": "inspections", + "tableTo": "inspection_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspector_credentials": { + "name": "inspector_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_r2_key": { + "name": "image_r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspector_credentials_tenant": { + "name": "idx_inspector_credentials_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspector_credentials_user": { + "name": "idx_inspector_credentials_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "partial_paid_at": { + "name": "partial_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voided_at": { + "name": "voided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qbo_sync_status": { + "name": "qbo_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + } + }, + "indexes": { + "idx_invoices_tenant": { + "name": "idx_invoices_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_invoices_inspection": { + "name": "idx_invoices_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_invoices_contact": { + "name": "idx_invoices_contact", + "columns": [ + "tenant_id", + "contact_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_tenant_id_tenants_id_fk": { + "name": "invoices_tenant_id_tenants_id_fk", + "tableFrom": "invoices", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_inspection_id_inspections_id_fk": { + "name": "invoices_inspection_id_inspections_id_fk", + "tableFrom": "invoices", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_contact_id_contacts_id_fk": { + "name": "invoices_contact_id_contacts_id_fk", + "tableFrom": "invoices", + "tableTo": "contacts", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_libraries": { + "name": "marketplace_libraries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_libraries_kind_featured": { + "name": "idx_marketplace_libraries_kind_featured", + "columns": [ + "kind", + "is_featured" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_templates": { + "name": "marketplace_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_templates": { + "name": "message_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "variables": { + "name": "variables", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seeded": { + "name": "is_seeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_templates_tenant_channel": { + "name": "idx_message_templates_tenant_channel", + "columns": [ + "tenant_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messaging_compliance": { + "name": "messaging_compliance", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'own'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subaccount_sid": { + "name": "subaccount_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_sid": { + "name": "customer_profile_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_status": { + "name": "customer_profile_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_sid": { + "name": "brand_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_status": { + "name": "brand_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_sid": { + "name": "campaign_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_status": { + "name": "campaign_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_sid": { + "name": "tfv_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_status": { + "name": "tfv_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "messaging_resource_sid": { + "name": "messaging_resource_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_meta": { + "name": "provider_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number": { + "name": "provisioned_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number_sid": { + "name": "provisioned_number_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_sender_attached": { + "name": "has_sender_attached", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "compliance_status": { + "name": "compliance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_preferences": { + "name": "notification_preferences", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "class_id": { + "name": "class_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notification_prefs_unique": { + "name": "idx_notification_prefs_unique", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "class_id", + "channel" + ], + "isUnique": true + }, + "idx_notification_prefs_subject": { + "name": "idx_notification_prefs_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orphaned_media": { + "name": "orphaned_media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_orphaned_media_key": { + "name": "idx_orphaned_media_key", + "columns": [ + "tenant_id", + "r2_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_webhook_events": { + "name": "processed_webhook_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "psq_responses": { + "name": "psq_responses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sent'" + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_psq_inspection": { + "name": "uq_psq_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": true + }, + "idx_psq_share_token": { + "name": "idx_psq_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_connections": { + "name": "qbo_connections", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "realm_id": { + "name": "realm_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_enc": { + "name": "access_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_enc": { + "name": "refresh_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "default_item_id": { + "name": "default_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_entity_map": { + "name": "qbo_entity_map", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_type": { + "name": "qbo_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_id": { + "name": "qbo_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_sync_token": { + "name": "qbo_sync_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_qbo_entity_map_qbo": { + "name": "idx_qbo_entity_map_qbo", + "columns": [ + "tenant_id", + "qbo_type", + "qbo_id" + ], + "isUnique": true + }, + "idx_qbo_entity_map_oi": { + "name": "idx_qbo_entity_map_oi", + "columns": [ + "tenant_id", + "oi_type", + "oi_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_sync_errors": { + "name": "qbo_sync_errors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_resolved": { + "name": "is_resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rating_systems": { + "name": "rating_systems", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "levels": { + "name": "levels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_rating_systems_tenant_slug": { + "name": "idx_rating_systems_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_rating_systems_tenant": { + "name": "idx_rating_systems_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rating_systems_tenant_id_tenants_id_fk": { + "name": "rating_systems_tenant_id_tenants_id_fk", + "tableFrom": "rating_systems", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_request_items": { + "name": "repair_request_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repair_request_id": { + "name": "repair_request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_title": { + "name": "section_title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_snapshot": { + "name": "comment_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_credit_cents": { + "name": "requested_credit_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "defect_title_snapshot": { + "name": "defect_title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_snapshot": { + "name": "location_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_snapshot": { + "name": "category_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_snapshot": { + "name": "trade_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_request_items_rr": { + "name": "idx_repair_request_items_rr", + "columns": [ + "repair_request_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_requests": { + "name": "repair_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_ref": { + "name": "created_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_intro": { + "name": "custom_intro", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_requests_inspection": { + "name": "idx_repair_requests_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_repair_requests_share_token": { + "name": "idx_repair_requests_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_exports": { + "name": "report_exports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_report_exports_inspection": { + "name": "idx_report_exports_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_pdfs": { + "name": "report_pdfs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rendered_at": { + "name": "rendered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ready'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_report_pdfs_inspection_type": { + "name": "uq_report_pdfs_inspection_type", + "columns": [ + "inspection_id", + "type", + "version_number" + ], + "isUnique": true + }, + "idx_report_pdfs_tenant": { + "name": "idx_report_pdfs_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_report_pdfs_status": { + "name": "idx_report_pdfs_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_report_pdfs_content_hash": { + "name": "idx_report_pdfs_content_hash", + "columns": [ + "inspection_id", + "type", + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": { + "report_pdfs_tenant_id_tenants_id_fk": { + "name": "report_pdfs_tenant_id_tenants_id_fk", + "tableFrom": "report_pdfs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_signoff": { + "name": "report_signoff", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualifications_ref": { + "name": "qualifications_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_ref": { + "name": "signature_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dual_role": { + "name": "is_dual_role", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_report_signoff_inspection": { + "name": "idx_report_signoff_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_report_signoff_role": { + "name": "uq_report_signoff_role", + "columns": [ + "inspection_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_versions": { + "name": "report_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_json": { + "name": "snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_amendment": { + "name": "is_amendment", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + } + }, + "indexes": { + "idx_report_versions_inspection": { + "name": "idx_report_versions_inspection", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": false + }, + "uq_report_versions_inspection_version": { + "name": "uq_report_versions_inspection_version", + "columns": [ + "inspection_id", + "version_number" + ], + "isUnique": true + }, + "idx_report_versions_verify_token": { + "name": "idx_report_versions_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reports": { + "name": "reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_service_id": { + "name": "inspection_service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_reports_inspection": { + "name": "idx_reports_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_reports_tenant": { + "name": "idx_reports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_reports_primary": { + "name": "uq_reports_primary", + "columns": [ + "inspection_id" + ], + "isUnique": true, + "where": "kind = 'primary'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_inspectors": { + "name": "service_inspectors", + "columns": { + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_service_inspectors_tenant": { + "name": "idx_service_inspectors_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "service_inspectors_service_id_user_id_pk": { + "columns": [ + "service_id", + "user_id" + ], + "name": "service_inspectors_service_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_event_type_slugs": { + "name": "default_event_type_slugs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_services_tenant": { + "name": "idx_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "services_tenant_id_tenants_id_fk": { + "name": "services_tenant_id_tenants_id_fk", + "tableFrom": "services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_template_id_templates_id_fk": { + "name": "services_template_id_templates_id_fk", + "tableFrom": "services", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_agreement_id_agreements_id_fk": { + "name": "services_agreement_id_agreements_id_fk", + "tableFrom": "services", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_enc": { + "name": "private_key_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Ed25519'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_consent_log": { + "name": "sms_consent_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disclosure_version": { + "name": "disclosure_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "captured_via": { + "name": "captured_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'contact'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": { + "idx_sms_consent_contact": { + "name": "idx_sms_consent_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_sms_consent_subject": { + "name": "idx_sms_consent_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_delivery_status": { + "name": "sms_delivery_status", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sms_delivery_status_msg": { + "name": "idx_sms_delivery_status_msg", + "columns": [ + "tenant_id", + "provider_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_disclosure_versions": { + "name": "sms_disclosure_versions", + "columns": { + "version": { + "name": "version", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tags_tenant_name": { + "name": "idx_tags_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + }, + "idx_tags_tenant": { + "name": "idx_tags_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_templates_tenant": { + "name": "idx_templates_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_templates_rating_system": { + "name": "idx_templates_rating_system", + "columns": [ + "rating_system_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "templates_tenant_id_tenants_id_fk": { + "name": "templates_tenant_id_tenants_id_fk", + "tableFrom": "templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_custom_holidays": { + "name": "tenant_custom_holidays", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_tenant_custom_holidays_tenant_date": { + "name": "uq_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": true + }, + "idx_tenant_custom_holidays_tenant_date": { + "name": "idx_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_library_imports": { + "name": "tenant_library_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "uq_tenant_library_import": { + "name": "uq_tenant_library_import", + "columns": [ + "tenant_id", + "library_id" + ], + "isUnique": true + }, + "idx_tenant_library_imports_tenant": { + "name": "idx_tenant_library_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_import_history": { + "name": "tenant_marketplace_import_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_version": { + "name": "target_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_affected": { + "name": "rows_affected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_history_tenant": { + "name": "idx_marketplace_history_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_marketplace_history_template": { + "name": "idx_marketplace_history_template", + "columns": [ + "template_id" + ], + "isUnique": false + }, + "idx_marketplace_history_library": { + "name": "idx_marketplace_history_library", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_imports": { + "name": "tenant_marketplace_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "marketplace_template_id": { + "name": "marketplace_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_template_id": { + "name": "local_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_mkt_imports_tmpl": { + "name": "idx_mkt_imports_tmpl", + "columns": [ + "marketplace_template_id" + ], + "isUnique": false + }, + "idx_mkt_imports_tenant": { + "name": "idx_mkt_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk": { + "name": "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "marketplace_templates", + "columnsFrom": [ + "marketplace_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tenant_marketplace_imports_local_template_id_templates_id_fk": { + "name": "tenant_marketplace_imports_local_template_id_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "templates", + "columnsFrom": [ + "local_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "usage_counters": { + "name": "usage_counters", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_usage_counters_tenant": { + "name": "idx_usage_counters_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_counters_tenant_id_metric_period_key_pk": { + "columns": [ + "tenant_id", + "metric", + "period_key" + ], + "name": "usage_counters_tenant_id_metric_period_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_templates": { + "name": "email_templates", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocks": { + "name": "blocks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_templates_tenant_id_tenants_id_fk": { + "name": "email_templates_tenant_id_tenants_id_fk", + "tableFrom": "email_templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_templates_tenant_id_trigger_pk": { + "columns": [ + "tenant_id", + "trigger" + ], + "name": "email_templates_tenant_id_trigger_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_configs": { + "name": "tenant_configs", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "support_email": { + "name": "support_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_address": { + "name": "company_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pdf_footer_shown": { + "name": "is_pdf_footer_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_page_numbers_shown": { + "name": "is_pdf_page_numbers_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_license_shown": { + "name": "is_pdf_license_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reply_to": { + "name": "reply_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_mode": { + "name": "email_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "video_mode": { + "name": "video_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'r2'" + }, + "sms_mode": { + "name": "sms_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "sender_display_name": { + "name": "sender_display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "point_of_contact": { + "name": "point_of_contact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company'" + }, + "billing_url": { + "name": "billing_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_url": { + "name": "review_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_phone": { + "name": "company_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "integration_config": { + "name": "integration_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secrets_enc": { + "name": "secrets_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dek_enc": { + "name": "dek_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ics_token": { + "name": "ics_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widget_allowed_origins": { + "name": "widget_allowed_origins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'signature'" + }, + "attention_thresholds": { + "name": "attention_thresholds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"agreement_unsigned_h\":72,\"invoice_overdue_h\":72,\"report_unpublished_h\":72}'" + }, + "inspection_prefs": { + "name": "inspection_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_estimates_shown": { + "name": "is_estimates_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_repair_list_enabled": { + "name": "is_repair_list_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_customer_repair_export_enabled": { + "name": "is_customer_repair_export_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unpaid_blocked": { + "name": "is_unpaid_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unsigned_agreement_blocked": { + "name": "is_unsigned_agreement_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "custom_referral_sources": { + "name": "custom_referral_sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dashboard_column_prefs": { + "name": "dashboard_column_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_concierge_review_required": { + "name": "is_concierge_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_inspector_choice_allowed": { + "name": "is_inspector_choice_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_pdf_pipeline_enabled": { + "name": "is_pdf_pipeline_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_team_mode_default": { + "name": "is_team_mode_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_apprentice_review_required": { + "name": "is_apprentice_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_guest_invites_enabled": { + "name": "is_guest_invites_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "require_defect_fields": { + "name": "require_defect_fields", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "agreement_retention_years": { + "name": "agreement_retention_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "reinspection_statuses": { + "name": "reinspection_statuses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_collab_editing_enabled": { + "name": "is_collab_editing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sms_byo_provider": { + "name": "sms_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_byo_provider": { + "name": "email_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "is_managed_eligible": { + "name": "is_managed_eligible", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "managed_provider": { + "name": "managed_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'twilio'" + }, + "is_reserve_schedule_enabled": { + "name": "is_reserve_schedule_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reserve_term_years": { + "name": "reserve_term_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "inflation_rate_bps": { + "name": "inflation_rate_bps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'UTC'" + }, + "booking_slot_mode": { + "name": "booking_slot_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "booking_slot_interval_min": { + "name": "booking_slot_interval_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "holiday_region": { + "name": "holiday_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "holiday_public_policy": { + "name": "holiday_public_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "holiday_internal_policy": { + "name": "holiday_internal_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'advisory'" + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en-US'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "is_archive_revoking_access": { + "name": "is_archive_revoking_access", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "legal_mode": { + "name": "legal_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'hosted'" + }, + "custom_privacy_url": { + "name": "custom_privacy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_terms_url": { + "name": "custom_terms_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privacy_body": { + "name": "privacy_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_body": { + "name": "terms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_configs_tenant_id_tenants_id_fk": { + "name": "tenant_configs_tenant_id_tenants_id_fk", + "tableFrom": "tenant_configs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "stripe_connect_account_id": { + "name": "stripe_connect_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "max_users": { + "name": "max_users", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'shared'" + }, + "applied_cmd_seq": { + "name": "applied_cmd_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_cred_seq": { + "name": "applied_cred_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_invites": { + "name": "tenant_invites", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inspector'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_invites_tenant": { + "name": "idx_invites_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_tenant_invites_pending_email": { + "name": "uq_tenant_invites_pending_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "status = 'pending'" + } + }, + "foreignKeys": { + "tenant_invites_tenant_id_tenants_id_fk": { + "name": "tenant_invites_tenant_id_tenants_id_fk", + "tableFrom": "tenant_invites", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_signature_base64": { + "name": "default_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_signature_enabled": { + "name": "is_signature_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manager'" + }, + "onboarding_state": { + "name": "onboarding_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_totp_enabled": { + "name": "is_totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_recovery_codes": { + "name": "totp_recovery_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_verified_at": { + "name": "totp_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_accepted": { + "name": "terms_accepted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + "deleted_at" + ], + "isUnique": false + }, + "uq_users_tenant_email": { + "name": "uq_users_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "deleted_at IS NULL" + }, + "idx_users_tenant": { + "name": "idx_users_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_users_slug_per_tenant": { + "name": "idx_users_slug_per_tenant", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_users_email": { + "name": "idx_users_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_slug": { + "name": "inspector_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_audit_tenant_created": { + "name": "idx_audit_tenant_created", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_audit_entity": { + "name": "idx_audit_entity", + "columns": [ + "entity_type", + "entity_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_tenant_id_tenants_id_fk": { + "name": "audit_logs_tenant_id_tenants_id_fk", + "tableFrom": "audit_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration_test_results": { + "name": "integration_test_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_ok": { + "name": "is_ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_by_user_id": { + "name": "tested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_at": { + "name": "tested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_integration_test_tenant_target": { + "name": "idx_integration_test_tenant_target", + "columns": [ + "tenant_id", + "target", + "tested_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_tenant_user_created": { + "name": "idx_notifications_tenant_user_created", + "columns": [ + "tenant_id", + "user_id", + "created_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_user_unread": { + "name": "idx_notifications_tenant_user_unread", + "columns": [ + "tenant_id", + "user_id", + "read_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_contact_created": { + "name": "idx_notifications_tenant_contact_created", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_tenant_id_tenants_id_fk": { + "name": "notifications_tenant_id_tenants_id_fk", + "tableFrom": "notifications", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parked_cmd_events": { + "name": "parked_cmd_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "envelope": { + "name": "envelope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_parked_cmd_events_received_at": { + "name": "idx_parked_cmd_events_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_cmd_events": { + "name": "processed_cmd_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cmd_type": { + "name": "cmd_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slug_reservations": { + "name": "slug_reservations", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_outbox": { + "name": "sync_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_tried_at": { + "name": "last_tried_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_sync_outbox_status_created": { + "name": "idx_sync_outbox_status_created", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_destruction_records": { + "name": "tenant_destruction_records", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_slug": { + "name": "tenant_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_deleted": { + "name": "rows_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_objects": { + "name": "r2_objects", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_bytes": { + "name": "r2_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "kv_keys": { + "name": "kv_keys", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_destruction_tenant": { + "name": "idx_destruction_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_destruction_destroyed_at": { + "name": "idx_destruction_destroyed_at", + "columns": [ + "destroyed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_legal_versions": { + "name": "tenant_legal_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "doc": { + "name": "doc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_material": { + "name": "is_material", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_tenant_legal_versions_doc_version": { + "name": "idx_tenant_legal_versions_doc_version", + "columns": [ + "tenant_id", + "doc", + "version" + ], + "isUnique": true + }, + "idx_tenant_legal_versions_latest": { + "name": "idx_tenant_legal_versions_latest", + "columns": [ + "tenant_id", + "doc", + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_uploads": { + "name": "client_uploads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_kind": { + "name": "uploaded_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_ref": { + "name": "uploaded_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_name": { + "name": "uploaded_by_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_client_uploads_inspection": { + "name": "idx_client_uploads_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uq_discount_codes_code_tenant": { + "columns": { + "upper(code)": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 413b4381e..020b04d14 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -190,6 +190,13 @@ "when": 1785714900847, "tag": "0026_charming_earthquake", "breakpoints": true + }, + { + "idx": 27, + "version": "6", + "when": 1785716850363, + "tag": "0027_sleepy_switch", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/lib/compliance/erasure-manifest.ts b/server/lib/compliance/erasure-manifest.ts index cad654365..4eb344146 100644 --- a/server/lib/compliance/erasure-manifest.ts +++ b/server/lib/compliance/erasure-manifest.ts @@ -150,6 +150,14 @@ export const ERASURE_MANIFEST: ErasureRule[] = [ { table: 'agreement_signers', column: 'signature_base64', category: 'user.biometric.signature', action: 'retain', legalBasis: 'art_17_3_e', retention: 'P6Y' }, { table: 'agreement_requests', column: 'signature_base64', category: 'user.biometric.signature', action: 'retain', legalBasis: 'art_17_3_e', retention: 'P6Y' }, { table: 'esign_audit_logs', column: 'signature', category: 'system.integrity', action: 'retain', legalBasis: 'art_17_3_e' }, + + // ── reports ─────────────────────────────────────────────────────────────── + // A report is findings about a named person's property, and `title` is the + // one free-text column a human writes — it routinely carries the address + // ("123 Oak St — Radon"). Anonymised rather than deleted: the row is the + // spine of a signed, delivered document, and removing it would strand the + // version chain that proves what was delivered. + { table: 'reports', column: 'title', category: 'user.address', action: 'anonymize', legalBasis: 'art_17_3_e', retention: 'P6Y' }, ]; /** @@ -195,6 +203,19 @@ export const ERASURE_OUT_OF_SCOPE: ErasureOutOfScopeEntry[] = [ { table: 'contact_role_profiles', column: 'email_template_id', reason: 'template reference, not personal data' }, { table: 'sms_consent_log', column: 'recipient_type', reason: 'role-kind enum, not personal data' }, { table: 'report_versions', column: 'signature', reason: 'report-content integrity seal, not personal data' }, + // ── reports ─────────────────────────────────────────────────────────────── + // A report is findings about a named person's property. Only `title` is + // free text a human writes, and it routinely carries the address ("123 Oak + // St — Radon"). The rest of the row is ids, enums and a timestamp, declared + // out of scope below. + { table: 'reports', column: 'inspection_id', reason: 'opaque id; the inspection row carries its own rules' }, + { table: 'reports', column: 'tenant_id', reason: 'tenant scope key, not personal data' }, + { table: 'reports', column: 'id', reason: 'opaque primary key' }, + { table: 'reports', column: 'kind', reason: 'primary/ancillary enum, not personal data' }, + { table: 'reports', column: 'inspection_service_id', reason: 'billing-line reference, not personal data' }, + { table: 'reports', column: 'template_id', reason: 'template reference, not personal data' }, + { table: 'reports', column: 'status', reason: 'workflow enum, not personal data' }, + { table: 'reports', column: 'created_at', reason: 'record timestamp, not personal data' }, // The tenant's own published Privacy / Terms. `body_snapshot` is the // company's prose, not a data subject's data, and the row's whole purpose is // to be immutable — erasing it would destroy the record of what a document diff --git a/server/lib/db/schema/index.ts b/server/lib/db/schema/index.ts index c6f8e636a..aac233baa 100644 --- a/server/lib/db/schema/index.ts +++ b/server/lib/db/schema/index.ts @@ -30,6 +30,7 @@ export { commentUsage, defectCategories, costItems, + reports, } from './inspection'; export { inspectorCredentials } from './inspection/inspector-credentials'; export { commercialSubtypes } from './commercial-subtypes'; diff --git a/server/lib/db/schema/inspection/index.ts b/server/lib/db/schema/inspection/index.ts index fb2719777..0e1710160 100644 --- a/server/lib/db/schema/inspection/index.ts +++ b/server/lib/db/schema/inspection/index.ts @@ -5,6 +5,7 @@ export * from './media'; export * from './comments'; export * from './assignment'; export * from './services'; +export * from './reports'; export * from './automation'; export * from './message-template'; export * from './concierge'; diff --git a/server/lib/db/schema/inspection/reports.ts b/server/lib/db/schema/inspection/reports.ts new file mode 100644 index 000000000..00b04c023 --- /dev/null +++ b/server/lib/db/schema/inspection/reports.ts @@ -0,0 +1,50 @@ +import { sqliteTable, text, integer, index, uniqueIndex } from 'drizzle-orm/sqlite-core'; +import { sql } from 'drizzle-orm'; + +/** + * One order, several reports. + * + * A standard inspection publishes today and the radon report publishes on + * Thursday, each with its own document, signature chain and notification — + * without the client waiting on the slowest one. Before this, `inspections` and + * `inspection_results` were one-to-one (`uq_results_inspection`), so an order + * could only ever produce a single document. + * + * A report's identity comes from its SERVICE and its TEMPLATE. There is + * deliberately no third catalogue: `services` and `event_types` stay separate + * because one service can require two visits (a radon drop-off and a pickup), + * which is a different question from what gets delivered. + */ +export const reports = sqliteTable('reports', { + id: text('id').primaryKey(), + tenantId: text('tenant_id').notNull(), + inspectionId: text('inspection_id').notNull(), + // 'primary' is not just another ancillary: it is the one that must exist, + // the one the pay gate keys on, and the one a client means by "my report". + kind: text('kind', { enum: ['primary', 'ancillary'] }).notNull(), + // The billing LINE this delivers (`inspection_services.id`), when there is + // one. Nullable and NOT a foreign key: a tenant may produce a report for + // something they did not bill separately. + // + // NOT named `service_id`, on purpose. In this schema `service_id` already + // means the CATALOGUE entry (`services.id`) — both + // `inspection_services.service_id` and `service_inspectors.service_id` are + // that. Naming this one `service_id` would read as the catalogue to anyone + // who has seen the other two, and a catalogue id here makes "which report + // did this billing line produce" unanswerable — which breaks pay splits, + // whose entire grain is `inspection_services.id`. + inspectionServiceId: text('inspection_service_id'), + // Denormalised pointer to the template this report was generated from, the + // same treatment `inspections.template_id` keeps for the primary report. + templateId: text('template_id'), + title: text('title').notNull(), + status: text('status', { enum: ['in_progress', 'published'] }).notNull().default('in_progress'), + createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), +}, (t) => [ + index('idx_reports_inspection').on(t.inspectionId), + index('idx_reports_tenant').on(t.tenantId), + // Exactly one primary per inspection, enforced by the database rather than + // by the service layer: "which one does the client mean" must not depend on + // which caller wrote last. Partial index — ancillary reports are unbounded. + uniqueIndex('uq_reports_primary').on(t.inspectionId).where(sql`kind = 'primary'`), +]); diff --git a/tests/unit/db/reports-schema.spec.ts b/tests/unit/db/reports-schema.spec.ts new file mode 100644 index 000000000..bef1cd477 --- /dev/null +++ b/tests/unit/db/reports-schema.spec.ts @@ -0,0 +1,110 @@ +/** + * The `reports` entity — one order, several reports. + * + * A standard inspection publishes today and the radon report publishes on + * Thursday, each with its own document. `uq_results_inspection` made that + * impossible; this table is the thing it becomes. + * + * The one-primary rule is enforced by a partial UNIQUE INDEX rather than by the + * service layer, because "which report does the client mean by my report" must + * not depend on which caller wrote last. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { and, eq } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +const TENANT = 'tenant-reports-1'; +const INSP = 'insp-reports-1'; +const OTHER_INSP = 'insp-reports-2'; + +describe('reports', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + + const addReport = (over: Partial = {}) => + db.insert(schema.reports).values({ + id: over.id ?? `r-${Math.random().toString(36).slice(2)}`, + tenantId: TENANT, + inspectionId: INSP, + kind: 'ancillary', + title: 'Report', + status: 'in_progress', + createdAt: new Date(), + ...over, + } as never); + + const listFor = (inspectionId: string) => + db.select().from(schema.reports) + .where(and(eq(schema.reports.tenantId, TENANT), eq(schema.reports.inspectionId, inspectionId))) + .all(); + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + }); + + afterEach(() => { sqlite.close(); }); + + it('allows several reports on one inspection', async () => { + // The entire point. uq_results_inspection made this impossible. + await addReport({ id: 'r-primary', kind: 'primary', title: 'Home Inspection Report' }); + await addReport({ id: 'r-radon', kind: 'ancillary', title: 'Radon Test Report' }); + + expect(await listFor(INSP)).toHaveLength(2); + }); + + it('allows exactly one primary report per inspection', async () => { + await addReport({ id: 'r-p1', kind: 'primary', title: 'Home Inspection Report' }); + await expect(addReport({ id: 'r-p2', kind: 'primary', title: 'Duplicate' })) + .rejects.toThrow(); + }); + + it('does not limit how many ancillary reports an inspection has', async () => { + // The uniqueness is PARTIAL — a job can deliver radon, sewer and mould. + await addReport({ id: 'r-a1', title: 'Radon' }); + await addReport({ id: 'r-a2', title: 'Sewer Scope' }); + await addReport({ id: 'r-a3', title: 'Mold' }); + + expect(await listFor(INSP)).toHaveLength(3); + }); + + it('scopes the one-primary rule to its own inspection', async () => { + // Two different orders each get their own primary; the index keys on + // inspection_id, not on the tenant. + await addReport({ id: 'r-p1', kind: 'primary', title: 'A' }); + await addReport({ id: 'r-p2', kind: 'primary', title: 'B', inspectionId: OTHER_INSP }); + + expect(await listFor(INSP)).toHaveLength(1); + expect(await listFor(OTHER_INSP)).toHaveLength(1); + }); + + it('defaults a new report to in_progress', async () => { + await addReport({ id: 'r-new', title: 'Radon' }); + const row = await db.select().from(schema.reports) + .where(eq(schema.reports.id, 'r-new')).get(); + expect(row?.status).toBe('in_progress'); + }); + + it('carries the billing LINE, not the catalogue entry', async () => { + // inspection_service_id is `inspection_services.id`. Storing a + // `services.id` here would make "which report did this billing line + // produce" unanswerable, which is the grain pay splits are built on. + await addReport({ id: 'r-line', title: 'Radon', inspectionServiceId: 'insp-svc-42' }); + const row = await db.select().from(schema.reports) + .where(eq(schema.reports.id, 'r-line')).get(); + expect(row?.inspectionServiceId).toBe('insp-svc-42'); + }); + + it('allows a report with no billing line', async () => { + // A tenant may produce a report for something they did not bill for. + await addReport({ id: 'r-free', title: 'Courtesy re-check' }); + const row = await db.select().from(schema.reports) + .where(eq(schema.reports.id, 'r-free')).get(); + expect(row?.inspectionServiceId).toBeNull(); + }); +}); From 338ec609388b4cfea97969a930b2ddf7b66cea09 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 08:45:40 +0800 Subject: [PATCH 14/29] feat(reports): move results and versions onto reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 2 of the per-deliverable-reports plan. `inspection_results` and `report_versions` gain a `report_id`, the uniqueness that used to be per INSPECTION becomes per REPORT, and every existing inspection is backfilled with one primary report carrying its current template. The statement order in the migration is load-bearing and is NOT what db:generate emitted. Drizzle put the `DROP INDEX` on the old results uniqueness FIRST, which opens a window in which two results rows can be written for one inspection. The shipped order is: add both columns, backfill, create the new unique indexes, and only then drop the old ones. Verified no table rebuild — every statement is ADD COLUMN or an index. db:check clean at 87 tables, lint:migchain intact at 29 snapshots. The backfill derives each report id from its inspection id rather than generating one. That makes it idempotent — a replayed migration creates nothing and changes nothing, which a test asserts — and it makes a row traceable to what produced it. Titles are generic on purpose: `reports.title` is anonymised by the erasure manifest precisely because it usually carries an address, and a backfill has no business inventing PII that was not there. The test that earns its place is the chain one. `report_versions` carries contentHash / prevHash / signature and the public verifier reads them, so a backfill that renumbered or reordered versions would invalidate signatures on reports ALREADY DELIVERED. Counting rows cannot see that; comparing the hashes before and after can, and does. Applied locally and checked: 3 inspections, 3 primaries, zero orphan results and zero orphan versions. The REMOTE apply is deliberately not done here — production holds 19 inspections and 6 versions, and that step goes through the D1 SOP with a backup, a bookmark, and a human looking at the statements first. --- migrations/0028_closed_robin_chapel.sql | 46 + migrations/meta/0028_snapshot.json | 10245 ++++++++++++++++ migrations/meta/_journal.json | 7 + server/lib/db/schema/inspection/core.ts | 10 +- server/lib/db/schema/report-versions.ts | 11 +- .../unit/db/report-results-migration.spec.ts | 175 + 6 files changed, 10491 insertions(+), 3 deletions(-) create mode 100644 migrations/0028_closed_robin_chapel.sql create mode 100644 migrations/meta/0028_snapshot.json create mode 100644 tests/unit/db/report-results-migration.spec.ts diff --git a/migrations/0028_closed_robin_chapel.sql b/migrations/0028_closed_robin_chapel.sql new file mode 100644 index 000000000..4304ba597 --- /dev/null +++ b/migrations/0028_closed_robin_chapel.sql @@ -0,0 +1,46 @@ +-- Move results and versions onto reports. +-- +-- Statement order is load-bearing and is NOT what db:generate emitted. Drizzle +-- put `DROP INDEX uq_results_inspection` first, which opens a window in which +-- two results rows can be written for one inspection. The order here is: +-- +-- 1. add both columns +-- 2. backfill (create one primary report per inspection, re-point both tables) +-- 3. create the new unique indexes +-- 4. only then drop the old ones +-- +-- The backfill derives each report id from its inspection id rather than +-- generating one, so it is idempotent and re-runnable, and so a row can be +-- traced back to what produced it. Titles are generic on purpose: `reports.title` +-- is anonymised by the erasure manifest because it usually carries an address, +-- and a backfill should not invent PII that was not there. + +ALTER TABLE `inspection_results` ADD `report_id` text;--> statement-breakpoint +ALTER TABLE `report_versions` ADD `report_id` text;--> statement-breakpoint + +INSERT INTO `reports` (`id`, `tenant_id`, `inspection_id`, `kind`, `inspection_service_id`, `template_id`, `title`, `status`, `created_at`) +SELECT 'rpt-' || i.`id`, + i.`tenant_id`, + i.`id`, + 'primary', + NULL, + i.`template_id`, + 'Inspection Report', + CASE WHEN EXISTS (SELECT 1 FROM `report_versions` rv WHERE rv.`inspection_id` = i.`id`) + THEN 'published' ELSE 'in_progress' END, + unixepoch() * 1000 +FROM `inspections` i +WHERE NOT EXISTS ( + SELECT 1 FROM `reports` r WHERE r.`inspection_id` = i.`id` AND r.`kind` = 'primary' +);--> statement-breakpoint + +UPDATE `inspection_results` SET `report_id` = 'rpt-' || `inspection_id` WHERE `report_id` IS NULL;--> statement-breakpoint +UPDATE `report_versions` SET `report_id` = 'rpt-' || `inspection_id` WHERE `report_id` IS NULL;--> statement-breakpoint + +CREATE UNIQUE INDEX `uq_results_report` ON `inspection_results` (`report_id`);--> statement-breakpoint +CREATE INDEX `idx_report_versions_report` ON `report_versions` (`report_id`,`version_number`);--> statement-breakpoint +CREATE UNIQUE INDEX `uq_report_versions_report_version` ON `report_versions` (`report_id`,`version_number`);--> statement-breakpoint + +DROP INDEX `uq_results_inspection`;--> statement-breakpoint +DROP INDEX `idx_report_versions_inspection`;--> statement-breakpoint +DROP INDEX `uq_report_versions_inspection_version`; diff --git a/migrations/meta/0028_snapshot.json b/migrations/meta/0028_snapshot.json new file mode 100644 index 000000000..50c6b3594 --- /dev/null +++ b/migrations/meta/0028_snapshot.json @@ -0,0 +1,10245 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "cad1adf3-bc98-4683-ab7c-ef129deb97d1", + "prevId": "1c4758c4-fbeb-413c-a7a0-35d128968933", + "tables": { + "agreement_requests": { + "name": "agreement_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signature_base64": { + "name": "inspector_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signed_at": { + "name": "inspector_signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_user_id": { + "name": "inspector_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_snapshot": { + "name": "content_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agreement_requests_token_unique": { + "name": "agreement_requests_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_agreement_requests_verify_token": { + "name": "idx_agreement_requests_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + }, + "idx_agreement_requests_tenant": { + "name": "idx_agreement_requests_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_agreement_requests_inspection": { + "name": "idx_agreement_requests_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_agreement_requests_token_hash": { + "name": "idx_agreement_requests_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agreement_requests_tenant_id_tenants_id_fk": { + "name": "agreement_requests_tenant_id_tenants_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspection_id_inspections_id_fk": { + "name": "agreement_requests_inspection_id_inspections_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_agreement_id_agreements_id_fk": { + "name": "agreement_requests_agreement_id_agreements_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspector_user_id_users_id_fk": { + "name": "agreement_requests_inspector_user_id_users_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "users", + "columnsFrom": [ + "inspector_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreement_signers": { + "name": "agreement_signers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_of": { + "name": "on_behalf_of", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_disclaimer": { + "name": "on_behalf_disclaimer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reminded_at": { + "name": "last_reminded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_agreement_signers_tenant_request": { + "name": "idx_agreement_signers_tenant_request", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": false + }, + "idx_agreement_signers_request_email": { + "name": "idx_agreement_signers_request_email", + "columns": [ + "request_id", + "email" + ], + "isUnique": true + }, + "idx_agreement_signers_token_hash": { + "name": "idx_agreement_signers_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreements": { + "name": "agreements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agreements_tenant": { + "name": "idx_agreements_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agreements_tenant_id_tenants_id_fk": { + "name": "agreements_tenant_id_tenants_id_fk", + "tableFrom": "agreements", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_logs": { + "name": "automation_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_key": { + "name": "recipient_role_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_contact_id": { + "name": "recipient_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notice_id": { + "name": "notice_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_logs_pending": { + "name": "idx_automation_logs_pending", + "columns": [ + "tenant_id", + "status", + "send_at" + ], + "isUnique": false + }, + "idx_automation_logs_insp": { + "name": "idx_automation_logs_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_automation_logs_event": { + "name": "uq_automation_logs_event", + "columns": [ + "automation_id", + "inspection_id", + "event_id", + "channel", + "recipient" + ], + "isUnique": true, + "where": "event_id IS NOT NULL" + } + }, + "foreignKeys": { + "automation_logs_tenant_id_tenants_id_fk": { + "name": "automation_logs_tenant_id_tenants_id_fk", + "tableFrom": "automation_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_kind": { + "name": "recipient_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_profile_id": { + "name": "recipient_role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delay_minutes": { + "name": "delay_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conditions": { + "name": "conditions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"email\"]'" + }, + "sms_body": { + "name": "sms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "in_app_template_id": { + "name": "in_app_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automations_tenant": { + "name": "idx_automations_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_tenant_id_tenants_id_fk": { + "name": "automations_tenant_id_tenants_id_fk", + "tableFrom": "automations", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability": { + "name": "availability", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "day_of_week": { + "name": "day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_availability_inspector": { + "name": "idx_availability_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_availability_window_unique": { + "name": "idx_availability_window_unique", + "columns": [ + "inspector_id", + "day_of_week", + "start_time" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_tenant_id_tenants_id_fk": { + "name": "availability_tenant_id_tenants_id_fk", + "tableFrom": "availability", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_inspector_id_users_id_fk": { + "name": "availability_inspector_id_users_id_fk", + "tableFrom": "availability", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability_overrides": { + "name": "availability_overrides", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_available": { + "name": "is_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transparency": { + "name": "transparency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_avail_overrides_insp": { + "name": "idx_avail_overrides_insp", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_avail_overrides_block_unique": { + "name": "idx_avail_overrides_block_unique", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": true, + "where": "is_available = 0 AND source IS NULL" + }, + "uq_avail_overrides_external": { + "name": "uq_avail_overrides_external", + "columns": [ + "inspector_id", + "source", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_overrides_tenant_id_tenants_id_fk": { + "name": "availability_overrides_tenant_id_tenants_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_overrides_inspector_id_users_id_fk": { + "name": "availability_overrides_inspector_id_users_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_blocks": { + "name": "calendar_blocks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_all_day": { + "name": "is_all_day", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_calendar_blocks_tenant_user_date": { + "name": "idx_calendar_blocks_tenant_user_date", + "columns": [ + "tenant_id", + "user_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connection_read_calendars": { + "name": "calendar_connection_read_calendars", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_calendar_id": { + "name": "external_calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_role": { + "name": "access_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_conn_read_cal": { + "name": "uq_conn_read_cal", + "columns": [ + "connection_id", + "external_calendar_id" + ], + "isUnique": true + }, + "idx_conn_read_cal_tenant": { + "name": "idx_conn_read_cal_tenant", + "columns": [ + "tenant_id", + "connection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connections": { + "name": "calendar_connections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_dek_enc": { + "name": "credentials_dek_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_calendar_connections_user_provider": { + "name": "uq_calendar_connections_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": true + }, + "idx_calendar_connections_tenant_user": { + "name": "idx_calendar_connections_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comment_usage": { + "name": "comment_usage", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_id": { + "name": "comment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_comment_usage_user_last_used": { + "name": "idx_comment_usage_user_last_used", + "columns": [ + "tenant_id", + "user_id", + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comment_usage_comment_id_comments_id_fk": { + "name": "comment_usage_comment_id_comments_id_fk", + "tableFrom": "comment_usage", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_usage_tenant_id_user_id_comment_id_pk": { + "columns": [ + "tenant_id", + "user_id", + "comment_id" + ], + "name": "comment_usage_tenant_id_user_id_comment_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_bucket": { + "name": "rating_bucket", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section": { + "name": "section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_ids": { + "name": "section_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_labels": { + "name": "item_labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_code": { + "name": "trigger_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_keywords": { + "name": "search_keywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repair_summary": { + "name": "repair_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_min_cents": { + "name": "estimate_min_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_max_cents": { + "name": "estimate_max_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recommended_contractor_type_id": { + "name": "recommended_contractor_type_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_comments_tenant": { + "name": "idx_comments_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_comments_rating_bucket": { + "name": "idx_comments_rating_bucket", + "columns": [ + "tenant_id", + "rating_bucket" + ], + "isUnique": false + }, + "idx_comments_library_id": { + "name": "idx_comments_library_id", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comments_tenant_id_tenants_id_fk": { + "name": "comments_tenant_id_tenants_id_fk", + "tableFrom": "comments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "commercial_subtypes": { + "name": "commercial_subtypes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_disabled": { + "name": "is_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_commercial_subtypes_tenant_name": { + "name": "idx_commercial_subtypes_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "commercial_subtypes_tenant_id_tenants_id_fk": { + "name": "commercial_subtypes_tenant_id_tenants_id_fk", + "tableFrom": "commercial_subtypes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "concierge_confirm_tokens": { + "name": "concierge_confirm_tokens", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_concierge_tokens_expiry": { + "name": "idx_concierge_tokens_expiry", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_concierge_confirm_token_hash": { + "name": "idx_concierge_confirm_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "concierge_confirm_tokens_inspection_id_inspections_id_fk": { + "name": "concierge_confirm_tokens_inspection_id_inspections_id_fk", + "tableFrom": "concierge_confirm_tokens", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_role_profiles": { + "name": "contact_role_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability_overrides": { + "name": "capability_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_crp_tenant": { + "name": "idx_crp_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_crp_tenant_key": { + "name": "uq_crp_tenant_key", + "columns": [ + "tenant_id", + "key" + ], + "isUnique": true, + "where": "is_active = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agency": { + "name": "agency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_user_id": { + "name": "agent_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_linked_at": { + "name": "agent_linked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_revoked_at": { + "name": "agent_revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_contacts_type": { + "name": "idx_contacts_type", + "columns": [ + "tenant_id", + "type" + ], + "isUnique": false + }, + "idx_contacts_tenant": { + "name": "idx_contacts_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_contacts_tenant_email": { + "name": "uq_contacts_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "email IS NOT NULL AND archived_at IS NULL" + }, + "uq_contacts_tenant_agent_user": { + "name": "uq_contacts_tenant_agent_user", + "columns": [ + "tenant_id", + "agent_user_id" + ], + "isUnique": true, + "where": "agent_user_id IS NOT NULL AND archived_at IS NULL" + }, + "idx_contacts_agent_user": { + "name": "idx_contacts_agent_user", + "columns": [ + "agent_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_tenant_id_tenants_id_fk": { + "name": "contacts_tenant_id_tenants_id_fk", + "tableFrom": "contacts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contractor_types": { + "name": "contractor_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_contractor_types_tenant": { + "name": "idx_contractor_types_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_items": { + "name": "cost_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "building_id": { + "name": "building_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_index": { + "name": "instance_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_id": { + "name": "unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_method": { + "name": "cost_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uom": { + "name": "uom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_cost_cents": { + "name": "unit_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lump_sum_cents": { + "name": "lump_sum_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eul": { + "name": "eul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_age": { + "name": "eff_age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rul": { + "name": "rul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suggested_remedy": { + "name": "suggested_remedy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_ref": { + "name": "section_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_ref": { + "name": "photo_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_cost_items_tenant_inspection": { + "name": "idx_cost_items_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_cost_items_finding_key": { + "name": "idx_cost_items_finding_key", + "columns": [ + "finding_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "defect_categories": { + "name": "defect_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6b7280'" + }, + "is_summary_driver": { + "name": "is_summary_driver", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_defect_categories_tenant": { + "name": "idx_defect_categories_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "discount_codes": { + "name": "discount_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uses_count": { + "name": "uses_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_discount_codes_tenant": { + "name": "idx_discount_codes_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_discount_codes_code_tenant": { + "name": "uq_discount_codes_code_tenant", + "columns": [ + "upper(code)", + "tenant_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "discount_codes_tenant_id_tenants_id_fk": { + "name": "discount_codes_tenant_id_tenants_id_fk", + "tableFrom": "discount_codes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "document_review_items": { + "name": "document_review_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_requested": { + "name": "is_requested", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_received": { + "name": "is_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_reviewed": { + "name": "is_reviewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_na": { + "name": "is_na", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_doc_review_inspection": { + "name": "idx_doc_review_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_doc_review_item": { + "name": "uq_doc_review_item", + "columns": [ + "inspection_id", + "document_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_suppressions": { + "name": "email_suppressions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_provider": { + "name": "source_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_email_suppressions_email": { + "name": "idx_email_suppressions_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "erasure_log": { + "name": "erasure_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_email": { + "name": "subject_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_basis": { + "name": "identity_basis", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "decisions_json": { + "name": "decisions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retained_count": { + "name": "retained_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "anonymized_count": { + "name": "anonymized_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_count": { + "name": "deleted_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "response_note": { + "name": "response_note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_erasure_log_tenant": { + "name": "idx_erasure_log_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "esign_audit_logs": { + "name": "esign_audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_esign_audit_logs_request": { + "name": "idx_esign_audit_logs_request", + "columns": [ + "tenant_id", + "request_id", + "created_at" + ], + "isUnique": false + }, + "idx_esign_audit_logs_event_dedup": { + "name": "idx_esign_audit_logs_event_dedup", + "columns": [ + "tenant_id", + "request_id", + "event" + ], + "isUnique": true, + "where": "event NOT LIKE 'signer.%'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "event_types": { + "name": "event_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_duration_min": { + "name": "default_duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "default_price_cents": { + "name": "default_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6366f1'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_event_types_tenant_slug": { + "name": "uq_event_types_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "event_types_tenant_id_tenants_id_fk": { + "name": "event_types_tenant_id_tenants_id_fk", + "tableFrom": "event_types", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_access_tokens": { + "name": "inspection_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_iat_token": { + "name": "idx_iat_token", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_iat_inspection": { + "name": "idx_iat_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_iat_recipient": { + "name": "idx_iat_recipient", + "columns": [ + "inspection_id", + "recipient_email" + ], + "isUnique": true + }, + "idx_iat_token_hash": { + "name": "idx_iat_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_access_tokens_tenant_id_tenants_id_fk": { + "name": "inspection_access_tokens_tenant_id_tenants_id_fk", + "tableFrom": "inspection_access_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_events": { + "name": "inspection_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type_id": { + "name": "event_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'scheduled'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "results_received_at": { + "name": "results_received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gcal_event_id": { + "name": "gcal_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_events_scheduled": { + "name": "idx_inspection_events_scheduled", + "columns": [ + "tenant_id", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_events_inspection": { + "name": "idx_inspection_events_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_events_tenant_id_tenants_id_fk": { + "name": "inspection_events_tenant_id_tenants_id_fk", + "tableFrom": "inspection_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspection_id_inspections_id_fk": { + "name": "inspection_events_inspection_id_inspections_id_fk", + "tableFrom": "inspection_events", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_events_event_type_id_event_types_id_fk": { + "name": "inspection_events_event_type_id_event_types_id_fk", + "tableFrom": "inspection_events", + "tableTo": "event_types", + "columnsFrom": [ + "event_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspector_id_users_id_fk": { + "name": "inspection_events_inspector_id_users_id_fk", + "tableFrom": "inspection_events", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_inspectors": { + "name": "inspection_inspectors", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lead'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_inspectors_tenant_user": { + "name": "idx_insp_inspectors_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "idx_insp_inspectors_user": { + "name": "idx_insp_inspectors_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_inspectors_inspection_id_user_id_pk": { + "columns": [ + "inspection_id", + "user_id" + ], + "name": "inspection_inspectors_inspection_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_item_tag_links": { + "name": "inspection_item_tag_links", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_links_tenant": { + "name": "idx_tag_links_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_tag_links_tag": { + "name": "idx_tag_links_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + }, + "idx_tag_links_inspection_item": { + "name": "idx_tag_links_inspection_item", + "columns": [ + "inspection_id", + "item_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_item_tag_links_inspection_id_item_id_tag_id_pk": { + "columns": [ + "inspection_id", + "item_id", + "tag_id" + ], + "name": "inspection_item_tag_links_inspection_id_item_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_media_pool": { + "name": "inspection_media_pool", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exif_data": { + "name": "exif_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "caption": { + "name": "caption", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'photo'" + }, + "stream_uid": { + "name": "stream_uid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "poster_pct": { + "name": "poster_pct", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_sec": { + "name": "duration_sec", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'stream'" + }, + "poster_key": { + "name": "poster_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_media_pool_tenant": { + "name": "idx_media_pool_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_media_pool_inspection": { + "name": "idx_media_pool_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_messages": { + "name": "inspection_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_role": { + "name": "from_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_user_id": { + "name": "from_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_msg_inspection": { + "name": "idx_msg_inspection", + "columns": [ + "inspection_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_contact": { + "name": "idx_msg_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_unread": { + "name": "idx_msg_unread", + "columns": [ + "tenant_id", + "contact_id", + "from_role" + ], + "isUnique": false, + "where": "\"inspection_messages\".\"read_at\" IS NULL" + } + }, + "foreignKeys": { + "inspection_messages_tenant_id_tenants_id_fk": { + "name": "inspection_messages_tenant_id_tenants_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_messages_inspection_id_inspections_id_fk": { + "name": "inspection_messages_inspection_id_inspections_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_people": { + "name": "inspection_people", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_profile_id": { + "name": "role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_ip_inspection": { + "name": "idx_ip_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_ip_tenant": { + "name": "idx_ip_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_ip_insp_contact_role": { + "name": "uq_ip_insp_contact_role", + "columns": [ + "inspection_id", + "contact_id", + "role_profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_requests": { + "name": "inspection_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_phone": { + "name": "client_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "property_city": { + "name": "property_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_state": { + "name": "property_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_zip": { + "name": "property_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_requests_tenant": { + "name": "idx_inspection_requests_tenant", + "columns": [ + "tenant_id", + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_requests_email": { + "name": "idx_inspection_requests_email", + "columns": [ + "tenant_id", + "client_email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_requests_tenant_id_tenants_id_fk": { + "name": "inspection_requests_tenant_id_tenants_id_fk", + "tableFrom": "inspection_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_results": { + "name": "inspection_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ydoc_state": { + "name": "ydoc_state", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_system_snapshot": { + "name": "rating_system_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_results_tenant": { + "name": "idx_results_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_results_inspection": { + "name": "idx_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_results_report": { + "name": "uq_results_report", + "columns": [ + "report_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_results_tenant_id_tenants_id_fk": { + "name": "inspection_results_tenant_id_tenants_id_fk", + "tableFrom": "inspection_results", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_results_inspection_id_inspections_id_fk": { + "name": "inspection_results_inspection_id_inspections_id_fk", + "tableFrom": "inspection_results", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_services": { + "name": "inspection_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_override_cents": { + "name": "price_override_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_snapshot": { + "name": "name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_snapshot_cents": { + "name": "price_snapshot_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_insp_services_tenant": { + "name": "idx_insp_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_insp_services_insp": { + "name": "idx_insp_services_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_services_tenant_id_tenants_id_fk": { + "name": "inspection_services_tenant_id_tenants_id_fk", + "tableFrom": "inspection_services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_services_inspection_id_inspections_id_fk": { + "name": "inspection_services_inspection_id_inspections_id_fk", + "tableFrom": "inspection_services", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_services_service_id_services_id_fk": { + "name": "inspection_services_service_id_services_id_fk", + "tableFrom": "inspection_services", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_types": { + "name": "inspection_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_types_tenant_name": { + "name": "idx_inspection_types_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_units": { + "name": "inspection_units", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_unit_id": { + "name": "parent_unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unit'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "attrs": { + "name": "attrs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_units_tenant_inspection": { + "name": "idx_inspection_units_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_inspection_units_parent": { + "name": "idx_inspection_units_parent", + "columns": [ + "parent_unit_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspections": { + "name": "inspections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_place_id": { + "name": "address_place_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_county": { + "name": "address_county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lat": { + "name": "address_lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lng": { + "name": "address_lng", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_geocoded_at": { + "name": "address_geocoded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'requested'" + }, + "report_status": { + "name": "report_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_notes": { + "name": "cancel_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_payment_required": { + "name": "is_payment_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_agreement_required": { + "name": "is_agreement_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_auto_sign_on_publish": { + "name": "is_auto_sign_on_publish", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "discount_code_id": { + "name": "discount_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discount_amount_cents": { + "name": "discount_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closing_date": { + "name": "closing_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referral_source": { + "name": "referral_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_notes": { + "name": "internal_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sqft": { + "name": "sqft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "foundation_type": { + "name": "foundation_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrooms": { + "name": "bedrooms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bathrooms": { + "name": "bathrooms", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lot_size": { + "name": "lot_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_facts": { + "name": "property_facts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_photo_id": { + "name": "cover_photo_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_crop": { + "name": "cover_crop", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_image_key": { + "name": "cover_image_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_tier": { + "name": "report_tier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_automations_disabled": { + "name": "is_automations_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "template_snapshot": { + "name": "template_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_snapshot_version": { + "name": "template_snapshot_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "profile_override": { + "name": "profile_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_defect_fields_override": { + "name": "require_defect_fields_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "concierge_status": { + "name": "concierge_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_team_mode": { + "name": "is_team_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "lead_inspector_id": { + "name": "lead_inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "helper_inspector_ids": { + "name": "helper_inspector_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "data_version": { + "name": "data_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source_inspection_id": { + "name": "source_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_inspection_id": { + "name": "root_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reinspection_round": { + "name": "reinspection_round", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_inspection_mode": { + "name": "unit_inspection_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'tagged'" + }, + "location_options": { + "name": "location_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sampling_declaration": { + "name": "sampling_declaration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pca_narrative": { + "name": "pca_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviations": { + "name": "deviations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_mode": { + "name": "report_photo_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_start_ms": { + "name": "scheduled_start_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_end_ms": { + "name": "scheduled_end_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "badge_layout_override": { + "name": "badge_layout_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_columns": { + "name": "report_photo_columns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referred_by_contact_id": { + "name": "referred_by_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspections_tenant": { + "name": "idx_inspections_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspections_request": { + "name": "idx_inspections_request", + "columns": [ + "request_id" + ], + "isUnique": false + }, + "idx_inspections_inspector": { + "name": "idx_inspections_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_inspections_tenant_status": { + "name": "idx_inspections_tenant_status", + "columns": [ + "tenant_id", + "status" + ], + "isUnique": false + }, + "idx_inspections_tenant_date": { + "name": "idx_inspections_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_inspector_date": { + "name": "idx_inspections_inspector_date", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_root": { + "name": "idx_inspections_root", + "columns": [ + "root_inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspections_tenant_id_tenants_id_fk": { + "name": "inspections_tenant_id_tenants_id_fk", + "tableFrom": "inspections", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_inspector_id_users_id_fk": { + "name": "inspections_inspector_id_users_id_fk", + "tableFrom": "inspections", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_template_id_templates_id_fk": { + "name": "inspections_template_id_templates_id_fk", + "tableFrom": "inspections", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_discount_code_id_discount_codes_id_fk": { + "name": "inspections_discount_code_id_discount_codes_id_fk", + "tableFrom": "inspections", + "tableTo": "discount_codes", + "columnsFrom": [ + "discount_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_request_id_inspection_requests_id_fk": { + "name": "inspections_request_id_inspection_requests_id_fk", + "tableFrom": "inspections", + "tableTo": "inspection_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspector_credentials": { + "name": "inspector_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_r2_key": { + "name": "image_r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspector_credentials_tenant": { + "name": "idx_inspector_credentials_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspector_credentials_user": { + "name": "idx_inspector_credentials_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "partial_paid_at": { + "name": "partial_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voided_at": { + "name": "voided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qbo_sync_status": { + "name": "qbo_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + } + }, + "indexes": { + "idx_invoices_tenant": { + "name": "idx_invoices_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_invoices_inspection": { + "name": "idx_invoices_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_invoices_contact": { + "name": "idx_invoices_contact", + "columns": [ + "tenant_id", + "contact_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_tenant_id_tenants_id_fk": { + "name": "invoices_tenant_id_tenants_id_fk", + "tableFrom": "invoices", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_inspection_id_inspections_id_fk": { + "name": "invoices_inspection_id_inspections_id_fk", + "tableFrom": "invoices", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_contact_id_contacts_id_fk": { + "name": "invoices_contact_id_contacts_id_fk", + "tableFrom": "invoices", + "tableTo": "contacts", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_libraries": { + "name": "marketplace_libraries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_libraries_kind_featured": { + "name": "idx_marketplace_libraries_kind_featured", + "columns": [ + "kind", + "is_featured" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_templates": { + "name": "marketplace_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_templates": { + "name": "message_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "variables": { + "name": "variables", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seeded": { + "name": "is_seeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_templates_tenant_channel": { + "name": "idx_message_templates_tenant_channel", + "columns": [ + "tenant_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messaging_compliance": { + "name": "messaging_compliance", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'own'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subaccount_sid": { + "name": "subaccount_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_sid": { + "name": "customer_profile_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_status": { + "name": "customer_profile_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_sid": { + "name": "brand_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_status": { + "name": "brand_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_sid": { + "name": "campaign_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_status": { + "name": "campaign_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_sid": { + "name": "tfv_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_status": { + "name": "tfv_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "messaging_resource_sid": { + "name": "messaging_resource_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_meta": { + "name": "provider_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number": { + "name": "provisioned_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number_sid": { + "name": "provisioned_number_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_sender_attached": { + "name": "has_sender_attached", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "compliance_status": { + "name": "compliance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_preferences": { + "name": "notification_preferences", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "class_id": { + "name": "class_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notification_prefs_unique": { + "name": "idx_notification_prefs_unique", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "class_id", + "channel" + ], + "isUnique": true + }, + "idx_notification_prefs_subject": { + "name": "idx_notification_prefs_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orphaned_media": { + "name": "orphaned_media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_orphaned_media_key": { + "name": "idx_orphaned_media_key", + "columns": [ + "tenant_id", + "r2_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_webhook_events": { + "name": "processed_webhook_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "psq_responses": { + "name": "psq_responses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sent'" + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_psq_inspection": { + "name": "uq_psq_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": true + }, + "idx_psq_share_token": { + "name": "idx_psq_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_connections": { + "name": "qbo_connections", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "realm_id": { + "name": "realm_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_enc": { + "name": "access_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_enc": { + "name": "refresh_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "default_item_id": { + "name": "default_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_entity_map": { + "name": "qbo_entity_map", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_type": { + "name": "qbo_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_id": { + "name": "qbo_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_sync_token": { + "name": "qbo_sync_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_qbo_entity_map_qbo": { + "name": "idx_qbo_entity_map_qbo", + "columns": [ + "tenant_id", + "qbo_type", + "qbo_id" + ], + "isUnique": true + }, + "idx_qbo_entity_map_oi": { + "name": "idx_qbo_entity_map_oi", + "columns": [ + "tenant_id", + "oi_type", + "oi_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_sync_errors": { + "name": "qbo_sync_errors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_resolved": { + "name": "is_resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rating_systems": { + "name": "rating_systems", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "levels": { + "name": "levels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_rating_systems_tenant_slug": { + "name": "idx_rating_systems_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_rating_systems_tenant": { + "name": "idx_rating_systems_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rating_systems_tenant_id_tenants_id_fk": { + "name": "rating_systems_tenant_id_tenants_id_fk", + "tableFrom": "rating_systems", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_request_items": { + "name": "repair_request_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repair_request_id": { + "name": "repair_request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_title": { + "name": "section_title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_snapshot": { + "name": "comment_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_credit_cents": { + "name": "requested_credit_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "defect_title_snapshot": { + "name": "defect_title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_snapshot": { + "name": "location_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_snapshot": { + "name": "category_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_snapshot": { + "name": "trade_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_request_items_rr": { + "name": "idx_repair_request_items_rr", + "columns": [ + "repair_request_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_requests": { + "name": "repair_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_ref": { + "name": "created_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_intro": { + "name": "custom_intro", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_requests_inspection": { + "name": "idx_repair_requests_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_repair_requests_share_token": { + "name": "idx_repair_requests_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_exports": { + "name": "report_exports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_report_exports_inspection": { + "name": "idx_report_exports_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_pdfs": { + "name": "report_pdfs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rendered_at": { + "name": "rendered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ready'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_report_pdfs_inspection_type": { + "name": "uq_report_pdfs_inspection_type", + "columns": [ + "inspection_id", + "type", + "version_number" + ], + "isUnique": true + }, + "idx_report_pdfs_tenant": { + "name": "idx_report_pdfs_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_report_pdfs_status": { + "name": "idx_report_pdfs_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_report_pdfs_content_hash": { + "name": "idx_report_pdfs_content_hash", + "columns": [ + "inspection_id", + "type", + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": { + "report_pdfs_tenant_id_tenants_id_fk": { + "name": "report_pdfs_tenant_id_tenants_id_fk", + "tableFrom": "report_pdfs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_signoff": { + "name": "report_signoff", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualifications_ref": { + "name": "qualifications_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_ref": { + "name": "signature_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dual_role": { + "name": "is_dual_role", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_report_signoff_inspection": { + "name": "idx_report_signoff_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_report_signoff_role": { + "name": "uq_report_signoff_role", + "columns": [ + "inspection_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_versions": { + "name": "report_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_json": { + "name": "snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_amendment": { + "name": "is_amendment", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_report_versions_report": { + "name": "idx_report_versions_report", + "columns": [ + "report_id", + "version_number" + ], + "isUnique": false + }, + "uq_report_versions_report_version": { + "name": "uq_report_versions_report_version", + "columns": [ + "report_id", + "version_number" + ], + "isUnique": true + }, + "idx_report_versions_verify_token": { + "name": "idx_report_versions_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reports": { + "name": "reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_service_id": { + "name": "inspection_service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_reports_inspection": { + "name": "idx_reports_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_reports_tenant": { + "name": "idx_reports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_reports_primary": { + "name": "uq_reports_primary", + "columns": [ + "inspection_id" + ], + "isUnique": true, + "where": "kind = 'primary'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_inspectors": { + "name": "service_inspectors", + "columns": { + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_service_inspectors_tenant": { + "name": "idx_service_inspectors_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "service_inspectors_service_id_user_id_pk": { + "columns": [ + "service_id", + "user_id" + ], + "name": "service_inspectors_service_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_event_type_slugs": { + "name": "default_event_type_slugs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_services_tenant": { + "name": "idx_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "services_tenant_id_tenants_id_fk": { + "name": "services_tenant_id_tenants_id_fk", + "tableFrom": "services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_template_id_templates_id_fk": { + "name": "services_template_id_templates_id_fk", + "tableFrom": "services", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_agreement_id_agreements_id_fk": { + "name": "services_agreement_id_agreements_id_fk", + "tableFrom": "services", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_enc": { + "name": "private_key_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Ed25519'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_consent_log": { + "name": "sms_consent_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disclosure_version": { + "name": "disclosure_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "captured_via": { + "name": "captured_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'contact'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": { + "idx_sms_consent_contact": { + "name": "idx_sms_consent_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_sms_consent_subject": { + "name": "idx_sms_consent_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_delivery_status": { + "name": "sms_delivery_status", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sms_delivery_status_msg": { + "name": "idx_sms_delivery_status_msg", + "columns": [ + "tenant_id", + "provider_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_disclosure_versions": { + "name": "sms_disclosure_versions", + "columns": { + "version": { + "name": "version", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tags_tenant_name": { + "name": "idx_tags_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + }, + "idx_tags_tenant": { + "name": "idx_tags_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_templates_tenant": { + "name": "idx_templates_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_templates_rating_system": { + "name": "idx_templates_rating_system", + "columns": [ + "rating_system_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "templates_tenant_id_tenants_id_fk": { + "name": "templates_tenant_id_tenants_id_fk", + "tableFrom": "templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_custom_holidays": { + "name": "tenant_custom_holidays", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_tenant_custom_holidays_tenant_date": { + "name": "uq_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": true + }, + "idx_tenant_custom_holidays_tenant_date": { + "name": "idx_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_library_imports": { + "name": "tenant_library_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "uq_tenant_library_import": { + "name": "uq_tenant_library_import", + "columns": [ + "tenant_id", + "library_id" + ], + "isUnique": true + }, + "idx_tenant_library_imports_tenant": { + "name": "idx_tenant_library_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_import_history": { + "name": "tenant_marketplace_import_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_version": { + "name": "target_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_affected": { + "name": "rows_affected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_history_tenant": { + "name": "idx_marketplace_history_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_marketplace_history_template": { + "name": "idx_marketplace_history_template", + "columns": [ + "template_id" + ], + "isUnique": false + }, + "idx_marketplace_history_library": { + "name": "idx_marketplace_history_library", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_imports": { + "name": "tenant_marketplace_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "marketplace_template_id": { + "name": "marketplace_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_template_id": { + "name": "local_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_mkt_imports_tmpl": { + "name": "idx_mkt_imports_tmpl", + "columns": [ + "marketplace_template_id" + ], + "isUnique": false + }, + "idx_mkt_imports_tenant": { + "name": "idx_mkt_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk": { + "name": "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "marketplace_templates", + "columnsFrom": [ + "marketplace_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tenant_marketplace_imports_local_template_id_templates_id_fk": { + "name": "tenant_marketplace_imports_local_template_id_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "templates", + "columnsFrom": [ + "local_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "usage_counters": { + "name": "usage_counters", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_usage_counters_tenant": { + "name": "idx_usage_counters_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_counters_tenant_id_metric_period_key_pk": { + "columns": [ + "tenant_id", + "metric", + "period_key" + ], + "name": "usage_counters_tenant_id_metric_period_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_templates": { + "name": "email_templates", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocks": { + "name": "blocks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_templates_tenant_id_tenants_id_fk": { + "name": "email_templates_tenant_id_tenants_id_fk", + "tableFrom": "email_templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_templates_tenant_id_trigger_pk": { + "columns": [ + "tenant_id", + "trigger" + ], + "name": "email_templates_tenant_id_trigger_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_configs": { + "name": "tenant_configs", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "support_email": { + "name": "support_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_address": { + "name": "company_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pdf_footer_shown": { + "name": "is_pdf_footer_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_page_numbers_shown": { + "name": "is_pdf_page_numbers_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_license_shown": { + "name": "is_pdf_license_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reply_to": { + "name": "reply_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_mode": { + "name": "email_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "video_mode": { + "name": "video_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'r2'" + }, + "sms_mode": { + "name": "sms_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "sender_display_name": { + "name": "sender_display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "point_of_contact": { + "name": "point_of_contact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company'" + }, + "billing_url": { + "name": "billing_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_url": { + "name": "review_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_phone": { + "name": "company_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "integration_config": { + "name": "integration_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secrets_enc": { + "name": "secrets_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dek_enc": { + "name": "dek_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ics_token": { + "name": "ics_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widget_allowed_origins": { + "name": "widget_allowed_origins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'signature'" + }, + "attention_thresholds": { + "name": "attention_thresholds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"agreement_unsigned_h\":72,\"invoice_overdue_h\":72,\"report_unpublished_h\":72}'" + }, + "inspection_prefs": { + "name": "inspection_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_estimates_shown": { + "name": "is_estimates_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_repair_list_enabled": { + "name": "is_repair_list_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_customer_repair_export_enabled": { + "name": "is_customer_repair_export_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unpaid_blocked": { + "name": "is_unpaid_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unsigned_agreement_blocked": { + "name": "is_unsigned_agreement_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "custom_referral_sources": { + "name": "custom_referral_sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dashboard_column_prefs": { + "name": "dashboard_column_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_concierge_review_required": { + "name": "is_concierge_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_inspector_choice_allowed": { + "name": "is_inspector_choice_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_pdf_pipeline_enabled": { + "name": "is_pdf_pipeline_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_team_mode_default": { + "name": "is_team_mode_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_apprentice_review_required": { + "name": "is_apprentice_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_guest_invites_enabled": { + "name": "is_guest_invites_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "require_defect_fields": { + "name": "require_defect_fields", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "agreement_retention_years": { + "name": "agreement_retention_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "reinspection_statuses": { + "name": "reinspection_statuses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_collab_editing_enabled": { + "name": "is_collab_editing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sms_byo_provider": { + "name": "sms_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_byo_provider": { + "name": "email_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "is_managed_eligible": { + "name": "is_managed_eligible", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "managed_provider": { + "name": "managed_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'twilio'" + }, + "is_reserve_schedule_enabled": { + "name": "is_reserve_schedule_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reserve_term_years": { + "name": "reserve_term_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "inflation_rate_bps": { + "name": "inflation_rate_bps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'UTC'" + }, + "booking_slot_mode": { + "name": "booking_slot_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "booking_slot_interval_min": { + "name": "booking_slot_interval_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "holiday_region": { + "name": "holiday_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "holiday_public_policy": { + "name": "holiday_public_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "holiday_internal_policy": { + "name": "holiday_internal_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'advisory'" + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en-US'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "is_archive_revoking_access": { + "name": "is_archive_revoking_access", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "legal_mode": { + "name": "legal_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'hosted'" + }, + "custom_privacy_url": { + "name": "custom_privacy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_terms_url": { + "name": "custom_terms_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privacy_body": { + "name": "privacy_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_body": { + "name": "terms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_configs_tenant_id_tenants_id_fk": { + "name": "tenant_configs_tenant_id_tenants_id_fk", + "tableFrom": "tenant_configs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "stripe_connect_account_id": { + "name": "stripe_connect_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "max_users": { + "name": "max_users", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'shared'" + }, + "applied_cmd_seq": { + "name": "applied_cmd_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_cred_seq": { + "name": "applied_cred_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_invites": { + "name": "tenant_invites", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inspector'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_invites_tenant": { + "name": "idx_invites_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_tenant_invites_pending_email": { + "name": "uq_tenant_invites_pending_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "status = 'pending'" + } + }, + "foreignKeys": { + "tenant_invites_tenant_id_tenants_id_fk": { + "name": "tenant_invites_tenant_id_tenants_id_fk", + "tableFrom": "tenant_invites", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_signature_base64": { + "name": "default_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_signature_enabled": { + "name": "is_signature_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manager'" + }, + "onboarding_state": { + "name": "onboarding_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_totp_enabled": { + "name": "is_totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_recovery_codes": { + "name": "totp_recovery_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_verified_at": { + "name": "totp_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_accepted": { + "name": "terms_accepted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + "deleted_at" + ], + "isUnique": false + }, + "uq_users_tenant_email": { + "name": "uq_users_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "deleted_at IS NULL" + }, + "idx_users_tenant": { + "name": "idx_users_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_users_slug_per_tenant": { + "name": "idx_users_slug_per_tenant", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_users_email": { + "name": "idx_users_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_slug": { + "name": "inspector_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_audit_tenant_created": { + "name": "idx_audit_tenant_created", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_audit_entity": { + "name": "idx_audit_entity", + "columns": [ + "entity_type", + "entity_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_tenant_id_tenants_id_fk": { + "name": "audit_logs_tenant_id_tenants_id_fk", + "tableFrom": "audit_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration_test_results": { + "name": "integration_test_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_ok": { + "name": "is_ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_by_user_id": { + "name": "tested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_at": { + "name": "tested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_integration_test_tenant_target": { + "name": "idx_integration_test_tenant_target", + "columns": [ + "tenant_id", + "target", + "tested_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_tenant_user_created": { + "name": "idx_notifications_tenant_user_created", + "columns": [ + "tenant_id", + "user_id", + "created_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_user_unread": { + "name": "idx_notifications_tenant_user_unread", + "columns": [ + "tenant_id", + "user_id", + "read_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_contact_created": { + "name": "idx_notifications_tenant_contact_created", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_tenant_id_tenants_id_fk": { + "name": "notifications_tenant_id_tenants_id_fk", + "tableFrom": "notifications", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parked_cmd_events": { + "name": "parked_cmd_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "envelope": { + "name": "envelope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_parked_cmd_events_received_at": { + "name": "idx_parked_cmd_events_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_cmd_events": { + "name": "processed_cmd_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cmd_type": { + "name": "cmd_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slug_reservations": { + "name": "slug_reservations", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_outbox": { + "name": "sync_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_tried_at": { + "name": "last_tried_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_sync_outbox_status_created": { + "name": "idx_sync_outbox_status_created", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_destruction_records": { + "name": "tenant_destruction_records", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_slug": { + "name": "tenant_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_deleted": { + "name": "rows_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_objects": { + "name": "r2_objects", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_bytes": { + "name": "r2_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "kv_keys": { + "name": "kv_keys", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_destruction_tenant": { + "name": "idx_destruction_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_destruction_destroyed_at": { + "name": "idx_destruction_destroyed_at", + "columns": [ + "destroyed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_legal_versions": { + "name": "tenant_legal_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "doc": { + "name": "doc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_material": { + "name": "is_material", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_tenant_legal_versions_doc_version": { + "name": "idx_tenant_legal_versions_doc_version", + "columns": [ + "tenant_id", + "doc", + "version" + ], + "isUnique": true + }, + "idx_tenant_legal_versions_latest": { + "name": "idx_tenant_legal_versions_latest", + "columns": [ + "tenant_id", + "doc", + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_uploads": { + "name": "client_uploads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_kind": { + "name": "uploaded_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_ref": { + "name": "uploaded_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_name": { + "name": "uploaded_by_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_client_uploads_inspection": { + "name": "idx_client_uploads_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uq_discount_codes_code_tenant": { + "columns": { + "upper(code)": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 020b04d14..44526c46a 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1785716850363, "tag": "0027_sleepy_switch", "breakpoints": true + }, + { + "idx": 28, + "version": "6", + "when": 1785717479440, + "tag": "0028_closed_robin_chapel", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/lib/db/schema/inspection/core.ts b/server/lib/db/schema/inspection/core.ts index 2c4b0dc3d..d4e4c1d57 100644 --- a/server/lib/db/schema/inspection/core.ts +++ b/server/lib/db/schema/inspection/core.ts @@ -257,8 +257,16 @@ export const inspectionResults = sqliteTable('inspection_results', { // source rating system afterwards never mutates an existing inspection. ratingSystemId: text('rating_system_id'), ratingSystemSnapshot: text('rating_system_snapshot', { mode: 'json' }), + // The report this document belongs to. One order can now deliver several — + // a standard report and a radon report — so the uniqueness that used to be + // per INSPECTION is per REPORT. Nullable only so the backfill can run in + // order; every row carries one afterwards. + // Appended at table end for D1 rebuild safety. + reportId: text('report_id'), }, (t) => [ index('idx_results_tenant').on(t.tenantId), index('idx_results_inspection').on(t.inspectionId), - uniqueIndex('uq_results_inspection').on(t.inspectionId), + // Was uq_results_inspection. A second report on one order is the whole + // point of the reports entity; the old index made it impossible. + uniqueIndex('uq_results_report').on(t.reportId), ]); diff --git a/server/lib/db/schema/report-versions.ts b/server/lib/db/schema/report-versions.ts index 8bae24c71..e190d6180 100644 --- a/server/lib/db/schema/report-versions.ts +++ b/server/lib/db/schema/report-versions.ts @@ -33,8 +33,15 @@ export const reportVersions = sqliteTable('report_versions', { publishedAt: integer('published_at', { mode: 'timestamp_ms' }).notNull(), publishedBy: text('published_by').notNull(), createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull().default(sql`(unixepoch() * 1000)`), + // The report this version belongs to. Two reports on one order publish + // independently and each needs its OWN v1 and its own prev_hash chain — + // interleaving them into one chain fails verification for both, including + // for versions published before the second report existed. + // Appended at table end for D1 rebuild safety. + reportId: text('report_id'), }, (t) => [ - index('idx_report_versions_inspection').on(t.inspectionId, t.versionNumber), - uniqueIndex('uq_report_versions_inspection_version').on(t.inspectionId, t.versionNumber), + index('idx_report_versions_report').on(t.reportId, t.versionNumber), + // Version numbers are monotonic per REPORT, not per inspection. + uniqueIndex('uq_report_versions_report_version').on(t.reportId, t.versionNumber), uniqueIndex('idx_report_versions_verify_token').on(t.verificationToken), ]); diff --git a/tests/unit/db/report-results-migration.spec.ts b/tests/unit/db/report-results-migration.spec.ts new file mode 100644 index 000000000..d38a2800b --- /dev/null +++ b/tests/unit/db/report-results-migration.spec.ts @@ -0,0 +1,175 @@ +/** + * Moving results and versions onto `reports`. + * + * The assertion that matters is the third one. A backfill that renumbers or + * reorders versions invalidates the signatures on reports ALREADY DELIVERED — + * `report_versions` carries contentHash / prevHash / signature, and the public + * verifier reads them. Counting rows would not notice that; comparing the hashes + * before and after does. + * + * These run against the real migration set, which `setupSchema` applies — so + * they exercise the shipped SQL rather than a re-implementation of it. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { eq, isNull, sql } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +const TENANT = 'tenant-mig-1'; + +describe('results and versions move onto reports', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + }); + + afterEach(() => { sqlite.close(); }); + + async function seedInspection(id: string, withVersions = 0) { + await db.insert(schema.inspections).values({ + id, tenantId: TENANT, propertyAddress: `${id} Main St`, date: '2026-09-01', + status: 'completed', paymentStatus: 'paid', price: 40000, createdAt: new Date(), + }); + await db.insert(schema.inspectionResults).values({ + id: `res-${id}`, tenantId: TENANT, inspectionId: id, + data: { sections: [] }, lastSyncedAt: new Date(), + }); + for (let n = 1; n <= withVersions; n++) { + await db.insert(schema.reportVersions).values({ + id: `v-${id}-${n}`, tenantId: TENANT, inspectionId: id, versionNumber: n, + snapshotJson: JSON.stringify({ n }), contentHash: `hash-${id}-${n}`, + prevHash: n > 1 ? `hash-${id}-${n - 1}` : null, + signature: `sig-${id}-${n}`, + publishedAt: new Date(), publishedBy: 'u1', createdAt: new Date(), + } as never); + } + } + + /** + * The same backfill the schema migration ships, re-run here so the tests + * exercise its actual semantics. Kept in step by asserting the OUTCOME — + * one primary per inspection, no orphans, an unchanged chain — rather than + * the SQL text, so a reworded migration does not fail this file spuriously. + */ + function runBackfill() { + sqlite.exec(` + INSERT INTO reports (id, tenant_id, inspection_id, kind, inspection_service_id, template_id, title, status, created_at) + SELECT 'rpt-' || i.id, i.tenant_id, i.id, 'primary', NULL, i.template_id, 'Inspection Report', + CASE WHEN EXISTS (SELECT 1 FROM report_versions rv WHERE rv.inspection_id = i.id) + THEN 'published' ELSE 'in_progress' END, + unixepoch() * 1000 + FROM inspections i + WHERE NOT EXISTS (SELECT 1 FROM reports r WHERE r.inspection_id = i.id AND r.kind = 'primary'); + `); + sqlite.exec(`UPDATE inspection_results SET report_id = 'rpt-' || inspection_id WHERE report_id IS NULL;`); + sqlite.exec(`UPDATE report_versions SET report_id = 'rpt-' || inspection_id WHERE report_id IS NULL;`); + } + + it('gives every existing inspection exactly one primary report', async () => { + await seedInspection('i1'); + await seedInspection('i2', 2); + runBackfill(); + + for (const id of ['i1', 'i2']) { + const primaries = await db.select().from(schema.reports) + .where(eq(schema.reports.inspectionId, id)).all(); + expect(primaries.filter(r => r.kind === 'primary')).toHaveLength(1); + } + }); + + it('leaves no orphan results or versions', async () => { + await seedInspection('i1', 1); + await seedInspection('i2', 2); + runBackfill(); + + const orphanResults = await db.select({ n: sql`count(*)` }) + .from(schema.inspectionResults).where(isNull(schema.inspectionResults.reportId)).get(); + const orphanVersions = await db.select({ n: sql`count(*)` }) + .from(schema.reportVersions).where(isNull(schema.reportVersions.reportId)).get(); + + expect(orphanResults?.n).toBe(0); + expect(orphanVersions?.n).toBe(0); + }); + + it('preserves each version chain across the move', async () => { + // A backfill that renumbers or reorders versions invalidates signatures + // on reports already delivered. Compare the chain itself, not the count. + await seedInspection('i1', 3); + const before = await db.select({ + id: schema.reportVersions.id, + n: schema.reportVersions.versionNumber, + contentHash: schema.reportVersions.contentHash, + prevHash: schema.reportVersions.prevHash, + signature: schema.reportVersions.signature, + }).from(schema.reportVersions).orderBy(schema.reportVersions.versionNumber).all(); + + runBackfill(); + + const after = await db.select({ + id: schema.reportVersions.id, + n: schema.reportVersions.versionNumber, + contentHash: schema.reportVersions.contentHash, + prevHash: schema.reportVersions.prevHash, + signature: schema.reportVersions.signature, + }).from(schema.reportVersions).orderBy(schema.reportVersions.versionNumber).all(); + + expect(after).toEqual(before); + }); + + it('is idempotent — a second run creates nothing and changes nothing', async () => { + // The id is derived from the inspection id rather than generated, which + // is what makes re-running safe if the migration is replayed. + await seedInspection('i1', 2); + runBackfill(); + const first = await db.select().from(schema.reports).all(); + + runBackfill(); + + expect(await db.select().from(schema.reports).all()).toEqual(first); + }); + + it('marks a report published only when versions exist', async () => { + await seedInspection('i-draft'); + await seedInspection('i-published', 1); + runBackfill(); + + const draft = await db.select().from(schema.reports) + .where(eq(schema.reports.inspectionId, 'i-draft')).get(); + const published = await db.select().from(schema.reports) + .where(eq(schema.reports.inspectionId, 'i-published')).get(); + + expect(draft?.status).toBe('in_progress'); + expect(published?.status).toBe('published'); + }); + + it('lets two reports on one inspection each hold their own results row', async () => { + // What the old uq_results_inspection made impossible, and the reason the + // uniqueness moved to report_id. + await seedInspection('i1'); + runBackfill(); + + await db.insert(schema.reports).values({ + id: 'rpt-radon', tenantId: TENANT, inspectionId: 'i1', kind: 'ancillary', + title: 'Radon Test Report', status: 'in_progress', createdAt: new Date(), + } as never); + await db.insert(schema.inspectionResults).values({ + id: 'res-radon', tenantId: TENANT, inspectionId: 'i1', + data: { sections: [] }, lastSyncedAt: new Date(), reportId: 'rpt-radon', + } as never); + + const rows = await db.select().from(schema.inspectionResults) + .where(eq(schema.inspectionResults.inspectionId, 'i1')).all(); + expect(rows).toHaveLength(2); + }); +}); From 62a56cd29268941a4423be33cdb56da6bd3c8c46 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 08:59:33 +0800 Subject: [PATCH 15/29] fix(reports): per-report signature chain and per-report collab document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 3 of the per-deliverable-reports plan, and it lands in the same breath as the model because both defects go live the moment a second report can exist — which, as of the previous commit, it can. Neither fails loudly. THE SIGNATURE CHAIN. report_versions carries contentHash / prevHash / signature, a tamper-evident chain that was numbered and linked per INSPECTION. Two reports publishing independently — the standard report Tuesday, radon Thursday — interleave into one chain, and every subsequent verification fails INCLUDING for versions published before the second report existed. Version numbers and the prevHash walk are now per report, and verification walks within its own report: reading by inspection would pick up the other document's version and call a valid chain broken, or a broken one valid. THE COLLABORATIVE DOCUMENT. The Durable Object id came from `${tenantId}:${inspectionId}`, so two inspectors working the standard report and the sewer report of one order landed in the SAME object and shared one Y.Doc. Nothing threw. The CRDT merged content belonging to two different documents, and the corruption surfaced when a client opened a report containing someone else's findings. The name is derived per report now, and the derivation lives in its own function so a test can assert the INPUT: two report ids are trivially unequal, so a test comparing only outputs would pass against an implementation that still keyed on the inspection. Resolving which report also fails CLOSED. An inspection with no report row gets a 404 rather than falling back to an inspection-keyed document — that fallback is precisely the shared-Y.Doc bug. snapshotOnPublish takes an optional reportId and defaults to the inspection's primary, so every existing caller keeps working while a caller that knows which deliverable it is publishing can say so. --- server/api/inspections/collab.ts | 39 ++++++++++++----- server/lib/collab/doc-name.ts | 18 ++++++++ server/lib/inspection/reports.ts | 34 +++++++++++++++ server/services/report-version.service.ts | 48 +++++++++++++++++++-- tests/unit/collab/collab-do-key.spec.ts | 41 ++++++++++++++++++ tests/unit/collab/collab-route-auth.spec.ts | 20 ++++++--- 6 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 server/lib/collab/doc-name.ts create mode 100644 server/lib/inspection/reports.ts create mode 100644 tests/unit/collab/collab-do-key.spec.ts diff --git a/server/api/inspections/collab.ts b/server/api/inspections/collab.ts index 646dcb349..98b1d1028 100644 --- a/server/api/inspections/collab.ts +++ b/server/api/inspections/collab.ts @@ -31,6 +31,8 @@ import { CollabRestoreRequestSchema, CollabSnapshotParamSchema } from '../../lib import type { HonoConfig } from '../../types/hono'; import { canAccessInspectionCollab } from '../../lib/collab/can-access'; import { getInspectionRoster } from '../../lib/inspection/roster'; +import { collabDocName } from '../../lib/collab/doc-name'; +import { resolvePrimaryReportId } from '../../lib/inspection/reports'; import { getDrizzle } from '../../lib/route-helpers'; /** @@ -39,7 +41,7 @@ import { getDrizzle } from '../../lib/route-helpers'; */ type AuthResult = | { ok: false; response: Response } - | { ok: true; tenantId: string; inspectionId: string; userId: string }; + | { ok: true; tenantId: string; inspectionId: string; reportId: string; userId: string }; /** * Shared fail-closed auth for every collab route (checks 1–5 above, excluding @@ -88,19 +90,36 @@ async function authorizeCollab(c: Context): Promise { return { ok: false, response: new Response('forbidden', { status: 403 }) }; } - return { ok: true, tenantId, inspectionId: id, userId }; + // Which DOCUMENT is being edited. The route is addressed by inspection, so + // the primary report is the answer for every caller today; when a client + // can open the radon report directly it passes its own id and this becomes + // a lookup rather than a default. + const primaryReportId = await resolvePrimaryReportId(getDrizzle(c), tenantId, id); + if (!primaryReportId) { + // No report row means nothing to edit. Fail closed rather than falling + // back to an inspection-keyed document, which is the shared-Y.Doc bug. + return { ok: false, response: new Response('not found', { status: 404 }) }; + } + + return { ok: true, tenantId, inspectionId: id, reportId: primaryReportId, userId }; } /** * Resolve the tenant-scoped DO stub for an authorized collab request. + * + * Keyed on the REPORT, not the inspection. Two inspectors working the standard + * report and the sewer report of one order used to land in the same Durable + * Object and share one Y.Doc — nothing threw, the CRDT merged content belonging + * to two different documents, and the corruption surfaced when a client read a + * report containing someone else's findings. * Only reached after `authorizeCollab` has proven `INSPECTION_DOC` is bound; * the explicit guard re-narrows for the type checker (the binding is optional * in the generated Env) and never throws in practice. */ -function collabStub(c: Context, tenantId: string, inspectionId: string) { +function collabStub(c: Context, tenantId: string, reportId: string) { const ns = c.env.INSPECTION_DOC; if (!ns) throw new Error('INSPECTION_DOC binding missing'); - const doId = ns.idFromName(`${tenantId}:${inspectionId}`); + const doId = ns.idFromName(collabDocName(tenantId, reportId)); return ns.get(doId); } @@ -115,7 +134,7 @@ const collabRoutes = createApiRouter() if (!auth.ok) return auth.response; // ── Forward the WS upgrade to the INSPECTION_DOC Durable Object ──────── - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request('https://do.local/ws', { method: 'GET', headers: { @@ -132,7 +151,7 @@ const collabRoutes = createApiRouter() const auth = await authorizeCollab(c); if (!auth.ok) return auth.response; - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request('https://do.local/snapshots', { method: 'GET', headers: { @@ -157,7 +176,7 @@ const collabRoutes = createApiRouter() return c.json({ error: 'invalid snapshot seq' }, 400); } - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request(`https://do.local/snapshots/${parsedParam.data.seq}`, { method: 'GET', headers: { @@ -173,7 +192,7 @@ const collabRoutes = createApiRouter() const auth = await authorizeCollab(c); if (!auth.ok) return auth.response; - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request('https://do.local/snapshots', { method: 'POST', headers: { @@ -201,7 +220,7 @@ const collabRoutes = createApiRouter() return c.json({ error: 'invalid restore request' }, 400); } - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request('https://do.local/restore', { method: 'POST', headers: { @@ -224,7 +243,7 @@ const collabRoutes = createApiRouter() const auth = await authorizeCollab(c); if (!auth.ok) return auth.response; - const stub = collabStub(c, auth.tenantId, auth.inspectionId); + const stub = collabStub(c, auth.tenantId, auth.reportId); const fwd = new Request('https://do.local/restructure', { method: 'POST', headers: { diff --git a/server/lib/collab/doc-name.ts b/server/lib/collab/doc-name.ts new file mode 100644 index 000000000..395031e33 --- /dev/null +++ b/server/lib/collab/doc-name.ts @@ -0,0 +1,18 @@ +/** + * The name a collaborative document's Durable Object is derived from. + * + * Keyed on the REPORT, never the inspection. This used to be + * `${tenantId}:${inspectionId}`, which meant two inspectors working the standard + * report and the sewer report of one order landed in the SAME Durable Object and + * shared one Y.Doc. Nothing threw. The CRDT merged content belonging to two + * different documents, and the corruption surfaced when a client opened a report + * containing someone else's findings. + * + * Extracted into its own function so the derivation can be asserted directly: + * two report ids are trivially unequal, so a test comparing only the OUTPUTS + * would also pass against an implementation that still keyed on the inspection. + * What has to be true is that the inspection id is not an input at all. + */ +export function collabDocName(tenantId: string, reportId: string): string { + return `${tenantId}:${reportId}`; +} diff --git a/server/lib/inspection/reports.ts b/server/lib/inspection/reports.ts new file mode 100644 index 000000000..312890a3c --- /dev/null +++ b/server/lib/inspection/reports.ts @@ -0,0 +1,34 @@ +/** + * Reads over the `reports` entity. + * + * One order can deliver several documents — a standard report and a radon + * report — so "the report" is only ever a well-defined phrase once you say + * WHICH. This module is where callers that still address things by inspection + * resolve that. + */ +import { and, eq } from 'drizzle-orm'; +import type { DrizzleD1Database } from 'drizzle-orm/d1'; +import { reports } from '../db/schema'; + +/** + * The primary report of an inspection, or null when it has none. + * + * Null is a real answer, not an error: an inspection whose report row has not + * been created yet has nothing to open, and callers must fail closed rather + * than fall back to an inspection-keyed document — that fallback is exactly the + * shared-Y.Doc corruption this entity exists to prevent. + */ +export async function resolvePrimaryReportId( + db: DrizzleD1Database, + tenantId: string, + inspectionId: string, +): Promise { + const row = await db.select({ id: reports.id }).from(reports) + .where(and( + eq(reports.tenantId, tenantId), + eq(reports.inspectionId, inspectionId), + eq(reports.kind, 'primary'), + )) + .get(); + return row?.id ?? null; +} diff --git a/server/services/report-version.service.ts b/server/services/report-version.service.ts index 81addff8a..5f4fc745b 100644 --- a/server/services/report-version.service.ts +++ b/server/services/report-version.service.ts @@ -11,7 +11,7 @@ */ import { drizzle } from 'drizzle-orm/d1'; import { and, eq, desc } from 'drizzle-orm'; -import { reportVersions, inspections, inspectionResults, inspectionUnits, users, inspectionInspectors, templates, tenantConfigs } from '../lib/db/schema'; +import { reports, reportVersions, inspections, inspectionResults, inspectionUnits, users, inspectionInspectors, templates, tenantConfigs } from '../lib/db/schema'; import { computeDiff, SNAPSHOT_SCHEMA_VERSION, type Snapshot, type SnapshotInspector, type DiffPayload } from '../lib/version-diff'; import { CredentialService } from './credential.service'; import { resolveProfile } from '../lib/report-style/resolve'; @@ -41,17 +41,50 @@ export class ReportVersionService { return drizzle(this.db); } + /** + * Resolve which report a publish belongs to. + * + * Callers that predate multi-report publishing pass only an inspection, and + * for them the answer is its primary report. Callers that know which + * deliverable they are publishing pass `reportId` and skip this. + */ + private async resolveReportId( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + db: any, tenantId: string, inspectionId: string, reportId?: string, + ): Promise { + if (reportId) return reportId; + const primary = await db.select({ id: reports.id }).from(reports) + .where(and( + eq(reports.tenantId, tenantId), + eq(reports.inspectionId, inspectionId), + eq(reports.kind, 'primary'), + )) + .get(); + return primary?.id ?? null; + } + async snapshotOnPublish( tenantId: string, inspectionId: string, publishedBy: string, summary?: string, + reportId?: string, ): Promise { const db = this.getDrizzle(); + const targetReportId = await this.resolveReportId(db, tenantId, inspectionId, reportId); - // Compute next version number. + // Version numbers and the prevHash chain are per REPORT, not per + // inspection. Two reports on one order publish independently — the + // standard report on Tuesday, radon on Thursday — and interleaving them + // into one chain fails verification for BOTH, including for versions + // published before the second report existed. const prev = await db.select().from(reportVersions) - .where(and(eq(reportVersions.tenantId, tenantId), eq(reportVersions.inspectionId, inspectionId))) + .where(and( + eq(reportVersions.tenantId, tenantId), + targetReportId + ? eq(reportVersions.reportId, targetReportId) + : eq(reportVersions.inspectionId, inspectionId), + )) .orderBy(desc(reportVersions.versionNumber)) .limit(1) .get(); @@ -106,6 +139,7 @@ export class ReportVersionService { id: crypto.randomUUID(), tenantId, inspectionId, + reportId: targetReportId, versionNumber: nextVersion, snapshotJson, summary: summary ?? null, @@ -236,9 +270,15 @@ export class ReportVersionService { let chainValid: boolean; if (row.versionNumber > 1) { + // Walk the chain within this REPORT. Reading by inspection would + // pick up the other report's version and report a valid chain as + // broken (or worse, a broken one as valid) the moment an order + // delivers more than one document. const prev = await db.select().from(reportVersions).where(and( eq(reportVersions.tenantId, row.tenantId), - eq(reportVersions.inspectionId, row.inspectionId), + row.reportId + ? eq(reportVersions.reportId, row.reportId) + : eq(reportVersions.inspectionId, row.inspectionId), eq(reportVersions.versionNumber, row.versionNumber - 1), )).get(); chainValid = !!prev && prev.contentHash === row.prevHash; diff --git a/tests/unit/collab/collab-do-key.spec.ts b/tests/unit/collab/collab-do-key.spec.ts new file mode 100644 index 000000000..14eacbf5c --- /dev/null +++ b/tests/unit/collab/collab-do-key.spec.ts @@ -0,0 +1,41 @@ +/** + * The collaborative document is keyed per REPORT. + * + * It used to be `${tenantId}:${inspectionId}`, so two inspectors working the + * standard report and the sewer report of one order landed in the same Durable + * Object and shared one Y.Doc. Nothing threw — the CRDT merged content belonging + * to two different documents, and the corruption surfaced when a client opened a + * report containing someone else's findings. That is the whole reason this task + * could not be deferred past the point where a second report can exist. + */ +import { describe, it, expect } from 'vitest'; +import { collabDocName } from '../../../server/lib/collab/doc-name'; + +const TENANT = 'tenant-1'; +const INSPECTION = 'insp-1'; +const PRIMARY = 'rpt-primary'; +const RADON = 'rpt-radon'; + +describe('collabDocName', () => { + it('derives a distinct name per report on the same inspection', () => { + expect(collabDocName(TENANT, PRIMARY)).not.toEqual(collabDocName(TENANT, RADON)); + }); + + it('does not derive the name from the inspection id', () => { + // Asserted on the INPUT, not just on inequality. Two report ids are + // trivially unequal, so an implementation that still keyed on the + // inspection would satisfy the test above while remaining broken. + expect(collabDocName(TENANT, PRIMARY)).not.toContain(INSPECTION); + }); + + it('separates the same report id across tenants', () => { + // The DO namespace is global; the tenant prefix is what stops one + // tenant's document from resolving to another's. + expect(collabDocName('tenant-a', PRIMARY)).not.toEqual(collabDocName('tenant-b', PRIMARY)); + }); + + it('is stable for the same inputs', () => { + // Reconnecting to a live editing session has to resolve the same DO. + expect(collabDocName(TENANT, PRIMARY)).toBe(collabDocName(TENANT, PRIMARY)); + }); +}); diff --git a/tests/unit/collab/collab-route-auth.spec.ts b/tests/unit/collab/collab-route-auth.spec.ts index a9f805414..5c2026c25 100644 --- a/tests/unit/collab/collab-route-auth.spec.ts +++ b/tests/unit/collab/collab-route-auth.spec.ts @@ -43,6 +43,14 @@ vi.mock('../../../server/lib/inspection/roster', () => ({ getInspectionRoster: (...args: unknown[]) => getInspectionRoster(...args), })); +// The collab document is keyed per REPORT now, so the route resolves which one +// before it can address a Durable Object. Mocked for the same reason as the +// roster: this spec is about the route's auth and DO wiring, not about SQL. +const resolvePrimaryReportId = vi.fn(); +vi.mock('../../../server/lib/inspection/reports', () => ({ + resolvePrimaryReportId: (...args: unknown[]) => resolvePrimaryReportId(...args), +})); + const member = (id: string) => ({ id, name: id, email: `${id}@example.com` }); const rosterOf = (lead: string | null, helpers: string[] = []) => ({ lead: lead ? member(lead) : null, @@ -96,6 +104,8 @@ function buildApp(opts: BuildAppOptions = {}) { : (inspectionOverride.leadInspectorId ?? inspectionOverride.inspectorId ?? null), helpers: legacyHelpers, }; + resolvePrimaryReportId.mockReset(); + resolvePrimaryReportId.mockResolvedValue('rpt-insp1'); getInspectionRoster.mockReset(); getInspectionRoster.mockResolvedValue(rosterOf(effectiveRoster.lead, effectiveRoster.helpers ?? [])); @@ -218,7 +228,7 @@ describe('collab WS route — auth gate', () => { // ── (c) Authorized — DO is contacted with correct identity headers ──────── - it('(c1) authorized primary inspector — DO idFromName called with tenantId:inspectionId', async () => { + it('(c1) authorized primary inspector — DO idFromName called with tenantId:reportId', async () => { const mockDo = makeMockDoNamespace(); const { app } = buildApp({ tenantId: 't1', @@ -236,7 +246,7 @@ describe('collab WS route — auth gate', () => { '/api/inspections/insp1/collab/ws', { headers: { Upgrade: 'websocket' } }, ); - expect(mockDo.idFromName).toHaveBeenCalledWith('t1:insp1'); + expect(mockDo.idFromName).toHaveBeenCalledWith('t1:rpt-insp1'); expect(mockDo.get).toHaveBeenCalled(); const fetchCall = mockDo.stubFetch.mock.calls[0][0] as Request; expect(fetchCall.headers.get('x-tenant-id')).toBe('t1'); @@ -261,7 +271,7 @@ describe('collab WS route — auth gate', () => { '/api/inspections/insp1/collab/ws', { headers: { Upgrade: 'websocket' } }, ); - expect(mockDo.idFromName).toHaveBeenCalledWith('t1:insp1'); + expect(mockDo.idFromName).toHaveBeenCalledWith('t1:rpt-insp1'); }); // ── (d) Role-based access — admin/manager bypass assignment check ──────── @@ -286,7 +296,7 @@ describe('collab WS route — auth gate', () => { ); // Must NOT be 403 — admin bypasses assignment check. expect(res.status).not.toBe(403); - expect(mockDo.idFromName).toHaveBeenCalledWith('t1:insp1'); + expect(mockDo.idFromName).toHaveBeenCalledWith('t1:rpt-insp1'); }); it('(d2) manager user who is NOT assigned to the inspection is authorized', async () => { @@ -308,7 +318,7 @@ describe('collab WS route — auth gate', () => { { headers: { Upgrade: 'websocket' } }, ); expect(res.status).not.toBe(403); - expect(mockDo.idFromName).toHaveBeenCalledWith('t1:insp1'); + expect(mockDo.idFromName).toHaveBeenCalledWith('t1:rpt-insp1'); }); it('(d3) inspector user who is NOT assigned is still denied 403', async () => { From c4d7f6b2fa8a3dcc2538a0626fe71c8660a9b180 Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 09:12:27 +0800 Subject: [PATCH 16/29] fix(collab): key the Durable Object's D1 projection on the report too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-keying the DO address was not sufficient, and this is the second path. InspectionDocDO writes its Y.Doc projection back to `inspection_results`, and that write matched on `inspection_id`. An inspection now has one results row PER report, so a correctly-routed radon document would still have overwritten the standard report's row — the same corruption as the shared Y.Doc, one layer down, and just as silent. Fixing the id derivation alone would have looked complete and left the bug. The DO now carries an `x-report-id` header and writes by `report_id` when it knows which document it is, falling back to the inspection predicate only when it does not — a DO awakened before any client has connected, which cannot yet have a sibling to clobber. INSPECTION_PRESENCE is deliberately left keyed per inspection: it persists no document, so there is no corruption path, only the question of whether two people editing different reports of one order should see each other. That is a UX call, not a correctness one. inspection-doc.ts is already grandfathered at 900+ lines; the baseline moves by the 16 lines this adds rather than splitting a Durable Object mid-change. --- scripts/file-size-baseline.json | 2 +- server/api/inspections/collab.ts | 6 ++++++ server/durable-objects/inspection-doc.ts | 20 ++++++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/scripts/file-size-baseline.json b/scripts/file-size-baseline.json index 04f9ef4a4..87b9660bf 100644 --- a/scripts/file-size-baseline.json +++ b/scripts/file-size-baseline.json @@ -4,7 +4,7 @@ "server/services/inspection/inspection-core.service.ts": 1117, "server/services/booking.service.ts": 966, "server/services/inspection/inspection-report.service.ts": 952, - "server/durable-objects/inspection-doc.ts": 912, + "server/durable-objects/inspection-doc.ts": 928, "server/lib/collab/results-doc.ts": 874, "app/routes/inspections.tsx": 867, "server/api/sms.ts": 843, diff --git a/server/api/inspections/collab.ts b/server/api/inspections/collab.ts index 98b1d1028..187e3db61 100644 --- a/server/api/inspections/collab.ts +++ b/server/api/inspections/collab.ts @@ -141,6 +141,7 @@ const collabRoutes = createApiRouter() 'Upgrade': 'websocket', 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, }); @@ -157,6 +158,7 @@ const collabRoutes = createApiRouter() headers: { 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, }); @@ -182,6 +184,7 @@ const collabRoutes = createApiRouter() headers: { 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, }); @@ -198,6 +201,7 @@ const collabRoutes = createApiRouter() headers: { 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, }); @@ -227,6 +231,7 @@ const collabRoutes = createApiRouter() 'Content-Type': 'application/json', 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, body: JSON.stringify({ seq: parsed.data.seq }), @@ -249,6 +254,7 @@ const collabRoutes = createApiRouter() headers: { 'x-tenant-id': auth.tenantId, 'x-inspection-id': auth.inspectionId, + 'x-report-id': auth.reportId, 'x-user-id': auth.userId, }, }); diff --git a/server/durable-objects/inspection-doc.ts b/server/durable-objects/inspection-doc.ts index f9cc4c0b1..caeb95509 100644 --- a/server/durable-objects/inspection-doc.ts +++ b/server/durable-objects/inspection-doc.ts @@ -213,6 +213,12 @@ export class InspectionDocDO extends DurableObject { */ private tenantId: string | null = null; private inspectionId: string | null = null; + // Which DOCUMENT this Y.Doc is. The DO is addressed per report, and the D1 + // projection must be written per report too — an inspection now has one + // results row PER report, so writing by inspection id would overwrite every + // sibling document with this one. Fixing the DO address alone leaves that + // second path open. + private reportId: string | null = null; /** True once identity has been written to DO storage (avoids redundant puts). */ private identityPersisted: boolean = false; /** @@ -287,8 +293,10 @@ export class InspectionDocDO extends DurableObject { // authorized route is the trust boundary). const headerTenantId = req.headers.get('x-tenant-id'); const headerInspectionId = req.headers.get('x-inspection-id'); + const headerReportId = req.headers.get('x-report-id'); if (headerTenantId) this.tenantId = headerTenantId; if (headerInspectionId) this.inspectionId = headerInspectionId; + if (headerReportId) this.reportId = headerReportId; // M3: fail-closed — reject the upgrade if identity is unknown from // both headers and stored state (hydrate() would have loaded it). @@ -398,8 +406,10 @@ export class InspectionDocDO extends DurableObject { // mirroring the /ws path identity acquisition (the DO trusts the route). const headerTenantId = req.headers.get('x-tenant-id'); const headerInspectionId = req.headers.get('x-inspection-id'); + const headerReportId = req.headers.get('x-report-id'); if (headerTenantId) this.tenantId = headerTenantId; if (headerInspectionId) this.inspectionId = headerInspectionId; + if (headerReportId) this.reportId = headerReportId; await this.restructure(); return Response.json({ ok: true }); @@ -504,8 +514,13 @@ export class InspectionDocDO extends DurableObject { }) .where( and( - eq(inspectionResults.tenantId, tenantId), - eq(inspectionResults.inspectionId, inspectionId), + eq(inspectionResults.tenantId, tenantId), + // Per REPORT when we know which one. An inspection can now + // carry several results rows, and matching on inspection id + // alone updates all of them with this document. + this.reportId + ? eq(inspectionResults.reportId, this.reportId) + : eq(inspectionResults.inspectionId, inspectionId), ), ); @@ -519,6 +534,7 @@ export class InspectionDocDO extends DurableObject { id: crypto.randomUUID(), tenantId, inspectionId, + ...(this.reportId ? { reportId: this.reportId } : {}), ydocState: stateUpdate, data: projection, lastSyncedAt: new Date(), From 80f2395fbaa4d78a7ac31f2425393a9c3178f60e Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 10:02:50 +0800 Subject: [PATCH 17/29] feat(reports): document the order-wide gate, and add a per-inspection unlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The report gate is order-wide: any required agreement left unsigned, or payment outstanding, blocks EVERY report on the inspection — not just the report belonging to the service whose agreement is missing. That was already the behaviour, as an accident of `agreement_requests` having no report dimension. It is now a stated rule, written at the gate itself, because one rule an inspector and a client can both recite without looking it up is worth more than a finer one neither can. Its cost is real and this is the part that needed solving: an add-on's unsigned addendum can hold back a report that is finished and that someone is waiting for. The release is a deliberate human action — an owner or manager opening one inspection and recording why — rather than resolving the gate per report, which would mean putting a service dimension on `agreement_requests`, a signed-evidence table with a retention rule and an erasure entry, to solve what one override solves. `reason` is required and stored, not defaulted. An override with no stated reason is indistinguishable later from a mistake, and this one is client-visible: it hands over a report the tenant's own rules said to hold. Unlocking twice keeps the ORIGINAL timestamp, person and reason, so the record shows who actually made the call rather than whoever pressed it last. Relocking clears the reason with it — it described a decision that no longer stands. Both routes are owner/manager only and both are audited, including the already-unlocked case, because someone asking is part of the story of who wanted the gate open. The two audit actions are registered in the AuditAction union, which is closed on purpose — the type check refused them until they were declared. Default stays locked. Reports being held until signed and paid is what a tenant expects, and defaulting open would quietly change that for them. The two routes live in their own module rather than in publish.ts: publishing is about whether a document is finished, this is about whether the people waiting for it may see it. Different questions, different reasons to change. Migration 0029 is three appended ADD COLUMNs, no rebuild. db:check clean. The append-order guard in pca-foundation-schema.spec.ts was extended rather than worked around — it exists to keep db:generate emitting ALTER rather than a table rebuild, and a new trio at the tail is exactly what it watches for. --- migrations/0029_rich_leader.sql | 3 + migrations/meta/0029_snapshot.json | 10266 ++++++++++++++++ migrations/meta/_journal.json | 7 + scripts/file-size-baseline.json | 4 +- server/api/inspections.ts | 2 + server/api/inspections/report-gate.ts | 93 + server/lib/audit.ts | 5 + server/lib/db/schema/inspection/core.ts | 20 + server/services/inspection.service.ts | 10 + .../inspection/inspection-publish.service.ts | 18 + .../inspection/inspection-status.service.ts | 44 + .../inspections/report-gate-unlock.spec.ts | 115 + .../reports/pca-foundation-schema.spec.ts | 10 +- 13 files changed, 10592 insertions(+), 5 deletions(-) create mode 100644 migrations/0029_rich_leader.sql create mode 100644 migrations/meta/0029_snapshot.json create mode 100644 server/api/inspections/report-gate.ts create mode 100644 tests/unit/inspections/report-gate-unlock.spec.ts diff --git a/migrations/0029_rich_leader.sql b/migrations/0029_rich_leader.sql new file mode 100644 index 000000000..cca5e9524 --- /dev/null +++ b/migrations/0029_rich_leader.sql @@ -0,0 +1,3 @@ +ALTER TABLE `inspections` ADD `unlocked_at` integer;--> statement-breakpoint +ALTER TABLE `inspections` ADD `unlocked_by` text;--> statement-breakpoint +ALTER TABLE `inspections` ADD `unlock_reason` text; \ No newline at end of file diff --git a/migrations/meta/0029_snapshot.json b/migrations/meta/0029_snapshot.json new file mode 100644 index 000000000..27683ad35 --- /dev/null +++ b/migrations/meta/0029_snapshot.json @@ -0,0 +1,10266 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e4241f07-66b5-42cf-be26-2bf9742f9cc7", + "prevId": "cad1adf3-bc98-4683-ab7c-ef129deb97d1", + "tables": { + "agreement_requests": { + "name": "agreement_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signature_base64": { + "name": "inspector_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_signed_at": { + "name": "inspector_signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_user_id": { + "name": "inspector_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_snapshot": { + "name": "content_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completion_policy": { + "name": "completion_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "agreement_requests_token_unique": { + "name": "agreement_requests_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_agreement_requests_verify_token": { + "name": "idx_agreement_requests_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + }, + "idx_agreement_requests_tenant": { + "name": "idx_agreement_requests_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_agreement_requests_inspection": { + "name": "idx_agreement_requests_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_agreement_requests_token_hash": { + "name": "idx_agreement_requests_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agreement_requests_tenant_id_tenants_id_fk": { + "name": "agreement_requests_tenant_id_tenants_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspection_id_inspections_id_fk": { + "name": "agreement_requests_inspection_id_inspections_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_agreement_id_agreements_id_fk": { + "name": "agreement_requests_agreement_id_agreements_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agreement_requests_inspector_user_id_users_id_fk": { + "name": "agreement_requests_inspector_user_id_users_id_fk", + "tableFrom": "agreement_requests", + "tableTo": "users", + "columnsFrom": [ + "inspector_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreement_signers": { + "name": "agreement_signers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "signature_base64": { + "name": "signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewed_at": { + "name": "viewed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_of": { + "name": "on_behalf_of", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "on_behalf_disclaimer": { + "name": "on_behalf_disclaimer", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_reminded_at": { + "name": "last_reminded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_agreement_signers_tenant_request": { + "name": "idx_agreement_signers_tenant_request", + "columns": [ + "tenant_id", + "request_id" + ], + "isUnique": false + }, + "idx_agreement_signers_request_email": { + "name": "idx_agreement_signers_request_email", + "columns": [ + "request_id", + "email" + ], + "isUnique": true + }, + "idx_agreement_signers_token_hash": { + "name": "idx_agreement_signers_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agreements": { + "name": "agreements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_agreements_tenant": { + "name": "idx_agreements_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agreements_tenant_id_tenants_id_fk": { + "name": "agreements_tenant_id_tenants_id_fk", + "tableFrom": "agreements", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automation_logs": { + "name": "automation_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_key": { + "name": "recipient_role_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "send_at": { + "name": "send_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_contact_id": { + "name": "recipient_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notice_id": { + "name": "notice_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automation_logs_pending": { + "name": "idx_automation_logs_pending", + "columns": [ + "tenant_id", + "status", + "send_at" + ], + "isUnique": false + }, + "idx_automation_logs_insp": { + "name": "idx_automation_logs_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_automation_logs_event": { + "name": "uq_automation_logs_event", + "columns": [ + "automation_id", + "inspection_id", + "event_id", + "channel", + "recipient" + ], + "isUnique": true, + "where": "event_id IS NOT NULL" + } + }, + "foreignKeys": { + "automation_logs_tenant_id_tenants_id_fk": { + "name": "automation_logs_tenant_id_tenants_id_fk", + "tableFrom": "automation_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "automations": { + "name": "automations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_kind": { + "name": "recipient_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_role_profile_id": { + "name": "recipient_role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delay_minutes": { + "name": "delay_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "subject_template": { + "name": "subject_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_template": { + "name": "body_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conditions": { + "name": "conditions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"email\"]'" + }, + "sms_body": { + "name": "sms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "in_app_template_id": { + "name": "in_app_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_automations_tenant": { + "name": "idx_automations_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "automations_tenant_id_tenants_id_fk": { + "name": "automations_tenant_id_tenants_id_fk", + "tableFrom": "automations", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability": { + "name": "availability", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "day_of_week": { + "name": "day_of_week", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_availability_inspector": { + "name": "idx_availability_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_availability_window_unique": { + "name": "idx_availability_window_unique", + "columns": [ + "inspector_id", + "day_of_week", + "start_time" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_tenant_id_tenants_id_fk": { + "name": "availability_tenant_id_tenants_id_fk", + "tableFrom": "availability", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_inspector_id_users_id_fk": { + "name": "availability_inspector_id_users_id_fk", + "tableFrom": "availability", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "availability_overrides": { + "name": "availability_overrides", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_available": { + "name": "is_available", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "transparency": { + "name": "transparency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_avail_overrides_insp": { + "name": "idx_avail_overrides_insp", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_avail_overrides_block_unique": { + "name": "idx_avail_overrides_block_unique", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": true, + "where": "is_available = 0 AND source IS NULL" + }, + "uq_avail_overrides_external": { + "name": "uq_avail_overrides_external", + "columns": [ + "inspector_id", + "source", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "availability_overrides_tenant_id_tenants_id_fk": { + "name": "availability_overrides_tenant_id_tenants_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "availability_overrides_inspector_id_users_id_fk": { + "name": "availability_overrides_inspector_id_users_id_fk", + "tableFrom": "availability_overrides", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_blocks": { + "name": "calendar_blocks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_time": { + "name": "start_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_time": { + "name": "end_time", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_all_day": { + "name": "is_all_day", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_calendar_blocks_tenant_user_date": { + "name": "idx_calendar_blocks_tenant_user_date", + "columns": [ + "tenant_id", + "user_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connection_read_calendars": { + "name": "calendar_connection_read_calendars", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_calendar_id": { + "name": "external_calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_role": { + "name": "access_role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_conn_read_cal": { + "name": "uq_conn_read_cal", + "columns": [ + "connection_id", + "external_calendar_id" + ], + "isUnique": true + }, + "idx_conn_read_cal_tenant": { + "name": "idx_conn_read_cal_tenant", + "columns": [ + "tenant_id", + "connection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "calendar_connections": { + "name": "calendar_connections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credentials_dek_enc": { + "name": "credentials_dek_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_calendar_connections_user_provider": { + "name": "uq_calendar_connections_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": true + }, + "idx_calendar_connections_tenant_user": { + "name": "idx_calendar_connections_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comment_usage": { + "name": "comment_usage", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_id": { + "name": "comment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_comment_usage_user_last_used": { + "name": "idx_comment_usage_user_last_used", + "columns": [ + "tenant_id", + "user_id", + "last_used_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comment_usage_comment_id_comments_id_fk": { + "name": "comment_usage_comment_id_comments_id_fk", + "tableFrom": "comment_usage", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "comment_usage_tenant_id_user_id_comment_id_pk": { + "columns": [ + "tenant_id", + "user_id", + "comment_id" + ], + "name": "comment_usage_tenant_id_user_id_comment_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_bucket": { + "name": "rating_bucket", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section": { + "name": "section", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "section_ids": { + "name": "section_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_labels": { + "name": "item_labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trigger_code": { + "name": "trigger_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_keywords": { + "name": "search_keywords", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repair_summary": { + "name": "repair_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_min_cents": { + "name": "estimate_min_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimate_max_cents": { + "name": "estimate_max_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recommended_contractor_type_id": { + "name": "recommended_contractor_type_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_comments_tenant": { + "name": "idx_comments_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_comments_rating_bucket": { + "name": "idx_comments_rating_bucket", + "columns": [ + "tenant_id", + "rating_bucket" + ], + "isUnique": false + }, + "idx_comments_library_id": { + "name": "idx_comments_library_id", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "comments_tenant_id_tenants_id_fk": { + "name": "comments_tenant_id_tenants_id_fk", + "tableFrom": "comments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "commercial_subtypes": { + "name": "commercial_subtypes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_disabled": { + "name": "is_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_commercial_subtypes_tenant_name": { + "name": "idx_commercial_subtypes_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "commercial_subtypes_tenant_id_tenants_id_fk": { + "name": "commercial_subtypes_tenant_id_tenants_id_fk", + "tableFrom": "commercial_subtypes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "concierge_confirm_tokens": { + "name": "concierge_confirm_tokens", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_concierge_tokens_expiry": { + "name": "idx_concierge_tokens_expiry", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_concierge_confirm_token_hash": { + "name": "idx_concierge_confirm_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "concierge_confirm_tokens_inspection_id_inspections_id_fk": { + "name": "concierge_confirm_tokens_inspection_id_inspections_id_fk", + "tableFrom": "concierge_confirm_tokens", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_role_profiles": { + "name": "contact_role_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_template_id": { + "name": "email_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sms_template_id": { + "name": "sms_template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_system": { + "name": "is_system", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capability_overrides": { + "name": "capability_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_crp_tenant": { + "name": "idx_crp_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_crp_tenant_key": { + "name": "uq_crp_tenant_key", + "columns": [ + "tenant_id", + "key" + ], + "isUnique": true, + "where": "is_active = 1" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contacts": { + "name": "contacts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agency": { + "name": "agency", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_user_id": { + "name": "agent_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_linked_at": { + "name": "agent_linked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agent_revoked_at": { + "name": "agent_revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_contacts_type": { + "name": "idx_contacts_type", + "columns": [ + "tenant_id", + "type" + ], + "isUnique": false + }, + "idx_contacts_tenant": { + "name": "idx_contacts_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_contacts_tenant_email": { + "name": "uq_contacts_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "email IS NOT NULL AND archived_at IS NULL" + }, + "uq_contacts_tenant_agent_user": { + "name": "uq_contacts_tenant_agent_user", + "columns": [ + "tenant_id", + "agent_user_id" + ], + "isUnique": true, + "where": "agent_user_id IS NOT NULL AND archived_at IS NULL" + }, + "idx_contacts_agent_user": { + "name": "idx_contacts_agent_user", + "columns": [ + "agent_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "contacts_tenant_id_tenants_id_fk": { + "name": "contacts_tenant_id_tenants_id_fk", + "tableFrom": "contacts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contractor_types": { + "name": "contractor_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_contractor_types_tenant": { + "name": "idx_contractor_types_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cost_items": { + "name": "cost_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "building_id": { + "name": "building_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_index": { + "name": "instance_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_id": { + "name": "unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system": { + "name": "system", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "component": { + "name": "component", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cost_method": { + "name": "cost_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uom": { + "name": "uom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_cost_cents": { + "name": "unit_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lump_sum_cents": { + "name": "lump_sum_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eul": { + "name": "eul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "eff_age": { + "name": "eff_age", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rul": { + "name": "rul", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "suggested_remedy": { + "name": "suggested_remedy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_ref": { + "name": "section_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_ref": { + "name": "photo_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_cost_items_tenant_inspection": { + "name": "idx_cost_items_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_cost_items_finding_key": { + "name": "idx_cost_items_finding_key", + "columns": [ + "finding_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "defect_categories": { + "name": "defect_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6b7280'" + }, + "is_summary_driver": { + "name": "is_summary_driver", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_defect_categories_tenant": { + "name": "idx_defect_categories_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "discount_codes": { + "name": "discount_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uses_count": { + "name": "uses_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_discount_codes_tenant": { + "name": "idx_discount_codes_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_discount_codes_code_tenant": { + "name": "uq_discount_codes_code_tenant", + "columns": [ + "upper(code)", + "tenant_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "discount_codes_tenant_id_tenants_id_fk": { + "name": "discount_codes_tenant_id_tenants_id_fk", + "tableFrom": "discount_codes", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "document_review_items": { + "name": "document_review_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_requested": { + "name": "is_requested", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_received": { + "name": "is_received", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_reviewed": { + "name": "is_reviewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_na": { + "name": "is_na", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_doc_review_inspection": { + "name": "idx_doc_review_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_doc_review_item": { + "name": "uq_doc_review_item", + "columns": [ + "inspection_id", + "document_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_suppressions": { + "name": "email_suppressions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_provider": { + "name": "source_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_email_suppressions_email": { + "name": "idx_email_suppressions_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "erasure_log": { + "name": "erasure_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_email": { + "name": "subject_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "identity_basis": { + "name": "identity_basis", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "decisions_json": { + "name": "decisions_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retained_count": { + "name": "retained_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "anonymized_count": { + "name": "anonymized_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "deleted_count": { + "name": "deleted_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "response_note": { + "name": "response_note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_erasure_log_tenant": { + "name": "idx_erasure_log_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "esign_audit_logs": { + "name": "esign_audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload_json": { + "name": "payload_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_esign_audit_logs_request": { + "name": "idx_esign_audit_logs_request", + "columns": [ + "tenant_id", + "request_id", + "created_at" + ], + "isUnique": false + }, + "idx_esign_audit_logs_event_dedup": { + "name": "idx_esign_audit_logs_event_dedup", + "columns": [ + "tenant_id", + "request_id", + "event" + ], + "isUnique": true, + "where": "event NOT LIKE 'signer.%'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "event_types": { + "name": "event_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_duration_min": { + "name": "default_duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "default_price_cents": { + "name": "default_price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#6366f1'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_event_types_tenant_slug": { + "name": "uq_event_types_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "event_types_tenant_id_tenants_id_fk": { + "name": "event_types_tenant_id_tenants_id_fk", + "tableFrom": "event_types", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_access_tokens": { + "name": "inspection_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'client'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_enc": { + "name": "token_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_iat_token": { + "name": "idx_iat_token", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_iat_inspection": { + "name": "idx_iat_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_iat_recipient": { + "name": "idx_iat_recipient", + "columns": [ + "inspection_id", + "recipient_email" + ], + "isUnique": true + }, + "idx_iat_token_hash": { + "name": "idx_iat_token_hash", + "columns": [ + "token_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_access_tokens_tenant_id_tenants_id_fk": { + "name": "inspection_access_tokens_tenant_id_tenants_id_fk", + "tableFrom": "inspection_access_tokens", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_events": { + "name": "inspection_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type_id": { + "name": "event_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'scheduled'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "results_received_at": { + "name": "results_received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "gcal_event_id": { + "name": "gcal_event_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_events_scheduled": { + "name": "idx_inspection_events_scheduled", + "columns": [ + "tenant_id", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_events_inspection": { + "name": "idx_inspection_events_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_events_tenant_id_tenants_id_fk": { + "name": "inspection_events_tenant_id_tenants_id_fk", + "tableFrom": "inspection_events", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspection_id_inspections_id_fk": { + "name": "inspection_events_inspection_id_inspections_id_fk", + "tableFrom": "inspection_events", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_events_event_type_id_event_types_id_fk": { + "name": "inspection_events_event_type_id_event_types_id_fk", + "tableFrom": "inspection_events", + "tableTo": "event_types", + "columnsFrom": [ + "event_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_events_inspector_id_users_id_fk": { + "name": "inspection_events_inspector_id_users_id_fk", + "tableFrom": "inspection_events", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_inspectors": { + "name": "inspection_inspectors", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'lead'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_insp_inspectors_tenant_user": { + "name": "idx_insp_inspectors_tenant_user", + "columns": [ + "tenant_id", + "user_id" + ], + "isUnique": false + }, + "idx_insp_inspectors_user": { + "name": "idx_insp_inspectors_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_inspectors_inspection_id_user_id_pk": { + "columns": [ + "inspection_id", + "user_id" + ], + "name": "inspection_inspectors_inspection_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_item_tag_links": { + "name": "inspection_item_tag_links", + "columns": { + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_links_tenant": { + "name": "idx_tag_links_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_tag_links_tag": { + "name": "idx_tag_links_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + }, + "idx_tag_links_inspection_item": { + "name": "idx_tag_links_inspection_item", + "columns": [ + "inspection_id", + "item_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inspection_item_tag_links_inspection_id_item_id_tag_id_pk": { + "columns": [ + "inspection_id", + "item_id", + "tag_id" + ], + "name": "inspection_item_tag_links_inspection_id_item_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_media_pool": { + "name": "inspection_media_pool", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "exif_data": { + "name": "exif_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "annotations": { + "name": "annotations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "caption": { + "name": "caption", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'photo'" + }, + "stream_uid": { + "name": "stream_uid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "poster_pct": { + "name": "poster_pct", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_sec": { + "name": "duration_sec", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'stream'" + }, + "poster_key": { + "name": "poster_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_media_pool_tenant": { + "name": "idx_media_pool_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_media_pool_inspection": { + "name": "idx_media_pool_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_messages": { + "name": "inspection_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "from_role": { + "name": "from_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attachments": { + "name": "attachments", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_user_id": { + "name": "from_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_msg_inspection": { + "name": "idx_msg_inspection", + "columns": [ + "inspection_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_contact": { + "name": "idx_msg_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_msg_unread": { + "name": "idx_msg_unread", + "columns": [ + "tenant_id", + "contact_id", + "from_role" + ], + "isUnique": false, + "where": "\"inspection_messages\".\"read_at\" IS NULL" + } + }, + "foreignKeys": { + "inspection_messages_tenant_id_tenants_id_fk": { + "name": "inspection_messages_tenant_id_tenants_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_messages_inspection_id_inspections_id_fk": { + "name": "inspection_messages_inspection_id_inspections_id_fk", + "tableFrom": "inspection_messages", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_people": { + "name": "inspection_people", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_profile_id": { + "name": "role_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_ip_inspection": { + "name": "idx_ip_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_ip_tenant": { + "name": "idx_ip_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_ip_insp_contact_role": { + "name": "uq_ip_insp_contact_role", + "columns": [ + "inspection_id", + "contact_id", + "role_profile_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_requests": { + "name": "inspection_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_phone": { + "name": "client_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "property_city": { + "name": "property_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_state": { + "name": "property_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_zip": { + "name": "property_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_amount_cents": { + "name": "total_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_requests_tenant": { + "name": "idx_inspection_requests_tenant", + "columns": [ + "tenant_id", + "status", + "scheduled_at" + ], + "isUnique": false + }, + "idx_inspection_requests_email": { + "name": "idx_inspection_requests_email", + "columns": [ + "tenant_id", + "client_email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_requests_tenant_id_tenants_id_fk": { + "name": "inspection_requests_tenant_id_tenants_id_fk", + "tableFrom": "inspection_requests", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_results": { + "name": "inspection_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ydoc_state": { + "name": "ydoc_state", + "type": "blob", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rating_system_snapshot": { + "name": "rating_system_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_results_tenant": { + "name": "idx_results_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_results_inspection": { + "name": "idx_results_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "uq_results_report": { + "name": "uq_results_report", + "columns": [ + "report_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "inspection_results_tenant_id_tenants_id_fk": { + "name": "inspection_results_tenant_id_tenants_id_fk", + "tableFrom": "inspection_results", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_results_inspection_id_inspections_id_fk": { + "name": "inspection_results_inspection_id_inspections_id_fk", + "tableFrom": "inspection_results", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_services": { + "name": "inspection_services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_override_cents": { + "name": "price_override_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_snapshot": { + "name": "name_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "price_snapshot_cents": { + "name": "price_snapshot_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "idx_insp_services_tenant": { + "name": "idx_insp_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_insp_services_insp": { + "name": "idx_insp_services_insp", + "columns": [ + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspection_services_tenant_id_tenants_id_fk": { + "name": "inspection_services_tenant_id_tenants_id_fk", + "tableFrom": "inspection_services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspection_services_inspection_id_inspections_id_fk": { + "name": "inspection_services_inspection_id_inspections_id_fk", + "tableFrom": "inspection_services", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inspection_services_service_id_services_id_fk": { + "name": "inspection_services_service_id_services_id_fk", + "tableFrom": "inspection_services", + "tableTo": "services", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_types": { + "name": "inspection_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "based_on": { + "name": "based_on", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_types_tenant_name": { + "name": "idx_inspection_types_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspection_units": { + "name": "inspection_units", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "parent_unit_id": { + "name": "parent_unit_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unit'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "attrs": { + "name": "attrs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspection_units_tenant_inspection": { + "name": "idx_inspection_units_tenant_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_inspection_units_parent": { + "name": "idx_inspection_units_parent", + "columns": [ + "parent_unit_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspections": { + "name": "inspections", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspector_id": { + "name": "inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "address_place_id": { + "name": "address_place_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_street": { + "name": "address_street", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_county": { + "name": "address_county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lat": { + "name": "address_lat", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_lng": { + "name": "address_lng", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address_geocoded_at": { + "name": "address_geocoded_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'requested'" + }, + "report_status": { + "name": "report_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unpaid'" + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_reason": { + "name": "cancel_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cancel_notes": { + "name": "cancel_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_payment_required": { + "name": "is_payment_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_agreement_required": { + "name": "is_agreement_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_auto_sign_on_publish": { + "name": "is_auto_sign_on_publish", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "discount_code_id": { + "name": "discount_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "discount_amount_cents": { + "name": "discount_amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closing_date": { + "name": "closing_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referral_source": { + "name": "referral_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "internal_notes": { + "name": "internal_notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sqft": { + "name": "sqft", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "foundation_type": { + "name": "foundation_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bedrooms": { + "name": "bedrooms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bathrooms": { + "name": "bathrooms", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lot_size": { + "name": "lot_size", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_facts": { + "name": "property_facts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_photo_id": { + "name": "cover_photo_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_crop": { + "name": "cover_crop", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover_image_key": { + "name": "cover_image_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_tier": { + "name": "report_tier", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_automations_disabled": { + "name": "is_automations_disabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "template_snapshot": { + "name": "template_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_snapshot_version": { + "name": "template_snapshot_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "profile_override": { + "name": "profile_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_defect_fields_override": { + "name": "require_defect_fields_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "concierge_status": { + "name": "concierge_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_team_mode": { + "name": "is_team_mode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "lead_inspector_id": { + "name": "lead_inspector_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "helper_inspector_ids": { + "name": "helper_inspector_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "data_version": { + "name": "data_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "source_inspection_id": { + "name": "source_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "root_inspection_id": { + "name": "root_inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reinspection_round": { + "name": "reinspection_round", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_inspection_mode": { + "name": "unit_inspection_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'tagged'" + }, + "location_options": { + "name": "location_options", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sampling_declaration": { + "name": "sampling_declaration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pca_narrative": { + "name": "pca_narrative", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deviations": { + "name": "deviations", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_mode": { + "name": "report_photo_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_start_ms": { + "name": "scheduled_start_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scheduled_end_ms": { + "name": "scheduled_end_ms", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_min": { + "name": "duration_min", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "badge_layout_override": { + "name": "badge_layout_override", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "report_photo_columns": { + "name": "report_photo_columns", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referred_by_contact_id": { + "name": "referred_by_contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unlocked_at": { + "name": "unlocked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unlocked_by": { + "name": "unlocked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unlock_reason": { + "name": "unlock_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_inspections_tenant": { + "name": "idx_inspections_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspections_request": { + "name": "idx_inspections_request", + "columns": [ + "request_id" + ], + "isUnique": false + }, + "idx_inspections_inspector": { + "name": "idx_inspections_inspector", + "columns": [ + "inspector_id" + ], + "isUnique": false + }, + "idx_inspections_tenant_status": { + "name": "idx_inspections_tenant_status", + "columns": [ + "tenant_id", + "status" + ], + "isUnique": false + }, + "idx_inspections_tenant_date": { + "name": "idx_inspections_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_inspector_date": { + "name": "idx_inspections_inspector_date", + "columns": [ + "inspector_id", + "date" + ], + "isUnique": false + }, + "idx_inspections_root": { + "name": "idx_inspections_root", + "columns": [ + "root_inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "inspections_tenant_id_tenants_id_fk": { + "name": "inspections_tenant_id_tenants_id_fk", + "tableFrom": "inspections", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_inspector_id_users_id_fk": { + "name": "inspections_inspector_id_users_id_fk", + "tableFrom": "inspections", + "tableTo": "users", + "columnsFrom": [ + "inspector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_template_id_templates_id_fk": { + "name": "inspections_template_id_templates_id_fk", + "tableFrom": "inspections", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_discount_code_id_discount_codes_id_fk": { + "name": "inspections_discount_code_id_discount_codes_id_fk", + "tableFrom": "inspections", + "tableTo": "discount_codes", + "columnsFrom": [ + "discount_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "inspections_request_id_inspection_requests_id_fk": { + "name": "inspections_request_id_inspection_requests_id_fk", + "tableFrom": "inspections", + "tableTo": "inspection_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "inspector_credentials": { + "name": "inspector_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_r2_key": { + "name": "image_r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_inspector_credentials_tenant": { + "name": "idx_inspector_credentials_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_inspector_credentials_user": { + "name": "idx_inspector_credentials_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_email": { + "name": "client_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_at": { + "name": "paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "partial_paid_at": { + "name": "partial_paid_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "voided_at": { + "name": "voided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qbo_sync_status": { + "name": "qbo_sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + } + }, + "indexes": { + "idx_invoices_tenant": { + "name": "idx_invoices_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_invoices_inspection": { + "name": "idx_invoices_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_invoices_contact": { + "name": "idx_invoices_contact", + "columns": [ + "tenant_id", + "contact_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invoices_tenant_id_tenants_id_fk": { + "name": "invoices_tenant_id_tenants_id_fk", + "tableFrom": "invoices", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_inspection_id_inspections_id_fk": { + "name": "invoices_inspection_id_inspections_id_fk", + "tableFrom": "invoices", + "tableTo": "inspections", + "columnsFrom": [ + "inspection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_contact_id_contacts_id_fk": { + "name": "invoices_contact_id_contacts_id_fk", + "tableFrom": "invoices", + "tableTo": "contacts", + "columnsFrom": [ + "contact_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_libraries": { + "name": "marketplace_libraries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_libraries_kind_featured": { + "name": "idx_marketplace_libraries_kind_featured", + "columns": [ + "kind", + "is_featured" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "marketplace_templates": { + "name": "marketplace_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "semver": { + "name": "semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "changelog": { + "name": "changelog", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_count": { + "name": "download_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_templates": { + "name": "message_templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "variables": { + "name": "variables", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seeded": { + "name": "is_seeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_templates_tenant_channel": { + "name": "idx_message_templates_tenant_channel", + "columns": [ + "tenant_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messaging_compliance": { + "name": "messaging_compliance", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'own'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subaccount_sid": { + "name": "subaccount_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_sid": { + "name": "customer_profile_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_profile_status": { + "name": "customer_profile_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_sid": { + "name": "brand_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "brand_status": { + "name": "brand_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_sid": { + "name": "campaign_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "campaign_status": { + "name": "campaign_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_sid": { + "name": "tfv_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tfv_status": { + "name": "tfv_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "messaging_resource_sid": { + "name": "messaging_resource_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_meta": { + "name": "provider_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number": { + "name": "provisioned_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provisioned_number_sid": { + "name": "provisioned_number_sid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_sender_attached": { + "name": "has_sender_attached", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "compliance_status": { + "name": "compliance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_preferences": { + "name": "notification_preferences", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "class_id": { + "name": "class_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notification_prefs_unique": { + "name": "idx_notification_prefs_unique", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "class_id", + "channel" + ], + "isUnique": true + }, + "idx_notification_prefs_subject": { + "name": "idx_notification_prefs_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "orphaned_media": { + "name": "orphaned_media", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_orphaned_media_key": { + "name": "idx_orphaned_media_key", + "columns": [ + "tenant_id", + "r2_key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_webhook_events": { + "name": "processed_webhook_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "psq_responses": { + "name": "psq_responses", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'sent'" + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sent_at": { + "name": "sent_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_psq_inspection": { + "name": "uq_psq_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": true + }, + "idx_psq_share_token": { + "name": "idx_psq_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_connections": { + "name": "qbo_connections", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "realm_id": { + "name": "realm_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_enc": { + "name": "access_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_enc": { + "name": "refresh_token_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "default_item_id": { + "name": "default_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_entity_map": { + "name": "qbo_entity_map", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_type": { + "name": "qbo_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_id": { + "name": "qbo_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "qbo_sync_token": { + "name": "qbo_sync_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_qbo_entity_map_qbo": { + "name": "idx_qbo_entity_map_qbo", + "columns": [ + "tenant_id", + "qbo_type", + "qbo_id" + ], + "isUnique": true + }, + "idx_qbo_entity_map_oi": { + "name": "idx_qbo_entity_map_oi", + "columns": [ + "tenant_id", + "oi_type", + "oi_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "qbo_sync_errors": { + "name": "qbo_sync_errors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_type": { + "name": "oi_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oi_id": { + "name": "oi_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_msg": { + "name": "error_msg", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_resolved": { + "name": "is_resolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rating_systems": { + "name": "rating_systems", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "levels": { + "name": "levels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_rating_systems_tenant_slug": { + "name": "idx_rating_systems_tenant_slug", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_rating_systems_tenant": { + "name": "idx_rating_systems_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rating_systems_tenant_id_tenants_id_fk": { + "name": "rating_systems_tenant_id_tenants_id_fk", + "tableFrom": "rating_systems", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_request_items": { + "name": "repair_request_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repair_request_id": { + "name": "repair_request_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finding_key": { + "name": "finding_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_title": { + "name": "section_title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "item_label": { + "name": "item_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment_snapshot": { + "name": "comment_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_credit_cents": { + "name": "requested_credit_cents", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "defect_title_snapshot": { + "name": "defect_title_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "location_snapshot": { + "name": "location_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_snapshot": { + "name": "category_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trade_snapshot": { + "name": "trade_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_request_items_rr": { + "name": "idx_repair_request_items_rr", + "columns": [ + "repair_request_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repair_requests": { + "name": "repair_requests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_ref": { + "name": "created_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_intro": { + "name": "custom_intro", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "share_token": { + "name": "share_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_repair_requests_inspection": { + "name": "idx_repair_requests_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "idx_repair_requests_share_token": { + "name": "idx_repair_requests_share_token", + "columns": [ + "share_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_exports": { + "name": "report_exports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_report_exports_inspection": { + "name": "idx_report_exports_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_pdfs": { + "name": "report_pdfs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rendered_at": { + "name": "rendered_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ready'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "uq_report_pdfs_inspection_type": { + "name": "uq_report_pdfs_inspection_type", + "columns": [ + "inspection_id", + "type", + "version_number" + ], + "isUnique": true + }, + "idx_report_pdfs_tenant": { + "name": "idx_report_pdfs_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_report_pdfs_status": { + "name": "idx_report_pdfs_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_report_pdfs_content_hash": { + "name": "idx_report_pdfs_content_hash", + "columns": [ + "inspection_id", + "type", + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": { + "report_pdfs_tenant_id_tenants_id_fk": { + "name": "report_pdfs_tenant_id_tenants_id_fk", + "tableFrom": "report_pdfs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_signoff": { + "name": "report_signoff", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "license": { + "name": "license", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "qualifications_ref": { + "name": "qualifications_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signed_at": { + "name": "signed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_ref": { + "name": "signature_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_dual_role": { + "name": "is_dual_role", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "idx_report_signoff_inspection": { + "name": "idx_report_signoff_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + }, + "uq_report_signoff_role": { + "name": "uq_report_signoff_role", + "columns": [ + "inspection_id", + "role" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "report_versions": { + "name": "report_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_json": { + "name": "snapshot_json", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prev_hash": { + "name": "prev_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signature": { + "name": "signature", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key_fingerprint": { + "name": "key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_amendment": { + "name": "is_amendment", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by": { + "name": "published_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch() * 1000)" + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_report_versions_report": { + "name": "idx_report_versions_report", + "columns": [ + "report_id", + "version_number" + ], + "isUnique": false + }, + "uq_report_versions_report_version": { + "name": "uq_report_versions_report_version", + "columns": [ + "report_id", + "version_number" + ], + "isUnique": true + }, + "idx_report_versions_verify_token": { + "name": "idx_report_versions_verify_token", + "columns": [ + "verification_token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "reports": { + "name": "reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_service_id": { + "name": "inspection_service_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'in_progress'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_reports_inspection": { + "name": "idx_reports_inspection", + "columns": [ + "inspection_id" + ], + "isUnique": false + }, + "idx_reports_tenant": { + "name": "idx_reports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_reports_primary": { + "name": "uq_reports_primary", + "columns": [ + "inspection_id" + ], + "isUnique": true, + "where": "kind = 'primary'" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "service_inspectors": { + "name": "service_inspectors", + "columns": { + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_service_inspectors_tenant": { + "name": "idx_service_inspectors_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "service_inspectors_service_id_user_id_pk": { + "columns": [ + "service_id", + "user_id" + ], + "name": "service_inspectors_service_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "services": { + "name": "services", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "price_cents": { + "name": "price_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "agreement_id": { + "name": "agreement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_event_type_slugs": { + "name": "default_event_type_slugs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_services_tenant": { + "name": "idx_services_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "services_tenant_id_tenants_id_fk": { + "name": "services_tenant_id_tenants_id_fk", + "tableFrom": "services", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_template_id_templates_id_fk": { + "name": "services_template_id_templates_id_fk", + "tableFrom": "services", + "tableTo": "templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "services_agreement_id_agreements_id_fk": { + "name": "services_agreement_id_agreements_id_fk", + "tableFrom": "services", + "tableTo": "agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "signing_keys": { + "name": "signing_keys", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_enc": { + "name": "private_key_enc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Ed25519'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_keys_tenant_id_tenants_id_fk": { + "name": "signing_keys_tenant_id_tenants_id_fk", + "tableFrom": "signing_keys", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_consent_log": { + "name": "sms_consent_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_type": { + "name": "recipient_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disclosure_version": { + "name": "disclosure_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "captured_via": { + "name": "captured_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_kind": { + "name": "subject_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'contact'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": { + "idx_sms_consent_contact": { + "name": "idx_sms_consent_contact", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + }, + "idx_sms_consent_subject": { + "name": "idx_sms_consent_subject", + "columns": [ + "tenant_id", + "subject_kind", + "subject_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_delivery_status": { + "name": "sms_delivery_status", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_sms_delivery_status_msg": { + "name": "idx_sms_delivery_status_msg", + "columns": [ + "tenant_id", + "provider_message_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sms_disclosure_versions": { + "name": "sms_disclosure_versions", + "columns": { + "version": { + "name": "version", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_seed": { + "name": "is_seed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tags_tenant_name": { + "name": "idx_tags_tenant_name", + "columns": [ + "tenant_id", + "name" + ], + "isUnique": true + }, + "idx_tags_tenant": { + "name": "idx_tags_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "templates": { + "name": "templates", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rating_system_id": { + "name": "rating_system_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commercial_subtype": { + "name": "commercial_subtype", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_featured": { + "name": "is_featured", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_templates_tenant": { + "name": "idx_templates_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_templates_rating_system": { + "name": "idx_templates_rating_system", + "columns": [ + "rating_system_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "templates_tenant_id_tenants_id_fk": { + "name": "templates_tenant_id_tenants_id_fk", + "tableFrom": "templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_custom_holidays": { + "name": "tenant_custom_holidays", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "uq_tenant_custom_holidays_tenant_date": { + "name": "uq_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": true + }, + "idx_tenant_custom_holidays_tenant_date": { + "name": "idx_tenant_custom_holidays_tenant_date", + "columns": [ + "tenant_id", + "date" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_library_imports": { + "name": "tenant_library_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "uq_tenant_library_import": { + "name": "uq_tenant_library_import", + "columns": [ + "tenant_id", + "library_id" + ], + "isUnique": true + }, + "idx_tenant_library_imports_tenant": { + "name": "idx_tenant_library_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_import_history": { + "name": "tenant_marketplace_import_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "library_id": { + "name": "library_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_version": { + "name": "target_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_affected": { + "name": "rows_affected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_marketplace_history_tenant": { + "name": "idx_marketplace_history_tenant", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_marketplace_history_template": { + "name": "idx_marketplace_history_template", + "columns": [ + "template_id" + ], + "isUnique": false + }, + "idx_marketplace_history_library": { + "name": "idx_marketplace_history_library", + "columns": [ + "library_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_marketplace_imports": { + "name": "tenant_marketplace_imports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "marketplace_template_id": { + "name": "marketplace_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_semver": { + "name": "imported_semver", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_template_id": { + "name": "local_template_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_mkt_imports_tmpl": { + "name": "idx_mkt_imports_tmpl", + "columns": [ + "marketplace_template_id" + ], + "isUnique": false + }, + "idx_mkt_imports_tenant": { + "name": "idx_mkt_imports_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk": { + "name": "tenant_marketplace_imports_marketplace_template_id_marketplace_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "marketplace_templates", + "columnsFrom": [ + "marketplace_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tenant_marketplace_imports_local_template_id_templates_id_fk": { + "name": "tenant_marketplace_imports_local_template_id_templates_id_fk", + "tableFrom": "tenant_marketplace_imports", + "tableTo": "templates", + "columnsFrom": [ + "local_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "usage_counters": { + "name": "usage_counters", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_usage_counters_tenant": { + "name": "idx_usage_counters_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_counters_tenant_id_metric_period_key_pk": { + "columns": [ + "tenant_id", + "metric", + "period_key" + ], + "name": "usage_counters_tenant_id_metric_period_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_templates": { + "name": "email_templates", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocks": { + "name": "blocks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_templates_tenant_id_tenants_id_fk": { + "name": "email_templates_tenant_id_tenants_id_fk", + "tableFrom": "email_templates", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_templates_tenant_id_trigger_pk": { + "columns": [ + "tenant_id", + "trigger" + ], + "name": "email_templates_tenant_id_trigger_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_configs": { + "name": "tenant_configs", + "columns": { + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "support_email": { + "name": "support_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_address": { + "name": "company_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pdf_footer_shown": { + "name": "is_pdf_footer_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_page_numbers_shown": { + "name": "is_pdf_page_numbers_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_pdf_license_shown": { + "name": "is_pdf_license_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reply_to": { + "name": "reply_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_mode": { + "name": "email_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "video_mode": { + "name": "video_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'r2'" + }, + "sms_mode": { + "name": "sms_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'platform'" + }, + "sender_display_name": { + "name": "sender_display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "point_of_contact": { + "name": "point_of_contact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company'" + }, + "billing_url": { + "name": "billing_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "review_url": { + "name": "review_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "company_phone": { + "name": "company_phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "integration_config": { + "name": "integration_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "secrets_enc": { + "name": "secrets_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dek_enc": { + "name": "dek_enc", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ics_token": { + "name": "ics_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widget_allowed_origins": { + "name": "widget_allowed_origins", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_profile_id": { + "name": "default_profile_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'signature'" + }, + "attention_thresholds": { + "name": "attention_thresholds", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"agreement_unsigned_h\":72,\"invoice_overdue_h\":72,\"report_unpublished_h\":72}'" + }, + "inspection_prefs": { + "name": "inspection_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_estimates_shown": { + "name": "is_estimates_shown", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_repair_list_enabled": { + "name": "is_repair_list_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_customer_repair_export_enabled": { + "name": "is_customer_repair_export_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unpaid_blocked": { + "name": "is_unpaid_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_unsigned_agreement_blocked": { + "name": "is_unsigned_agreement_blocked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "custom_referral_sources": { + "name": "custom_referral_sources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dashboard_column_prefs": { + "name": "dashboard_column_prefs", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_concierge_review_required": { + "name": "is_concierge_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_inspector_choice_allowed": { + "name": "is_inspector_choice_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_pdf_pipeline_enabled": { + "name": "is_pdf_pipeline_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_team_mode_default": { + "name": "is_team_mode_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_apprentice_review_required": { + "name": "is_apprentice_review_required", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_guest_invites_enabled": { + "name": "is_guest_invites_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "require_defect_fields": { + "name": "require_defect_fields", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "agreement_retention_years": { + "name": "agreement_retention_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "reinspection_statuses": { + "name": "reinspection_statuses", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_collab_editing_enabled": { + "name": "is_collab_editing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sms_byo_provider": { + "name": "sms_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email_byo_provider": { + "name": "email_byo_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'resend'" + }, + "is_managed_eligible": { + "name": "is_managed_eligible", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "managed_provider": { + "name": "managed_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'twilio'" + }, + "is_reserve_schedule_enabled": { + "name": "is_reserve_schedule_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reserve_term_years": { + "name": "reserve_term_years", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 12 + }, + "inflation_rate_bps": { + "name": "inflation_rate_bps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'UTC'" + }, + "booking_slot_mode": { + "name": "booking_slot_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "booking_slot_interval_min": { + "name": "booking_slot_interval_min", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 30 + }, + "holiday_region": { + "name": "holiday_region", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "holiday_public_policy": { + "name": "holiday_public_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "holiday_internal_policy": { + "name": "holiday_internal_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'advisory'" + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en-US'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'USD'" + }, + "is_archive_revoking_access": { + "name": "is_archive_revoking_access", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "legal_mode": { + "name": "legal_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'hosted'" + }, + "custom_privacy_url": { + "name": "custom_privacy_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_terms_url": { + "name": "custom_terms_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privacy_body": { + "name": "privacy_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_body": { + "name": "terms_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_configs_tenant_id_tenants_id_fk": { + "name": "tenant_configs_tenant_id_tenants_id_fk", + "tableFrom": "tenant_configs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenants": { + "name": "tenants", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "stripe_connect_account_id": { + "name": "stripe_connect_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "max_users": { + "name": "max_users", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 5 + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'shared'" + }, + "applied_cmd_seq": { + "name": "applied_cmd_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "applied_cred_seq": { + "name": "applied_cred_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_invites": { + "name": "tenant_invites", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'inspector'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_invites_tenant": { + "name": "idx_invites_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "uq_tenant_invites_pending_email": { + "name": "uq_tenant_invites_pending_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "status = 'pending'" + } + }, + "foreignKeys": { + "tenant_invites_tenant_id_tenants_id_fk": { + "name": "tenant_invites_tenant_id_tenants_id_fk", + "tableFrom": "tenant_invites", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_signature_base64": { + "name": "default_signature_base64", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_signature_enabled": { + "name": "is_signature_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'manager'" + }, + "onboarding_state": { + "name": "onboarding_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totp_secret": { + "name": "totp_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_totp_enabled": { + "name": "is_totp_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "totp_recovery_codes": { + "name": "totp_recovery_codes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "totp_verified_at": { + "name": "totp_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mentor_id": { + "name": "mentor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assigned_section_ids": { + "name": "assigned_section_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terms_accepted": { + "name": "terms_accepted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + "deleted_at" + ], + "isUnique": false + }, + "uq_users_tenant_email": { + "name": "uq_users_tenant_email", + "columns": [ + "tenant_id", + "email" + ], + "isUnique": true, + "where": "deleted_at IS NULL" + }, + "idx_users_tenant": { + "name": "idx_users_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_users_slug_per_tenant": { + "name": "idx_users_slug_per_tenant", + "columns": [ + "tenant_id", + "slug" + ], + "isUnique": true + }, + "idx_users_email": { + "name": "idx_users_email", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_logs": { + "name": "audit_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspector_slug": { + "name": "inspector_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_audit_tenant_created": { + "name": "idx_audit_tenant_created", + "columns": [ + "tenant_id", + "created_at" + ], + "isUnique": false + }, + "idx_audit_entity": { + "name": "idx_audit_entity", + "columns": [ + "entity_type", + "entity_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "audit_logs_tenant_id_tenants_id_fk": { + "name": "audit_logs_tenant_id_tenants_id_fk", + "tableFrom": "audit_logs", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration_test_results": { + "name": "integration_test_results", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_ok": { + "name": "is_ok", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_by_user_id": { + "name": "tested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tested_at": { + "name": "tested_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_integration_test_tenant_target": { + "name": "idx_integration_test_tenant_target", + "columns": [ + "tenant_id", + "target", + "tested_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "contact_id": { + "name": "contact_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_tenant_user_created": { + "name": "idx_notifications_tenant_user_created", + "columns": [ + "tenant_id", + "user_id", + "created_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_user_unread": { + "name": "idx_notifications_tenant_user_unread", + "columns": [ + "tenant_id", + "user_id", + "read_at" + ], + "isUnique": false + }, + "idx_notifications_tenant_contact_created": { + "name": "idx_notifications_tenant_contact_created", + "columns": [ + "tenant_id", + "contact_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_tenant_id_tenants_id_fk": { + "name": "notifications_tenant_id_tenants_id_fk", + "tableFrom": "notifications", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "parked_cmd_events": { + "name": "parked_cmd_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "envelope": { + "name": "envelope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_parked_cmd_events_received_at": { + "name": "idx_parked_cmd_events_received_at", + "columns": [ + "received_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "processed_cmd_events": { + "name": "processed_cmd_events", + "columns": { + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cmd_type": { + "name": "cmd_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slug_reservations": { + "name": "slug_reservations", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_outbox": { + "name": "sync_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_tried_at": { + "name": "last_tried_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_sync_outbox_status_created": { + "name": "idx_sync_outbox_status_created", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_destruction_records": { + "name": "tenant_destruction_records", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tenant_slug": { + "name": "tenant_slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rows_deleted": { + "name": "rows_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_objects": { + "name": "r2_objects", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "r2_bytes": { + "name": "r2_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "kv_keys": { + "name": "kv_keys", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_destruction_tenant": { + "name": "idx_destruction_tenant", + "columns": [ + "tenant_id" + ], + "isUnique": false + }, + "idx_destruction_destroyed_at": { + "name": "idx_destruction_destroyed_at", + "columns": [ + "destroyed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tenant_legal_versions": { + "name": "tenant_legal_versions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "doc": { + "name": "doc", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body_snapshot": { + "name": "body_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_material": { + "name": "is_material", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published_by_user_id": { + "name": "published_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_tenant_legal_versions_doc_version": { + "name": "idx_tenant_legal_versions_doc_version", + "columns": [ + "tenant_id", + "doc", + "version" + ], + "isUnique": true + }, + "idx_tenant_legal_versions_latest": { + "name": "idx_tenant_legal_versions_latest", + "columns": [ + "tenant_id", + "doc", + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "client_uploads": { + "name": "client_uploads", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inspection_id": { + "name": "inspection_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_kind": { + "name": "uploaded_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_ref": { + "name": "uploaded_by_ref", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "uploaded_by_name": { + "name": "uploaded_by_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_key": { + "name": "r2_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_client_uploads_inspection": { + "name": "idx_client_uploads_inspection", + "columns": [ + "tenant_id", + "inspection_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "uq_discount_codes_code_tenant": { + "columns": { + "upper(code)": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 44526c46a..2cc5f0e98 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -204,6 +204,13 @@ "when": 1785717479440, "tag": "0028_closed_robin_chapel", "breakpoints": true + }, + { + "idx": 29, + "version": "6", + "when": 1785721030453, + "tag": "0029_rich_leader", + "breakpoints": true } ] } \ No newline at end of file diff --git a/scripts/file-size-baseline.json b/scripts/file-size-baseline.json index 87b9660bf..f7226fd1c 100644 --- a/scripts/file-size-baseline.json +++ b/scripts/file-size-baseline.json @@ -10,7 +10,7 @@ "server/api/sms.ts": 843, "app/components/portal/sections/ReportView.tsx": 813, "app/routes/settings-communication.tsx": 777, - "server/services/inspection.service.ts": 757, + "server/services/inspection.service.ts": 767, "server/api/admin/admin-settings.ts": 742, "server/api/inspections/report-delivery.ts": 736, "app/routes/settings-communication-templates.tsx": 731, @@ -21,9 +21,9 @@ "app/hooks/usePhotoOps.ts": 661, "server/lib/messaging/providers/telnyx-compliance.ts": 657, "app/components/editor/ItemEditor.tsx": 637, + "server/services/inspection/inspection-publish.service.ts": 634, "app/components/inspection-edit/CompliancePanel.tsx": 623, "app/components/portal/sections/RepairBuilderSection.tsx": 616, - "server/services/inspection/inspection-publish.service.ts": 616, "app/hooks/useInspection.ts": 600, "server/services/marketplace.service.ts": 587, "server/services/concierge.service.ts": 586, diff --git a/server/api/inspections.ts b/server/api/inspections.ts index 421af98e1..b17dd11ca 100644 --- a/server/api/inspections.ts +++ b/server/api/inspections.ts @@ -26,6 +26,7 @@ import bulkRoutes from './inspections/bulk'; import mediaRoutes from './inspections/media'; import mediaStudioRoutes from './inspections/media-studio'; import publishRoutes from './inspections/publish'; +import reportGateRoutes from './inspections/report-gate'; import reportDeliveryRoutes from './inspections/report-delivery'; import sendSmsRoutes from './inspections/send-sms'; import agreementsRoutes from './inspections/agreements'; @@ -47,6 +48,7 @@ export const inspectionsRoutes = createApiRouter() .route('/', mediaRoutes) .route('/', mediaStudioRoutes) .route('/', publishRoutes) + .route('/', reportGateRoutes) .route('/', reportDeliveryRoutes) // Communication A3.4 — manual SMS via the shared sendOneSms TCPA core. .route('/', sendSmsRoutes) diff --git a/server/api/inspections/report-gate.ts b/server/api/inspections/report-gate.ts new file mode 100644 index 000000000..c38ea8fa2 --- /dev/null +++ b/server/api/inspections/report-gate.ts @@ -0,0 +1,93 @@ +/** + * The report ACCESS gate — releasing it, and putting it back. + * + * Separate from publish.ts on purpose. Publishing is about whether a document is + * finished; this is about whether the people waiting for it are allowed to see + * it. They answer different questions and change for different reasons. + */ +import { createRoute, z } from '@hono/zod-openapi'; +import { createApiRouter } from '../../lib/openapi-router'; +import { requireRole } from '../../lib/middleware/rbac'; +import { withMcpMetadata } from '../../lib/route-metadata-standards'; +import { createApiResponseSchema } from '../../lib/validations/shared.schema'; +import { auditFromContext } from '../../lib/audit'; +import { getTenantId } from '../../lib/route-helpers'; + +/** + * POST /api/inspections/:id/unlock-report + * + * Releases the order-wide report gate for one inspection. Owner/manager only — + * this hands a client a report the tenant's own rules said to hold, so it is not + * an inspector-level action. + */ +const unlockReportRoute = createRoute(withMcpMetadata({ + method: 'post', + path: '/{id}/unlock-report', + tags: ['inspections'], + summary: 'Unlock the report gate for this inspection', + middleware: [requireRole('owner', 'manager')] as const, + request: { + params: z.object({ id: z.string().describe('Inspection id') }), + body: { content: { 'application/json': { schema: z.object({ + // Required, and deliberately not defaulted. An override with no + // stated reason is indistinguishable later from a mistake. + reason: z.string().trim().min(1).max(500) + .describe('Why this report is being released before the agreement or payment gate is satisfied.'), + }) } } }, + }, + responses: { + 200: { + content: { 'application/json': { schema: createApiResponseSchema(z.object({ unlocked: z.boolean() })) } }, + description: 'Gate released', + }, + }, + operationId: 'unlockInspectionReport', + description: 'Releases the agreement/payment gate for every report on this inspection. The gate is order-wide, so this is the way to hand over a finished report that is held back by paperwork attached to something else on the same job. Records who unlocked it and why.', +}, { scopes: ['write'], tier: 'extended' })); + +/** + * POST /api/inspections/:id/relock-report + * Puts the gate back, clearing the original unlock record. + */ +const relockReportRoute = createRoute(withMcpMetadata({ + method: 'post', + path: '/{id}/relock-report', + tags: ['inspections'], + summary: 'Restore the report gate for this inspection', + middleware: [requireRole('owner', 'manager')] as const, + request: { params: z.object({ id: z.string().describe('Inspection id') }) }, + responses: { + 200: { + content: { 'application/json': { schema: createApiResponseSchema(z.object({ unlocked: z.boolean() })) } }, + description: 'Gate restored', + }, + }, + operationId: 'relockInspectionReport', + description: 'Restores the agreement/payment gate for this inspection and clears the recorded unlock reason.', +}, { scopes: ['write'], tier: 'extended' })); + +const reportGateRoutes = createApiRouter() + .openapi(unlockReportRoute, async (c) => { + const tenantId = getTenantId(c); + const { id } = c.req.valid('param'); + const { reason } = c.req.valid('json'); + const userId = c.get('user')?.sub ?? ''; + const { alreadyUnlocked } = await c.var.services.inspection.unlockReportGate(tenantId, id, userId, reason); + // Audited even when it was already unlocked: someone asked for this, and + // the attempt is part of the story of who wanted the gate open. + auditFromContext(c, 'inspection.report_unlocked', 'inspection', { + entityId: id, + metadata: { reason, alreadyUnlocked }, + }); + return c.json({ success: true as const, data: { unlocked: true } }, 200); + }) + .openapi(relockReportRoute, async (c) => { + const tenantId = getTenantId(c); + const { id } = c.req.valid('param'); + await c.var.services.inspection.relockReportGate(tenantId, id); + auditFromContext(c, 'inspection.report_relocked', 'inspection', { entityId: id }); + return c.json({ success: true as const, data: { unlocked: false } }, 200); + }) +; + +export default reportGateRoutes; diff --git a/server/lib/audit.ts b/server/lib/audit.ts index b5c729961..1a2ca038e 100644 --- a/server/lib/audit.ts +++ b/server/lib/audit.ts @@ -12,6 +12,11 @@ export type AuditAction = | 'inspection.status_changed' | 'inspection.complete' | 'inspection.send_pdf' + // The order-wide report gate released for one inspection, and put back. + // Audited because an unlock hands a client a report the tenant's own rules + // said to hold, and the reason for that is worth keeping. + | 'inspection.report_unlocked' + | 'inspection.report_relocked' | 'inspection.send_sms' | 'inspection.send_text_fallback' | 'inspection.bulk_assign' diff --git a/server/lib/db/schema/inspection/core.ts b/server/lib/db/schema/inspection/core.ts index d4e4c1d57..0d7749dff 100644 --- a/server/lib/db/schema/inspection/core.ts +++ b/server/lib/db/schema/inspection/core.ts @@ -201,6 +201,26 @@ export const inspections = sqliteTable('inspections', { // App-layer soft reference to contacts.id; no FK per Schema Rules. // Appended at table end for D1 rebuild safety. referredByContactId: text('referred_by_contact_id'), + // Manual release of the report gate for THIS inspection. + // + // The gate is order-wide by design: any required agreement left unsigned, or + // payment outstanding, blocks every report on the inspection. That rule is + // simple to explain and matches what a client thinks they bought — one job, + // one set of paperwork — but it means an add-on's unsigned addendum can hold + // back a report that is finished and that someone is waiting for. + // + // The release for that is a deliberate human action, not a finer-grained + // gate: an owner or manager opens this one inspection and says why. Making + // the gate itself per-report would put a service dimension on + // `agreement_requests`, which is signed evidence with a retention rule, to + // solve a problem a one-line override solves. + // + // NULL = still gated. The reason is required at the API, not by the column, + // so existing rows do not need one. + // Appended at table end for D1 rebuild safety. + unlockedAt: integer('unlocked_at', { mode: 'timestamp_ms' }), + unlockedBy: text('unlocked_by'), + unlockReason: text('unlock_reason'), }, (t) => [ index('idx_inspections_tenant').on(t.tenantId), index('idx_inspections_request').on(t.requestId), diff --git a/server/services/inspection.service.ts b/server/services/inspection.service.ts index 5994dccf0..7e2be2d06 100644 --- a/server/services/inspection.service.ts +++ b/server/services/inspection.service.ts @@ -670,6 +670,16 @@ export class InspectionService { return this.status.markPaymentReceived(tenantId, inspectionId); } + async unlockReportGate( + tenantId: string, inspectionId: string, userId: string, reason: string, + ): Promise<{ alreadyUnlocked: boolean }> { + return this.status.unlockReportGate(tenantId, inspectionId, userId, reason); + } + + async relockReportGate(tenantId: string, inspectionId: string): Promise { + return this.status.relockReportGate(tenantId, inspectionId); + } + /** * Spec 5B P2B — Compute defect category counts for a single inspection. * diff --git a/server/services/inspection/inspection-publish.service.ts b/server/services/inspection/inspection-publish.service.ts index 591ce7036..3ea916dc3 100644 --- a/server/services/inspection/inspection-publish.service.ts +++ b/server/services/inspection/inspection-publish.service.ts @@ -91,11 +91,29 @@ export class InspectionPublishService extends InspectionSubService { paymentRequired: inspections.paymentRequired, paymentStatus: inspections.paymentStatus, agreementRequired: inspections.agreementRequired, + unlockedAt: inspections.unlockedAt, }).from(inspections) .where(and(eq(inspections.id, inspectionId), eq(inspections.tenantId, tenantId))) .get(); if (!insp) return null; + // A manual unlock releases the gate for this whole inspection, which is + // the same scope the gate itself has. + // + // THE GATE IS ORDER-WIDE, DELIBERATELY. Any required agreement left + // unsigned, or payment outstanding, blocks EVERY report on this + // inspection — not just the report belonging to the service whose + // agreement is missing. One job, one set of paperwork, one rule a client + // and an inspector can both state without looking it up. + // + // The cost of that rule is real: an add-on's unsigned addendum can hold + // back a report that is finished and that someone is waiting for. The + // answer is this unlock — a named person opening one inspection and + // recording why — rather than resolving the gate per report, which would + // require a service dimension on `agreement_requests`, a signed-evidence + // table with a retention rule, to solve what one override solves. + if (insp.unlockedAt) return null; + // Resolve the outstanding gate. Agreement before payment (signed first). let reason: 'payment' | 'agreement' | null = null; let agreementToken: string | null = null; diff --git a/server/services/inspection/inspection-status.service.ts b/server/services/inspection/inspection-status.service.ts index 416b65c97..63219eb86 100644 --- a/server/services/inspection/inspection-status.service.ts +++ b/server/services/inspection/inspection-status.service.ts @@ -113,4 +113,48 @@ export class InspectionStatusService extends InspectionSubService { .set({ paymentStatus: 'paid' }) .where(and(eq(inspections.id, inspectionId), eq(inspections.tenantId, tenantId))); } + + /** + * Release the report gate for one inspection. + * + * The gate is order-wide: an unsigned agreement or an outstanding payment + * blocks every report on the inspection. This is the deliberate way out when + * a finished report is being held back by paperwork attached to something + * else on the same job. + * + * `reason` is required and stored. An override with no stated reason is + * indistinguishable later from a mistake, and this one is visible to a + * client — it hands over a report that the tenant's own rules said to hold. + * + * Idempotent: unlocking an already-unlocked inspection keeps the ORIGINAL + * timestamp, person and reason, so the record shows who actually made the + * call rather than whoever pressed it last. + */ + async unlockReportGate( + tenantId: string, inspectionId: string, userId: string, reason: string, + ): Promise<{ alreadyUnlocked: boolean }> { + const db = this.getDrizzle(); + const existing = await db.select({ unlockedAt: inspections.unlockedAt }) + .from(inspections) + .where(and(eq(inspections.id, inspectionId), eq(inspections.tenantId, tenantId))) + .get(); + if (!existing) throw Errors.NotFound('Inspection not found'); + if (existing.unlockedAt) return { alreadyUnlocked: true }; + + await db.update(inspections) + .set({ unlockedAt: new Date(), unlockedBy: userId, unlockReason: reason }) + .where(and(eq(inspections.id, inspectionId), eq(inspections.tenantId, tenantId))); + return { alreadyUnlocked: false }; + } + + /** + * Put the gate back. The reason for the original unlock is cleared with it — + * it described a decision that no longer stands. + */ + async relockReportGate(tenantId: string, inspectionId: string): Promise { + const db = this.getDrizzle(); + await db.update(inspections) + .set({ unlockedAt: null, unlockedBy: null, unlockReason: null }) + .where(and(eq(inspections.id, inspectionId), eq(inspections.tenantId, tenantId))); + } } diff --git a/tests/unit/inspections/report-gate-unlock.spec.ts b/tests/unit/inspections/report-gate-unlock.spec.ts new file mode 100644 index 000000000..2871ff2e6 --- /dev/null +++ b/tests/unit/inspections/report-gate-unlock.spec.ts @@ -0,0 +1,115 @@ +/** + * The report gate is ORDER-WIDE, and the way out is a manual unlock. + * + * Any required agreement left unsigned, or payment outstanding, blocks every + * report on the inspection — not just the one belonging to the service whose + * agreement is missing. That is one rule an inspector and a client can both + * state without looking it up, and it is what a client means by "my paperwork". + * + * Its cost is that an add-on's unsigned addendum can hold back a report that is + * finished and that someone is waiting for. The release is a named person + * opening one inspection and recording why — not a finer-grained gate, which + * would put a service dimension on `agreement_requests`, a signed-evidence table + * with a retention rule, to solve what one override solves. + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { eq } from 'drizzle-orm'; +import { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import * as schema from '../../../server/lib/db/schema'; +import { createTestDb, setupSchema } from '../db'; + +vi.mock('drizzle-orm/d1', () => ({ drizzle: vi.fn() })); +import { drizzle as mockDrizzle } from 'drizzle-orm/d1'; +import { InspectionStatusService } from '../../../server/services/inspection/inspection-status.service'; + +const TENANT = 'tenant-gate-1'; +const INSP = 'insp-gate-1'; +const OWNER = 'user-owner'; + +describe('report gate unlock', () => { + let db: BetterSQLite3Database; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sqlite: any; + let svc: InspectionStatusService; + + const row = () => db.select().from(schema.inspections) + .where(eq(schema.inspections.id, INSP)).get(); + + beforeEach(async () => { + const setup = createTestDb(); + db = setup.db; + sqlite = setup.sqlite; + await setupSchema(sqlite); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (mockDrizzle as any).mockReturnValue(db); + + await db.insert(schema.tenants).values({ + id: TENANT, name: 'T', slug: 't', createdAt: new Date(), + }); + await db.insert(schema.inspections).values({ + id: INSP, tenantId: TENANT, propertyAddress: '1 Main St', date: '2026-09-01', + status: 'completed', paymentStatus: 'unpaid', price: 45000, createdAt: new Date(), + agreementRequired: true, paymentRequired: true, + } as never); + + svc = new InspectionStatusService({} as never); + }); + + afterEach(() => { sqlite.close(); }); + + it('starts locked — nothing is unlocked by default', async () => { + // A tenant arriving from another platform expects reports to be held + // until signed and paid. Defaulting open would quietly change that. + expect((await row())?.unlockedAt).toBeNull(); + }); + + it('records who unlocked it and why', async () => { + await svc.unlockReportGate(TENANT, INSP, OWNER, 'Client is at closing; radon addendum still out for signature.'); + + const after = await row(); + expect(after?.unlockedAt).toBeTruthy(); + expect(after?.unlockedBy).toBe(OWNER); + expect(after?.unlockReason).toContain('radon addendum'); + }); + + it('keeps the ORIGINAL record when unlocked twice', async () => { + // The record has to show who actually made the call, not whoever pressed + // the button last. + await svc.unlockReportGate(TENANT, INSP, OWNER, 'first reason'); + const first = await row(); + + const second = await svc.unlockReportGate(TENANT, INSP, 'someone-else', 'second reason'); + + expect(second.alreadyUnlocked).toBe(true); + const after = await row(); + expect(after?.unlockedBy).toBe(OWNER); + expect(after?.unlockReason).toBe('first reason'); + expect(after?.unlockedAt).toEqual(first?.unlockedAt); + }); + + it('relocking clears the reason with it', async () => { + // The reason described a decision that no longer stands. + await svc.unlockReportGate(TENANT, INSP, OWNER, 'closing today'); + await svc.relockReportGate(TENANT, INSP); + + const after = await row(); + expect(after?.unlockedAt).toBeNull(); + expect(after?.unlockedBy).toBeNull(); + expect(after?.unlockReason).toBeNull(); + }); + + it('refuses to unlock an inspection in another tenant', async () => { + await expect(svc.unlockReportGate('other-tenant', INSP, OWNER, 'nope')) + .rejects.toThrow(/not found/i); + expect((await row())?.unlockedAt).toBeNull(); + }); + + it('can be unlocked again after a relock', async () => { + await svc.unlockReportGate(TENANT, INSP, OWNER, 'first'); + await svc.relockReportGate(TENANT, INSP); + const again = await svc.unlockReportGate(TENANT, INSP, 'user-two', 'second'); + + expect(again.alreadyUnlocked).toBe(false); + expect((await row())?.unlockReason).toBe('second'); + }); +}); diff --git a/tests/unit/reports/pca-foundation-schema.spec.ts b/tests/unit/reports/pca-foundation-schema.spec.ts index e150a0f31..4bbab95d5 100644 --- a/tests/unit/reports/pca-foundation-schema.spec.ts +++ b/tests/unit/reports/pca-foundation-schema.spec.ts @@ -21,10 +21,11 @@ describe('Commercial PCA Phase F foundation columns', () => { // appended report_photo_mode; A-polish 9b then appended the scheduled instant // trio; Report Style Presets (Plan 1a) then appended the badge_layout_override // + report_photo_columns tweak pair; the two-layer role model then appended - // referred_by_contact_id. All appended at the tail (never mid-list) so - // db:generate emits ALTER ADD COLUMN, not a rebuild. + // referred_by_contact_id; the report-gate unlock then appended its trio. + // All appended at the tail (never mid-list) so db:generate emits + // ALTER ADD COLUMN, not a rebuild. const names = getTableConfig(inspections).columns.map((c) => c.name); - const tail = names.slice(-12); + const tail = names.slice(-15); expect(tail).toEqual([ 'unit_inspection_mode', 'location_options', @@ -38,6 +39,9 @@ describe('Commercial PCA Phase F foundation columns', () => { 'badge_layout_override', 'report_photo_columns', 'referred_by_contact_id', + 'unlocked_at', + 'unlocked_by', + 'unlock_reason', ]); }); From a2ddd943c02c8356abcf4b13ec3ab3961886627e Mon Sep 17 00:00:00 2001 From: important-new Date: Mon, 3 Aug 2026 11:19:02 +0800 Subject: [PATCH 18/29] feat(hub): unlock control for the order-wide report gate, and settings copy that states the rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two surfaces for one rule. SETTINGS. The gate's description only mentioned booking — clients must sign before the booking is confirmed. It also holds every report, which is the rule this branch committed to, so an operator would have met it the hard way. The scope note appears only when the setting is on and says plainly that an unsigned agreement holds back EVERY report on that inspection, not only the report for the service the agreement covers, and how to release it. THE CONTROL. Deliberately not a GateToggle. That component's own comment states this hub's law — "a switch, not a button… nothing to confirm and nothing to lose by flipping it back" — and it owns ONE gate on the card for the artifact it gates. This owns BOTH gates, spans the order, and hands a client a report the tenant's rules said to hold. Three failures of the switch test, so: a quiet text control opening a modal, with the reason required to enable the confirm. It lives on the REPORT card, by the same rule GateToggle follows — the card for the artifact it gates — because what it gates is the reports. The weight is on the STATE, not the action. Unlocked, it stops being a control and becomes a standing record: who released it, when, and their reason QUOTED rather than paraphrased, so it reads as somebody's words. Requiring a reason is pointless if nobody reads it back, and that is the one place any visual weight is spent. Locked, it is a line of grey text that does not compete with Publish. The hub resolves the unlocker to a NAME server-side rather than shipping an opaque id; a deleted teammate degrades to "a teammate" so it never renders "Released by on …". Admin-only in the UI as well as at the route. Chrome, both themes: modal opens, confirm stays disabled until a reason is typed, the round trip lands and the control becomes the record, and "Put the gate back" returns it to the action. Amber on light is #b45309 on #f8fafc (~6.5:1). One thing the browser caught that no test could: the control rendered as permanently locked because the LOCAL database was a migration behind, so the hub query selected a column that did not exist and the loader swallowed the failure. Same silent-empty shape as the services catalogue earlier in this branch — a hub loader failing quietly is indistinguishable from a feature that does not work. --- .../inspector-portal/ReportGateUnlock.tsx | 128 ++++++++++++++++++ .../report-gate-unlock.test.tsx | 123 +++++++++++++++++ .../settings/BookingPoliciesPanel.tsx | 8 ++ app/lib/inspection-order-actions.ts | 35 +++++ app/routes/inspector-portal.tsx | 22 +++ messages/en/settings-components.json | 14 ++ scripts/file-size-baseline.json | 4 +- server/lib/validations/inspection/read.ts | 3 + .../inspection/inspection-publish.service.ts | 21 +++ 9 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 app/components/inspector-portal/ReportGateUnlock.tsx create mode 100644 app/components/inspector-portal/report-gate-unlock.test.tsx diff --git a/app/components/inspector-portal/ReportGateUnlock.tsx b/app/components/inspector-portal/ReportGateUnlock.tsx new file mode 100644 index 000000000..1035b925b --- /dev/null +++ b/app/components/inspector-portal/ReportGateUnlock.tsx @@ -0,0 +1,128 @@ +import { useState } from "react"; +import { useFetcher } from "react-router"; +import { Modal, Button } from "@core/shared-ui"; +import { m } from "~/paraglide/messages"; + +/** + * Releasing the order-wide report gate for this inspection. + * + * Deliberately NOT a `GateToggle`. That component's rule is "a switch, not a + * button… nothing to confirm and nothing to lose by flipping it back", and it + * owns ONE gate on the card for the artifact it gates. This owns BOTH gates for + * the whole order, it hands a client a report the tenant's own rules said to + * hold, and it demands a reason. All three of those fail the switch test. + * + * The weight is on the STATE, not the action. Unlocked, this reads as a standing + * record — who released it, when, and why — because requiring a reason is + * pointless if nobody ever reads it back. Locked, it is a quiet piece of text + * that does not compete with the card's real actions; nobody comes to this + * screen looking to override anything. + */ +export function ReportGateUnlock({ + unlockedAt, + unlockedByName, + unlockReason, + formatDate, +}: { + unlockedAt: string | null; + unlockedByName: string | null; + unlockReason: string | null; + /** Hub-supplied formatter so the date reads in the tenant's timezone. */ + formatDate: (iso: string) => string; +}) { + const fetcher = useFetcher(); + const [open, setOpen] = useState(false); + const [reason, setReason] = useState(""); + const busy = fetcher.state !== "idle"; + + function submit() { + fetcher.submit( + { intent: "unlock-report", reason: reason.trim() }, + { method: "post" }, + ); + setOpen(false); + setReason(""); + } + + if (unlockedAt) { + return ( +
+

+ {m.hub_gate_unlocked_heading()} +

+

+ {m.hub_gate_unlocked_by({ + name: unlockedByName ?? m.hub_gate_unlocked_unknown_person(), + date: formatDate(unlockedAt), + })} +

+ {/* The reason is the whole point of asking for one. It is quoted + rather than paraphrased so it reads as somebody's words. */} + {unlockReason && ( +

“{unlockReason}”

+ )} + +
+ ); + } + + return ( +
+ + + setOpen(false)} + title={m.hub_gate_unlock_title()} + size="sm" + footer={ + <> + + + + } + > +

{m.hub_gate_unlock_body()}

+