From ca8d967aaed714484f3c9e299d06261a685e9d4e Mon Sep 17 00:00:00 2001 From: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> Date: Thu, 12 Nov 2026 15:44:35 +0200 Subject: [PATCH 1/4] i18n(web): Counts are off, in every catalogue Two strings for a place with Counts turned off everywhere (#506): the Counts list and Start a session say so, and who can turn them back on. Signed-off-by: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> --- apps/web/src/locales/ar/messages.po | 10 ++++++++++ apps/web/src/locales/de/messages.po | 10 ++++++++++ apps/web/src/locales/en/messages.po | 10 ++++++++++ apps/web/src/locales/es/messages.po | 10 ++++++++++ apps/web/src/locales/fr/messages.po | 10 ++++++++++ apps/web/src/locales/it/messages.po | 10 ++++++++++ apps/web/src/locales/uk/messages.po | 10 ++++++++++ apps/web/src/locales/vi/messages.po | 10 ++++++++++ 8 files changed, 80 insertions(+) diff --git a/apps/web/src/locales/ar/messages.po b/apps/web/src/locales/ar/messages.po index f504028e..442b90e9 100644 --- a/apps/web/src/locales/ar/messages.po +++ b/apps/web/src/locales/ar/messages.po @@ -29306,3 +29306,13 @@ msgstr "جهاز تنقية مياه" msgctxt "how many shelves" msgid "Shelves" msgstr "عدد الرفوف" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "العدّ متوقف" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "يفعّله المالك أو المشرف من «ما الذي تتابعه» في الموقع." diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 78d3cfe7..0b6f57c3 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -29306,3 +29306,13 @@ msgstr "Wasserfilteranlage" msgctxt "how many shelves" msgid "Shelves" msgstr "Regalböden" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Zählungen sind aus" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Ein Eigentümer oder Admin schaltet sie unter „Was erfasst wird“ eines Standorts ein." diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index 72cdea57..857c6499 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -29306,3 +29306,13 @@ msgstr "Water purifier" msgctxt "how many shelves" msgid "Shelves" msgstr "Shelves" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Counts are off" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "An owner or admin turns them on in a location's What to track." diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index 855e7ce4..fe016ba5 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -29306,3 +29306,13 @@ msgstr "Purificador de agua" msgctxt "how many shelves" msgid "Shelves" msgstr "Estantes" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Los conteos están desactivados" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Un propietario o administrador los activa en «Qué seguir» de una ubicación." diff --git a/apps/web/src/locales/fr/messages.po b/apps/web/src/locales/fr/messages.po index 2c8e2211..9f656ecd 100644 --- a/apps/web/src/locales/fr/messages.po +++ b/apps/web/src/locales/fr/messages.po @@ -29306,3 +29306,13 @@ msgstr "Purificateur d’eau" msgctxt "how many shelves" msgid "Shelves" msgstr "Tablettes" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Les comptages sont désactivés" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Un propriétaire ou un administrateur les active dans « Ce que vous suivez » d'un lieu." diff --git a/apps/web/src/locales/it/messages.po b/apps/web/src/locales/it/messages.po index dbf94a86..f6789aa4 100644 --- a/apps/web/src/locales/it/messages.po +++ b/apps/web/src/locales/it/messages.po @@ -29306,3 +29306,13 @@ msgstr "Depuratore d’acqua" msgctxt "how many shelves" msgid "Shelves" msgstr "Ripiani" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "I conteggi sono disattivati" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Un proprietario o un amministratore li attiva in «Cosa monitorare» di un luogo." diff --git a/apps/web/src/locales/uk/messages.po b/apps/web/src/locales/uk/messages.po index f932916d..afb98391 100644 --- a/apps/web/src/locales/uk/messages.po +++ b/apps/web/src/locales/uk/messages.po @@ -31289,3 +31289,13 @@ msgstr "Очищувач води" msgctxt "how many shelves" msgid "Shelves" msgstr "Полиці" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Підрахунки вимкнено" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Власник або адміністратор вмикає їх у розділі «Що відстежувати» локації." diff --git a/apps/web/src/locales/vi/messages.po b/apps/web/src/locales/vi/messages.po index b482ffa7..c6555177 100644 --- a/apps/web/src/locales/vi/messages.po +++ b/apps/web/src/locales/vi/messages.po @@ -21354,3 +21354,13 @@ msgstr "Máy lọc nước" msgctxt "how many shelves" msgid "Shelves" msgstr "Số tầng kệ" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "Counts are off" +msgstr "Kiểm kê đang tắt" + +#: src/components/counts/counts-list.tsx +#: src/components/scan/session/session-screen.tsx +msgid "An owner or admin turns them on in a location's What to track." +msgstr "Chủ sở hữu hoặc quản trị viên bật nó trong mục Theo dõi gì của một địa điểm." From c5475569ec1dd7d360d7baad7e99eeb2294559f8 Mon Sep 17 00:00:00 2001 From: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> Date: Thu, 12 Nov 2026 20:01:14 +0200 Subject: [PATCH 2/4] feat(counts): a location can turn Counts off Counts is now a module, like Labels and Lending, for a location that doesn't count. It is in every preset, so every location keeps its counts until an owner or admin turns it off in What to track; nothing is deleted. Off in a location: - the place page has no Count here, Count regularly or Past counts; boxes no Count this box; the map's room card no Count this room; Start a session no count there; the location page no Counts link; Tools hides Counts once it is off everywhere, and the Counts list says so; - the counts' reads are 404 module_off and writes 409 (the list, a count's page, its CSV, its lines, the open-count lookup, starting a count); a count saved on a phone meanwhile is dropped; a planned count can't be made or changed, and leaves the agenda, so it reminds no one; - packs, moves and box checks are unchanged. Migration 0177 is additive: kept.module_enabled() gains counts in every preset and the agenda view filters planned counts by it. Docs: Modules and presets, Counts and stocktakes, features, README. Fixes #506 Signed-off-by: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> --- README.md | 5 +- apps/docs/src/content/docs/ar/users/counts.md | 4 + .../src/content/docs/ar/users/features.md | 2 +- .../docs/src/content/docs/ar/users/modules.md | 3 +- apps/docs/src/content/docs/de/users/counts.md | 10 + .../src/content/docs/de/users/features.md | 2 +- .../docs/src/content/docs/de/users/modules.md | 3 +- apps/docs/src/content/docs/es/users/counts.md | 10 + .../src/content/docs/es/users/features.md | 2 +- .../docs/src/content/docs/es/users/modules.md | 5 +- apps/docs/src/content/docs/fr/users/counts.md | 10 + .../src/content/docs/fr/users/features.md | 2 +- .../docs/src/content/docs/fr/users/modules.md | 3 +- apps/docs/src/content/docs/it/users/counts.md | 10 + .../src/content/docs/it/users/features.md | 2 +- .../docs/src/content/docs/it/users/modules.md | 3 +- apps/docs/src/content/docs/uk/users/counts.md | 10 + .../src/content/docs/uk/users/features.md | 2 +- .../docs/src/content/docs/uk/users/modules.md | 3 +- apps/docs/src/content/docs/users/counts.md | 9 + apps/docs/src/content/docs/users/features.md | 2 +- apps/docs/src/content/docs/users/modules.md | 5 +- apps/docs/src/content/docs/vi/users/counts.md | 4 + .../src/content/docs/vi/users/features.md | 2 +- .../docs/src/content/docs/vi/users/modules.md | 3 +- apps/server/migrations/0177_counts_module.sql | 173 + .../server/migrations/meta/0177_snapshot.json | 24385 ++++++++++++++++ apps/server/migrations/meta/_journal.json | 7 + apps/server/src/locations/locations.test.ts | 11 +- apps/server/src/schedules/service.ts | 7 +- .../server/src/sessions/counts-module.test.ts | 228 + apps/server/src/sessions/counts.ts | 34 +- apps/server/src/sessions/service.ts | 5 + apps/web/src/api/inventory/mock/fixtures.ts | 11 +- apps/web/src/api/mock/fixtures.ts | 14 +- apps/web/src/components/app-shell.tsx | 9 +- .../web/src/components/counts/counts-list.tsx | 12 +- apps/web/src/components/hints/tours.test.ts | 3 + apps/web/src/components/hints/tours.ts | 2 +- .../scan/session/session-screen.test.tsx | 40 +- .../scan/session/session-screen.tsx | 55 +- .../web/src/components/things/action-menu.tsx | 2 +- apps/web/src/lib/labels.ts | 1 + apps/web/src/map/card.tsx | 10 +- apps/web/src/map/map-view.tsx | 3 + apps/web/src/routes/_app/loc.$id.tsx | 21 +- apps/web/src/routes/_app/p.$id.tsx | 61 +- .../src/test/screens/counts-module.test.tsx | 87 + packages/shared/src/modules.test.ts | 11 +- packages/shared/src/modules.ts | 3 + 50 files changed, 25219 insertions(+), 92 deletions(-) create mode 100644 apps/server/migrations/0177_counts_module.sql create mode 100644 apps/server/migrations/meta/0177_snapshot.json create mode 100644 apps/server/src/sessions/counts-module.test.ts create mode 100644 apps/web/src/test/screens/counts-module.test.tsx diff --git a/README.md b/README.md index f4e69aea..0ef7831e 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,9 @@ Every feature, with its page: [all features](https://keptinventory.info/users/fe if you have one; its places and things as a graph ([the map](https://keptinventory.info/users/location-map/)). - **Labels and scanning.** Print label sheets with a short ID and a QR code; scan one to open the - box it is on. Scan sessions count a shelf, pack a box or move a pile in one go. If the scanner - doesn't start, Try again starts it afresh; on a phone, sheets stay above the keyboard. + box it is on. Scan sessions count a shelf, pack a box or move a pile in one go; counts can be + turned off where nobody counts. If the scanner doesn't start, Try again starts it afresh; on a + phone, sheets stay above the keyboard. - **Lending and borrowing.** Who has what and when it's due back; borrowers need no account. Reminders before and after the due date, one list of everything out, and each thing's trail of who had it and how it came back ([lending](https://keptinventory.info/users/lending/)). diff --git a/apps/docs/src/content/docs/ar/users/counts.md b/apps/docs/src/content/docs/ar/users/counts.md index e3f3ce9c..9a288eab 100644 --- a/apps/docs/src/content/docs/ar/users/counts.md +++ b/apps/docs/src/content/docs/ar/users/counts.md @@ -64,3 +64,7 @@ description: "يُحفظ كل عدّ وفحص للصناديق، مع ما وُ | رؤية عمليات العدّ، صفحة عدّ، مقارنة، تصدير CSV | ✓ | ✓ | | العدّ، الانضمام لعدّ | ✓ | فحص: لا شيء يُحفظ، لا يُسرد | | تخطيط عدّ | ✓، مع الجداول مفعلة | — | + +## إيقاف العدّ + +العدّ وحدة مفعّلة في كل قالب جاهز. يوقفها المالك أو المشرف من **إعدادات الموقع → ما الذي تتابعه** (**عمليات العدّ**). حين تتوقف في موقع، لا تظهر في أماكنه **عُدّ هنا** ولا **عُدّ بانتظام** ولا **عمليات العدّ السابقة**، ولا **عُدّ هذا الصندوق** في صناديقه، ولا **عُدّ هذه الغرفة** في غرفه على الخريطة، ولا يعرض **ابدأ جلسة** العدّ فيه. وتختفي **عمليات العدّ** من الأدوات متى توقفت في كل مواقعك. وتتوقف تذكيرات العدّ المخطط له. أما **فحص الصندوق** فيبقى. لا يُحذف شيء: أعِد تفعيلها تجد كل عدّ في مكانه. diff --git a/apps/docs/src/content/docs/ar/users/features.md b/apps/docs/src/content/docs/ar/users/features.md index 6d4bbc15..aecd0ae1 100644 --- a/apps/docs/src/content/docs/ar/users/features.md +++ b/apps/docs/src/content/docs/ar/users/features.md @@ -33,7 +33,7 @@ description: "كل ما يفعله Kept، في قائمة واحدة: الجرد - **[الرسم البياني](/ar/users/graph/)**: أماكن موقع، صناديق وأشياء في ٢D أو ٣D. - **[المعرِّفات القصيرة والملصقات](/ar/users/short-ids/)**: معرِّف قصير ورمز QR على كل صندوق؛ امسح واحدًا لفتحه. ألواح طابعة ملصقات أو أوراق مكتبية. الوحدة: التصنيفات ورموز QR. - **[جلسات المسح](/ar/users/scanning-sessions/)**: احصِ رفًّا، احزم صندوقًا، حرِّك كومة، مسحٌ بعد مسح. -- **[الإحصائيات والجرد الفعلي](/ar/users/counts/)**: احصِ مكانًا، رَ ما ينقص، قارن بالجرد الأخير. +- **[الإحصائيات والجرد الفعلي](/ar/users/counts/)**: احصِ مكانًا، رَ ما ينقص، قارن بالجرد الأخير. والموقع الذي لا يُجري عدًّا يوقفها. ## الأسرة diff --git a/apps/docs/src/content/docs/ar/users/modules.md b/apps/docs/src/content/docs/ar/users/modules.md index 44f1c19b..e666fde5 100644 --- a/apps/docs/src/content/docs/ar/users/modules.md +++ b/apps/docs/src/content/docs/ar/users/modules.md @@ -28,6 +28,7 @@ description: "ما الذي يتتبعه كل موقع: الأساسيات، و | الوحدة | من | ما توفره | |---|---|---| | **الملصقات ورموز QR** | الأساسيات | ملصقات قابلة للطباعة للأشياء والأماكن؛ مسح الملصق يفتح ما هو عليه. | +| **عمليات العدّ** | الأساسيات | **عُدّ هنا** و**عُدّ بانتظام** و**عمليات العدّ السابقة** في صفحة المكان، و**عُدّ هذا الصندوق**، و**عمليات العدّ** تحت الأدوات: كل عدّ وكل فحص صندوق محفوظ. | | **المال والإنفاق** | المنزلية | الأسعار، الإجماليات و**الإحصاءات**. كل عملة على سطرها الخاص، لا يتم تحويلها أبدًا. | | **الإيصالات والضمانات** | المنزلية | تواريخ الضمانات؛ الضمان الذي أوشك على الانتهاء يظهر كتذكير. | | **التذكيرات** | المنزلية | الجداول: ما هو مستحق، بالتاريخ أو بالقراءة. | @@ -60,7 +61,7 @@ description: "ما الذي يتتبعه كل موقع: الأساسيات، و - **الوحدات التابعة تذهب أيضًا.** الوقود يحتاج السيارات، والانتقال إلى منزل جديد يحتاج الملصقات ورموز QR: إيقاف تشغيل ما تحتاجه وحدة يؤدي إلى إيقاف تشغيل تلك الوحدة أيضًا. مفتاحها بعد ذلك يقرأ *يحتاج السيارات، متوقفة في هذا الموقع*. - **القائمة تتبع كل مواقعك.** تحت **المتابعة**، يظهر مدخل (الجداول، الإعارة، - الأوراق، المستهلكات، السيارات، الانتقال، الإحصاءات) بمجرد تفعيل وحدته في أي من مواقعك. تحت **الأدوات**، **الملصقات** تظهر بينما الملصقات مفعلة في مكان ما، و**عمليات العدّ** موجودة دائمًا. النشاط، سلة المهملات، والإشعارات موجودة دائمًا أيضًا. + الأوراق، المستهلكات، السيارات، الانتقال، الإحصاءات) بمجرد تفعيل وحدته في أي من مواقعك. تحت **الأدوات**، **الملصقات** تظهر بينما الملصقات مفعلة في مكان ما، و**عمليات العدّ** بينما العدّ مفعّل في مكان ما. النشاط، سلة المهملات، والإشعارات موجودة دائمًا أيضًا. ## تغييره لاحقًا diff --git a/apps/docs/src/content/docs/de/users/counts.md b/apps/docs/src/content/docs/de/users/counts.md index b48a9b92..81ded500 100644 --- a/apps/docs/src/content/docs/de/users/counts.md +++ b/apps/docs/src/content/docs/de/users/counts.md @@ -88,3 +88,13 @@ einen ganzen Standort regelmäßig zu zählen, plane es auf seinem obersten Ort. | Zählungen sehen, eine Zählungsseite, vergleichen, Als CSV exportieren | ✓ | ✓ | | Zählen, einer Zählung beitreten | ✓ | Eine Prüfung: nichts gespeichert, nicht gelistet | | Eine Zählung planen | ✓, mit Zeitplänen an | — | + +## Zählungen ausschalten + +Zählungen sind ein Modul, in jeder Voreinstellung eingeschaltet. Ein Eigentümer oder Admin schaltet sie +unter **Standorteinstellungen → Was erfasst wird** (**Zählungen**) aus. Sind sie an einem Standort +aus, haben seine Orte kein **Hier zählen**, **Regelmäßig zählen** oder **Frühere Zählungen**, seine +Kisten kein **Diese Kiste zählen**, seine Räume auf der Karte kein **Diesen Raum zählen**, und +**Sitzung starten** bietet dort kein Zählen an. **Zählungen** unter Werkzeuge verschwindet, sobald +sie an allen deinen Standorten aus sind. Geplante Zählungen erinnern nicht mehr. Der +**Kistencheck** bleibt. Nichts wird gelöscht: wieder eingeschaltet, ist jede Zählung wieder da. diff --git a/apps/docs/src/content/docs/de/users/features.md b/apps/docs/src/content/docs/de/users/features.md index 68135da1..ee9cbc85 100644 --- a/apps/docs/src/content/docs/de/users/features.md +++ b/apps/docs/src/content/docs/de/users/features.md @@ -42,7 +42,7 @@ ein Standort schaltet sie ein unter **Standorteinstellungen → Was erfasst wird sie zu öffnen. Etikettenrollen oder Bürobögen. Modul: Etiketten und QR-Codes. - **[Scansitzungen](/de/users/scanning-sessions/)**: ein Regal zählen, eine Box packen, einen Stapel verschieben, ein Scan nach dem anderen. -- **[Zählungen und Inventuren](/de/users/counts/)**: einen Ort zählen, sehen was fehlt, mit der letzten Zählung vergleichen. +- **[Zählungen und Inventuren](/de/users/counts/)**: einen Ort zählen, sehen was fehlt, mit der letzten Zählung vergleichen. Ein Standort, der nicht zählt, schaltet sie aus. ## Haushalt diff --git a/apps/docs/src/content/docs/de/users/modules.md b/apps/docs/src/content/docs/de/users/modules.md index dd53db15..8a5d852d 100644 --- a/apps/docs/src/content/docs/de/users/modules.md +++ b/apps/docs/src/content/docs/de/users/modules.md @@ -23,6 +23,7 @@ Die folgenden Namen sind die Schalter unter **Was erfasst wird**. | Module | Von | Was es bringt | |---|---|---| | **Etiketten und QR-Codes** | Essentials | Druckbare Etiketten für Dinge und Orte; Scannen einer Etikette öffnet, worauf sie angebracht ist. | +| **Zählungen** | Essentials | **Hier zählen**, **Regelmäßig zählen** und **Frühere Zählungen** auf einem Ort, **Diese Kiste zählen** und **Zählungen** unter Werkzeuge: jede Zählung und jeder Kistencheck, aufbewahrt. | | **Geld und Ausgaben** | Household | Preise, Gesamtbeträge und **Einblicke**. Jede Währung auf ihrer eigenen Linie, nie umgerechnet. | | **Belege und Garantien** | Household | Garantiedaten; eine ablaufende Garantie erscheint als Erinnerung. | | **Erinnerungen** | Household | Zeitpläne: was fällig ist, nach Datum oder nach Messwert. | @@ -45,7 +46,7 @@ Ein ausgeschaltetes Modul wird überall ausgeblendet, wo es erscheinen würde (s - **Erinnerungen pausieren mit ihrem Modul.** Zeitpläne und ablaufende Dinge warten mit **Erinnerungen**, Garantie- und Registrierungsdaten mit **Belegen und Garantien**, Dokumentdaten mit **Unterlagen** (die Dokumente eines Fahrzeugs laufen auch weiter, während **Fahrzeuge** eingeschaltet ist), Ausleihen mit **Verleihen**, niedriger Bestand mit **Dingen die zur Neige gehen**. Erinnerungen über längst nicht gelesene Zähler laufen ständig weiter. - **Abhängigkeiten gehen ebenfalls mit.** Tanken benötigt Fahrzeuge, und Umzug benötigt Etiketten und QR-Codes: das Ausschalten des benötigten Moduls schaltet auch dieses Modul aus. Sein Schalter zeigt dann *Benötigt Fahrzeuge, aus in diesem Standort*. -- **Das Menü folgt allen deinen Standorten.** Unter **Nachverfolgung** erscheint ein Eintrag (Zeitpläne, Verleih, Unterlagen, Verbrauchsgüter, Fahrzeuge, Umzug, Einblicke) einmal, wenn sein Modul in einem deiner Standorte eingeschaltet ist. Unter **Werkzeuge** erscheint **Etiketten**, solange Etikett irgendwo eingeschaltet ist, und **Zählungen** ist immer dort vorhanden. Aktivität, Papierkorb und Benachrichtigungen sind ebenfalls immer vorhanden. +- **Das Menü folgt allen deinen Standorten.** Unter **Nachverfolgung** erscheint ein Eintrag (Zeitpläne, Verleih, Unterlagen, Verbrauchsgüter, Fahrzeuge, Umzug, Einblicke) einmal, wenn sein Modul in einem deiner Standorte eingeschaltet ist. Unter **Werkzeuge** erscheint **Etiketten**, solange Etikett irgendwo eingeschaltet ist, und **Zählungen**, solange Zählungen irgendwo eingeschaltet sind. Aktivität, Papierkorb und Benachrichtigungen sind ebenfalls immer vorhanden. ## Später ändern diff --git a/apps/docs/src/content/docs/es/users/counts.md b/apps/docs/src/content/docs/es/users/counts.md index 9461b715..c670f0a0 100644 --- a/apps/docs/src/content/docs/es/users/counts.md +++ b/apps/docs/src/content/docs/es/users/counts.md @@ -64,3 +64,13 @@ En la página de un lugar, **Contar regularmente** planifica un conteo cada 1 a | Ver Conteos, la página de un conteo, comparar, Exportar CSV | ✓ | ✓ | | Contar, unirse a un conteo | ✓ | Una revisión: no se guarda nada, no se lista | | Planificar un conteo | ✓, con Schedules activado | — | + +## Desactivar los conteos + +Conteos es un módulo, activo en todos los preajustes. Un propietario o administrador lo desactiva +en **Configuración de ubicación → Qué seguir** (**Conteos**). Desactivado en una ubicación, sus +lugares no tienen **Contar aquí**, **Contar regularmente** ni **Conteos pasados**, sus cajas no +tienen **Contar esta caja**, sus habitaciones en el mapa no tienen **Contar esta habitación**, e +**Iniciar una sesión** no ofrece contar allí. **Conteos** en Herramientas desaparece cuando está +desactivado en todas tus ubicaciones. Los conteos planificados dejan de recordarse. La **Revisión +de caja** se queda. No se borra nada: al volver a activarlo, cada conteo sigue ahí. diff --git a/apps/docs/src/content/docs/es/users/features.md b/apps/docs/src/content/docs/es/users/features.md index 7bc82de3..7346c323 100644 --- a/apps/docs/src/content/docs/es/users/features.md +++ b/apps/docs/src/content/docs/es/users/features.md @@ -44,7 +44,7 @@ una ubicación las activa en **Configuración de ubicación → Qué seguir** - **[Sesiones de escaneo](/es/users/scanning-sessions/)**: cuenta una estantería, empaqueta una caja, mueve una pila, un escaneo tras otro. - **[Conteos e inventarios](/es/users/counts/)**: cuenta una ubicación, ve qué falta, compáralo con - el último conteo. + el último conteo. Una ubicación que no cuenta los desactiva. ## El hogar diff --git a/apps/docs/src/content/docs/es/users/modules.md b/apps/docs/src/content/docs/es/users/modules.md index 07414b05..c324bee4 100644 --- a/apps/docs/src/content/docs/es/users/modules.md +++ b/apps/docs/src/content/docs/es/users/modules.md @@ -28,6 +28,7 @@ Los nombres siguientes son los interruptores en **Qué seguir**. | Módulo | Desde | Lo que aporta | |---|---|---| | **Etiquetas y códigos QR** | Essentials | Etiquetas imprimibles para objetos y lugares; escanear una etiqueta abre aquello en lo que está. | +| **Conteos** | Essentials | **Contar aquí**, **Contar regularmente** y **Conteos pasados** en un lugar, **Contar esta caja** y **Conteos** en Herramientas: cada conteo y cada revisión de caja, guardados. | | **Dinero y gastos** | Household | Precios, totales y **Perspectivas**. Cada moneda en su propia línea, sin convertir nunca. | | **Recibos y garantías** | Household | Fechas de garantía; una garantía próxima a caducar aparece como recordatorio. | | **Recordatorios** | Household | Programaciones: qué toca, por fecha o por lectura. | @@ -62,8 +63,8 @@ recordatorios se pausan. Volver a activarlo recupera todo, porque no se borró n *Necesita Vehículos, desactivado en esta ubicación*. - **El menú sigue a todas tus ubicaciones.** En **Rastreo**, una entrada (Calendarios, Préstamos, Documentación, Consumibles, Vehículos, Mudanza, Perspectivas) aparece una vez que su módulo está activo en cualquiera de tus - ubicaciones. En **Herramientas**, **Etiquetas** aparece mientras Etiquetas esté activo en algún lugar, y **Conteos** está - siempre ahí. Actividad, Papelera y Notificaciones también están siempre ahí. + ubicaciones. En **Herramientas**, **Etiquetas** aparece mientras Etiquetas esté activo en algún lugar, y **Conteos** mientras + Conteos lo esté. Actividad, Papelera y Notificaciones también están siempre ahí. ## Cambiarlo después diff --git a/apps/docs/src/content/docs/fr/users/counts.md b/apps/docs/src/content/docs/fr/users/counts.md index a92d7f94..bf5fb412 100644 --- a/apps/docs/src/content/docs/fr/users/counts.md +++ b/apps/docs/src/content/docs/fr/users/counts.md @@ -64,3 +64,13 @@ Sur la page d'un emplacement, **Compter régulièrement** planifie un comptage t | Voir Comptages, la page d'un comptage, comparer, exporter en CSV | ✓ | ✓ | | Compter, rejoindre un comptage | ✓ | Une coche : rien n'est enregistré, pas listé | | Planifier un comptage | ✓, avec Schedules activé | — | + +## Désactiver les comptages + +Comptages est un module, activé dans chaque préréglage. Un propriétaire ou un administrateur le +désactive dans **Paramètres du lieu → Ce que vous suivez** (**Comptages**). Désactivé dans un lieu, +ses emplacements n'ont plus **Compter ici**, **Compter régulièrement** ni **Comptages passés**, ses +boîtes plus **Compter cette boîte**, ses pièces sur la carte plus **Compter cette pièce**, et +**Démarrer une session** n'y propose plus de comptage. **Comptages** sous Outils disparaît dès +qu'il est désactivé dans tous vos lieux. Les comptages planifiés ne rappellent plus rien. +**Vérifier une boîte** reste. Rien n'est supprimé : réactivé, chaque comptage est de nouveau là. diff --git a/apps/docs/src/content/docs/fr/users/features.md b/apps/docs/src/content/docs/fr/users/features.md index bcbc5f77..b54b6cfb 100644 --- a/apps/docs/src/content/docs/fr/users/features.md +++ b/apps/docs/src/content/docs/fr/users/features.md @@ -45,7 +45,7 @@ un lieu les active dans **Paramètres du lieu → Ce que vous suivez** - **[Sessions de scan](/fr/users/scanning-sessions/)** : comptez une étagère, emballez une boîte, déplacez un tas, un scan après l'autre. - **[Comptages et inventaires](/fr/users/counts/)** : comptez un emplacement, voyez ce qui manque, comparez au - dernier comptage. + dernier comptage. Un lieu qui ne compte pas les désactive. ## Le foyer diff --git a/apps/docs/src/content/docs/fr/users/modules.md b/apps/docs/src/content/docs/fr/users/modules.md index 5ceb427f..41901687 100644 --- a/apps/docs/src/content/docs/fr/users/modules.md +++ b/apps/docs/src/content/docs/fr/users/modules.md @@ -22,6 +22,7 @@ Les noms ci-dessous sont les interrupteurs de **Ce que vous suivez**. | Module | Depuis | Ce que cela apporte | |---|---|---| | **Étiquettes et codes QR** | Essentiel | Des étiquettes imprimables pour les objets et les emplacements ; scanner une étiquette ouvre ce sur quoi elle est apposée. | +| **Comptages** | Essentiel | **Compter ici**, **Compter régulièrement** et **Comptages passés** sur un emplacement, **Compter cette boîte**, et **Comptages** sous Outils : chaque comptage et chaque vérification de boîte, conservés. | | **Argent et dépenses** | Foyer | Les prix, les totaux et **Statistiques**. Chaque devise sur sa propre ligne, jamais convertie. | | **Reçus et garanties** | Foyer | Les dates de garantie ; une garantie qui expire apparaît comme un rappel. | | **Rappels** | Foyer | Les plannings : ce qui est dû, par date ou par relevé. | @@ -44,7 +45,7 @@ Un module désactivé est masqué partout où il apparaîtrait (ses écrans, ses - **Les rappels se mettent en pause avec leur module.** Les plannings et les objets qui expirent attendent avec **Rappels**, les dates de garantie et d'immatriculation avec **Reçus et garanties**, les dates de documents avec **Documents** (les documents d'un véhicule continuent aussi de fonctionner tant que **Véhicules** est activé), les prêts avec **Prêts**, le stock faible avec **Consommables**. Les rappels concernant les compteurs non relevés récemment fonctionnent toujours. - **Les dépendances disparaissent aussi.** Le carburant nécessite Véhicules, et le déménagement nécessite Étiquettes et codes QR : désactiver celui dont un module a besoin désactive aussi ce module. Son interrupteur indique alors *Nécessite Véhicules, désactivé dans ce lieu*. -- **Le menu suit tous vos lieux.** Sous **Suivi**, une entrée (Plannings, Prêts, Documents, Consommables, Véhicules, Déménagement, Statistiques) s'affiche dès que son module est activé dans l'un de vos lieux. Sous **Outils**, **Étiquettes** s'affiche tant que les étiquettes sont activées quelque part, et **Comptages** est toujours présent. Activité, Corbeille et Notifications sont toujours présents aussi. +- **Le menu suit tous vos lieux.** Sous **Suivi**, une entrée (Plannings, Prêts, Documents, Consommables, Véhicules, Déménagement, Statistiques) s'affiche dès que son module est activé dans l'un de vos lieux. Sous **Outils**, **Étiquettes** s'affiche tant que les étiquettes sont activées quelque part, et **Comptages** tant que les comptages le sont. Activité, Corbeille et Notifications sont toujours présents aussi. ## Changer plus tard diff --git a/apps/docs/src/content/docs/it/users/counts.md b/apps/docs/src/content/docs/it/users/counts.md index f4033be1..0cdc1a48 100644 --- a/apps/docs/src/content/docs/it/users/counts.md +++ b/apps/docs/src/content/docs/it/users/counts.md @@ -64,3 +64,13 @@ Nella pagina di un posto, **Conta regolarmente** pianifica un conteggio ogni 1-2 | Visualizza Conteggi, pagina del conteggio, confronta, esporta CSV | ✓ | ✓ | | Conta, partecipa a un conteggio | ✓ | Un controllo: niente salvato, non elencato | | Pianifica un conteggio | ✓, con Pianificazioni attive | — | + +## Disattivare i conteggi + +Conteggi è un modulo, attivo in ogni preset. Un proprietario o un amministratore lo disattiva in +**Impostazioni del luogo → Cosa monitorare** (**Conteggi**). Disattivato in un luogo, i suoi posti +non hanno più **Conta qui**, **Conta regolarmente** né **Conteggi passati**, le sue scatole non +hanno **Conta questa scatola**, le sue stanze sulla mappa non hanno **Conta questa stanza**, e +**Avvia una sessione** non vi propone il conteggio. **Conteggi** sotto Strumenti sparisce quando è +disattivato in tutti i tuoi luoghi. I conteggi pianificati smettono di ricordarsi. Il **Controllo +scatola** resta. Non si cancella nulla: riattivato, ogni conteggio è di nuovo lì. diff --git a/apps/docs/src/content/docs/it/users/features.md b/apps/docs/src/content/docs/it/users/features.md index b2d37dc2..3c0e763c 100644 --- a/apps/docs/src/content/docs/it/users/features.md +++ b/apps/docs/src/content/docs/it/users/features.md @@ -44,7 +44,7 @@ un luogo le attiva in **Impostazioni del luogo → Cosa monitorare** - **[Sessioni di scansione](/it/users/scanning-sessions/)**: conta una mensola, impacchetta una scatola, sposta una pila, una scansione dopo l'altra. - **[Conteggi e inventari](/it/users/counts/)**: conta un posto, vedi cosa manca, confronta con il - conteggio precedente. + conteggio precedente. Un luogo che non conta li disattiva. ## La casa diff --git a/apps/docs/src/content/docs/it/users/modules.md b/apps/docs/src/content/docs/it/users/modules.md index f875e22d..574e320b 100644 --- a/apps/docs/src/content/docs/it/users/modules.md +++ b/apps/docs/src/content/docs/it/users/modules.md @@ -22,6 +22,7 @@ I nomi qui sotto sono gli interruttori in **Cosa monitorare**. | Modulo | Da | Cosa aggiunge | |---|---|---| | **Etichette e codici QR** | Essenziali | Etichette stampabili per oggetti e posti; scansionare un'etichetta apre quello su cui è attaccata. | +| **Conteggi** | Essenziali | **Conta qui**, **Conta regolarmente** e **Conteggi passati** su un luogo, **Conta questa scatola** e **Conteggi** sotto Strumenti: ogni conteggio e ogni controllo scatola, conservati. | | **Soldi e spese** | Famiglia | Prezzi, totali e **Statistiche**. Ogni valuta su una riga a parte, mai convertita. | | **Ricevute e garanzie** | Famiglia | Date di garanzia; una garanzia in scadenza appare come promemoria. | | **Promemoria** | Famiglia | Pianificazioni: cosa è in scadenza, per data o per lettura. | @@ -44,7 +45,7 @@ Un modulo disattivato è nascosto ovunque apparirebbe (le sue schermate, le voci - **I promemoria vanno in pausa con il loro modulo.** Le pianificazioni e le cose che scadono aspettano con **Promemoria**, le date di garanzia e di registrazione con **Ricevute e garanzie**, le date dei documenti con **Documenti** (i documenti di un veicolo continuano a funzionare finché **Veicoli** è attivo), i prestiti con **Prestiti**, le scorte basse con **Oggetti che si esauriscono**. I promemoria sui contatori non letti di recente funzionano sempre. - **Anche i moduli dipendenti vengono disattivati.** Carburante richiede Veicoli e Trasloco richiede Etichette e codici QR: disattivare ciò di cui un modulo ha bisogno disattiva anche quel modulo. Il suo interruttore poi mostra *Richiede Veicoli, disattivato in questo luogo*. -- **Il menu segue tutti i tuoi luoghi.** Sotto **Monitoraggio**, una voce (Programmazioni, Prestiti, Documenti, Consumabili, Veicoli, Trasloco, Statistiche) appare quando il suo modulo è attivo in almeno uno dei tuoi luoghi. Sotto **Strumenti**, **Etichette** appare finché Etichette è attiva da qualche parte, e **Conteggi** è sempre presente. Anche Attività, Cestino e Notifiche sono sempre presenti. +- **Il menu segue tutti i tuoi luoghi.** Sotto **Monitoraggio**, una voce (Programmazioni, Prestiti, Documenti, Consumabili, Veicoli, Trasloco, Statistiche) appare quando il suo modulo è attivo in almeno uno dei tuoi luoghi. Sotto **Strumenti**, **Etichette** appare finché Etichette è attiva da qualche parte, e **Conteggi** finché i conteggi sono attivi da qualche parte. Anche Attività, Cestino e Notifiche sono sempre presenti. ## Cambiarlo in seguito diff --git a/apps/docs/src/content/docs/uk/users/counts.md b/apps/docs/src/content/docs/uk/users/counts.md index 020acb9a..8f2736ec 100644 --- a/apps/docs/src/content/docs/uk/users/counts.md +++ b/apps/docs/src/content/docs/uk/users/counts.md @@ -88,3 +88,13 @@ description: Кожен підрахунок і перевірка коробк | Бачити Підрахунки, сторінку підрахунку, порівнювати, експортувати CSV | ✓ | ✓ | | Підрахунок, приєднання до підрахунку | ✓ | Перевірка: нічого не зберігається і не потрапляє до списку | | Планування підрахунку | ✓, з увімкненими Розкладами | — | + +## Як вимкнути підрахунки + +Підрахунки є модулем, увімкненим у кожному пресеті. Власник або адміністратор вимикає його в +**Налаштування локації → Що відстежувати** (**Підрахунки**). Коли його вимкнено в локації, на її +місцях немає **Порахувати тут**, **Підраховувати регулярно** чи **Минулі підрахунки**, у коробках +немає **Порахувати цю коробку**, у кімнатах на мапі немає **Порахувати цю кімнату**, а **Почати +сеанс** не пропонує там підрахунку. **Підрахунки** в Інструментах зникають, коли модуль вимкнено в +усіх ваших локаціях. Заплановані підрахунки більше не нагадують. **Перевірка коробки** лишається. +Нічого не видаляється: після ввімкнення всі підрахунки знову на місці. diff --git a/apps/docs/src/content/docs/uk/users/features.md b/apps/docs/src/content/docs/uk/users/features.md index 3933540b..13460d9b 100644 --- a/apps/docs/src/content/docs/uk/users/features.md +++ b/apps/docs/src/content/docs/uk/users/features.md @@ -38,7 +38,7 @@ description: "Усе, що робить Kept, в одному списку: ін - **[Сесії сканування](/uk/users/scanning-sessions/)**: порахуйте полицю, спакуйте коробку, перемістіть купу речей, скан за сканом. - **[Підрахунки та інвентаризації](/uk/users/counts/)**: порахуйте місце, побачте, чого бракує, - порівняйте з попереднім підрахунком. + порівняйте з попереднім підрахунком. Локація, де не рахують, вимикає їх. ## Домашнє господарство diff --git a/apps/docs/src/content/docs/uk/users/modules.md b/apps/docs/src/content/docs/uk/users/modules.md index 96695032..2d7e6016 100644 --- a/apps/docs/src/content/docs/uk/users/modules.md +++ b/apps/docs/src/content/docs/uk/users/modules.md @@ -23,6 +23,7 @@ description: "Що кожна локація відстежує: пресети | Модуль | Звідки | Що дає | |---|---|---| | **Етикетки та QR-коди** | Основне | Друк етикеток для речей та місць; сканування етикетки відкриває те, на чому вона. | +| **Підрахунки** | Основне | **Порахувати тут**, **Підраховувати регулярно** та **Минулі підрахунки** на місці, **Порахувати цю коробку** і **Підрахунки** в Інструментах: кожен підрахунок і кожна перевірка коробки зберігаються. | | **Гроші та витрати** | Домогосподарство | Ціни, суми та **Аналітика**. Кожна валюта в окремому рядку, ніколи не конвертується. | | **Чеки та гарантії** | Домогосподарство | Терміни гарантії; закінчується гарантія — показується як нагадування. | | **Нагадування** | Домогосподарство | Розклад: що коли закінчується, за датою або за показниками. | @@ -45,7 +46,7 @@ description: "Що кожна локація відстежує: пресети - **Нагадування ставляться на паузу разом із модулем.** Розклад та речі, термін яких закінчується, чекають із **Нагадуваннями**, дати гарантії та реєстрації — із **Чеками та гарантіями**, дати документів — із **Документами** (документи транспортного засобу також працюють, поки увімкнені **Транспортні засоби**), позичення — із **Позичанням**, низький запас — із **Речами, які у вас закінчуються**. Нагадування про показники, давно не зчитувані, завжди працюють. - **Залежні йдуть разом.** Паливо потребує Транспортні засоби, а Переїзд — Етикетки та QR-коди: вимкнення того, що потрібно модулю, вимикає й його. Його перемикач тоді читається *Потрібні Транспортні засоби, вимкнено в цій локації*. -- **Меню слідує за всіма вашими локаціями.** У **Відстежуванні** запис (Розклад, Позичання, Документи, Сповживані, Транспортні засоби, Переїзд, Аналітика) з'являється, якщо його модуль увімкнено хоча б в одній з ваших локацій. У **Інструменти** **Етикетки** показується, поки увімкнені Етикетки десь, а **Підрахунки** завжди там. Активність, Кошик та Сповіщення завжди там теж. +- **Меню слідує за всіма вашими локаціями.** У **Відстежуванні** запис (Розклад, Позичання, Документи, Сповживані, Транспортні засоби, Переїзд, Аналітика) з'являється, якщо його модуль увімкнено хоча б в одній з ваших локацій. У **Інструменти** **Етикетки** показується, поки увімкнені Етикетки десь, а **Підрахунки** показуються, поки десь увімкнено підрахунки. Активність, Кошик та Сповіщення завжди там теж. ## Зміна пізніше diff --git a/apps/docs/src/content/docs/users/counts.md b/apps/docs/src/content/docs/users/counts.md index 07c144c6..55902730 100644 --- a/apps/docs/src/content/docs/users/counts.md +++ b/apps/docs/src/content/docs/users/counts.md @@ -88,3 +88,12 @@ too. To count a whole location regularly, plan it on its top place. | See Counts, a count's page, compare, export CSV | ✓ | ✓ | | Count, join a count | ✓ | A check: nothing saved, not listed | | Plan a count | ✓, with Schedules on | — | + +## Turning Counts off + +Counts is a module, on in every preset. An owner or an admin turns it off in **Location settings +→ What to track** (**Counts**). Off in a location, its places have no **Count here**, **Count +regularly** or **Past counts**, its boxes no **Count this box**, its rooms on the map no **Count +this room**, and **Start a session** offers no count there. **Counts** under Tools goes once it +is off in all your locations. Planned counts stop reminding. **Box check** stays. Nothing is +deleted: turned back on, every count is there again. diff --git a/apps/docs/src/content/docs/users/features.md b/apps/docs/src/content/docs/users/features.md index 967f2398..65d65762 100644 --- a/apps/docs/src/content/docs/users/features.md +++ b/apps/docs/src/content/docs/users/features.md @@ -43,7 +43,7 @@ a location turns them on in **Location settings → What to track** - **[Scan sessions](/users/scanning-sessions/)**: count a shelf, pack a box, move a pile, one scan after another. - **[Counts and stocktakes](/users/counts/)**: count a place, see what's missing, compare with the - last count. + last count. A location that doesn't count turns them off. ## The household diff --git a/apps/docs/src/content/docs/users/modules.md b/apps/docs/src/content/docs/users/modules.md index d839e953..5cf26304 100644 --- a/apps/docs/src/content/docs/users/modules.md +++ b/apps/docs/src/content/docs/users/modules.md @@ -28,6 +28,7 @@ The names below are the switches in **What to track**. | Module | From | What it brings | |---|---|---| | **Labels and QR codes** | Essentials | Printable labels for things and places; scanning a label opens what it's on. | +| **Counts** | Essentials | **Count here**, **Count regularly** and **Past counts** on a place, **Count this box**, and **Counts** under Tools: every count and box check, kept. | | **Money and spending** | Household | Prices, totals and **Insights**. Each currency on its own line, never converted. | | **Receipts and warranties** | Household | Warranty dates; an expiring warranty shows as a reminder. | | **Reminders** | Household | Schedules: what's due, by date or by reading. | @@ -63,8 +64,8 @@ deleted. *Needs Vehicles, off in this location*. - **The menu follows all your locations.** Under **Tracking**, an entry (Schedules, Lending, Paperwork, Consumables, Vehicles, Moving, Insights) shows once its module is on in any of your - locations. Under **Tools**, **Labels** shows while Labels is on somewhere, and **Counts** is - always there. Activity, Trash and Notifications are always there too. + locations. Under **Tools**, **Labels** shows while Labels is on somewhere, and **Counts** while + Counts is. Activity, Trash and Notifications are always there. ## Changing it later diff --git a/apps/docs/src/content/docs/vi/users/counts.md b/apps/docs/src/content/docs/vi/users/counts.md index ca81313b..5061c1fe 100644 --- a/apps/docs/src/content/docs/vi/users/counts.md +++ b/apps/docs/src/content/docs/vi/users/counts.md @@ -64,3 +64,7 @@ Trên trang của một vị trí, **Kiểm kê định kỳ** lên kế hoạch | Xem Kiểm kê, trang kiểm kê, so sánh, xuất CSV | ✓ | ✓ | | Kiểm kê, tham gia kiểm kê | ✓ | Một lần kiểm tra: không lưu gì, không liệt kê | | Lên kế hoạch kiểm kê | ✓, với Lịch trình bật | — | + +## Tắt kiểm kê + +Kiểm kê là một mô-đun, bật trong mọi mẫu cài đặt. Chủ sở hữu hoặc quản trị viên tắt nó trong **Cài đặt địa điểm → Theo dõi gì** (**Kiểm kê**). Khi tắt ở một địa điểm, các vị trí của nó không còn **Kiểm kê tại đây**, **Kiểm kê định kỳ** hay **Các lần kiểm kê trước**, các hộp không còn **Kiểm kê hộp này**, các phòng trên bản đồ không còn **Kiểm kê phòng này**, và **Bắt đầu phiên** không cho kiểm kê ở đó. **Kiểm kê** trong Công cụ biến mất khi nó tắt ở mọi địa điểm của bạn. Các lần kiểm kê dự định ngừng nhắc. **Kiểm tra hộp** vẫn còn. Không có gì bị xóa: bật lại, mọi lần kiểm kê vẫn còn đó. diff --git a/apps/docs/src/content/docs/vi/users/features.md b/apps/docs/src/content/docs/vi/users/features.md index a74ee57a..a413b417 100644 --- a/apps/docs/src/content/docs/vi/users/features.md +++ b/apps/docs/src/content/docs/vi/users/features.md @@ -26,7 +26,7 @@ Mọi thứ Kept làm được, mỗi thứ có trang giải thích. Một số - **[Sơ đồ](/vi/users/graph/)**: vị trí, hộp và đồ vật của một địa điểm trong 2D hoặc 3D. - **[ID ngắn và nhãn](/vi/users/short-ids/)**: ID ngắn và mã QR trên mỗi hộp; quét một cái để mở nó. Cuộn máy dán nhãn hoặc giấy in văn phòng. Mô-đun: Nhãn và mã QR. - **[Phiên quét](/vi/users/scanning-sessions/)**: đếm một kệ, đóng gói một hộp, di chuyển một đống, lần quét sau lần quét. -- **[Đếm và kiểm kê](/vi/users/counts/)**: đếm một vị trí, xem cái gì thiếu, so sánh với lần đếm cuối. +- **[Đếm và kiểm kê](/vi/users/counts/)**: đếm một vị trí, xem cái gì thiếu, so sánh với lần đếm cuối. Địa điểm không kiểm kê có thể tắt nó. ## Gia đình diff --git a/apps/docs/src/content/docs/vi/users/modules.md b/apps/docs/src/content/docs/vi/users/modules.md index b44adcf6..a5487e3f 100644 --- a/apps/docs/src/content/docs/vi/users/modules.md +++ b/apps/docs/src/content/docs/vi/users/modules.md @@ -22,6 +22,7 @@ Các tên dưới đây là các công tắc trong **Theo dõi gì**. | Module | Từ | Nó mang lại | |---|---|---| | **Nhãn và mã QR** | Thiết yếu | Nhãn in được cho đồ vật và vị trí; quét một nhãn mở ra đồ vật hoặc vị trí mà nhãn đó đang gắn trên. | +| **Kiểm kê** | Thiết yếu | **Kiểm kê tại đây**, **Kiểm kê định kỳ** và **Các lần kiểm kê trước** trên một vị trí, **Kiểm kê hộp này**, và **Kiểm kê** trong Công cụ: mọi lần kiểm kê và kiểm tra hộp đều được lưu. | | **Tiền và chi tiêu** | Hộ gia đình | Giá, tổng và **Thống kê**. Mỗi loại tiền tệ trên một dòng riêng, không bao giờ được chuyển đổi. | | **Biên lai và bảo hành** | Hộ gia đình | Ngày bảo hành; một bảo hành sắp hết hạn hiện ra như một lời nhắc. | | **Lời nhắc** | Hộ gia đình | Lịch trình: những gì đến hạn, theo ngày hoặc theo số đo. | @@ -46,7 +47,7 @@ Một mô-đun đang tắt bị ẩn mọi nơi nó sẽ xuất hiện (màn hì - **Những gì phụ thuộc cũng tắt theo.** Nhiên liệu cần Phương tiện, và Chuyển nhà cần Nhãn và mã QR: khi bạn tắt thứ một mô-đun cần, mô-đun đó cũng bị tắt theo. Công tắc của nó sau đó hiển thị *Cần Phương tiện, tắt trong địa điểm này*. -- **Menu tuân theo tất cả các địa điểm của bạn.** Dưới **Theo dõi**, một mục (Lịch trình, Cho mượn, Giấy tờ, Đồ vật bạn dùng hết, Phương tiện, Chuyển nhà, Thống kê) hiện ra khi mô-đun của nó đang bật trong bất kỳ địa điểm nào của bạn. Dưới **Công cụ**, **Nhãn** hiển thị trong khi **Nhãn** đang bật ở bất kỳ đâu, và **Kiểm kê** luôn có sẵn. Hoạt động, Thùng rác và Thông báo cũng luôn có sẵn. +- **Menu tuân theo tất cả các địa điểm của bạn.** Dưới **Theo dõi**, một mục (Lịch trình, Cho mượn, Giấy tờ, Đồ vật bạn dùng hết, Phương tiện, Chuyển nhà, Thống kê) hiện ra khi mô-đun của nó đang bật trong bất kỳ địa điểm nào của bạn. Dưới **Công cụ**, **Nhãn** hiển thị trong khi **Nhãn** đang bật ở bất kỳ đâu, và **Kiểm kê** hiển thị trong khi Kiểm kê đang bật ở bất kỳ đâu. Hoạt động, Thùng rác và Thông báo cũng luôn có sẵn. ## Thay đổi sau này diff --git a/apps/server/migrations/0177_counts_module.sql b/apps/server/migrations/0177_counts_module.sql new file mode 100644 index 00000000..34bd48c7 --- /dev/null +++ b/apps/server/migrations/0177_counts_module.sql @@ -0,0 +1,173 @@ +-- Counts is a module a location can turn off (#506), like Labels and Lending. It is in every +-- preset (@kept/shared modules.ts), so a location keeps its counts until someone turns them off: +-- an existing location has no `location_modules` row for it, and its preset now says on. +-- +-- 1. kept.module_enabled(): the presets' modules as modules.ts has them, Counts in each. +-- kept.module_on() is unchanged (Counts depends on nothing); src/db/agenda.test.ts checks the +-- two agree. +-- 2. The agenda: a planned count (a schedule of kind `count`) is on it only while Counts is on +-- too, so with Counts off it reminds no one and leaves Home's Needs you. The rest of the view is +-- 0152's. +-- Additive: both are CREATE OR REPLACE with the same signature and columns; the release before +-- this one never asks about 'counts', and a planned count it reads stays on its agenda. + +CREATE OR REPLACE FUNCTION kept.module_enabled(p_location uuid, p_module text) RETURNS boolean +LANGUAGE sql STABLE SET search_path = pg_catalog, public AS $$ + SELECT coalesce( + (SELECT lm.enabled FROM public.location_modules lm + WHERE lm.location_id = p_location AND lm.module = p_module), + (SELECT p_module = ANY (CASE l.preset + WHEN 'essentials' THEN ARRAY['labels', 'counts', 'ai_capture', 'ai_assistant'] + WHEN 'household' THEN ARRAY['labels', 'counts', 'money', 'warranties', 'schedules', + 'lending', 'paperwork', 'vehicles', 'ai_capture', + 'ai_assistant'] + WHEN 'complete' THEN ARRAY['labels', 'counts', 'money', 'warranties', 'schedules', + 'lending', 'paperwork', 'vehicles', 'fuel', 'consumables', + 'moving', 'secrets', 'ai_capture', 'ai_assistant', 'mcp'] + ELSE '{}'::text[] END) + FROM public.locations l WHERE l.id = p_location), + false) +$$; +--> statement-breakpoint +CREATE OR REPLACE VIEW public.agenda_items WITH (security_invoker = on) AS +WITH loc AS ( + SELECT l.id, l.timezone, (now() AT TIME ZONE l.timezone)::date AS today, + kept.module_on(l.id, 'schedules') AS on_schedules, + kept.module_on(l.id, 'warranties') AS on_warranties, + kept.module_on(l.id, 'paperwork') AS on_paperwork, + kept.module_on(l.id, 'vehicles') AS on_vehicles, + kept.module_on(l.id, 'lending') AS on_lending, + kept.module_on(l.id, 'consumables') AS on_consumables, + kept.module_on(l.id, 'counts') AS on_counts + FROM public.locations l + WHERE l.deleted_at IS NULL) +-- Schedules: due from the lead, or from the day the estimate reaches it; overdue after the due +-- point. A one-off that is done (step 2) is on no agenda. +SELECT 'schedule'::text AS source_type, s.id AS source_id, s.location_id, s.thing_id, + s.place_id, CASE WHEN n.state = 'overdue' THEN 'overdue' ELSE 'due' END AS kind, + n.due_on, n.due_value, s.meter_id, n.state, + CASE WHEN n.basis = 'units' OR n.due_on IS NULL THEN 'meter:' || n.due_value::text + ELSE 'date:' || n.due_on::text END AS due_period, + 'schedules'::text AS module, s.name AS title, + n.estimated_on, n.estimated + FROM public.schedules s + JOIN loc ON loc.id = s.location_id + LEFT JOIN public.things t ON t.id = s.thing_id + LEFT JOIN public.places p ON p.id = s.place_id + CROSS JOIN LATERAL kept.schedule_due(s, loc.today, now()) n + WHERE s.active AND loc.on_schedules AND n.state <> 'done' + AND (s.kind <> 'count' OR loc.on_counts) + AND (s.thing_id IS NULL OR (t.deleted_at IS NULL AND t.lifecycle = 'in_use')) + AND (s.place_id IS NULL OR p.deleted_at IS NULL) +UNION ALL +-- Warranties: expiring from the lead to the last day covered (lifetime: none); expired after. +SELECT 'warranty', w.id, w.location_id, w.thing_id, NULL::uuid, 'expiring', + w.effective_ends_on, NULL::numeric, NULL::uuid, + CASE WHEN loc.today > w.effective_ends_on THEN 'expired' + WHEN loc.today >= w.effective_ends_on - w.lead_days THEN 'expiring' + ELSE 'upcoming' END, + 'date:' || w.effective_ends_on::text, 'warranties', w.provider, NULL::date, false + FROM public.warranties w + JOIN loc ON loc.id = w.location_id + JOIN public.things t ON t.id = w.thing_id + WHERE w.effective_ends_on IS NOT NULL AND loc.on_warranties + AND t.deleted_at IS NULL AND t.lifecycle = 'in_use' +UNION ALL +-- Registration (Q4: D55's warranty-registration deadline): due from 14 days before. +SELECT 'registration', w.id, w.location_id, w.thing_id, NULL::uuid, 'due', + w.registration_deadline, NULL::numeric, NULL::uuid, + CASE WHEN loc.today > w.registration_deadline THEN 'overdue' + WHEN loc.today >= w.registration_deadline - 14 THEN 'due' + ELSE 'upcoming' END, + 'date:' || w.registration_deadline::text, 'warranties', w.provider, NULL::date, false + FROM public.warranties w + JOIN loc ON loc.id = w.location_id + JOIN public.things t ON t.id = w.thing_id + WHERE NOT w.registered AND w.registration_deadline IS NOT NULL + AND loc.on_warranties + AND t.deleted_at IS NULL AND t.lifecycle = 'in_use' +UNION ALL +-- Documents, current ones only: expiring from the lead, overdue after (an expired lease matters). +-- A vehicle's document also while Vehicles alone is on (step 5, step 4's Q5). +SELECT 'document', d.id, d.location_id, d.thing_id, d.place_id, + CASE WHEN loc.today > d.expires_on THEN 'overdue' ELSE 'expiring' END, + d.expires_on, NULL::numeric, NULL::uuid, + CASE WHEN loc.today > d.expires_on THEN 'overdue' + WHEN loc.today >= d.expires_on - d.lead_days THEN 'expiring' + ELSE 'upcoming' END, + 'date:' || d.expires_on::text, + CASE WHEN loc.on_paperwork THEN 'paperwork' ELSE 'vehicles' END, + d.title, NULL::date, false + FROM public.expiring_documents d + JOIN loc ON loc.id = d.location_id + LEFT JOIN public.things t ON t.id = d.thing_id + LEFT JOIN public.places p ON p.id = d.place_id + WHERE d.superseded_by_id IS NULL + AND (loc.on_paperwork + OR (t.id IS NOT NULL AND loc.on_vehicles AND kept.is_vehicle_type(t.type_id))) + AND (d.thing_id IS NULL OR (t.deleted_at IS NULL AND t.lifecycle = 'in_use')) + AND (d.place_id IS NULL OR p.deleted_at IS NULL) +UNION ALL +-- Loans out and in, open, with a due date: overdue from the day after (D56, D57). With a lead +-- (1.14, a hand-over's due-soon days, 2 by default), due from that many days before: kind `due` +-- until the due date, so the scan reminds of it too, then `overdue`. +SELECT 'loan', o.id, o.location_id, o.thing_id, NULL::uuid, + CASE WHEN o.lead_days > 0 AND loc.today <= o.due_on THEN 'due' ELSE 'overdue' END, + o.due_on, NULL::numeric, NULL::uuid, + CASE WHEN loc.today > o.due_on THEN 'overdue' + WHEN loc.today >= o.due_on - o.lead_days THEN 'due' + ELSE 'upcoming' END, + 'date:' || o.due_on::text, 'lending', NULL::text, NULL::date, false + FROM public.loans o + JOIN loc ON loc.id = o.location_id + JOIN public.things t ON t.id = o.thing_id + WHERE o.returned_at IS NULL AND o.due_on IS NOT NULL AND loc.on_lending + AND t.deleted_at IS NULL AND t.lifecycle = 'in_use' +UNION ALL +-- Thing expiries (D141): expiring from the thing's lead (30 days by default), overdue after. +SELECT 'thing_expiry', t.id, t.location_id, t.id, NULL::uuid, + CASE WHEN loc.today > t.expires_on THEN 'overdue' ELSE 'expiring' END, + t.expires_on, NULL::numeric, NULL::uuid, + CASE WHEN loc.today > t.expires_on THEN 'overdue' + WHEN loc.today >= t.expires_on - coalesce(t.expiry_lead_days, 30) THEN 'expiring' + ELSE 'upcoming' END, + 'date:' || t.expires_on::text, 'schedules', NULL::text, NULL::date, false + FROM public.things t + JOIN loc ON loc.id = t.location_id + WHERE t.expires_on IS NOT NULL AND loc.on_schedules + AND t.deleted_at IS NULL AND t.lifecycle = 'in_use' +UNION ALL +-- Stale readings (step 5; D52, Q19): due from the latest accepted reading's local day plus the +-- meter's nudge_days, then rolled forward in steps of nudge_days to the last step on or before +-- today (0089), so a reading still not taken nudges again each period, under a new key +-- ('date:' || the step) that stays the same within the period; none before the first reading, +-- none with nudge_days NULL. Core: no module. +SELECT 'reading_stale', m.id, m.location_id, m.thing_id, NULL::uuid, 'due', + p.due_on, NULL::numeric, m.id, + CASE WHEN loc.today >= p.due_on THEN 'due' ELSE 'upcoming' END, + 'date:' || p.due_on::text, NULL::text, m.label, NULL::date, false + FROM public.meters m + JOIN loc ON loc.id = m.location_id + JOIN public.things t ON t.id = m.thing_id + CROSS JOIN LATERAL ( + SELECT (d.taken_at AT TIME ZONE loc.timezone)::date AS read_on + FROM public.meter_readings d + WHERE d.meter_id = m.id AND d.state = 'accepted' + ORDER BY d.taken_at DESC LIMIT 1) r + CROSS JOIN LATERAL ( + SELECT r.read_on + + m.nudge_days * greatest(1, (loc.today - r.read_on) / m.nudge_days) AS due_on) p + WHERE m.nudge_days IS NOT NULL AND t.deleted_at IS NULL AND t.lifecycle = 'in_use' +UNION ALL +-- Low stock (step 7; D14, D29, D113): a live thing in use below its "keep at least" while +-- Consumables is on, due from the location's day it ran low (stock_rules.low_since) and keyed on +-- it, so each time it runs low reminds once; restocked, it leaves the agenda and the scan closes +-- the reminder. +SELECT 'stock', r.id, r.location_id, r.thing_id, NULL::uuid, 'due', + r.low_since, NULL::numeric, NULL::uuid, 'due', + 'date:' || r.low_since::text, 'consumables', NULL::text, NULL::date, false + FROM public.stock_rules r + JOIN loc ON loc.id = r.location_id + JOIN public.things t ON t.id = r.thing_id + WHERE loc.on_consumables AND r.low_since IS NOT NULL AND t.quantity < r.min_quantity + AND t.deleted_at IS NULL AND t.lifecycle = 'in_use'; diff --git a/apps/server/migrations/meta/0177_snapshot.json b/apps/server/migrations/meta/0177_snapshot.json new file mode 100644 index 00000000..3d046733 --- /dev/null +++ b/apps/server/migrations/meta/0177_snapshot.json @@ -0,0 +1,24385 @@ +{ + "id": "0dedf0eb-2df9-40ec-8f83-9a9fc77ebfed", + "prevId": "2527df7d-6b96-4ecf-a9e7-fcaa6483394d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_breakers": { + "name": "ai_breakers", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "until": { + "name": "until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trips": { + "name": "trips", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recent_errors": { + "name": "recent_errors", + "type": "timestamp with time zone[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::timestamptz[]" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_breakers_provider_id_ai_providers_id_fk": { + "name": "ai_breakers_provider_id_ai_providers_id_fk", + "tableFrom": "ai_breakers", + "columnsFrom": [ + "provider_id" + ], + "tableTo": "ai_providers", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_breakers_reason_chk": { + "name": "ai_breakers_reason_chk", + "value": "\"reason\" IN ('rate_limited', 'quota', 'auth', 'provider_down')" + }, + "ai_breakers_trip_pair_chk": { + "name": "ai_breakers_trip_pair_chk", + "value": "(reason IS NULL) = (until IS NULL)" + }, + "ai_breakers_trips_chk": { + "name": "ai_breakers_trips_chk", + "value": "trips >= 0" + } + }, + "isRLSEnabled": false + }, + "public.ai_budgets": { + "name": "ai_budgets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_per_minute": { + "name": "tokens_per_minute", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_per_day": { + "name": "tokens_per_day", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_per_month": { + "name": "tokens_per_month", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "monthly_cap_amount": { + "name": "monthly_cap_amount", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "cap_currency": { + "name": "cap_currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "paused_until": { + "name": "paused_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "paused_reason": { + "name": "paused_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "warned_80_month": { + "name": "warned_80_month", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "warned_100_month": { + "name": "warned_100_month", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "set_by": { + "name": "set_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ai_budgets_account_idx": { + "name": "ai_budgets_account_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_budgets_location_idx": { + "name": "ai_budgets_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_budgets_user_idx": { + "name": "ai_budgets_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_budgets_paused_idx": { + "name": "ai_budgets_paused_idx", + "columns": [ + { + "expression": "paused_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "paused_until IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "ai_budgets_owner_account_id_owner_accounts_id_fk": { + "name": "ai_budgets_owner_account_id_owner_accounts_id_fk", + "tableFrom": "ai_budgets", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "ai_budgets_location_id_locations_id_fk": { + "name": "ai_budgets_location_id_locations_id_fk", + "tableFrom": "ai_budgets", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "ai_budgets_user_id_user_id_fk": { + "name": "ai_budgets_user_id_user_id_fk", + "tableFrom": "ai_budgets", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "ai_budgets_cap_currency_currencies_code_fk": { + "name": "ai_budgets_cap_currency_currencies_code_fk", + "tableFrom": "ai_budgets", + "columnsFrom": [ + "cap_currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_budgets_scope_uq": { + "name": "ai_budgets_scope_uq", + "columns": [ + "scope", + "owner_account_id", + "location_id", + "user_id", + "task" + ], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": { + "ai_budgets_scope_chk": { + "name": "ai_budgets_scope_chk", + "value": "\"scope\" IN ('instance', 'instance_account', 'account', 'location', 'member', 'user')" + }, + "ai_budgets_task_chk": { + "name": "ai_budgets_task_chk", + "value": "\"task\" IN ('extraction', 'assistant', 'embeddings')" + }, + "ai_budgets_paused_reason_chk": { + "name": "ai_budgets_paused_reason_chk", + "value": "\"paused_reason\" IN ('manual', 'cap_money', 'cap_tokens', 'tokens_day')" + }, + "ai_budgets_tpm_chk": { + "name": "ai_budgets_tpm_chk", + "value": "tokens_per_minute > 0" + }, + "ai_budgets_tpd_chk": { + "name": "ai_budgets_tpd_chk", + "value": "tokens_per_day > 0" + }, + "ai_budgets_month_tokens_chk": { + "name": "ai_budgets_month_tokens_chk", + "value": "tokens_per_month > 0" + }, + "ai_budgets_money_chk": { + "name": "ai_budgets_money_chk", + "value": "monthly_cap_amount >= 0" + }, + "ai_budgets_money_pair_chk": { + "name": "ai_budgets_money_pair_chk", + "value": "(monthly_cap_amount IS NULL) = (cap_currency IS NULL)" + }, + "ai_budgets_pause_pair_chk": { + "name": "ai_budgets_pause_pair_chk", + "value": "(paused_until IS NULL) = (paused_reason IS NULL)" + }, + "ai_budgets_scope_ids_chk": { + "name": "ai_budgets_scope_ids_chk", + "value": "CASE scope\n WHEN 'instance' THEN owner_account_id IS NULL AND location_id IS NULL AND user_id IS NULL\n WHEN 'instance_account' THEN location_id IS NULL AND user_id IS NULL\n WHEN 'account' THEN owner_account_id IS NOT NULL AND location_id IS NULL AND user_id IS NULL\n WHEN 'location' THEN owner_account_id IS NOT NULL AND location_id IS NOT NULL AND user_id IS NULL\n WHEN 'member' THEN owner_account_id IS NOT NULL AND location_id IS NULL AND user_id IS NOT NULL\n ELSE owner_account_id IS NULL AND location_id IS NULL AND user_id IS NOT NULL END" + }, + "ai_budgets_minute_day_chk": { + "name": "ai_budgets_minute_day_chk", + "value": "(tokens_per_minute IS NULL AND tokens_per_day IS NULL)\n OR (scope IN ('instance', 'account') AND task IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.ai_cost_windows": { + "name": "ai_cost_windows", + "schema": "", + "columns": { + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "month_start": { + "name": "month_start", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ai_cost_windows_pk": { + "name": "ai_cost_windows_pk", + "columns": [ + "bucket", + "month_start", + "currency" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_leases": { + "name": "ai_leases", + "schema": "", + "columns": { + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ai_leases_pk": { + "name": "ai_leases_pk", + "columns": [ + "lease_key", + "slot" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_leases_slot_chk": { + "name": "ai_leases_slot_chk", + "value": "slot BETWEEN 1 AND 4" + } + }, + "isRLSEnabled": false + }, + "public.ai_model_prices": { + "name": "ai_model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "provider_kind": { + "name": "provider_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "input_per_mtok": { + "name": "input_per_mtok", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": true + }, + "output_per_mtok": { + "name": "output_per_mtok", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": true + }, + "reasoning_per_mtok": { + "name": "reasoning_per_mtok", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": false + }, + "cached_input_per_mtok": { + "name": "cached_input_per_mtok", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": false + }, + "per_image": { + "name": "per_image", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "effective_from": { + "name": "effective_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "listing_fetched_at": { + "name": "listing_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_model_prices_current_uq": { + "name": "ai_model_prices_current_uq", + "columns": [ + { + "expression": "provider_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "superseded_at IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "ai_model_prices_currency_currencies_code_fk": { + "name": "ai_model_prices_currency_currencies_code_fk", + "tableFrom": "ai_model_prices", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_model_prices_version_uq": { + "name": "ai_model_prices_version_uq", + "columns": [ + "provider_kind", + "model", + "version" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "ai_model_prices_source_chk": { + "name": "ai_model_prices_source_chk", + "value": "\"source\" IN ('admin', 'provider_listing')" + }, + "ai_model_prices_kind_chk": { + "name": "ai_model_prices_kind_chk", + "value": "provider_kind IN ('openai', 'anthropic', 'google', 'openai_compatible', 'openrouter', 'groq')" + }, + "ai_model_prices_model_chk": { + "name": "ai_model_prices_model_chk", + "value": "char_length(model) BETWEEN 1 AND 120" + }, + "ai_model_prices_version_chk": { + "name": "ai_model_prices_version_chk", + "value": "version >= 1" + }, + "ai_model_prices_rates_chk": { + "name": "ai_model_prices_rates_chk", + "value": "input_per_mtok >= 0 AND output_per_mtok >= 0 AND reasoning_per_mtok >= 0\n AND cached_input_per_mtok >= 0 AND per_image >= 0" + }, + "ai_model_prices_listing_chk": { + "name": "ai_model_prices_listing_chk", + "value": "source = 'admin' OR listing_fetched_at IS NOT NULL" + }, + "ai_model_prices_superseded_chk": { + "name": "ai_model_prices_superseded_chk", + "value": "superseded_at IS NULL OR superseded_at >= effective_from" + } + }, + "isRLSEnabled": false + }, + "public.ai_provider_limits": { + "name": "ai_provider_limits", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "limit_tokens": { + "name": "limit_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining_tokens": { + "name": "remaining_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "output_limit_tokens": { + "name": "output_limit_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_used": { + "name": "output_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_window_start": { + "name": "output_window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "ai_provider_limits_provider_id_ai_providers_id_fk": { + "name": "ai_provider_limits_provider_id_ai_providers_id_fk", + "tableFrom": "ai_provider_limits", + "columnsFrom": [ + "provider_id" + ], + "tableTo": "ai_providers", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_providers": { + "name": "ai_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_list": { + "name": "model_list", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_list_at": { + "name": "model_list_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key_ciphertext": { + "name": "key_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "key_hint": { + "name": "key_hint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "models": { + "name": "models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'low'" + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ai_providers_active_uq": { + "name": "ai_providers_active_uq", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(owner_account_id, user_id, '00000000-0000-0000-0000-000000000000'::uuid)", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "disabled_at IS NULL", + "concurrently": false + }, + "ai_providers_account_idx": { + "name": "ai_providers_account_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_providers_user_idx": { + "name": "ai_providers_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "ai_providers_owner_account_id_owner_accounts_id_fk": { + "name": "ai_providers_owner_account_id_owner_accounts_id_fk", + "tableFrom": "ai_providers", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "ai_providers_user_id_user_id_fk": { + "name": "ai_providers_user_id_user_id_fk", + "tableFrom": "ai_providers", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_providers_scope_chk": { + "name": "ai_providers_scope_chk", + "value": "\"scope\" IN ('instance', 'account', 'user')" + }, + "ai_providers_kind_chk": { + "name": "ai_providers_kind_chk", + "value": "\"kind\" IN ('openai', 'anthropic', 'google', 'openai_compatible', 'openrouter', 'groq')" + }, + "ai_providers_reasoning_chk": { + "name": "ai_providers_reasoning_chk", + "value": "reasoning IN ('provider-default', 'none', 'minimal', 'low', 'medium', 'high')" + }, + "ai_providers_label_chk": { + "name": "ai_providers_label_chk", + "value": "char_length(label) <= 60" + }, + "ai_providers_base_url_chk": { + "name": "ai_providers_base_url_chk", + "value": "base_url IS NULL OR (base_url ~ '^https?://' AND char_length(base_url) <= 300)" + }, + "ai_providers_key_hint_chk": { + "name": "ai_providers_key_hint_chk", + "value": "key_hint ~ '^[A-Za-z0-9_-]{0,4}$'" + }, + "ai_providers_models_chk": { + "name": "ai_providers_models_chk", + "value": "jsonb_typeof(models) = 'object'" + }, + "ai_providers_capabilities_chk": { + "name": "ai_providers_capabilities_chk", + "value": "jsonb_typeof(capabilities) = 'object'" + }, + "ai_providers_scope_ids_chk": { + "name": "ai_providers_scope_ids_chk", + "value": "CASE scope WHEN 'instance' THEN owner_account_id IS NULL AND user_id IS NULL\n WHEN 'account' THEN owner_account_id IS NOT NULL AND user_id IS NULL\n ELSE user_id IS NOT NULL AND owner_account_id IS NULL END" + }, + "ai_providers_key_pair_chk": { + "name": "ai_providers_key_pair_chk", + "value": "(key_ciphertext IS NULL) = (key_version IS NULL)" + }, + "ai_providers_compatible_chk": { + "name": "ai_providers_compatible_chk", + "value": "kind <> 'openai_compatible' OR base_url IS NOT NULL" + }, + "ai_providers_keyed_chk": { + "name": "ai_providers_keyed_chk", + "value": "kind = 'openai_compatible' OR key_ciphertext IS NOT NULL OR disabled_at IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.ai_usage_months": { + "name": "ai_usage_months", + "schema": "", + "columns": { + "month": { + "name": "month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "paying_scope": { + "name": "paying_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paying_account_id": { + "name": "paying_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "paying_user_id": { + "name": "paying_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_kind": { + "name": "provider_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_currency": { + "name": "cost_currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "calls": { + "name": "calls", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sent_calls": { + "name": "sent_calls", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_tokens": { + "name": "cached_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "images": { + "name": "images", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cost_amount": { + "name": "cost_amount", + "type": "numeric(18, 6)", + "primaryKey": false, + "notNull": false + }, + "unknown_cost_calls": { + "name": "unknown_cost_calls", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "outcomes": { + "name": "outcomes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "ai_usage_months_owner_idx": { + "name": "ai_usage_months_owner_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_usage_months_location_idx": { + "name": "ai_usage_months_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_usage_months_user_idx": { + "name": "ai_usage_months_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ai_usage_months_payer_idx": { + "name": "ai_usage_months_payer_idx", + "columns": [ + { + "expression": "paying_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ai_usage_months_uq": { + "name": "ai_usage_months_uq", + "columns": [ + "month", + "paying_scope", + "paying_account_id", + "paying_user_id", + "location_id", + "user_id", + "task", + "provider_kind", + "model", + "cost_currency" + ], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": { + "ai_usage_months_paying_scope_chk": { + "name": "ai_usage_months_paying_scope_chk", + "value": "\"paying_scope\" IN ('instance', 'account', 'user')" + } + }, + "isRLSEnabled": false + }, + "public.ai_usage_windows": { + "name": "ai_usage_windows", + "schema": "", + "columns": { + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "calls": { + "name": "calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "ai_usage_windows_start_idx": { + "name": "ai_usage_windows_start_idx", + "columns": [ + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ai_usage_windows_pk": { + "name": "ai_usage_windows_pk", + "columns": [ + "bucket", + "window_kind", + "window_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_usage_windows_window_kind_chk": { + "name": "ai_usage_windows_window_kind_chk", + "value": "\"window_kind\" IN ('minute', 'day', 'month')" + } + }, + "isRLSEnabled": false + }, + "public.llm_calls": { + "name": "llm_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "uuidv7()" + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "date_trunc('milliseconds', now())" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "budget_task": { + "name": "budget_task", + "type": "text", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "CASE WHEN task LIKE 'extract\\_%' OR task = 'enrich_aliases' THEN 'extraction'\n WHEN task LIKE 'assistant\\_%' THEN 'assistant'\n WHEN task LIKE 'embed\\_%' THEN 'embeddings' ELSE 'test' END" + } + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "paying_scope": { + "name": "paying_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "paying_account_id": { + "name": "paying_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "paying_user_id": { + "name": "paying_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fell_back": { + "name": "fell_back", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_id": { + "name": "provider_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_kind": { + "name": "provider_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent": { + "name": "sent", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "estimate_tokens": { + "name": "estimate_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "usage_estimated": { + "name": "usage_estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image_count": { + "name": "image_count", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "image_tokens_each": { + "name": "image_tokens_each", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_bytes": { + "name": "image_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_status": { + "name": "http_status", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "cost_amount": { + "name": "cost_amount", + "type": "numeric(16, 6)", + "primaryKey": false, + "notNull": false + }, + "cost_currency": { + "name": "cost_currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "cost_source": { + "name": "cost_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "extraction_id": { + "name": "extraction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rl_remaining_tokens": { + "name": "rl_remaining_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rl_reset_at": { + "name": "rl_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "llm_calls_user_idx": { + "name": "llm_calls_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "llm_calls_location_idx": { + "name": "llm_calls_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "llm_calls_owner_idx": { + "name": "llm_calls_owner_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "llm_calls_payer_idx": { + "name": "llm_calls_payer_idx", + "columns": [ + { + "expression": "paying_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "llm_calls_instance_idx": { + "name": "llm_calls_instance_idx", + "columns": [ + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "paying_scope = 'instance'", + "concurrently": false + }, + "llm_calls_thing_idx": { + "name": "llm_calls_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "thing_id IS NOT NULL", + "concurrently": false + }, + "llm_calls_extraction_idx": { + "name": "llm_calls_extraction_idx", + "columns": [ + { + "expression": "extraction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "extraction_id IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "llm_calls_pk": { + "name": "llm_calls_pk", + "columns": [ + "id", + "at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "llm_calls_task_chk": { + "name": "llm_calls_task_chk", + "value": "\"task\" IN ('extract_thing', 'extract_receipt', 'extract_label', 'extract_reading', 'assistant_turn', 'assistant_followup', 'embed_thing', 'embed_query', 'connection_test', 'enrich_aliases', 'extract_room', 'extract_room_merge', 'extract_plan')" + }, + "llm_calls_outcome_chk": { + "name": "llm_calls_outcome_chk", + "value": "\"outcome\" IN ('ok', 'refused', 'rate_limited', 'over_budget', 'provider_error', 'timeout', 'schema_invalid', 'truncated')" + }, + "llm_calls_cost_source_chk": { + "name": "llm_calls_cost_source_chk", + "value": "\"cost_source\" IN ('provider', 'price_table', 'price_table_later', 'unknown', 'not_sent')" + }, + "llm_calls_paying_scope_chk": { + "name": "llm_calls_paying_scope_chk", + "value": "\"paying_scope\" IN ('instance', 'account', 'user')" + }, + "llm_calls_request_id_chk": { + "name": "llm_calls_request_id_chk", + "value": "char_length(request_id) <= 64" + }, + "llm_calls_attempt_chk": { + "name": "llm_calls_attempt_chk", + "value": "attempt BETWEEN 1 AND 20" + }, + "llm_calls_prompt_version_chk": { + "name": "llm_calls_prompt_version_chk", + "value": "char_length(prompt_version) <= 20" + }, + "llm_calls_image_count_chk": { + "name": "llm_calls_image_count_chk", + "value": "image_count >= 0" + }, + "llm_calls_finish_reason_chk": { + "name": "llm_calls_finish_reason_chk", + "value": "char_length(finish_reason) <= 40" + }, + "llm_calls_error_code_chk": { + "name": "llm_calls_error_code_chk", + "value": "error_code ~ '^[a-z0-9_]{1,40}$'" + }, + "llm_calls_cost_pair_chk": { + "name": "llm_calls_cost_pair_chk", + "value": "(cost_amount IS NULL) = (cost_currency IS NULL)" + }, + "llm_calls_not_sent_chk": { + "name": "llm_calls_not_sent_chk", + "value": "sent OR (input_tokens IS NULL AND output_tokens IS NULL AND cost_amount IS NULL)" + }, + "llm_calls_sent_source_chk": { + "name": "llm_calls_sent_source_chk", + "value": "sent = (cost_source <> 'not_sent')" + }, + "llm_calls_price_chk": { + "name": "llm_calls_price_chk", + "value": "cost_source NOT IN ('price_table', 'price_table_later') OR price_id IS NOT NULL" + }, + "llm_calls_payer_chk": { + "name": "llm_calls_payer_chk", + "value": "CASE paying_scope\n WHEN 'instance' THEN paying_account_id IS NULL AND paying_user_id IS NULL\n WHEN 'account' THEN paying_account_id IS NOT NULL AND paying_user_id IS NULL\n ELSE paying_user_id IS NOT NULL AND paying_account_id IS NULL END" + } + }, + "isRLSEnabled": false + }, + "public.admin_alerts": { + "name": "admin_alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_at": { + "name": "first_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_at": { + "name": "last_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mailed_at": { + "name": "mailed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "admin_alerts_dedupe_key_unique": { + "name": "admin_alerts_dedupe_key_unique", + "columns": [ + "dedupe_key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "admin_alerts_kind_chk": { + "name": "admin_alerts_kind_chk", + "value": "\"kind\" IN ('failed_jobs_rising', 'audit_default_partition', 'llm_default_partition', 'ai_instance_cap_warning', 'ai_instance_cap_reached', 'ai_instance_key_rejected', 'backup_failed', 'reminders_not_scanned', 'backup_stale', 'disk_space_low', 'bucket_versioning_off', 'restore_drill_due', 'backup_suspicious_size', 'backup_verify_failed', 'webhook_failing')" + }, + "admin_alerts_count_chk": { + "name": "admin_alerts_count_chk", + "value": "count >= 1" + }, + "admin_alerts_seen_chk": { + "name": "admin_alerts_seen_chk", + "value": "last_at >= first_at" + } + }, + "isRLSEnabled": false + }, + "public.assistant_messages": { + "name": "assistant_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "step": { + "name": "step", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "parts": { + "name": "parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cited_location_ids": { + "name": "cited_location_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + }, + "redacted_at": { + "name": "redacted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assistant_messages_thread_idx": { + "name": "assistant_messages_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "assistant_messages_turn_idx": { + "name": "assistant_messages_turn_idx", + "columns": [ + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "assistant_messages_thread_fk": { + "name": "assistant_messages_thread_fk", + "tableFrom": "assistant_messages", + "columnsFrom": [ + "user_id", + "thread_id" + ], + "tableTo": "assistant_threads", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "assistant_messages_turn_fk": { + "name": "assistant_messages_turn_fk", + "tableFrom": "assistant_messages", + "columnsFrom": [ + "user_id", + "turn_id" + ], + "tableTo": "assistant_turns", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "assistant_messages_user_id_uq": { + "name": "assistant_messages_user_id_uq", + "columns": [ + "user_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "assistant_messages_role_chk": { + "name": "assistant_messages_role_chk", + "value": "\"role\" IN ('user', 'assistant', 'tool')" + }, + "assistant_messages_parts_chk": { + "name": "assistant_messages_parts_chk", + "value": "jsonb_typeof(parts) = 'array'" + }, + "assistant_messages_step_chk": { + "name": "assistant_messages_step_chk", + "value": "step BETWEEN 0 AND 20" + } + }, + "isRLSEnabled": false + }, + "public.assistant_proposals": { + "name": "assistant_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "name": "batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "args_hash": { + "name": "args_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "before": { + "name": "before", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "refs": { + "name": "refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "audit_event_id": { + "name": "audit_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "assistant_proposals_thread_idx": { + "name": "assistant_proposals_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "assistant_proposals_turn_idx": { + "name": "assistant_proposals_turn_idx", + "columns": [ + { + "expression": "turn_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "assistant_proposals_open_idx": { + "name": "assistant_proposals_open_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'open'", + "concurrently": false + }, + "assistant_proposals_location_idx": { + "name": "assistant_proposals_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "assistant_proposals_location_id_locations_id_fk": { + "name": "assistant_proposals_location_id_locations_id_fk", + "tableFrom": "assistant_proposals", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "assistant_proposals_thread_fk": { + "name": "assistant_proposals_thread_fk", + "tableFrom": "assistant_proposals", + "columnsFrom": [ + "user_id", + "thread_id" + ], + "tableTo": "assistant_threads", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "assistant_proposals_turn_fk": { + "name": "assistant_proposals_turn_fk", + "tableFrom": "assistant_proposals", + "columnsFrom": [ + "user_id", + "turn_id" + ], + "tableTo": "assistant_turns", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "assistant_proposals_status_chk": { + "name": "assistant_proposals_status_chk", + "value": "\"status\" IN ('open', 'confirmed', 'cancelled', 'expired', 'conflict', 'failed')" + }, + "assistant_proposals_tool_chk": { + "name": "assistant_proposals_tool_chk", + "value": "tool ~ '^[a-z][a-z0-9_]{0,63}$'" + }, + "assistant_proposals_args_chk": { + "name": "assistant_proposals_args_chk", + "value": "jsonb_typeof(args) = 'object'" + }, + "assistant_proposals_args_hash_chk": { + "name": "assistant_proposals_args_hash_chk", + "value": "args_hash ~ '^[0-9a-f]{64}$'" + }, + "assistant_proposals_objects_chk": { + "name": "assistant_proposals_objects_chk", + "value": "jsonb_typeof(before) = 'object' AND jsonb_typeof(refs) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.assistant_threads": { + "name": "assistant_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "search_tsv": { + "name": "search_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now() + interval '90 days'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "assistant_threads_user_idx": { + "name": "assistant_threads_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "assistant_threads_tsv_idx": { + "name": "assistant_threads_tsv_idx", + "columns": [ + { + "expression": "search_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "assistant_threads_expiry_idx": { + "name": "assistant_threads_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "assistant_threads_user_id_user_id_fk": { + "name": "assistant_threads_user_id_user_id_fk", + "tableFrom": "assistant_threads", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "assistant_threads_user_id_uq": { + "name": "assistant_threads_user_id_uq", + "columns": [ + "user_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "assistant_threads_title_chk": { + "name": "assistant_threads_title_chk", + "value": "char_length(title) <= 120" + }, + "assistant_threads_context_chk": { + "name": "assistant_threads_context_chk", + "value": "jsonb_typeof(context) = 'object'" + }, + "assistant_threads_locale_chk": { + "name": "assistant_threads_locale_chk", + "value": "char_length(locale) BETWEEN 1 AND 20" + } + }, + "isRLSEnabled": false + }, + "public.assistant_tool_results": { + "name": "assistant_tool_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "call_id": { + "name": "call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "redacted_at": { + "name": "redacted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "assistant_tool_results_message_idx": { + "name": "assistant_tool_results_message_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "assistant_tool_results_redact_idx": { + "name": "assistant_tool_results_redact_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "redacted_at IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "assistant_tool_results_message_fk": { + "name": "assistant_tool_results_message_fk", + "tableFrom": "assistant_tool_results", + "columnsFrom": [ + "user_id", + "message_id" + ], + "tableTo": "assistant_messages", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "assistant_tool_results_call_id_chk": { + "name": "assistant_tool_results_call_id_chk", + "value": "char_length(call_id) BETWEEN 1 AND 100" + }, + "assistant_tool_results_tool_chk": { + "name": "assistant_tool_results_tool_chk", + "value": "tool ~ '^[a-z][a-z0-9_]{0,63}$'" + }, + "assistant_tool_results_redacted_chk": { + "name": "assistant_tool_results_redacted_chk", + "value": "(output IS NULL) = (redacted_at IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.assistant_turns": { + "name": "assistant_turns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_until": { + "name": "paused_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steps": { + "name": "steps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "location_ids": { + "name": "location_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "assistant_turns_one_live_uq": { + "name": "assistant_turns_one_live_uq", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status IN ('queued', 'running', 'waiting_provider')", + "concurrently": false + }, + "assistant_turns_thread_idx": { + "name": "assistant_turns_thread_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "assistant_turns_thread_fk": { + "name": "assistant_turns_thread_fk", + "tableFrom": "assistant_turns", + "columnsFrom": [ + "user_id", + "thread_id" + ], + "tableTo": "assistant_threads", + "columnsTo": [ + "user_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "assistant_turns_user_id_uq": { + "name": "assistant_turns_user_id_uq", + "columns": [ + "user_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "assistant_turns_status_chk": { + "name": "assistant_turns_status_chk", + "value": "\"status\" IN ('queued', 'running', 'waiting_provider', 'paused_budget', 'done', 'failed', 'cancelled')" + }, + "assistant_turns_status_reason_chk": { + "name": "assistant_turns_status_reason_chk", + "value": "char_length(status_reason) <= 60" + }, + "assistant_turns_steps_chk": { + "name": "assistant_turns_steps_chk", + "value": "steps BETWEEN 0 AND 20" + } + }, + "isRLSEnabled": false + }, + "public.audit_event_subjects": { + "name": "audit_event_subjects", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_at": { + "name": "event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "audit_event_subjects_thing_idx": { + "name": "audit_event_subjects_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "audit_event_subjects_event_fk": { + "name": "audit_event_subjects_event_fk", + "tableFrom": "audit_event_subjects", + "columnsFrom": [ + "event_id", + "event_at", + "location_id" + ], + "tableTo": "audit_events", + "columnsTo": [ + "id", + "at", + "location_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "audit_event_subjects_event_id_event_at_thing_id_pk": { + "name": "audit_event_subjects_event_id_event_at_thing_id_pk", + "columns": [ + "event_id", + "event_at", + "thing_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "uuidv7()" + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "date_trunc('milliseconds', now())" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "root_thing_id": { + "name": "root_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "diff": { + "name": "diff", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "undo_of": { + "name": "undo_of", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "undoable_until": { + "name": "undoable_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "audit_events_entity_idx": { + "name": "audit_events_entity_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_events_location_at_idx": { + "name": "audit_events_location_at_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_events_actor_idx": { + "name": "audit_events_actor_idx", + "columns": [ + { + "expression": "actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_events_root_thing_idx": { + "name": "audit_events_root_thing_idx", + "columns": [ + { + "expression": "root_thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "root_thing_id IS NOT NULL", + "concurrently": false + }, + "audit_events_account_at_idx": { + "name": "audit_events_account_at_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "location_id IS NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "audit_events_id_at_pk": { + "name": "audit_events_id_at_pk", + "columns": [ + "id", + "at" + ] + } + }, + "uniqueConstraints": { + "audit_events_id_at_location_uq": { + "name": "audit_events_id_at_location_uq", + "columns": [ + "id", + "at", + "location_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "audit_events_actor_type_chk": { + "name": "audit_events_actor_type_chk", + "value": "\"actor_type\" IN ('user', 'token', 'system', 'import')" + } + }, + "isRLSEnabled": false + }, + "auth.account": { + "name": "account", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.jwks": { + "name": "jwks", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "crv": { + "name": "crv", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_access_token": { + "name": "oauth_access_token", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmation": { + "name": "confirmation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauthAccessToken_clientId_idx": { + "name": "oauthAccessToken_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthAccessToken_sessionId_idx": { + "name": "oauthAccessToken_sessionId_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthAccessToken_userId_idx": { + "name": "oauthAccessToken_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthAccessToken_authorizationCodeId_idx": { + "name": "oauthAccessToken_authorizationCodeId_idx", + "columns": [ + { + "expression": "authorization_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthAccessToken_refreshId_idx": { + "name": "oauthAccessToken_refreshId_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "schemaTo": "auth", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": [ + "refresh_id" + ], + "tableTo": "oauth_refresh_token", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_client": { + "name": "oauth_client", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_discovery_id": { + "name": "client_discovery_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "client_credentials_scopes": { + "name": "client_credentials_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_type": { + "name": "application_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "dpop_bound_access_tokens": { + "name": "dpop_bound_access_tokens", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauthClient_userId_idx": { + "name": "oauthClient_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_client_assertion": { + "name": "oauth_client_assertion", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_client_resource": { + "name": "oauth_client_resource", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauthClientResource_clientId_resourceId_uidx": { + "name": "oauthClientResource_clientId_resourceId_uidx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthClientResource_clientId_idx": { + "name": "oauthClientResource_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthClientResource_resourceId_idx": { + "name": "oauthClientResource_resourceId_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_client_resource_client_id_oauth_client_client_id_fk": { + "name": "oauth_client_resource_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_client_resource", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "schemaTo": "auth", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_client_resource_resource_id_oauth_resource_identifier_fk": { + "name": "oauth_client_resource_resource_id_oauth_resource_identifier_fk", + "tableFrom": "oauth_client_resource", + "columnsFrom": [ + "resource_id" + ], + "tableTo": "oauth_resource", + "schemaTo": "auth", + "columnsTo": [ + "identifier" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_consent": { + "name": "oauth_consent", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauthConsent_clientId_idx": { + "name": "oauthConsent_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthConsent_userId_idx": { + "name": "oauthConsent_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "schemaTo": "auth", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rotation_replay_response": { + "name": "rotation_replay_response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rotation_replay_expires_at": { + "name": "rotation_replay_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmation": { + "name": "confirmation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauthRefreshToken_clientId_idx": { + "name": "oauthRefreshToken_clientId_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthRefreshToken_sessionId_idx": { + "name": "oauthRefreshToken_sessionId_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthRefreshToken_userId_idx": { + "name": "oauthRefreshToken_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauthRefreshToken_authorizationCodeId_idx": { + "name": "oauthRefreshToken_authorizationCodeId_idx", + "columns": [ + { + "expression": "authorization_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "client_id" + ], + "tableTo": "oauth_client", + "schemaTo": "auth", + "columnsTo": [ + "client_id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_resource": { + "name": "oauth_resource", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_ttl": { + "name": "access_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refresh_token_ttl": { + "name": "refresh_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signing_algorithm": { + "name": "signing_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signing_key_id": { + "name": "signing_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_scopes": { + "name": "allowed_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "custom_claims": { + "name": "custom_claims", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dpop_bound_access_tokens_required": { + "name": "dpop_bound_access_tokens_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_resource_identifier_unique": { + "name": "oauth_resource_identifier_unique", + "columns": [ + "identifier" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.passkey": { + "name": "passkey", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "passkey_credentialID_idx": { + "name": "passkey_credentialID_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.rate_limit": { + "name": "rate_limit", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_request": { + "name": "last_request", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "rate_limit_key_unique": { + "name": "rate_limit_key_unique", + "columns": [ + "key" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.session": { + "name": "session", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.session_mfa": { + "name": "session_mfa", + "schema": "auth", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "satisfied_at": { + "name": "satisfied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_mfa_session_id_session_id_fk": { + "name": "session_mfa_session_id_session_id_fk", + "tableFrom": "session_mfa", + "columnsFrom": [ + "session_id" + ], + "tableTo": "session", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sign_in_failures": { + "name": "sign_in_failures", + "schema": "auth", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_failure_at": { + "name": "last_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.two_factor": { + "name": "two_factor", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "failed_verification_count": { + "name": "failed_verification_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "twoFactor_secret_idx": { + "name": "twoFactor_secret_idx", + "columns": [ + { + "expression": "secret", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "twoFactor_userId_idx": { + "name": "twoFactor_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.user": { + "name": "user", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "nullsNotDistinct": false + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.user_deletion": { + "name": "user_deletion", + "schema": "auth", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "purge_after": { + "name": "purge_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_deletion_user_id_user_id_fk": { + "name": "user_deletion_user_id_user_id_fk", + "tableFrom": "user_deletion", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verification": { + "name": "verification", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()::text" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.extractions": { + "name": "extractions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_id": { + "name": "purchase_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meter_id": { + "name": "meter_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "service_record_id": { + "name": "service_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_capture_id": { + "name": "room_capture_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "floor_plan_id": { + "name": "floor_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_until": { + "name": "paused_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "llm_call_id": { + "name": "llm_call_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "output_cap": { + "name": "output_cap", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "requested_by": { + "name": "requested_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "extractions_room_capture_idx": { + "name": "extractions_room_capture_idx", + "columns": [ + { + "expression": "room_capture_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "room_capture_id IS NOT NULL", + "concurrently": false + }, + "extractions_floor_plan_idx": { + "name": "extractions_floor_plan_idx", + "columns": [ + { + "expression": "floor_plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "floor_plan_id IS NOT NULL", + "concurrently": false + }, + "extractions_live_uq": { + "name": "extractions_live_uq", + "columns": [ + { + "expression": "attachment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status IN ('queued', 'running', 'paused_budget', 'waiting_provider')", + "concurrently": false + }, + "extractions_thing_idx": { + "name": "extractions_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "thing_id IS NOT NULL", + "concurrently": false + }, + "extractions_purchase_idx": { + "name": "extractions_purchase_idx", + "columns": [ + { + "expression": "purchase_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "purchase_id IS NOT NULL", + "concurrently": false + }, + "extractions_service_record_idx": { + "name": "extractions_service_record_idx", + "columns": [ + { + "expression": "service_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "service_record_id IS NOT NULL", + "concurrently": false + }, + "extractions_paused_idx": { + "name": "extractions_paused_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'paused_budget'", + "concurrently": false + } + }, + "foreignKeys": { + "extractions_location_id_locations_id_fk": { + "name": "extractions_location_id_locations_id_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "extractions_attachment_fk": { + "name": "extractions_attachment_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "attachment_id" + ], + "tableTo": "attachments", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_thing_fk": { + "name": "extractions_thing_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_purchase_fk": { + "name": "extractions_purchase_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "purchase_id" + ], + "tableTo": "purchases", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_meter_fk": { + "name": "extractions_meter_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "meter_id" + ], + "tableTo": "meters", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_service_record_fk": { + "name": "extractions_service_record_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "service_record_id" + ], + "tableTo": "service_records", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_room_capture_fk": { + "name": "extractions_room_capture_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "room_capture_id" + ], + "tableTo": "room_captures", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "extractions_floor_plan_fk": { + "name": "extractions_floor_plan_fk", + "tableFrom": "extractions", + "columnsFrom": [ + "location_id", + "floor_plan_id" + ], + "tableTo": "floor_plans", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "extractions_location_id_uq": { + "name": "extractions_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + }, + "extractions_attempt_uq": { + "name": "extractions_attempt_uq", + "columns": [ + "attachment_id", + "attempt" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "extractions_mode_chk": { + "name": "extractions_mode_chk", + "value": "\"mode\" IN ('thing', 'receipt', 'label', 'reading', 'room', 'plan')" + }, + "extractions_status_chk": { + "name": "extractions_status_chk", + "value": "\"status\" IN ('queued', 'running', 'succeeded', 'failed', 'paused_budget', 'waiting_provider', 'no_provider', 'superseded')" + }, + "extractions_attempt_chk": { + "name": "extractions_attempt_chk", + "value": "attempt BETWEEN 1 AND 50" + }, + "extractions_status_reason_chk": { + "name": "extractions_status_reason_chk", + "value": "char_length(status_reason) <= 60" + }, + "extractions_output_cap_chk": { + "name": "extractions_output_cap_chk", + "value": "output_cap BETWEEN 64 AND 200000" + }, + "extractions_one_draft_chk": { + "name": "extractions_one_draft_chk", + "value": "num_nonnulls(thing_id, purchase_id, meter_id, service_record_id) <= 1" + }, + "extractions_result_chk": { + "name": "extractions_result_chk", + "value": "result IS NULL OR jsonb_typeof(result) = 'object'" + }, + "extractions_applied_chk": { + "name": "extractions_applied_chk", + "value": "jsonb_typeof(applied) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.file_text": { + "name": "file_text", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tsv": { + "name": "tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "to_tsvector('simple', kept.search_text(left(text, 100000)))" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "file_text_tsv_idx": { + "name": "file_text_tsv_idx", + "columns": [ + { + "expression": "tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "file_text_location_idx": { + "name": "file_text_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "file_text_file_fk": { + "name": "file_text_file_fk", + "tableFrom": "file_text", + "columnsFrom": [ + "location_id", + "file_id" + ], + "tableTo": "files", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "file_text_source_chk": { + "name": "file_text_source_chk", + "value": "\"source\" IN ('pdf', 'extraction')" + }, + "file_text_text_chk": { + "name": "file_text_text_chk", + "value": "char_length(text) <= 200000" + } + }, + "isRLSEnabled": false + }, + "public.inbox_items": { + "name": "inbox_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_id": { + "name": "purchase_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meter_reading_id": { + "name": "meter_reading_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "extraction_id": { + "name": "extraction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "other_thing_id": { + "name": "other_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_capture_id": { + "name": "room_capture_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "char(6)", + "primaryKey": false, + "notNull": false + }, + "batch_id": { + "name": "batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolution": { + "name": "resolution", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_open_subject_uq": { + "name": "inbox_open_subject_uq", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(thing_id, purchase_id, meter_reading_id)", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(other_thing_id, '00000000-0000-0000-0000-000000000000'::uuid)", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(code, '')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "resolved_at IS NULL", + "concurrently": false + }, + "inbox_open_loc_idx": { + "name": "inbox_open_loc_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "resolved_at IS NULL", + "concurrently": false + }, + "inbox_open_mine_idx": { + "name": "inbox_open_mine_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "resolved_at IS NULL", + "concurrently": false + }, + "inbox_resolved_idx": { + "name": "inbox_resolved_idx", + "columns": [ + { + "expression": "resolved_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "resolved_at IS NOT NULL", + "concurrently": false + }, + "inbox_items_thing_idx": { + "name": "inbox_items_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "thing_id IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "inbox_items_location_id_locations_id_fk": { + "name": "inbox_items_location_id_locations_id_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "inbox_items_thing_fk": { + "name": "inbox_items_thing_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "inbox_items_purchase_fk": { + "name": "inbox_items_purchase_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "purchase_id" + ], + "tableTo": "purchases", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "inbox_items_meter_reading_fk": { + "name": "inbox_items_meter_reading_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "meter_reading_id" + ], + "tableTo": "meter_readings", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "inbox_items_extraction_fk": { + "name": "inbox_items_extraction_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "extraction_id" + ], + "tableTo": "extractions", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "inbox_items_room_capture_fk": { + "name": "inbox_items_room_capture_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "room_capture_id" + ], + "tableTo": "room_captures", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "inbox_items_other_thing_fk": { + "name": "inbox_items_other_thing_fk", + "tableFrom": "inbox_items", + "columnsFrom": [ + "location_id", + "other_thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "inbox_items_location_id_uq": { + "name": "inbox_items_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "inbox_items_kind_chk": { + "name": "inbox_items_kind_chk", + "value": "\"kind\" IN ('draft', 'reading', 'label_claim', 'currency', 'duplicate', 'receipt', 'sync_drop', 'room')" + }, + "inbox_items_resolution_chk": { + "name": "inbox_items_resolution_chk", + "value": "\"resolution\" IN ('accepted', 'edited', 'discarded', 'merged', 'linked', 'restored', 'dismissed')" + }, + "inbox_items_code_chk": { + "name": "inbox_items_code_chk", + "value": "code ~ '^[0-9A-HJKMNP-TV-Z]{6}$'" + }, + "inbox_items_payload_chk": { + "name": "inbox_items_payload_chk", + "value": "jsonb_typeof(payload) = 'object'" + }, + "inbox_items_resolved_chk": { + "name": "inbox_items_resolved_chk", + "value": "(resolved_at IS NULL) = (resolution IS NULL)" + }, + "inbox_items_subject_chk": { + "name": "inbox_items_subject_chk", + "value": "kind = 'sync_drop' OR num_nonnulls(thing_id, purchase_id, meter_reading_id, code, room_capture_id) >= 1" + } + }, + "isRLSEnabled": false + }, + "public.template_locations": { + "name": "template_locations", + "schema": "", + "columns": { + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "template_locations_location_idx": { + "name": "template_locations_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "template_locations_location_id_locations_id_fk": { + "name": "template_locations_location_id_locations_id_fk", + "tableFrom": "template_locations", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "template_locations_template_fk": { + "name": "template_locations_template_fk", + "tableFrom": "template_locations", + "columnsFrom": [ + "owner_account_id", + "template_id" + ], + "tableTo": "templates", + "columnsTo": [ + "owner_account_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "template_locations_pk": { + "name": "template_locations_pk", + "columns": [ + "template_id", + "location_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type_id": { + "name": "type_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "applies_to": { + "name": "applies_to", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'thing'" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "templates_type_idx": { + "name": "templates_type_idx", + "columns": [ + { + "expression": "type_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "templates_owner_account_id_owner_accounts_id_fk": { + "name": "templates_owner_account_id_owner_accounts_id_fk", + "tableFrom": "templates", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "templates_type_id_types_id_fk": { + "name": "templates_type_id_types_id_fk", + "tableFrom": "templates", + "columnsFrom": [ + "type_id" + ], + "tableTo": "types", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "templates_owner_account_id_uq": { + "name": "templates_owner_account_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "templates_applies_to_chk": { + "name": "templates_applies_to_chk", + "value": "applies_to IN ('thing', 'place')" + }, + "templates_name_chk": { + "name": "templates_name_chk", + "value": "char_length(name) BETWEEN 1 AND 80" + }, + "templates_payload_chk": { + "name": "templates_payload_chk", + "value": "jsonb_typeof(payload) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.condition_report_rooms": { + "name": "condition_report_rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "report_id": { + "name": "report_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "condition_report_rooms_place_uq": { + "name": "condition_report_rooms_place_uq", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "place_id IS NOT NULL", + "concurrently": false + }, + "condition_report_rooms_report_idx": { + "name": "condition_report_rooms_report_idx", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "condition_report_rooms_place_idx": { + "name": "condition_report_rooms_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "condition_report_rooms_report_fk": { + "name": "condition_report_rooms_report_fk", + "tableFrom": "condition_report_rooms", + "columnsFrom": [ + "location_id", + "report_id" + ], + "tableTo": "condition_reports", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "condition_report_rooms_location_id_uq": { + "name": "condition_report_rooms_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "condition_report_rooms_condition_chk": { + "name": "condition_report_rooms_condition_chk", + "value": "\"condition\" IN ('good', 'fair', 'poor', 'damaged')" + }, + "condition_report_rooms_name_chk": { + "name": "condition_report_rooms_name_chk", + "value": "char_length(btrim(name)) BETWEEN 1 AND 200" + }, + "condition_report_rooms_note_chk": { + "name": "condition_report_rooms_note_chk", + "value": "char_length(note) <= 4000" + } + }, + "isRLSEnabled": false + }, + "public.condition_reports": { + "name": "condition_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "held_on": { + "name": "held_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_by": { + "name": "finished_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "condition_reports_location_idx": { + "name": "condition_reports_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "held_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "condition_reports_location_id_locations_id_fk": { + "name": "condition_reports_location_id_locations_id_fk", + "tableFrom": "condition_reports", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "condition_reports_location_id_uq": { + "name": "condition_reports_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "condition_reports_kind_chk": { + "name": "condition_reports_kind_chk", + "value": "\"kind\" IN ('move_in', 'move_out')" + }, + "condition_reports_notes_chk": { + "name": "condition_reports_notes_chk", + "value": "char_length(notes) <= 4000" + }, + "condition_reports_finished_chk": { + "name": "condition_reports_finished_chk", + "value": "(finished_at IS NULL) = (finished_by IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.stock_rules": { + "name": "stock_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "min_quantity": { + "name": "min_quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true + }, + "low_since": { + "name": "low_since", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "stock_rules_location_idx": { + "name": "stock_rules_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "stock_rules_location_id_locations_id_fk": { + "name": "stock_rules_location_id_locations_id_fk", + "tableFrom": "stock_rules", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "stock_rules_thing_fk": { + "name": "stock_rules_thing_fk", + "tableFrom": "stock_rules", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "stock_rules_thing_uq": { + "name": "stock_rules_thing_uq", + "columns": [ + "thing_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "stock_rules_min_quantity_chk": { + "name": "stock_rules_min_quantity_chk", + "value": "min_quantity > 0 AND min_quantity <= 1000000" + } + }, + "isRLSEnabled": false + }, + "public.currencies": { + "name": "currencies", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "char(3)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "minor_units": { + "name": "minor_units", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "symbol": { + "name": "symbol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "currencies_code_chk": { + "name": "currencies_code_chk", + "value": "code ~ '^[A-Z]{3}$'" + }, + "currencies_minor_units_chk": { + "name": "currencies_minor_units_chk", + "value": "minor_units BETWEEN 0 AND 4" + } + }, + "isRLSEnabled": false + }, + "public.embedding_state": { + "name": "embedding_state", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "model_key": { + "name": "model_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pending": { + "name": "pending", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "paused_reason": { + "name": "paused_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_until": { + "name": "paused_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "embedding_state_location_id_locations_id_fk": { + "name": "embedding_state_location_id_locations_id_fk", + "tableFrom": "embedding_state", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_state_model_key_chk": { + "name": "embedding_state_model_key_chk", + "value": "char_length(model_key) BETWEEN 1 AND 200" + }, + "embedding_state_source_chk": { + "name": "embedding_state_source_chk", + "value": "source IN ('provider', 'local')" + }, + "embedding_state_pending_chk": { + "name": "embedding_state_pending_chk", + "value": "pending >= 0" + }, + "embedding_state_paused_reason_chk": { + "name": "embedding_state_paused_reason_chk", + "value": "char_length(paused_reason) <= 60" + } + }, + "isRLSEnabled": false + }, + "public.thing_embeddings": { + "name": "thing_embeddings", + "schema": "", + "columns": { + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_key": { + "name": "model_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dims": { + "name": "dims", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector", + "primaryKey": false, + "notNull": true + }, + "embedded_at": { + "name": "embedded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "thing_embeddings_loc_model_idx": { + "name": "thing_embeddings_loc_model_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "thing_embeddings_thing_fk": { + "name": "thing_embeddings_thing_fk", + "tableFrom": "thing_embeddings", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "thing_embeddings_pk": { + "name": "thing_embeddings_pk", + "columns": [ + "thing_id", + "model_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "thing_embeddings_model_key_chk": { + "name": "thing_embeddings_model_key_chk", + "value": "char_length(model_key) BETWEEN 1 AND 200" + }, + "thing_embeddings_dims_chk": { + "name": "thing_embeddings_dims_chk", + "value": "dims BETWEEN 1 AND 2000" + }, + "thing_embeddings_hash_chk": { + "name": "thing_embeddings_hash_chk", + "value": "content_hash ~ '^[0-9a-f]{64}$'" + }, + "thing_embeddings_vector_chk": { + "name": "thing_embeddings_vector_chk", + "value": "vector_dims(embedding) = dims" + } + }, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_id": { + "name": "purchase_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meter_reading_id": { + "name": "meter_reading_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "warranty_id": { + "name": "warranty_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "claim_id": { + "name": "claim_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "loan_id": { + "name": "loan_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "incident_id": { + "name": "incident_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "valuation_id": { + "name": "valuation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "service_record_id": { + "name": "service_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiring_document_id": { + "name": "expiring_document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fuel_entry_id": { + "name": "fuel_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "condition_room_id": { + "name": "condition_room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "floor_plan_id": { + "name": "floor_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "in_review": { + "name": "in_review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "attachments_warranty_idx": { + "name": "attachments_warranty_idx", + "columns": [ + { + "expression": "warranty_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_claim_idx": { + "name": "attachments_claim_idx", + "columns": [ + { + "expression": "claim_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_loan_idx": { + "name": "attachments_loan_idx", + "columns": [ + { + "expression": "loan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_incident_idx": { + "name": "attachments_incident_idx", + "columns": [ + { + "expression": "incident_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_valuation_idx": { + "name": "attachments_valuation_idx", + "columns": [ + { + "expression": "valuation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_service_record_idx": { + "name": "attachments_service_record_idx", + "columns": [ + { + "expression": "service_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_expiring_document_idx": { + "name": "attachments_expiring_document_idx", + "columns": [ + { + "expression": "expiring_document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_fuel_entry_idx": { + "name": "attachments_fuel_entry_idx", + "columns": [ + { + "expression": "fuel_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_condition_room_idx": { + "name": "attachments_condition_room_idx", + "columns": [ + { + "expression": "condition_room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_floor_plan_idx": { + "name": "attachments_floor_plan_idx", + "columns": [ + { + "expression": "floor_plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_file_idx": { + "name": "attachments_file_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_thing_idx": { + "name": "attachments_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_place_idx": { + "name": "attachments_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_purchase_idx": { + "name": "attachments_purchase_idx", + "columns": [ + { + "expression": "purchase_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_meter_reading_idx": { + "name": "attachments_meter_reading_idx", + "columns": [ + { + "expression": "meter_reading_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_location_idx": { + "name": "attachments_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "attachments_location_id_locations_id_fk": { + "name": "attachments_location_id_locations_id_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "attachments_thing_fk": { + "name": "attachments_thing_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_place_fk": { + "name": "attachments_place_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_purchase_fk": { + "name": "attachments_purchase_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "purchase_id" + ], + "tableTo": "purchases", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_meter_reading_fk": { + "name": "attachments_meter_reading_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "meter_reading_id" + ], + "tableTo": "meter_readings", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_warranty_fk": { + "name": "attachments_warranty_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "warranty_id" + ], + "tableTo": "warranties", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_claim_fk": { + "name": "attachments_claim_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "claim_id" + ], + "tableTo": "claims", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_loan_fk": { + "name": "attachments_loan_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "loan_id" + ], + "tableTo": "loans", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_incident_fk": { + "name": "attachments_incident_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "incident_id" + ], + "tableTo": "incidents", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_valuation_fk": { + "name": "attachments_valuation_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "valuation_id" + ], + "tableTo": "valuations", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_service_record_fk": { + "name": "attachments_service_record_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "service_record_id" + ], + "tableTo": "service_records", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_expiring_document_fk": { + "name": "attachments_expiring_document_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "expiring_document_id" + ], + "tableTo": "expiring_documents", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_fuel_entry_fk": { + "name": "attachments_fuel_entry_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "fuel_entry_id" + ], + "tableTo": "fuel_entries", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_condition_room_fk": { + "name": "attachments_condition_room_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "condition_room_id" + ], + "tableTo": "condition_report_rooms", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "attachments_floor_plan_fk": { + "name": "attachments_floor_plan_fk", + "tableFrom": "attachments", + "columnsFrom": [ + "location_id", + "floor_plan_id" + ], + "tableTo": "floor_plans", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "attachments_location_id_uq": { + "name": "attachments_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "attachments_role_chk": { + "name": "attachments_role_chk", + "value": "\"role\" IN ('photo', 'receipt', 'invoice', 'manual', 'warranty_doc', 'proof', 'condition_out', 'condition_in', 'registration', 'document')" + }, + "attachments_file_or_url_chk": { + "name": "attachments_file_or_url_chk", + "value": "num_nonnulls(file_id, url) = 1" + }, + "attachments_one_subject_chk": { + "name": "attachments_one_subject_chk", + "value": "num_nonnulls(thing_id, place_id, purchase_id, meter_reading_id, warranty_id, claim_id,\n loan_id, incident_id, valuation_id, service_record_id,\n expiring_document_id, fuel_entry_id, condition_room_id,\n floor_plan_id) <= 1" + }, + "attachments_name_chk": { + "name": "attachments_name_chk", + "value": "name IS NULL OR char_length(name) BETWEEN 1 AND 200" + }, + "attachments_url_chk": { + "name": "attachments_url_chk", + "value": "url IS NULL OR (url ~ '^https?://' AND char_length(url) <= 2000)" + } + }, + "isRLSEnabled": false + }, + "public.file_derivatives": { + "name": "file_derivatives", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "file_derivatives_storage_key_idx": { + "name": "file_derivatives_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "file_derivatives_file_fk": { + "name": "file_derivatives_file_fk", + "tableFrom": "file_derivatives", + "columnsFrom": [ + "location_id", + "file_id" + ], + "tableTo": "files", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "file_derivatives_pk": { + "name": "file_derivatives_pk", + "columns": [ + "file_id", + "variant" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "file_derivatives_variant_chk": { + "name": "file_derivatives_variant_chk", + "value": "\"variant\" IN ('display', 'thumb', 'share', 'poster')" + }, + "file_derivatives_size_chk": { + "name": "file_derivatives_size_chk", + "value": "width > 0 AND height > 0 AND bytes > 0" + } + }, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "char(64)", + "primaryKey": false, + "notNull": true + }, + "bytes": { + "name": "bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "class": { + "name": "class", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "has_gps": { + "name": "has_gps", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "derivative_state": { + "name": "derivative_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attached_at": { + "name": "attached_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "files_storage_key_idx": { + "name": "files_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "files_unattached_idx": { + "name": "files_unattached_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "files_location_id_locations_id_fk": { + "name": "files_location_id_locations_id_fk", + "tableFrom": "files", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "files_location_sha_uq": { + "name": "files_location_sha_uq", + "columns": [ + "location_id", + "sha256" + ], + "nullsNotDistinct": false + }, + "files_location_id_uq": { + "name": "files_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "files_class_chk": { + "name": "files_class_chk", + "value": "\"class\" IN ('evidence', 'photo', 'document', 'video')" + }, + "files_derivative_state_chk": { + "name": "files_derivative_state_chk", + "value": "\"derivative_state\" IN ('ready', 'unavailable', 'not_applicable')" + }, + "files_sha256_chk": { + "name": "files_sha256_chk", + "value": "sha256 ~ '^[0-9a-f]{64}$'" + }, + "files_bytes_chk": { + "name": "files_bytes_chk", + "value": "bytes > 0" + }, + "files_storage_key_chk": { + "name": "files_storage_key_chk", + "value": "char_length(storage_key) BETWEEN 1 AND 300" + }, + "files_mime_chk": { + "name": "files_mime_chk", + "value": "char_length(mime) BETWEEN 1 AND 100" + }, + "files_size_chk": { + "name": "files_size_chk", + "value": "(width IS NULL OR width > 0) AND (height IS NULL OR height > 0)" + } + }, + "isRLSEnabled": false + }, + "public.floor_plans": { + "name": "floor_plans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reading'" + }, + "turn": { + "name": "turn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "keep_plan": { + "name": "keep_plan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "applied_event_id": { + "name": "applied_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "floor_plans_location_idx": { + "name": "floor_plans_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "floor_plans_location_id_locations_id_fk": { + "name": "floor_plans_location_id_locations_id_fk", + "tableFrom": "floor_plans", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "floor_plans_location_id_uq": { + "name": "floor_plans_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "floor_plans_status_chk": { + "name": "floor_plans_status_chk", + "value": "\"status\" IN ('reading', 'ready', 'failed', 'off', 'applied', 'discarded')" + }, + "floor_plans_turn_chk": { + "name": "floor_plans_turn_chk", + "value": "turn IN (0, 90, 180, 270)" + }, + "floor_plans_result_chk": { + "name": "floor_plans_result_chk", + "value": "result IS NULL OR jsonb_typeof(result) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.fuel_entries": { + "name": "fuel_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "taken_at": { + "name": "taken_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric(10, 3)", + "primaryKey": false, + "notNull": true + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "is_full": { + "name": "is_full", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "missed_before": { + "name": "missed_before", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meter_reading_id": { + "name": "meter_reading_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logged_by": { + "name": "logged_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "fuel_entries_thing_idx": { + "name": "fuel_entries_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "taken_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "fuel_entries_vendor_idx": { + "name": "fuel_entries_vendor_idx", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "fuel_entries_reading_uq": { + "name": "fuel_entries_reading_uq", + "columns": [ + { + "expression": "meter_reading_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "meter_reading_id IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "fuel_entries_location_id_locations_id_fk": { + "name": "fuel_entries_location_id_locations_id_fk", + "tableFrom": "fuel_entries", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "fuel_entries_currency_currencies_code_fk": { + "name": "fuel_entries_currency_currencies_code_fk", + "tableFrom": "fuel_entries", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "fuel_entries_vendor_id_vendors_id_fk": { + "name": "fuel_entries_vendor_id_vendors_id_fk", + "tableFrom": "fuel_entries", + "columnsFrom": [ + "vendor_id" + ], + "tableTo": "vendors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "fuel_entries_thing_fk": { + "name": "fuel_entries_thing_fk", + "tableFrom": "fuel_entries", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "fuel_entries_location_id_uq": { + "name": "fuel_entries_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "fuel_entries_amount_chk": { + "name": "fuel_entries_amount_chk", + "value": "amount > 0" + }, + "fuel_entries_unit_chk": { + "name": "fuel_entries_unit_chk", + "value": "unit IN ('L', 'kWh', 'gal')" + }, + "fuel_entries_cost_chk": { + "name": "fuel_entries_cost_chk", + "value": "cost >= 0" + }, + "fuel_entries_money_chk": { + "name": "fuel_entries_money_chk", + "value": "(cost IS NULL) = (currency IS NULL)" + }, + "fuel_entries_note_chk": { + "name": "fuel_entries_note_chk", + "value": "char_length(note) <= 500" + } + }, + "isRLSEnabled": false + }, + "public.import_runs": { + "name": "import_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_version": { + "name": "source_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "mapping": { + "name": "mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "choices": { + "name": "choices", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "rows": { + "name": "rows", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dry_run_report": { + "name": "dry_run_report", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "progress": { + "name": "progress", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total": { + "name": "total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archive_bytes": { + "name": "archive_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "archive_sha256": { + "name": "archive_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archive_ready_at": { + "name": "archive_ready_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "inspect": { + "name": "inspect", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "secrets_key_ciphertext": { + "name": "secrets_key_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "new_location": { + "name": "new_location", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "import_runs_location_idx": { + "name": "import_runs_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "import_runs_draft_idx": { + "name": "import_runs_draft_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "location_id IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "import_runs_location_id_locations_id_fk": { + "name": "import_runs_location_id_locations_id_fk", + "tableFrom": "import_runs", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "import_runs_location_id_uq": { + "name": "import_runs_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "import_runs_source_chk": { + "name": "import_runs_source_chk", + "value": "\"source\" IN ('csv', 'homebox_zip', 'homebox_api', 'kept_zip', 'lubelogger_csv')" + }, + "import_runs_status_chk": { + "name": "import_runs_status_chk", + "value": "\"status\" IN ('draft', 'checked', 'running', 'done', 'failed', 'cancelled')" + }, + "import_runs_source_version_chk": { + "name": "import_runs_source_version_chk", + "value": "char_length(source_version) <= 40" + }, + "import_runs_mapping_chk": { + "name": "import_runs_mapping_chk", + "value": "jsonb_typeof(mapping) = 'object'" + }, + "import_runs_choices_chk": { + "name": "import_runs_choices_chk", + "value": "jsonb_typeof(choices) = 'object'" + }, + "import_runs_rows_chk": { + "name": "import_runs_rows_chk", + "value": "rows IS NULL OR jsonb_typeof(rows) = 'array'" + }, + "import_runs_progress_chk": { + "name": "import_runs_progress_chk", + "value": "progress >= 0 AND (total IS NULL OR progress <= total)" + }, + "import_runs_error_chk": { + "name": "import_runs_error_chk", + "value": "char_length(error) <= 500" + }, + "import_runs_target_chk": { + "name": "import_runs_target_chk", + "value": "location_id IS NOT NULL OR status IN ('draft', 'failed', 'cancelled')\n OR (status = 'checked' AND new_location IS NOT NULL)" + }, + "import_runs_new_location_chk": { + "name": "import_runs_new_location_chk", + "value": "new_location IS NULL OR (jsonb_typeof(new_location) = 'object'\n AND source = 'homebox_zip' AND location_id IS NULL)" + }, + "import_runs_archive_chk": { + "name": "import_runs_archive_chk", + "value": "(archive_bytes IS NULL AND archive_sha256 IS NULL AND archive_ready_at IS NULL)\n OR source IN ('homebox_zip', 'kept_zip')" + }, + "import_runs_archive_bytes_chk": { + "name": "import_runs_archive_bytes_chk", + "value": "archive_bytes >= 0" + }, + "import_runs_archive_sha256_chk": { + "name": "import_runs_archive_sha256_chk", + "value": "archive_sha256 ~ '^[0-9a-f]{64}$'" + }, + "import_runs_inspect_chk": { + "name": "import_runs_inspect_chk", + "value": "inspect IS NULL OR jsonb_typeof(inspect) = 'object'" + }, + "import_runs_secrets_key_chk": { + "name": "import_runs_secrets_key_chk", + "value": "(secrets_key_ciphertext IS NULL) = (key_version IS NULL)\n AND (secrets_key_ciphertext IS NULL OR source = 'kept_zip')" + } + }, + "isRLSEnabled": false + }, + "public.import_source_ids": { + "name": "import_source_ids", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "import_source_ids_run_idx": { + "name": "import_source_ids_run_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "import_source_ids_entity_idx": { + "name": "import_source_ids_entity_idx", + "columns": [ + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "import_source_ids_location_id_locations_id_fk": { + "name": "import_source_ids_location_id_locations_id_fk", + "tableFrom": "import_source_ids", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "import_source_ids_run_fk": { + "name": "import_source_ids_run_fk", + "tableFrom": "import_source_ids", + "columnsFrom": [ + "location_id", + "run_id" + ], + "tableTo": "import_runs", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "import_source_ids_pk": { + "name": "import_source_ids_pk", + "columns": [ + "location_id", + "source", + "source_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "import_source_ids_source_chk": { + "name": "import_source_ids_source_chk", + "value": "\"source\" IN ('csv', 'homebox_zip', 'homebox_api', 'kept_zip', 'lubelogger_csv', 'homebox')" + }, + "import_source_ids_entity_type_chk": { + "name": "import_source_ids_entity_type_chk", + "value": "\"entity_type\" IN ('thing', 'place', 'purchase', 'attachment', 'file', 'tag', 'type', 'type_field', 'brand', 'vendor', 'person', 'template', 'meter', 'reading', 'box_check', 'stock_rule', 'warranty', 'service_record', 'schedule', 'loan', 'claim', 'incident', 'valuation', 'expiring_document', 'fuel_entry', 'service_line')" + }, + "import_source_ids_source_id_chk": { + "name": "import_source_ids_source_id_chk", + "value": "char_length(source_id) BETWEEN 1 AND 200" + } + }, + "isRLSEnabled": false + }, + "public.kit_parts": { + "name": "kit_parts", + "schema": "", + "columns": { + "kit_id": { + "name": "kit_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kit_parts_location_idx": { + "name": "kit_parts_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kit_parts_thing_id_things_id_fk": { + "name": "kit_parts_thing_id_things_id_fk", + "tableFrom": "kit_parts", + "columnsFrom": [ + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "kit_parts_kit_fk": { + "name": "kit_parts_kit_fk", + "tableFrom": "kit_parts", + "columnsFrom": [ + "location_id", + "kit_id" + ], + "tableTo": "kits", + "columnsTo": [ + "location_id", + "thing_id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "kit_parts_pk": { + "name": "kit_parts_pk", + "columns": [ + "kit_id", + "thing_id" + ] + } + }, + "uniqueConstraints": { + "kit_parts_thing_uq": { + "name": "kit_parts_thing_uq", + "columns": [ + "thing_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "kit_parts_quantity_chk": { + "name": "kit_parts_quantity_chk", + "value": "quantity > 0" + }, + "kit_parts_not_itself_chk": { + "name": "kit_parts_not_itself_chk", + "value": "thing_id <> kit_id" + } + }, + "isRLSEnabled": false + }, + "public.kits": { + "name": "kits", + "schema": "", + "columns": { + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kits_location_idx": { + "name": "kits_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kits_location_id_locations_id_fk": { + "name": "kits_location_id_locations_id_fk", + "tableFrom": "kits", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "kits_thing_fk": { + "name": "kits_thing_fk", + "tableFrom": "kits", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kits_location_id_uq": { + "name": "kits_location_id_uq", + "columns": [ + "location_id", + "thing_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_batch_codes": { + "name": "label_batch_codes", + "schema": "", + "columns": { + "batch_id": { + "name": "batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "char(6)", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "label_batch_codes_code_idx": { + "name": "label_batch_codes_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "label_batch_codes_code_short_ids_code_fk": { + "name": "label_batch_codes_code_short_ids_code_fk", + "tableFrom": "label_batch_codes", + "columnsFrom": [ + "code" + ], + "tableTo": "short_ids", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "label_batch_codes_batch_fk": { + "name": "label_batch_codes_batch_fk", + "tableFrom": "label_batch_codes", + "columnsFrom": [ + "location_id", + "batch_id" + ], + "tableTo": "label_batches", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "label_batch_codes_pk": { + "name": "label_batch_codes_pk", + "columns": [ + "batch_id", + "code" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.label_batches": { + "name": "label_batches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock": { + "name": "stock", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_cell": { + "name": "start_cell", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "code_count": { + "name": "code_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "printed_confirmed_at": { + "name": "printed_confirmed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "label_batches_location_idx": { + "name": "label_batches_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "label_batches_location_id_locations_id_fk": { + "name": "label_batches_location_id_locations_id_fk", + "tableFrom": "label_batches", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_batches_location_id_uq": { + "name": "label_batches_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "label_batches_kind_chk": { + "name": "label_batches_kind_chk", + "value": "\"kind\" IN ('things', 'places', 'blank')" + }, + "label_batches_stock_chk": { + "name": "label_batches_stock_chk", + "value": "stock ~ '^[a-z0-9_]{1,40}$'" + }, + "label_batches_start_cell_chk": { + "name": "label_batches_start_cell_chk", + "value": "start_cell BETWEEN 1 AND 200" + }, + "label_batches_code_count_chk": { + "name": "label_batches_code_count_chk", + "value": "code_count BETWEEN 1 AND 1000" + } + }, + "isRLSEnabled": false + }, + "public.handovers": { + "name": "handovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "person_id": { + "name": "person_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_thing_id": { + "name": "root_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "due_on": { + "name": "due_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_out": { + "name": "condition_out", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "handovers_person_idx": { + "name": "handovers_person_idx", + "columns": [ + { + "expression": "person_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "handovers_root_idx": { + "name": "handovers_root_idx", + "columns": [ + { + "expression": "root_thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "handovers_location_id_locations_id_fk": { + "name": "handovers_location_id_locations_id_fk", + "tableFrom": "handovers", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "handovers_root_thing_id_things_id_fk": { + "name": "handovers_root_thing_id_things_id_fk", + "tableFrom": "handovers", + "columnsFrom": [ + "root_thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "handovers_location_id_uq": { + "name": "handovers_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "handovers_direction_chk": { + "name": "handovers_direction_chk", + "value": "\"direction\" IN ('out', 'in')" + }, + "handovers_condition_out_chk": { + "name": "handovers_condition_out_chk", + "value": "\"condition_out\" IN ('new', 'good', 'fair', 'poor', 'broken')" + }, + "handovers_lead_days_chk": { + "name": "handovers_lead_days_chk", + "value": "lead_days BETWEEN 0 AND 60" + }, + "handovers_notes_chk": { + "name": "handovers_notes_chk", + "value": "char_length(notes) <= 2000" + } + }, + "isRLSEnabled": false + }, + "public.loans": { + "name": "loans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "person_id": { + "name": "person_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "due_on": { + "name": "due_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "returned_at": { + "name": "returned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "return_place_id": { + "name": "return_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_container_id": { + "name": "return_container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "previous_place_id": { + "name": "previous_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "previous_container_id": { + "name": "previous_container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "split_from_thing_id": { + "name": "split_from_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "handover_id": { + "name": "handover_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "path_at_lend": { + "name": "path_at_lend", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_out": { + "name": "condition_out", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition_in": { + "name": "condition_in", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "return_notes": { + "name": "return_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "damaged": { + "name": "damaged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "taken_back_by": { + "name": "taken_back_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "loans_handover_idx": { + "name": "loans_handover_idx", + "columns": [ + { + "expression": "handover_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "loans_one_open_uq": { + "name": "loans_one_open_uq", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "returned_at IS NULL", + "concurrently": false + }, + "loans_due_idx": { + "name": "loans_due_idx", + "columns": [ + { + "expression": "due_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "returned_at IS NULL", + "concurrently": false + }, + "loans_person_idx": { + "name": "loans_person_idx", + "columns": [ + { + "expression": "person_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "loans_thing_idx": { + "name": "loans_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "loans_location_id_locations_id_fk": { + "name": "loans_location_id_locations_id_fk", + "tableFrom": "loans", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "loans_handover_id_handovers_id_fk": { + "name": "loans_handover_id_handovers_id_fk", + "tableFrom": "loans", + "columnsFrom": [ + "handover_id" + ], + "tableTo": "handovers", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "loans_thing_fk": { + "name": "loans_thing_fk", + "tableFrom": "loans", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "loans_location_id_uq": { + "name": "loans_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "loans_direction_chk": { + "name": "loans_direction_chk", + "value": "\"direction\" IN ('out', 'in')" + }, + "loans_outcome_chk": { + "name": "loans_outcome_chk", + "value": "\"outcome\" IN ('returned', 'lost')" + }, + "loans_condition_out_chk": { + "name": "loans_condition_out_chk", + "value": "\"condition_out\" IN ('new', 'good', 'fair', 'poor', 'broken')" + }, + "loans_condition_in_chk": { + "name": "loans_condition_in_chk", + "value": "\"condition_in\" IN ('new', 'good', 'fair', 'poor', 'broken')" + }, + "loans_return_notes_chk": { + "name": "loans_return_notes_chk", + "value": "char_length(return_notes) <= 2000" + }, + "loans_path_chk": { + "name": "loans_path_chk", + "value": "char_length(path_at_lend) <= 1000" + }, + "loans_lead_days_chk": { + "name": "loans_lead_days_chk", + "value": "lead_days BETWEEN 0 AND 60" + }, + "loans_notes_chk": { + "name": "loans_notes_chk", + "value": "char_length(notes) <= 2000" + }, + "loans_due_chk": { + "name": "loans_due_chk", + "value": "due_on IS NULL OR due_on >= (started_at AT TIME ZONE 'UTC')::date - 1" + }, + "loans_returned_chk": { + "name": "loans_returned_chk", + "value": "returned_at IS NULL OR returned_at >= started_at" + }, + "loans_return_where_chk": { + "name": "loans_return_where_chk", + "value": "num_nonnulls(return_place_id, return_container_id) <= 1" + } + }, + "isRLSEnabled": false + }, + "public.mailbox_messages": { + "name": "mailbox_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mailbox_id": { + "name": "mailbox_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "uid_validity": { + "name": "uid_validity", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_id": { + "name": "purchase_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mailbox_messages_recent_idx": { + "name": "mailbox_messages_recent_idx", + "columns": [ + { + "expression": "mailbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mailbox_messages_mailbox_fk": { + "name": "mailbox_messages_mailbox_fk", + "tableFrom": "mailbox_messages", + "columnsFrom": [ + "location_id", + "mailbox_id" + ], + "tableTo": "mailboxes", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mailbox_messages_uid_uq": { + "name": "mailbox_messages_uid_uq", + "columns": [ + "mailbox_id", + "uid_validity", + "uid" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "mailbox_messages_outcome_chk": { + "name": "mailbox_messages_outcome_chk", + "value": "\"outcome\" IN ('receipt', 'no_receipt', 'sender_not_allowed', 'too_large', 'failed')" + }, + "mailbox_messages_from_chk": { + "name": "mailbox_messages_from_chk", + "value": "from_address IS NULL OR char_length(from_address) <= 254" + }, + "mailbox_messages_subject_chk": { + "name": "mailbox_messages_subject_chk", + "value": "subject IS NULL OR char_length(subject) <= 200" + } + }, + "isRLSEnabled": false + }, + "public.mailboxes": { + "name": "mailboxes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'imap'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_senders": { + "name": "allowed_senders", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "config_ciphertext": { + "name": "config_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uid_validity": { + "name": "uid_validity", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "last_uid": { + "name": "last_uid", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mailboxes_location_id_locations_id_fk": { + "name": "mailboxes_location_id_locations_id_fk", + "tableFrom": "mailboxes", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mailboxes_location_uq": { + "name": "mailboxes_location_uq", + "columns": [ + "location_id" + ], + "nullsNotDistinct": false + }, + "mailboxes_location_id_uq": { + "name": "mailboxes_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "mailboxes_kind_chk": { + "name": "mailboxes_kind_chk", + "value": "\"kind\" IN ('imap')" + }, + "mailboxes_disabled_reason_chk": { + "name": "mailboxes_disabled_reason_chk", + "value": "\"disabled_reason\" IN ('creator_left', 'auth_failed')" + }, + "mailboxes_host_chk": { + "name": "mailboxes_host_chk", + "value": "char_length(host) BETWEEN 1 AND 253" + }, + "mailboxes_username_chk": { + "name": "mailboxes_username_chk", + "value": "char_length(username) BETWEEN 1 AND 254" + }, + "mailboxes_folder_chk": { + "name": "mailboxes_folder_chk", + "value": "char_length(folder) BETWEEN 1 AND 200" + }, + "mailboxes_senders_chk": { + "name": "mailboxes_senders_chk", + "value": "cardinality(allowed_senders) <= 20" + }, + "mailboxes_key_version_chk": { + "name": "mailboxes_key_version_chk", + "value": "key_version >= 1" + }, + "mailboxes_disabled_chk": { + "name": "mailboxes_disabled_chk", + "value": "active OR disabled_reason IS NOT NULL" + }, + "mailboxes_last_error_chk": { + "name": "mailboxes_last_error_chk", + "value": "last_error IS NULL OR char_length(last_error) <= 32" + } + }, + "isRLSEnabled": false + }, + "public.map_nodes": { + "name": "map_nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "x_cm": { + "name": "x_cm", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "z_cm": { + "name": "z_cm", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "w_cm": { + "name": "w_cm", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "d_cm": { + "name": "d_cm", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rot": { + "name": "rot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "room_kind": { + "name": "room_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "height_cm": { + "name": "height_cm", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "placed_by_kept": { + "name": "placed_by_kept", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "map_nodes_place_uq": { + "name": "map_nodes_place_uq", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "map_nodes_thing_uq": { + "name": "map_nodes_thing_uq", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "map_nodes_location_idx": { + "name": "map_nodes_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "map_nodes_location_id_locations_id_fk": { + "name": "map_nodes_location_id_locations_id_fk", + "tableFrom": "map_nodes", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "map_nodes_place_fk": { + "name": "map_nodes_place_fk", + "tableFrom": "map_nodes", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "map_nodes_thing_fk": { + "name": "map_nodes_thing_fk", + "tableFrom": "map_nodes", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "map_nodes_room_kind_chk": { + "name": "map_nodes_room_kind_chk", + "value": "\"room_kind\" IN ('room', 'balcony', 'terrace', 'garden')" + }, + "map_nodes_surface_chk": { + "name": "map_nodes_surface_chk", + "value": "\"surface\" IN ('wood', 'tile', 'concrete', 'deck', 'grass')" + }, + "map_nodes_one_subject_chk": { + "name": "map_nodes_one_subject_chk", + "value": "num_nonnulls(place_id, thing_id) = 1" + }, + "map_nodes_rot_chk": { + "name": "map_nodes_rot_chk", + "value": "rot IN (0, 90, 180, 270)" + }, + "map_nodes_xz_chk": { + "name": "map_nodes_xz_chk", + "value": "x_cm BETWEEN 0 AND 100000 AND z_cm BETWEEN 0 AND 100000" + }, + "map_nodes_size_chk": { + "name": "map_nodes_size_chk", + "value": "(w_cm IS NULL AND d_cm IS NULL) OR (w_cm BETWEEN 50 AND 10000 AND d_cm BETWEEN 50 AND 10000)" + }, + "map_nodes_model_chk": { + "name": "map_nodes_model_chk", + "value": "model IS NULL OR model ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "map_nodes_room_chk": { + "name": "map_nodes_room_chk", + "value": "(room_kind IS NULL AND surface IS NULL) OR w_cm IS NOT NULL" + }, + "map_nodes_height_chk": { + "name": "map_nodes_height_chk", + "value": "height_cm IS NULL OR (height_cm BETWEEN 0 AND 300 AND w_cm IS NULL AND host_id IS NULL)" + }, + "map_nodes_not_own_host_chk": { + "name": "map_nodes_not_own_host_chk", + "value": "host_id IS NULL OR host_id <> coalesce(place_id, thing_id)" + } + }, + "isRLSEnabled": false + }, + "public.meter_events": { + "name": "meter_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "meter_id": { + "name": "meter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "offset": { + "name": "offset", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "meter_events_meter_idx": { + "name": "meter_events_meter_idx", + "columns": [ + { + "expression": "meter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "meter_events_meter_fk": { + "name": "meter_events_meter_fk", + "tableFrom": "meter_events", + "columnsFrom": [ + "location_id", + "meter_id" + ], + "tableTo": "meters", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "meter_events_kind_chk": { + "name": "meter_events_kind_chk", + "value": "\"kind\" IN ('replaced')" + } + }, + "isRLSEnabled": false + }, + "public.meter_readings": { + "name": "meter_readings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "meter_id": { + "name": "meter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": true + }, + "taken_at": { + "name": "taken_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "logged_by": { + "name": "logged_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "review_reason": { + "name": "review_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "meter_readings_meter_taken_idx": { + "name": "meter_readings_meter_taken_idx", + "columns": [ + { + "expression": "meter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "taken_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "meter_readings_meter_fk": { + "name": "meter_readings_meter_fk", + "tableFrom": "meter_readings", + "columnsFrom": [ + "location_id", + "meter_id" + ], + "tableTo": "meters", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "meter_readings_location_id_uq": { + "name": "meter_readings_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "meter_readings_source_chk": { + "name": "meter_readings_source_chk", + "value": "\"source\" IN ('manual', 'photo', 'fuel', 'service', 'import', 'home_assistant')" + }, + "meter_readings_state_chk": { + "name": "meter_readings_state_chk", + "value": "\"state\" IN ('accepted', 'needs_review')" + }, + "meter_readings_value_chk": { + "name": "meter_readings_value_chk", + "value": "value >= 0" + }, + "meter_readings_note_chk": { + "name": "meter_readings_note_chk", + "value": "char_length(note) <= 500" + }, + "meter_readings_review_reason_chk": { + "name": "meter_readings_review_reason_chk", + "value": "char_length(review_reason) <= 200" + } + }, + "isRLSEnabled": false + }, + "public.meters": { + "name": "meters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "offset": { + "name": "offset", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "max_per_day": { + "name": "max_per_day", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "nudge_days": { + "name": "nudge_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "meters_thing_idx": { + "name": "meters_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "meters_thing_fk": { + "name": "meters_thing_fk", + "tableFrom": "meters", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "meters_location_id_uq": { + "name": "meters_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "meters_kind_chk": { + "name": "meters_kind_chk", + "value": "\"kind\" IN ('distance', 'hours', 'custom')" + }, + "meters_unit_chk": { + "name": "meters_unit_chk", + "value": "char_length(unit) BETWEEN 1 AND 12" + }, + "meters_label_chk": { + "name": "meters_label_chk", + "value": "label IS NULL OR char_length(label) BETWEEN 1 AND 80" + }, + "meters_max_per_day_chk": { + "name": "meters_max_per_day_chk", + "value": "max_per_day IS NULL OR max_per_day > 0" + }, + "meters_nudge_days_chk": { + "name": "meters_nudge_days_chk", + "value": "nudge_days IS NULL OR nudge_days BETWEEN 7 AND 365" + } + }, + "isRLSEnabled": false + }, + "public.export_runs": { + "name": "export_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "include_secrets": { + "name": "include_secrets", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "incident_id": { + "name": "incident_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "thing_ids": { + "name": "thing_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "progress_done": { + "name": "progress_done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_total": { + "name": "progress_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes": { + "name": "bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "downloads": { + "name": "downloads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_downloaded_at": { + "name": "last_downloaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now() + interval '7 days'" + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secrets_key_ciphertext": { + "name": "secrets_key_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "export_runs_location_idx": { + "name": "export_runs_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "export_runs_expires_idx": { + "name": "export_runs_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "export_runs_creator_idx": { + "name": "export_runs_creator_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "export_runs_location_id_locations_id_fk": { + "name": "export_runs_location_id_locations_id_fk", + "tableFrom": "export_runs", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "export_runs_created_by_user_id_fk": { + "name": "export_runs_created_by_user_id_fk", + "tableFrom": "export_runs", + "columnsFrom": [ + "created_by" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "export_runs_token_hash_uq": { + "name": "export_runs_token_hash_uq", + "columns": [ + "token_hash" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "export_runs_kind_chk": { + "name": "export_runs_kind_chk", + "value": "\"kind\" IN ('claim_pack', 'location', 'me')" + }, + "export_runs_status_chk": { + "name": "export_runs_status_chk", + "value": "\"status\" IN ('queued', 'running', 'done', 'failed', 'cancelled', 'expired')" + }, + "export_runs_secrets_chk": { + "name": "export_runs_secrets_chk", + "value": "NOT include_secrets OR kind <> 'claim_pack'" + }, + "export_runs_scope_chk": { + "name": "export_runs_scope_chk", + "value": "num_nonnulls(incident_id, thing_ids) <= 1" + }, + "export_runs_progress_chk": { + "name": "export_runs_progress_chk", + "value": "progress_done >= 0 AND progress_total >= 0 AND progress_done <= progress_total" + }, + "export_runs_error_chk": { + "name": "export_runs_error_chk", + "value": "error ~ '^[a-z_]{1,32}$'" + }, + "export_runs_token_hash_chk": { + "name": "export_runs_token_hash_chk", + "value": "token_hash ~ '^[0-9a-f]{64}$'" + }, + "export_runs_storage_key_chk": { + "name": "export_runs_storage_key_chk", + "value": "storage_key IS NULL OR storage_key = 'x/' || id::text || '.zip'" + }, + "export_runs_thing_ids_chk": { + "name": "export_runs_thing_ids_chk", + "value": "cardinality(thing_ids) BETWEEN 1 AND 1000" + }, + "export_runs_kind_scope_chk": { + "name": "export_runs_kind_scope_chk", + "value": "kind = 'claim_pack' OR num_nonnulls(incident_id, thing_ids) = 0" + }, + "export_runs_options_chk": { + "name": "export_runs_options_chk", + "value": "jsonb_typeof(options) = 'object'" + }, + "export_runs_sha256_chk": { + "name": "export_runs_sha256_chk", + "value": "sha256 ~ '^[0-9a-f]{64}$'" + }, + "export_runs_secrets_key_chk": { + "name": "export_runs_secrets_key_chk", + "value": "(secrets_key_ciphertext IS NULL) = (key_version IS NULL)\n AND (secrets_key_ciphertext IS NULL OR include_secrets)" + } + }, + "isRLSEnabled": false + }, + "public.fx_rates": { + "name": "fx_rates", + "schema": "", + "columns": { + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_ccy": { + "name": "from_ccy", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "to_ccy": { + "name": "to_ccy", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "rate": { + "name": "rate", + "type": "numeric(18, 8)", + "primaryKey": false, + "notNull": true + }, + "valid_from": { + "name": "valid_from", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "fx_rates_owner_account_id_owner_accounts_id_fk": { + "name": "fx_rates_owner_account_id_owner_accounts_id_fk", + "tableFrom": "fx_rates", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "fx_rates_from_ccy_currencies_code_fk": { + "name": "fx_rates_from_ccy_currencies_code_fk", + "tableFrom": "fx_rates", + "columnsFrom": [ + "from_ccy" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "fx_rates_to_ccy_currencies_code_fk": { + "name": "fx_rates_to_ccy_currencies_code_fk", + "tableFrom": "fx_rates", + "columnsFrom": [ + "to_ccy" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": { + "fx_rates_pk": { + "name": "fx_rates_pk", + "columns": [ + "owner_account_id", + "from_ccy", + "to_ccy", + "valid_from" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "fx_rates_rate_chk": { + "name": "fx_rates_rate_chk", + "value": "rate > 0" + }, + "fx_rates_pair_chk": { + "name": "fx_rates_pair_chk", + "value": "from_ccy <> to_ccy" + } + }, + "isRLSEnabled": false + }, + "public.incident_things": { + "name": "incident_things", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "incident_id": { + "name": "incident_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "incident_things_thing_idx": { + "name": "incident_things_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "incident_things_incident_fk": { + "name": "incident_things_incident_fk", + "tableFrom": "incident_things", + "columnsFrom": [ + "location_id", + "incident_id" + ], + "tableTo": "incidents", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "incident_things_thing_fk": { + "name": "incident_things_thing_fk", + "tableFrom": "incident_things", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "incident_things_pk": { + "name": "incident_things_pk", + "columns": [ + "incident_id", + "thing_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.incidents": { + "name": "incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_on": { + "name": "occurred_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "police_reference": { + "name": "police_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "insurer_reference": { + "name": "insurer_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "incidents_location_idx": { + "name": "incidents_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "incidents_location_id_locations_id_fk": { + "name": "incidents_location_id_locations_id_fk", + "tableFrom": "incidents", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "incidents_location_id_uq": { + "name": "incidents_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "incidents_kind_chk": { + "name": "incidents_kind_chk", + "value": "\"kind\" IN ('burglary', 'fire', 'flood', 'loss', 'other')" + }, + "incidents_police_reference_chk": { + "name": "incidents_police_reference_chk", + "value": "char_length(police_reference) <= 100" + }, + "incidents_insurer_reference_chk": { + "name": "incidents_insurer_reference_chk", + "value": "char_length(insurer_reference) <= 100" + }, + "incidents_notes_chk": { + "name": "incidents_notes_chk", + "value": "char_length(notes) <= 5000" + } + }, + "isRLSEnabled": false + }, + "public.valuations": { + "name": "valuations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "valued_on": { + "name": "valued_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "valuations_thing_idx": { + "name": "valuations_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "valued_on", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "valuations_location_id_locations_id_fk": { + "name": "valuations_location_id_locations_id_fk", + "tableFrom": "valuations", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "valuations_currency_currencies_code_fk": { + "name": "valuations_currency_currencies_code_fk", + "tableFrom": "valuations", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "valuations_thing_fk": { + "name": "valuations_thing_fk", + "tableFrom": "valuations", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "valuations_location_id_uq": { + "name": "valuations_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "valuations_source_chk": { + "name": "valuations_source_chk", + "value": "\"source\" IN ('purchase', 'appraisal', 'estimate', 'insurer')" + }, + "valuations_value_chk": { + "name": "valuations_value_chk", + "value": "value >= 0" + }, + "valuations_notes_chk": { + "name": "valuations_notes_chk", + "value": "char_length(notes) <= 2000" + } + }, + "isRLSEnabled": false + }, + "public.move_boxes": { + "name": "move_boxes", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "move_id": { + "name": "move_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "container_id": { + "name": "container_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'packed'" + }, + "destination_place_id": { + "name": "destination_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state_at": { + "name": "state_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "state_by": { + "name": "state_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "move_boxes_container_idx": { + "name": "move_boxes_container_idx", + "columns": [ + { + "expression": "container_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "move_boxes_destination_idx": { + "name": "move_boxes_destination_idx", + "columns": [ + { + "expression": "destination_place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "move_boxes_container_id_things_id_fk": { + "name": "move_boxes_container_id_things_id_fk", + "tableFrom": "move_boxes", + "columnsFrom": [ + "container_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "move_boxes_destination_place_id_places_id_fk": { + "name": "move_boxes_destination_place_id_places_id_fk", + "tableFrom": "move_boxes", + "columnsFrom": [ + "destination_place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "move_boxes_move_fk": { + "name": "move_boxes_move_fk", + "tableFrom": "move_boxes", + "columnsFrom": [ + "location_id", + "move_id" + ], + "tableTo": "moves", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "move_boxes_pk": { + "name": "move_boxes_pk", + "columns": [ + "move_id", + "container_id" + ] + } + }, + "uniqueConstraints": { + "move_boxes_number_uq": { + "name": "move_boxes_number_uq", + "columns": [ + "move_id", + "number" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "move_boxes_state_chk": { + "name": "move_boxes_state_chk", + "value": "\"state\" IN ('packed', 'in_transit', 'unpacked')" + }, + "move_boxes_number_chk": { + "name": "move_boxes_number_chk", + "value": "number BETWEEN 1 AND 999" + } + }, + "isRLSEnabled": false + }, + "public.moves": { + "name": "moves", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_location_id": { + "name": "to_location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "moves_location_idx": { + "name": "moves_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "moves_to_location_idx": { + "name": "moves_to_location_idx", + "columns": [ + { + "expression": "to_location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "moves_location_id_locations_id_fk": { + "name": "moves_location_id_locations_id_fk", + "tableFrom": "moves", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "moves_to_location_id_locations_id_fk": { + "name": "moves_to_location_id_locations_id_fk", + "tableFrom": "moves", + "columnsFrom": [ + "to_location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "moves_location_id_uq": { + "name": "moves_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "moves_status_chk": { + "name": "moves_status_chk", + "value": "\"status\" IN ('active', 'done')" + }, + "moves_name_chk": { + "name": "moves_name_chk", + "value": "char_length(btrim(name)) BETWEEN 1 AND 200" + }, + "moves_finished_chk": { + "name": "moves_finished_chk", + "value": "(status = 'done') = (finished_at IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.calendar_feeds": { + "name": "calendar_feeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fetched_at": { + "name": "last_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fetches": { + "name": "fetches", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "calendar_feeds_user_idx": { + "name": "calendar_feeds_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "calendar_feeds_user_id_user_id_fk": { + "name": "calendar_feeds_user_id_user_id_fk", + "tableFrom": "calendar_feeds", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "calendar_feeds_token_hash_uq": { + "name": "calendar_feeds_token_hash_uq", + "columns": [ + "token_hash" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "calendar_feeds_token_hash_chk": { + "name": "calendar_feeds_token_hash_chk", + "value": "token_hash ~ '^[0-9a-f]{64}$'" + }, + "calendar_feeds_fetches_chk": { + "name": "calendar_feeds_fetches_chk", + "value": "fetches >= 0" + } + }, + "isRLSEnabled": false + }, + "public.notification_channels": { + "name": "notification_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_host": { + "name": "display_host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config_ciphertext": { + "name": "config_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failing_since": { + "name": "failing_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notification_channels_email_uq": { + "name": "notification_channels_email_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "kind = 'email'", + "concurrently": false + }, + "notification_channels_webpush_uq": { + "name": "notification_channels_webpush_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "kind = 'webpush'", + "concurrently": false + }, + "notification_channels_user_idx": { + "name": "notification_channels_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "notification_channels_user_id_user_id_fk": { + "name": "notification_channels_user_id_user_id_fk", + "tableFrom": "notification_channels", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "notification_channels_kind_chk": { + "name": "notification_channels_kind_chk", + "value": "\"kind\" IN ('email', 'webpush', 'webhook', 'ntfy', 'telegram', 'apprise')" + }, + "notification_channels_label_chk": { + "name": "notification_channels_label_chk", + "value": "char_length(label) <= 60" + }, + "notification_channels_display_host_chk": { + "name": "notification_channels_display_host_chk", + "value": "char_length(display_host) <= 255" + }, + "notification_channels_config_chk": { + "name": "notification_channels_config_chk", + "value": "(kind IN ('webhook', 'ntfy', 'telegram', 'apprise')) = (config_ciphertext IS NOT NULL)\n AND (config_ciphertext IS NULL) = (key_version IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.notification_preferences": { + "name": "notification_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notification_preferences_location_idx": { + "name": "notification_preferences_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "notification_preferences_user_id_user_id_fk": { + "name": "notification_preferences_user_id_user_id_fk", + "tableFrom": "notification_preferences", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "notification_preferences_location_id_locations_id_fk": { + "name": "notification_preferences_location_id_locations_id_fk", + "tableFrom": "notification_preferences", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "notification_preferences_uq": { + "name": "notification_preferences_uq", + "columns": [ + "user_id", + "location_id", + "kind", + "channel" + ], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": { + "notification_preferences_kind_chk": { + "name": "notification_preferences_kind_chk", + "value": "\"kind\" IN ('schedule', 'warranty', 'registration', 'document', 'loan', 'thing_expiry', 'reading_stale', 'stock', 'membership', 'ai_cap', 'ai_summary')" + }, + "notification_preferences_channel_chk": { + "name": "notification_preferences_channel_chk", + "value": "\"channel\" IN ('inapp', 'email', 'webpush', 'webhook', 'ntfy', 'telegram', 'apprise')" + }, + "notification_preferences_account_chk": { + "name": "notification_preferences_account_chk", + "value": "kind <> 'ai_summary' OR location_id IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.push_subscriptions": { + "name": "push_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "p256dh": { + "name": "p256dh", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth": { + "name": "auth", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failures": { + "name": "failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "push_subscriptions_user_idx": { + "name": "push_subscriptions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "push_subscriptions_user_id_user_id_fk": { + "name": "push_subscriptions_user_id_user_id_fk", + "tableFrom": "push_subscriptions", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "push_subscriptions_endpoint_uq": { + "name": "push_subscriptions_endpoint_uq", + "columns": [ + "endpoint" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "push_subscriptions_endpoint_chk": { + "name": "push_subscriptions_endpoint_chk", + "value": "endpoint ~ '^https://' AND char_length(endpoint) <= 1000" + }, + "push_subscriptions_keys_chk": { + "name": "push_subscriptions_keys_chk", + "value": "char_length(p256dh) <= 200 AND char_length(auth) <= 100" + }, + "push_subscriptions_label_chk": { + "name": "push_subscriptions_label_chk", + "value": "char_length(label) <= 60" + }, + "push_subscriptions_failures_chk": { + "name": "push_subscriptions_failures_chk", + "value": "failures >= 0" + } + }, + "isRLSEnabled": false + }, + "public.backup_runs": { + "name": "backup_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "storage_mode": { + "name": "storage_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "db_bytes": { + "name": "db_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "bytes_added": { + "name": "bytes_added", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "bytes_total": { + "name": "bytes_total", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "files_total": { + "name": "files_total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "files_new": { + "name": "files_new", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "missing": { + "name": "missing", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "readable_locations": { + "name": "readable_locations", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "readable_bytes": { + "name": "readable_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "same_volume": { + "name": "same_volume", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "bucket_versioning_ok": { + "name": "bucket_versioning_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "from_version": { + "name": "from_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_version": { + "name": "to_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "backup_runs_started_idx": { + "name": "backup_runs_started_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "backup_runs_kind_idx": { + "name": "backup_runs_kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "backup_runs_kind_chk": { + "name": "backup_runs_kind_chk", + "value": "\"kind\" IN ('nightly', 'manual', 'pre_upgrade', 'drill', 'verify')" + }, + "backup_runs_status_chk": { + "name": "backup_runs_status_chk", + "value": "\"status\" IN ('running', 'ok', 'warning', 'failed')" + }, + "backup_runs_storage_mode_chk": { + "name": "backup_runs_storage_mode_chk", + "value": "\"storage_mode\" IN ('local', 's3')" + }, + "backup_runs_target_chk": { + "name": "backup_runs_target_chk", + "value": "char_length(target) BETWEEN 1 AND 300" + }, + "backup_runs_snapshot_id_chk": { + "name": "backup_runs_snapshot_id_chk", + "value": "snapshot_id ~ '^[0-9a-f]{8,64}$'" + }, + "backup_runs_error_chk": { + "name": "backup_runs_error_chk", + "value": "error ~ '^[a-z_]{1,48}$'" + }, + "backup_runs_detail_chk": { + "name": "backup_runs_detail_chk", + "value": "jsonb_typeof(detail) = 'object'" + }, + "backup_runs_finished_chk": { + "name": "backup_runs_finished_chk", + "value": "(status = 'running') = (finished_at IS NULL)" + }, + "backup_runs_missing_chk": { + "name": "backup_runs_missing_chk", + "value": "missing >= 0" + } + }, + "isRLSEnabled": false + }, + "public.release_history": { + "name": "release_history", + "schema": "", + "columns": { + "version": { + "name": "version", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_migration": { + "name": "last_migration", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_migrated_at": { + "name": "first_migrated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_booted_at": { + "name": "last_booted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "release_history_version_chk": { + "name": "release_history_version_chk", + "value": "version ~ '^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$'" + }, + "release_history_revision_chk": { + "name": "release_history_revision_chk", + "value": "revision ~ '^[0-9a-f]{7,40}$'" + }, + "release_history_last_migration_chk": { + "name": "release_history_last_migration_chk", + "value": "char_length(last_migration) BETWEEN 1 AND 100" + } + }, + "isRLSEnabled": false + }, + "public.paperless_connections": { + "name": "paperless_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_ciphertext": { + "name": "config_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "server_version": { + "name": "server_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "paperless_connections_location_id_locations_id_fk": { + "name": "paperless_connections_location_id_locations_id_fk", + "tableFrom": "paperless_connections", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "paperless_connections_location_uq": { + "name": "paperless_connections_location_uq", + "columns": [ + "location_id" + ], + "nullsNotDistinct": false + }, + "paperless_connections_location_id_uq": { + "name": "paperless_connections_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "paperless_connections_base_url_chk": { + "name": "paperless_connections_base_url_chk", + "value": "char_length(base_url) BETWEEN 1 AND 500" + }, + "paperless_connections_key_version_chk": { + "name": "paperless_connections_key_version_chk", + "value": "key_version >= 1" + }, + "paperless_connections_version_chk": { + "name": "paperless_connections_version_chk", + "value": "server_version IS NULL OR char_length(server_version) <= 40" + } + }, + "isRLSEnabled": false + }, + "public.paperless_links": { + "name": "paperless_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type_name": { + "name": "type_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correspondent": { + "name": "correspondent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_on": { + "name": "created_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "copied_attachment_id": { + "name": "copied_attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paperless_links_location_idx": { + "name": "paperless_links_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paperless_links_copied_idx": { + "name": "paperless_links_copied_idx", + "columns": [ + { + "expression": "copied_attachment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "paperless_links_thing_id_things_id_fk": { + "name": "paperless_links_thing_id_things_id_fk", + "tableFrom": "paperless_links", + "columnsFrom": [ + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "paperless_links_place_id_places_id_fk": { + "name": "paperless_links_place_id_places_id_fk", + "tableFrom": "paperless_links", + "columnsFrom": [ + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "paperless_links_copied_attachment_id_attachments_id_fk": { + "name": "paperless_links_copied_attachment_id_attachments_id_fk", + "tableFrom": "paperless_links", + "columnsFrom": [ + "copied_attachment_id" + ], + "tableTo": "attachments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "paperless_links_connection_fk": { + "name": "paperless_links_connection_fk", + "tableFrom": "paperless_links", + "columnsFrom": [ + "location_id", + "connection_id" + ], + "tableTo": "paperless_connections", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "paperless_links_thing_uq": { + "name": "paperless_links_thing_uq", + "columns": [ + "thing_id", + "document_id" + ], + "nullsNotDistinct": false + }, + "paperless_links_place_uq": { + "name": "paperless_links_place_uq", + "columns": [ + "place_id", + "document_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "paperless_links_kind_chk": { + "name": "paperless_links_kind_chk", + "value": "\"kind\" IN ('receipt', 'invoice', 'manual', 'warranty_doc', 'document')" + }, + "paperless_links_subject_chk": { + "name": "paperless_links_subject_chk", + "value": "num_nonnulls(thing_id, place_id) = 1" + }, + "paperless_links_document_chk": { + "name": "paperless_links_document_chk", + "value": "document_id > 0" + }, + "paperless_links_title_chk": { + "name": "paperless_links_title_chk", + "value": "char_length(title) BETWEEN 1 AND 300" + }, + "paperless_links_type_chk": { + "name": "paperless_links_type_chk", + "value": "type_name IS NULL OR char_length(type_name) <= 128" + }, + "paperless_links_correspondent_chk": { + "name": "paperless_links_correspondent_chk", + "value": "correspondent IS NULL OR char_length(correspondent) <= 128" + } + }, + "isRLSEnabled": false + }, + "public.places": { + "name": "places", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind_key": { + "name": "kind_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'room'" + }, + "is_unplaced": { + "name": "is_unplaced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "child_order": { + "name": "child_order", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'name'" + }, + "custom": { + "name": "custom", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "layout": { + "name": "layout", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cell": { + "name": "cell", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trash_batch_id": { + "name": "trash_batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "change_xid": { + "name": "change_xid", + "type": "xid8", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "places_one_unplaced_uq": { + "name": "places_one_unplaced_uq", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_unplaced", + "concurrently": false + }, + "places_parent_idx": { + "name": "places_parent_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "places_trash_purge_idx": { + "name": "places_trash_purge_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "deleted_at IS NOT NULL", + "concurrently": false + }, + "places_sync_idx": { + "name": "places_sync_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_xid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "places_location_id_locations_id_fk": { + "name": "places_location_id_locations_id_fk", + "tableFrom": "places", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "places_parent_fk": { + "name": "places_parent_fk", + "tableFrom": "places", + "columnsFrom": [ + "location_id", + "parent_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "places_location_id_uq": { + "name": "places_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "places_unplaced_top_level_chk": { + "name": "places_unplaced_top_level_chk", + "value": "NOT is_unplaced OR parent_id IS NULL" + }, + "places_not_own_parent_chk": { + "name": "places_not_own_parent_chk", + "value": "parent_id IS NULL OR parent_id <> id" + }, + "places_custom_chk": { + "name": "places_custom_chk", + "value": "jsonb_typeof(custom) = 'object'" + }, + "places_child_order_chk": { + "name": "places_child_order_chk", + "value": "child_order IN ('name', 'custom')" + }, + "places_layout_chk": { + "name": "places_layout_chk", + "value": "layout IS NULL OR jsonb_typeof(layout) = 'object'" + }, + "places_cell_chk": { + "name": "places_cell_chk", + "value": "cell IS NULL OR jsonb_typeof(cell) = 'object'" + }, + "places_icon_chk": { + "name": "places_icon_chk", + "value": "icon IS NULL OR icon ~ '^(lucide|tabler|kept):[a-z0-9-]+$'" + } + }, + "isRLSEnabled": false + }, + "public.purchase_lines": { + "name": "purchase_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "purchase_id": { + "name": "purchase_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "unit_price": { + "name": "unit_price", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "purchase_lines_purchase_idx": { + "name": "purchase_lines_purchase_idx", + "columns": [ + { + "expression": "purchase_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "purchase_lines_purchase_fk": { + "name": "purchase_lines_purchase_fk", + "tableFrom": "purchase_lines", + "columnsFrom": [ + "location_id", + "purchase_id" + ], + "tableTo": "purchases", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "purchase_lines_location_id_uq": { + "name": "purchase_lines_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "purchase_lines_description_chk": { + "name": "purchase_lines_description_chk", + "value": "char_length(description) BETWEEN 1 AND 300" + }, + "purchase_lines_quantity_chk": { + "name": "purchase_lines_quantity_chk", + "value": "quantity > 0" + }, + "purchase_lines_unit_price_chk": { + "name": "purchase_lines_unit_price_chk", + "value": "unit_price >= 0" + } + }, + "isRLSEnabled": false + }, + "public.purchases": { + "name": "purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchased_on": { + "name": "purchased_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "tax": { + "name": "tax", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'confirmed'" + }, + "created_via": { + "name": "created_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'app'" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "purchases_location_date_idx": { + "name": "purchases_location_date_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchased_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "purchases_vendor_idx": { + "name": "purchases_vendor_idx", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "purchases_location_id_locations_id_fk": { + "name": "purchases_location_id_locations_id_fk", + "tableFrom": "purchases", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "purchases_vendor_id_vendors_id_fk": { + "name": "purchases_vendor_id_vendors_id_fk", + "tableFrom": "purchases", + "columnsFrom": [ + "vendor_id" + ], + "tableTo": "vendors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "purchases_currency_currencies_code_fk": { + "name": "purchases_currency_currencies_code_fk", + "tableFrom": "purchases", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "purchases_location_id_uq": { + "name": "purchases_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "purchases_review_state_chk": { + "name": "purchases_review_state_chk", + "value": "\"review_state\" IN ('draft', 'confirmed')" + }, + "purchases_created_via_chk": { + "name": "purchases_created_via_chk", + "value": "\"created_via\" IN ('app', 'mcp', 'assistant', 'import', 'email')" + }, + "purchases_dated_chk": { + "name": "purchases_dated_chk", + "value": "purchased_on IS NOT NULL OR review_state = 'draft'" + }, + "purchases_total_chk": { + "name": "purchases_total_chk", + "value": "total >= 0" + }, + "purchases_tax_chk": { + "name": "purchases_tax_chk", + "value": "tax >= 0" + }, + "purchases_money_chk": { + "name": "purchases_money_chk", + "value": "(total IS NULL AND tax IS NULL) OR currency IS NOT NULL" + }, + "purchases_notes_chk": { + "name": "purchases_notes_chk", + "value": "char_length(notes) <= 5000" + } + }, + "isRLSEnabled": false + }, + "public.brand_logos": { + "name": "brand_logos", + "schema": "", + "columns": { + "brand_id": { + "name": "brand_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "png": { + "name": "png", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "brand_logos_owner_idx": { + "name": "brand_logos_owner_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "brand_logos_owner_account_id_owner_accounts_id_fk": { + "name": "brand_logos_owner_account_id_owner_accounts_id_fk", + "tableFrom": "brand_logos", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "brand_logos_brand_fk": { + "name": "brand_logos_brand_fk", + "tableFrom": "brand_logos", + "columnsFrom": [ + "owner_account_id", + "brand_id" + ], + "tableTo": "brands", + "columnsTo": [ + "owner_account_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "brand_logos_png_chk": { + "name": "brand_logos_png_chk", + "value": "octet_length(png) BETWEEN 8 AND 262144" + }, + "brand_logos_size_chk": { + "name": "brand_logos_size_chk", + "value": "width BETWEEN 1 AND 256 AND height BETWEEN 1 AND 256" + }, + "brand_logos_sha256_chk": { + "name": "brand_logos_sha256_chk", + "value": "sha256 ~ '^[0-9a-f]{64}$'" + } + }, + "isRLSEnabled": false + }, + "public.brands": { + "name": "brands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "support_phone": { + "name": "support_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_url": { + "name": "claim_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_warranty_months": { + "name": "default_warranty_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "brands_owner_account_id_owner_accounts_id_fk": { + "name": "brands_owner_account_id_owner_accounts_id_fk", + "tableFrom": "brands", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "brands_owner_id_uq": { + "name": "brands_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "brands_name_chk": { + "name": "brands_name_chk", + "value": "char_length(name) BETWEEN 1 AND 120" + }, + "brands_website_chk": { + "name": "brands_website_chk", + "value": "website IS NULL OR char_length(website) <= 2000" + }, + "brands_claim_url_chk": { + "name": "brands_claim_url_chk", + "value": "claim_url IS NULL OR char_length(claim_url) <= 2000" + }, + "brands_support_phone_chk": { + "name": "brands_support_phone_chk", + "value": "support_phone IS NULL OR char_length(support_phone) <= 40" + }, + "brands_default_warranty_chk": { + "name": "brands_default_warranty_chk", + "value": "default_warranty_months IS NULL OR default_warranty_months >= 0" + } + }, + "isRLSEnabled": false + }, + "public.people": { + "name": "people", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_user_id": { + "name": "member_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "people_owner_account_id_owner_accounts_id_fk": { + "name": "people_owner_account_id_owner_accounts_id_fk", + "tableFrom": "people", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "people_member_user_id_user_id_fk": { + "name": "people_member_user_id_user_id_fk", + "tableFrom": "people", + "columnsFrom": [ + "member_user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "people_owner_id_uq": { + "name": "people_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "people_display_name_chk": { + "name": "people_display_name_chk", + "value": "char_length(display_name) BETWEEN 1 AND 120" + } + }, + "isRLSEnabled": false + }, + "public.person_contacts": { + "name": "person_contacts", + "schema": "", + "columns": { + "person_id": { + "name": "person_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "person_contacts_owner_account_id_owner_accounts_id_fk": { + "name": "person_contacts_owner_account_id_owner_accounts_id_fk", + "tableFrom": "person_contacts", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "person_contacts_person_fk": { + "name": "person_contacts_person_fk", + "tableFrom": "person_contacts", + "columnsFrom": [ + "owner_account_id", + "person_id" + ], + "tableTo": "people", + "columnsTo": [ + "owner_account_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "person_contacts_phone_chk": { + "name": "person_contacts_phone_chk", + "value": "phone IS NULL OR char_length(phone) <= 40" + }, + "person_contacts_email_chk": { + "name": "person_contacts_email_chk", + "value": "email IS NULL OR char_length(email) <= 320" + }, + "person_contacts_notes_chk": { + "name": "person_contacts_notes_chk", + "value": "notes IS NULL OR char_length(notes) <= 5000" + } + }, + "isRLSEnabled": false + }, + "public.place_kinds": { + "name": "place_kinds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "place_kinds_owner_account_id_owner_accounts_id_fk": { + "name": "place_kinds_owner_account_id_owner_accounts_id_fk", + "tableFrom": "place_kinds", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "place_kinds_owner_key_uq": { + "name": "place_kinds_owner_key_uq", + "columns": [ + "owner_account_id", + "key" + ], + "nullsNotDistinct": true + }, + "place_kinds_owner_id_uq": { + "name": "place_kinds_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "place_kinds_key_chk": { + "name": "place_kinds_key_chk", + "value": "key ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "place_kinds_icon_chk": { + "name": "place_kinds_icon_chk", + "value": "icon ~ '^(lucide|tabler|kept):[a-z0-9-]+$'" + }, + "place_kinds_name_chk": { + "name": "place_kinds_name_chk", + "value": "name IS NULL OR char_length(name) BETWEEN 1 AND 80" + }, + "place_kinds_named_chk": { + "name": "place_kinds_named_chk", + "value": "owner_account_id IS NULL OR name IS NOT NULL OR key IN ('floor', 'room', 'zone', 'closet', 'furniture', 'cube', 'shelf', 'drawer')" + } + }, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "colour": { + "name": "colour", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "tags_owner_account_id_owner_accounts_id_fk": { + "name": "tags_owner_account_id_owner_accounts_id_fk", + "tableFrom": "tags", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_owner_id_uq": { + "name": "tags_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "tags_name_chk": { + "name": "tags_name_chk", + "value": "char_length(name) BETWEEN 1 AND 60" + }, + "tags_colour_chk": { + "name": "tags_colour_chk", + "value": "colour IS NULL OR colour ~ '^#[0-9A-Fa-f]{6}$'" + } + }, + "isRLSEnabled": false + }, + "public.type_fields": { + "name": "type_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type_id": { + "name": "type_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_kind_id": { + "name": "place_kind_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repeatable": { + "name": "repeatable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "secret": { + "name": "secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "type_fields_type_key_uq": { + "name": "type_fields_type_key_uq", + "columns": [ + { + "expression": "type_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "type_id IS NOT NULL", + "concurrently": false + }, + "type_fields_place_kind_key_uq": { + "name": "type_fields_place_kind_key_uq", + "columns": [ + { + "expression": "place_kind_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "place_kind_id IS NOT NULL", + "concurrently": false + }, + "type_fields_owner_idx": { + "name": "type_fields_owner_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "type_fields_owner_account_id_owner_accounts_id_fk": { + "name": "type_fields_owner_account_id_owner_accounts_id_fk", + "tableFrom": "type_fields", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "type_fields_type_id_types_id_fk": { + "name": "type_fields_type_id_types_id_fk", + "tableFrom": "type_fields", + "columnsFrom": [ + "type_id" + ], + "tableTo": "types", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "type_fields_place_kind_id_place_kinds_id_fk": { + "name": "type_fields_place_kind_id_place_kinds_id_fk", + "tableFrom": "type_fields", + "columnsFrom": [ + "place_kind_id" + ], + "tableTo": "place_kinds", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "type_fields_type_account_fk": { + "name": "type_fields_type_account_fk", + "tableFrom": "type_fields", + "columnsFrom": [ + "owner_account_id", + "type_id" + ], + "tableTo": "types", + "columnsTo": [ + "owner_account_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "type_fields_place_kind_account_fk": { + "name": "type_fields_place_kind_account_fk", + "tableFrom": "type_fields", + "columnsFrom": [ + "owner_account_id", + "place_kind_id" + ], + "tableTo": "place_kinds", + "columnsTo": [ + "owner_account_id", + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "type_fields_kind_chk": { + "name": "type_fields_kind_chk", + "value": "\"kind\" IN ('text', 'number', 'date', 'select', 'multi_select', 'boolean', 'url', 'money', 'person', 'vendor', 'file')" + }, + "type_fields_one_owner_chk": { + "name": "type_fields_one_owner_chk", + "value": "num_nonnulls(type_id, place_kind_id) = 1" + }, + "type_fields_key_chk": { + "name": "type_fields_key_chk", + "value": "key ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "type_fields_secret_text_chk": { + "name": "type_fields_secret_text_chk", + "value": "NOT secret OR kind = 'text'" + }, + "type_fields_label_chk": { + "name": "type_fields_label_chk", + "value": "label IS NULL OR char_length(label) BETWEEN 1 AND 80" + }, + "type_fields_unit_chk": { + "name": "type_fields_unit_chk", + "value": "unit IS NULL OR char_length(unit) BETWEEN 1 AND 12" + }, + "type_fields_options_chk": { + "name": "type_fields_options_chk", + "value": "options IS NULL OR jsonb_typeof(options) = 'array'" + } + }, + "isRLSEnabled": false + }, + "public.types": { + "name": "types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "builtin_key": { + "name": "builtin_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copied_from_id": { + "name": "copied_from_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "search_names": { + "name": "search_names", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "colour": { + "name": "colour", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "default_meter": { + "name": "default_meter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_field_group": { + "name": "is_field_group", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "field_groups": { + "name": "field_groups", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + }, + "default_warranty_months": { + "name": "default_warranty_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "types_builtin_key_uq": { + "name": "types_builtin_key_uq", + "columns": [ + { + "expression": "builtin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "owner_account_id IS NULL", + "concurrently": false + }, + "types_parent_idx": { + "name": "types_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "types_copied_from_idx": { + "name": "types_copied_from_idx", + "columns": [ + { + "expression": "copied_from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "types_owner_account_id_owner_accounts_id_fk": { + "name": "types_owner_account_id_owner_accounts_id_fk", + "tableFrom": "types", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "types_parent_fk": { + "name": "types_parent_fk", + "tableFrom": "types", + "columnsFrom": [ + "parent_id" + ], + "tableTo": "types", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "types_copied_from_fk": { + "name": "types_copied_from_fk", + "tableFrom": "types", + "columnsFrom": [ + "copied_from_id" + ], + "tableTo": "types", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "types_owner_id_uq": { + "name": "types_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "types_builtin_chk": { + "name": "types_builtin_chk", + "value": "(owner_account_id IS NULL) = (builtin_key IS NOT NULL)" + }, + "types_builtin_key_chk": { + "name": "types_builtin_key_chk", + "value": "builtin_key IS NULL OR builtin_key ~ '^[a-z][a-z0-9_]{0,39}$'" + }, + "types_named_chk": { + "name": "types_named_chk", + "value": "name IS NOT NULL OR builtin_key IS NOT NULL OR copied_from_id IS NOT NULL" + }, + "types_name_chk": { + "name": "types_name_chk", + "value": "name IS NULL OR char_length(name) BETWEEN 1 AND 80" + }, + "types_icon_chk": { + "name": "types_icon_chk", + "value": "icon ~ '^(lucide|tabler|kept):[a-z0-9-]+$'" + }, + "types_colour_chk": { + "name": "types_colour_chk", + "value": "colour IS NULL OR colour ~ '^#[0-9A-Fa-f]{6}$'" + }, + "types_capabilities_chk": { + "name": "types_capabilities_chk", + "value": "capabilities <@ ARRAY['container', 'metered', 'warranty', 'serialized', 'consumable', 'expires']::text[]" + }, + "types_default_meter_chk": { + "name": "types_default_meter_chk", + "value": "default_meter IS NULL OR jsonb_typeof(default_meter) IN ('object', 'null')" + }, + "types_field_group_chk": { + "name": "types_field_group_chk", + "value": "NOT is_field_group OR (parent_id IS NULL AND cardinality(field_groups) = 0)" + }, + "types_not_own_parent_chk": { + "name": "types_not_own_parent_chk", + "value": "parent_id IS NULL OR parent_id <> id" + }, + "types_default_warranty_chk": { + "name": "types_default_warranty_chk", + "value": "default_warranty_months IS NULL OR default_warranty_months >= 0" + } + }, + "isRLSEnabled": false + }, + "public.vendors": { + "name": "vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'other'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendors_owner_account_id_owner_accounts_id_fk": { + "name": "vendors_owner_account_id_owner_accounts_id_fk", + "tableFrom": "vendors", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "vendors_owner_id_uq": { + "name": "vendors_owner_id_uq", + "columns": [ + "owner_account_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "vendors_kind_chk": { + "name": "vendors_kind_chk", + "value": "\"kind\" IN ('store', 'online', 'service_centre', 'station', 'other')" + }, + "vendors_name_chk": { + "name": "vendors_name_chk", + "value": "char_length(name) BETWEEN 1 AND 120" + }, + "vendors_address_chk": { + "name": "vendors_address_chk", + "value": "address IS NULL OR char_length(address) <= 500" + }, + "vendors_phone_chk": { + "name": "vendors_phone_chk", + "value": "phone IS NULL OR char_length(phone) <= 40" + }, + "vendors_website_chk": { + "name": "vendors_website_chk", + "value": "website IS NULL OR char_length(website) <= 2000" + } + }, + "isRLSEnabled": false + }, + "public.notification_digests": { + "name": "notification_digests", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "digest_on": { + "name": "digest_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notification_digests_channel_idx": { + "name": "notification_digests_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "notification_digests_user_id_user_id_fk": { + "name": "notification_digests_user_id_user_id_fk", + "tableFrom": "notification_digests", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "notification_digests_channel_id_notification_channels_id_fk": { + "name": "notification_digests_channel_id_notification_channels_id_fk", + "tableFrom": "notification_digests", + "columnsFrom": [ + "channel_id" + ], + "tableTo": "notification_channels", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "notification_digests_pk": { + "name": "notification_digests_pk", + "columns": [ + "user_id", + "digest_on", + "channel_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "occurrence_id": { + "name": "occurrence_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notifications_user_idx": { + "name": "notifications_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "notifications_unread_idx": { + "name": "notifications_unread_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "read_at IS NULL", + "concurrently": false + }, + "notifications_occurrence_idx": { + "name": "notifications_occurrence_idx", + "columns": [ + { + "expression": "occurrence_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "notifications_user_id_user_id_fk": { + "name": "notifications_user_id_user_id_fk", + "tableFrom": "notifications", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "notifications_location_id_locations_id_fk": { + "name": "notifications_location_id_locations_id_fk", + "tableFrom": "notifications", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "notifications_occurrence_id_reminder_occurrences_id_fk": { + "name": "notifications_occurrence_id_reminder_occurrences_id_fk", + "tableFrom": "notifications", + "columnsFrom": [ + "occurrence_id" + ], + "tableTo": "reminder_occurrences", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "notifications_user_occurrence_uq": { + "name": "notifications_user_occurrence_uq", + "columns": [ + "user_id", + "occurrence_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "notifications_kind_chk": { + "name": "notifications_kind_chk", + "value": "\"kind\" IN ('reminder', 'membership_added', 'membership_ended', 'ai_cap', 'ai_summary', 'export_ready')" + }, + "notifications_payload_chk": { + "name": "notifications_payload_chk", + "value": "jsonb_typeof(payload) = 'object'" + }, + "notifications_reminder_chk": { + "name": "notifications_reminder_chk", + "value": "(kind = 'reminder') = (occurrence_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.reminder_deliveries": { + "name": "reminder_deliveries", + "schema": "", + "columns": { + "occurrence_id": { + "name": "occurrence_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "not_before": { + "name": "not_before", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reminder_deliveries_digest_idx": { + "name": "reminder_deliveries_digest_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'digest'", + "concurrently": false + }, + "reminder_deliveries_queued_idx": { + "name": "reminder_deliveries_queued_idx", + "columns": [ + { + "expression": "not_before", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'queued'", + "concurrently": false + }, + "reminder_deliveries_channel_idx": { + "name": "reminder_deliveries_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "reminder_deliveries_occurrence_id_reminder_occurrences_id_fk": { + "name": "reminder_deliveries_occurrence_id_reminder_occurrences_id_fk", + "tableFrom": "reminder_deliveries", + "columnsFrom": [ + "occurrence_id" + ], + "tableTo": "reminder_occurrences", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reminder_deliveries_user_id_user_id_fk": { + "name": "reminder_deliveries_user_id_user_id_fk", + "tableFrom": "reminder_deliveries", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reminder_deliveries_channel_id_notification_channels_id_fk": { + "name": "reminder_deliveries_channel_id_notification_channels_id_fk", + "tableFrom": "reminder_deliveries", + "columnsFrom": [ + "channel_id" + ], + "tableTo": "notification_channels", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "reminder_deliveries_pk": { + "name": "reminder_deliveries_pk", + "columns": [ + "occurrence_id", + "user_id", + "channel_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reminder_deliveries_status_chk": { + "name": "reminder_deliveries_status_chk", + "value": "\"status\" IN ('digest', 'queued', 'sending', 'sent', 'failed', 'skipped')" + }, + "reminder_deliveries_error_chk": { + "name": "reminder_deliveries_error_chk", + "value": "error ~ '^[a-z_0-9]{1,40}$'" + } + }, + "isRLSEnabled": false + }, + "public.reminder_occurrences": { + "name": "reminder_occurrences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "due_period": { + "name": "due_period", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "due_on": { + "name": "due_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "reminder_occurrences_open_idx": { + "name": "reminder_occurrences_open_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "state = 'open'", + "concurrently": false + }, + "reminder_occurrences_source_idx": { + "name": "reminder_occurrences_source_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "reminder_occurrences_location_id_locations_id_fk": { + "name": "reminder_occurrences_location_id_locations_id_fk", + "tableFrom": "reminder_occurrences", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "reminder_occurrences_thing_fk": { + "name": "reminder_occurrences_thing_fk", + "tableFrom": "reminder_occurrences", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "reminder_occurrences_place_fk": { + "name": "reminder_occurrences_place_fk", + "tableFrom": "reminder_occurrences", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reminder_occurrences_location_id_uq": { + "name": "reminder_occurrences_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + }, + "reminder_occurrences_key_uq": { + "name": "reminder_occurrences_key_uq", + "columns": [ + "thing_id", + "place_id", + "location_id", + "source_type", + "source_id", + "kind", + "due_period" + ], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": { + "reminder_occurrences_source_type_chk": { + "name": "reminder_occurrences_source_type_chk", + "value": "\"source_type\" IN ('schedule', 'warranty', 'registration', 'document', 'loan', 'thing_expiry', 'stock', 'reading_stale')" + }, + "reminder_occurrences_kind_chk": { + "name": "reminder_occurrences_kind_chk", + "value": "\"kind\" IN ('due', 'overdue', 'expiring')" + }, + "reminder_occurrences_state_chk": { + "name": "reminder_occurrences_state_chk", + "value": "\"state\" IN ('open', 'done', 'superseded', 'cancelled')" + }, + "reminder_occurrences_due_period_chk": { + "name": "reminder_occurrences_due_period_chk", + "value": "due_period ~ '^(date:\\d{4}-\\d{2}-\\d{2}|meter:\\d+(\\.\\d{1,3})?)$'" + }, + "reminder_occurrences_subject_chk": { + "name": "reminder_occurrences_subject_chk", + "value": "num_nonnulls(thing_id, place_id) <= 1" + }, + "reminder_occurrences_closed_chk": { + "name": "reminder_occurrences_closed_chk", + "value": "(state = 'open') = (closed_at IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.report_runs": { + "name": "report_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "location_ids": { + "name": "location_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inventory'" + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "progress_done": { + "name": "progress_done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_total": { + "name": "progress_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes": { + "name": "bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now() + interval '24 hours'" + }, + "email_when_done": { + "name": "email_when_done", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "report_runs_user_idx": { + "name": "report_runs_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "report_runs_expires_idx": { + "name": "report_runs_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "report_runs_user_id_user_id_fk": { + "name": "report_runs_user_id_user_id_fk", + "tableFrom": "report_runs", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "report_runs_location_id_locations_id_fk": { + "name": "report_runs_location_id_locations_id_fk", + "tableFrom": "report_runs", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "report_runs_owner_account_id_owner_accounts_id_fk": { + "name": "report_runs_owner_account_id_owner_accounts_id_fk", + "tableFrom": "report_runs", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "report_runs_status_chk": { + "name": "report_runs_status_chk", + "value": "\"status\" IN ('queued', 'running', 'done', 'failed')" + }, + "report_runs_kind_chk": { + "name": "report_runs_kind_chk", + "value": "\"kind\" IN ('inventory', 'insurance', 'vehicle_history', 'condition', 'move')" + }, + "report_runs_scope_chk": { + "name": "report_runs_scope_chk", + "value": "num_nonnulls(location_id, owner_account_id) = 1" + }, + "report_runs_locations_chk": { + "name": "report_runs_locations_chk", + "value": "cardinality(location_ids) BETWEEN 1 AND 1000\n AND (location_id IS NULL OR location_ids = ARRAY[location_id])" + }, + "report_runs_thing_chk": { + "name": "report_runs_thing_chk", + "value": "(kind = 'vehicle_history') = (thing_id IS NOT NULL)\n AND (thing_id IS NULL OR location_id IS NOT NULL)" + }, + "report_runs_options_chk": { + "name": "report_runs_options_chk", + "value": "jsonb_typeof(options) = 'object'" + }, + "report_runs_progress_chk": { + "name": "report_runs_progress_chk", + "value": "progress_done >= 0 AND progress_total >= 0 AND progress_done <= progress_total" + }, + "report_runs_error_chk": { + "name": "report_runs_error_chk", + "value": "error ~ '^[a-z_]{1,32}$'" + }, + "report_runs_expires_chk": { + "name": "report_runs_expires_chk", + "value": "expires_at > created_at" + } + }, + "isRLSEnabled": false + }, + "public.report_schedules": { + "name": "report_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_run_on": { + "name": "last_run_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "report_schedules_uq": { + "name": "report_schedules_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "report_schedules_location_idx": { + "name": "report_schedules_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "report_schedules_user_id_user_id_fk": { + "name": "report_schedules_user_id_user_id_fk", + "tableFrom": "report_schedules", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "report_schedules_location_id_locations_id_fk": { + "name": "report_schedules_location_id_locations_id_fk", + "tableFrom": "report_schedules", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "report_schedules_kind_chk": { + "name": "report_schedules_kind_chk", + "value": "\"kind\" IN ('inventory', 'insurance')" + }, + "report_schedules_cadence_chk": { + "name": "report_schedules_cadence_chk", + "value": "\"cadence\" IN ('weekly', 'monthly')" + }, + "report_schedules_error_chk": { + "name": "report_schedules_error_chk", + "value": "last_error ~ '^[a-z_]{1,32}$'" + } + }, + "isRLSEnabled": false + }, + "public.room_captures": { + "name": "room_captures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "container_id": { + "name": "container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "batch_id": { + "name": "batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reading'" + }, + "codes": { + "name": "codes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "room_captures_location_idx": { + "name": "room_captures_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "room_captures_batch_idx": { + "name": "room_captures_batch_idx", + "columns": [ + { + "expression": "batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "room_captures_location_id_locations_id_fk": { + "name": "room_captures_location_id_locations_id_fk", + "tableFrom": "room_captures", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "room_captures_place_fk": { + "name": "room_captures_place_fk", + "tableFrom": "room_captures", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "room_captures_container_fk": { + "name": "room_captures_container_fk", + "tableFrom": "room_captures", + "columnsFrom": [ + "location_id", + "container_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "room_captures_location_id_uq": { + "name": "room_captures_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "room_captures_status_chk": { + "name": "room_captures_status_chk", + "value": "\"status\" IN ('reading', 'merging', 'ready', 'failed', 'off')" + }, + "room_captures_target_chk": { + "name": "room_captures_target_chk", + "value": "num_nonnulls(place_id, container_id) = 1" + }, + "room_captures_codes_chk": { + "name": "room_captures_codes_chk", + "value": "cardinality(codes) <= 60" + }, + "room_captures_photos_chk": { + "name": "room_captures_photos_chk", + "value": "cardinality(attachment_ids) BETWEEN 1 AND 5" + }, + "room_captures_result_chk": { + "name": "room_captures_result_chk", + "value": "result IS NULL OR jsonb_typeof(result) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.scan_session_lines": { + "name": "scan_session_lines", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_place_id": { + "name": "to_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_container_id": { + "name": "to_container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "was_place_id": { + "name": "was_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "was_container_id": { + "name": "was_container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "read_by": { + "name": "read_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "area_place_id": { + "name": "area_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "found_qty": { + "name": "found_qty", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": false + }, + "expected_qty": { + "name": "expected_qty", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": false + }, + "short_not_here": { + "name": "short_not_here", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "scan_session_lines_thing_idx": { + "name": "scan_session_lines_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scan_session_lines_thing_id_things_id_fk": { + "name": "scan_session_lines_thing_id_things_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scan_session_lines_to_place_id_places_id_fk": { + "name": "scan_session_lines_to_place_id_places_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "to_place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_session_lines_to_container_id_things_id_fk": { + "name": "scan_session_lines_to_container_id_things_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "to_container_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_session_lines_was_place_id_places_id_fk": { + "name": "scan_session_lines_was_place_id_places_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "was_place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_session_lines_was_container_id_things_id_fk": { + "name": "scan_session_lines_was_container_id_things_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "was_container_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_session_lines_area_place_id_places_id_fk": { + "name": "scan_session_lines_area_place_id_places_id_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "area_place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_session_lines_session_fk": { + "name": "scan_session_lines_session_fk", + "tableFrom": "scan_session_lines", + "columnsFrom": [ + "location_id", + "session_id" + ], + "tableTo": "scan_sessions", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "scan_session_lines_pk": { + "name": "scan_session_lines_pk", + "columns": [ + "session_id", + "thing_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scan_session_lines_action_chk": { + "name": "scan_session_lines_action_chk", + "value": "\"action\" IN ('seen', 'not_here', 'move', 'missing', 'elsewhere', 'on_loan', 'in_repair', 'trashed', 'not_counted')" + }, + "scan_session_lines_to_chk": { + "name": "scan_session_lines_to_chk", + "value": "num_nonnulls(to_place_id, to_container_id) <= 1" + }, + "scan_session_lines_qty_chk": { + "name": "scan_session_lines_qty_chk", + "value": "(found_qty IS NULL OR found_qty > 0) AND (expected_qty IS NULL OR expected_qty >= 0)\n AND (NOT short_not_here OR found_qty IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.scan_sessions": { + "name": "scan_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_place_id": { + "name": "target_place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_container_id": { + "name": "target_container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_inside": { + "name": "target_inside", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "started_by": { + "name": "started_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expected": { + "name": "expected", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seen": { + "name": "seen", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "not_here": { + "name": "not_here", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "moved": { + "name": "moved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped": { + "name": "skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "missing": { + "name": "missing", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "elsewhere": { + "name": "elsewhere", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "on_loan": { + "name": "on_loan", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "in_repair": { + "name": "in_repair", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "not_counted": { + "name": "not_counted", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "counted_place_ids": { + "name": "counted_place_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": false + }, + "schedule_id": { + "name": "schedule_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "finished_by": { + "name": "finished_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "audit_event_id": { + "name": "audit_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scan_sessions_location_idx": { + "name": "scan_sessions_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scan_sessions_schedule_idx": { + "name": "scan_sessions_schedule_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scan_sessions_open_idx": { + "name": "scan_sessions_open_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "finished_at IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "scan_sessions_location_id_locations_id_fk": { + "name": "scan_sessions_location_id_locations_id_fk", + "tableFrom": "scan_sessions", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scan_sessions_target_place_id_places_id_fk": { + "name": "scan_sessions_target_place_id_places_id_fk", + "tableFrom": "scan_sessions", + "columnsFrom": [ + "target_place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_sessions_target_container_id_things_id_fk": { + "name": "scan_sessions_target_container_id_things_id_fk", + "tableFrom": "scan_sessions", + "columnsFrom": [ + "target_container_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scan_sessions_schedule_id_schedules_id_fk": { + "name": "scan_sessions_schedule_id_schedules_id_fk", + "tableFrom": "scan_sessions", + "columnsFrom": [ + "schedule_id" + ], + "tableTo": "schedules", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "scan_sessions_location_id_uq": { + "name": "scan_sessions_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "scan_sessions_mode_chk": { + "name": "scan_sessions_mode_chk", + "value": "\"mode\" IN ('count', 'pack', 'move')" + }, + "scan_sessions_target_chk": { + "name": "scan_sessions_target_chk", + "value": "num_nonnulls(target_place_id, target_container_id) <= 1" + }, + "scan_sessions_totals_chk": { + "name": "scan_sessions_totals_chk", + "value": "seen >= 0 AND not_here >= 0 AND moved >= 0 AND skipped >= 0\n AND (expected IS NULL OR expected >= 0)\n AND (missing IS NULL OR missing >= 0) AND (elsewhere IS NULL OR elsewhere >= 0)\n AND (on_loan IS NULL OR on_loan >= 0) AND (in_repair IS NULL OR in_repair >= 0)\n AND (not_counted IS NULL OR not_counted >= 0)" + } + }, + "isRLSEnabled": false + }, + "public.expiring_documents": { + "name": "expiring_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_on": { + "name": "expires_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "superseded_by_id": { + "name": "superseded_by_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issued_on": { + "name": "issued_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "expiring_documents_expires_idx": { + "name": "expiring_documents_expires_idx", + "columns": [ + { + "expression": "expires_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "superseded_by_id IS NULL", + "concurrently": false + }, + "expiring_documents_location_idx": { + "name": "expiring_documents_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "expiring_documents_thing_idx": { + "name": "expiring_documents_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "expiring_documents_place_idx": { + "name": "expiring_documents_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "expiring_documents_location_id_locations_id_fk": { + "name": "expiring_documents_location_id_locations_id_fk", + "tableFrom": "expiring_documents", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "expiring_documents_currency_currencies_code_fk": { + "name": "expiring_documents_currency_currencies_code_fk", + "tableFrom": "expiring_documents", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "expiring_documents_thing_fk": { + "name": "expiring_documents_thing_fk", + "tableFrom": "expiring_documents", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "expiring_documents_place_fk": { + "name": "expiring_documents_place_fk", + "tableFrom": "expiring_documents", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expiring_documents_location_id_uq": { + "name": "expiring_documents_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "expiring_documents_kind_chk": { + "name": "expiring_documents_kind_chk", + "value": "\"kind\" IN ('registration', 'insurance', 'licence', 'inspection', 'lease', 'contract', 'other')" + }, + "expiring_documents_cost_chk": { + "name": "expiring_documents_cost_chk", + "value": "(cost IS NULL) = (currency IS NULL) AND (cost IS NULL OR cost >= 0)" + }, + "expiring_documents_issued_chk": { + "name": "expiring_documents_issued_chk", + "value": "issued_on IS NULL OR issued_on <= expires_on" + }, + "expiring_documents_title_chk": { + "name": "expiring_documents_title_chk", + "value": "char_length(title) BETWEEN 1 AND 120" + }, + "expiring_documents_lead_days_chk": { + "name": "expiring_documents_lead_days_chk", + "value": "lead_days BETWEEN 0 AND 365" + }, + "expiring_documents_subject_chk": { + "name": "expiring_documents_subject_chk", + "value": "num_nonnulls(thing_id, place_id) <= 1" + }, + "expiring_documents_other_chk": { + "name": "expiring_documents_other_chk", + "value": "kind <> 'other' OR title IS NOT NULL" + }, + "expiring_documents_superseded_chk": { + "name": "expiring_documents_superseded_chk", + "value": "superseded_by_id IS DISTINCT FROM id" + } + }, + "isRLSEnabled": false + }, + "public.schedules": { + "name": "schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "every_months": { + "name": "every_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "every_units": { + "name": "every_units", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "meter_id": { + "name": "meter_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "due_on": { + "name": "due_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 14 + }, + "lead_units": { + "name": "lead_units", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "base_on": { + "name": "base_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "base_value": { + "name": "base_value", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "anchor_on": { + "name": "anchor_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "anchor_value": { + "name": "anchor_value", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until_value": { + "name": "snoozed_until_value", + "type": "numeric(14, 3)", + "primaryKey": false, + "notNull": false + }, + "skip_next": { + "name": "skip_next", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "schedules_one_count_uq": { + "name": "schedules_one_count_uq", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "kind = 'count' AND active", + "concurrently": false + }, + "schedules_thing_idx": { + "name": "schedules_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "schedules_place_idx": { + "name": "schedules_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "schedules_location_idx": { + "name": "schedules_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "active", + "concurrently": false + } + }, + "foreignKeys": { + "schedules_location_id_locations_id_fk": { + "name": "schedules_location_id_locations_id_fk", + "tableFrom": "schedules", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "schedules_thing_fk": { + "name": "schedules_thing_fk", + "tableFrom": "schedules", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "schedules_place_fk": { + "name": "schedules_place_fk", + "tableFrom": "schedules", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "schedules_meter_fk": { + "name": "schedules_meter_fk", + "tableFrom": "schedules", + "columnsFrom": [ + "location_id", + "meter_id" + ], + "tableTo": "meters", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "schedules_location_id_uq": { + "name": "schedules_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "schedules_kind_chk": { + "name": "schedules_kind_chk", + "value": "\"kind\" IN ('task', 'count')" + }, + "schedules_count_chk": { + "name": "schedules_count_chk", + "value": "kind <> 'count' OR (place_id IS NOT NULL AND every_months BETWEEN 1 AND 24\n AND every_units IS NULL AND due_on IS NULL)" + }, + "schedules_name_chk": { + "name": "schedules_name_chk", + "value": "char_length(name) BETWEEN 1 AND 120" + }, + "schedules_every_months_chk": { + "name": "schedules_every_months_chk", + "value": "every_months BETWEEN 1 AND 600" + }, + "schedules_every_units_chk": { + "name": "schedules_every_units_chk", + "value": "every_units > 0" + }, + "schedules_lead_days_chk": { + "name": "schedules_lead_days_chk", + "value": "lead_days BETWEEN 0 AND 365" + }, + "schedules_lead_units_chk": { + "name": "schedules_lead_units_chk", + "value": "lead_units >= 0" + }, + "schedules_subject_chk": { + "name": "schedules_subject_chk", + "value": "num_nonnulls(thing_id, place_id) = 1" + }, + "schedules_rule_chk": { + "name": "schedules_rule_chk", + "value": "num_nonnulls(every_months, every_units, due_on) >= 1" + }, + "schedules_meter_chk": { + "name": "schedules_meter_chk", + "value": "(every_units IS NULL) = (meter_id IS NULL)" + }, + "schedules_meter_thing_chk": { + "name": "schedules_meter_thing_chk", + "value": "meter_id IS NULL OR thing_id IS NOT NULL" + }, + "schedules_one_off_chk": { + "name": "schedules_one_off_chk", + "value": "due_on IS NULL OR (every_months IS NULL AND every_units IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.service_completions": { + "name": "service_completions", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "service_record_id": { + "name": "service_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "service_completions_schedule_idx": { + "name": "service_completions_schedule_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "service_completions_record_fk": { + "name": "service_completions_record_fk", + "tableFrom": "service_completions", + "columnsFrom": [ + "location_id", + "service_record_id" + ], + "tableTo": "service_records", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "service_completions_schedule_fk": { + "name": "service_completions_schedule_fk", + "tableFrom": "service_completions", + "columnsFrom": [ + "location_id", + "schedule_id" + ], + "tableTo": "schedules", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "service_completions_pk": { + "name": "service_completions_pk", + "columns": [ + "service_record_id", + "schedule_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_field_policies": { + "name": "secret_field_policies", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type_field_id": { + "name": "type_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reveal_roles": { + "name": "reveal_roles", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['owner', 'admin']::text[]" + }, + "reveal_user_ids": { + "name": "reveal_user_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + }, + "ai_allowed": { + "name": "ai_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "secret_field_policies_location_id_locations_id_fk": { + "name": "secret_field_policies_location_id_locations_id_fk", + "tableFrom": "secret_field_policies", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "secret_field_policies_type_field_id_type_fields_id_fk": { + "name": "secret_field_policies_type_field_id_type_fields_id_fk", + "tableFrom": "secret_field_policies", + "columnsFrom": [ + "type_field_id" + ], + "tableTo": "type_fields", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "secret_field_policies_pk": { + "name": "secret_field_policies_pk", + "columns": [ + "location_id", + "type_field_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "secret_field_policies_roles_chk": { + "name": "secret_field_policies_roles_chk", + "value": "reveal_roles <@ ARRAY['owner', 'admin', 'member', 'viewer']::text[]" + } + }, + "isRLSEnabled": false + }, + "public.secret_values": { + "name": "secret_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type_field_id": { + "name": "type_field_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "field_key": { + "name": "field_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ciphertext": { + "name": "ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "secret_values_thing_current_uq": { + "name": "secret_values_thing_current_uq", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "superseded_at IS NULL AND thing_id IS NOT NULL", + "concurrently": false + }, + "secret_values_place_current_uq": { + "name": "secret_values_place_current_uq", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "superseded_at IS NULL AND place_id IS NOT NULL", + "concurrently": false + }, + "secret_values_type_field_idx": { + "name": "secret_values_type_field_idx", + "columns": [ + { + "expression": "type_field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "secret_values_key_version_idx": { + "name": "secret_values_key_version_idx", + "columns": [ + { + "expression": "key_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "secret_values_type_field_id_type_fields_id_fk": { + "name": "secret_values_type_field_id_type_fields_id_fk", + "tableFrom": "secret_values", + "columnsFrom": [ + "type_field_id" + ], + "tableTo": "type_fields", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "secret_values_thing_fk": { + "name": "secret_values_thing_fk", + "tableFrom": "secret_values", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "secret_values_place_fk": { + "name": "secret_values_place_fk", + "tableFrom": "secret_values", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "secret_values_one_subject_chk": { + "name": "secret_values_one_subject_chk", + "value": "num_nonnulls(thing_id, place_id) = 1" + }, + "secret_values_key_version_chk": { + "name": "secret_values_key_version_chk", + "value": "key_version > 0" + }, + "secret_values_ciphertext_chk": { + "name": "secret_values_ciphertext_chk", + "value": "ciphertext IS NULL OR jsonb_typeof(ciphertext) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.service_lines": { + "name": "service_lines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "service_record_id": { + "name": "service_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": false + }, + "unit_cost": { + "name": "unit_cost", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "sort": { + "name": "sort", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "consumable_thing_id": { + "name": "consumable_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "consumed_quantity": { + "name": "consumed_quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "service_lines_record_idx": { + "name": "service_lines_record_idx", + "columns": [ + { + "expression": "service_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "service_lines_consumable_idx": { + "name": "service_lines_consumable_idx", + "columns": [ + { + "expression": "consumable_thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "consumable_thing_id IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "service_lines_record_fk": { + "name": "service_lines_record_fk", + "tableFrom": "service_lines", + "columnsFrom": [ + "location_id", + "service_record_id" + ], + "tableTo": "service_records", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "service_lines_kind_chk": { + "name": "service_lines_kind_chk", + "value": "\"kind\" IN ('part', 'labour', 'fluid', 'other')" + }, + "service_lines_description_chk": { + "name": "service_lines_description_chk", + "value": "char_length(description) BETWEEN 1 AND 300" + }, + "service_lines_quantity_chk": { + "name": "service_lines_quantity_chk", + "value": "quantity > 0" + }, + "service_lines_unit_cost_chk": { + "name": "service_lines_unit_cost_chk", + "value": "unit_cost >= 0" + }, + "service_lines_consumed_chk": { + "name": "service_lines_consumed_chk", + "value": "consumed_quantity > 0" + } + }, + "isRLSEnabled": false + }, + "public.service_records": { + "name": "service_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "serviced_on": { + "name": "serviced_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "meter_reading_id": { + "name": "meter_reading_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logged_by": { + "name": "logged_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'confirmed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "service_records_thing_idx": { + "name": "service_records_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "serviced_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "service_records_place_idx": { + "name": "service_records_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "serviced_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "service_records_reading_idx": { + "name": "service_records_reading_idx", + "columns": [ + { + "expression": "meter_reading_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "service_records_vendor_idx": { + "name": "service_records_vendor_idx", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "service_records_reading_uq": { + "name": "service_records_reading_uq", + "columns": [ + { + "expression": "meter_reading_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "meter_reading_id IS NOT NULL", + "concurrently": false + }, + "service_records_drafts_idx": { + "name": "service_records_drafts_idx", + "columns": [ + { + "expression": "logged_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "review_state = 'draft'", + "concurrently": false + } + }, + "foreignKeys": { + "service_records_location_id_locations_id_fk": { + "name": "service_records_location_id_locations_id_fk", + "tableFrom": "service_records", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "service_records_currency_currencies_code_fk": { + "name": "service_records_currency_currencies_code_fk", + "tableFrom": "service_records", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "service_records_thing_fk": { + "name": "service_records_thing_fk", + "tableFrom": "service_records", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "service_records_place_fk": { + "name": "service_records_place_fk", + "tableFrom": "service_records", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "service_records_location_id_uq": { + "name": "service_records_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "service_records_review_state_chk": { + "name": "service_records_review_state_chk", + "value": "\"review_state\" IN ('draft', 'confirmed')" + }, + "service_records_subject_chk": { + "name": "service_records_subject_chk", + "value": "num_nonnulls(thing_id, place_id) = 1" + }, + "service_records_money_chk": { + "name": "service_records_money_chk", + "value": "(total IS NULL) = (currency IS NULL)" + }, + "service_records_total_chk": { + "name": "service_records_total_chk", + "value": "total >= 0" + }, + "service_records_notes_chk": { + "name": "service_records_notes_chk", + "value": "char_length(notes) <= 5000" + } + }, + "isRLSEnabled": false + }, + "public.share_links": { + "name": "share_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_viewed_at": { + "name": "last_viewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "share_links_location_idx": { + "name": "share_links_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "share_links_thing_idx": { + "name": "share_links_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "share_links_place_idx": { + "name": "share_links_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "share_links_location_id_locations_id_fk": { + "name": "share_links_location_id_locations_id_fk", + "tableFrom": "share_links", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "share_links_thing_id_things_id_fk": { + "name": "share_links_thing_id_things_id_fk", + "tableFrom": "share_links", + "columnsFrom": [ + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "share_links_place_id_places_id_fk": { + "name": "share_links_place_id_places_id_fk", + "tableFrom": "share_links", + "columnsFrom": [ + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "share_links_token_hash_uq": { + "name": "share_links_token_hash_uq", + "columns": [ + "token_hash" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "share_links_scope_type_chk": { + "name": "share_links_scope_type_chk", + "value": "\"scope_type\" IN ('thing', 'place', 'view')" + }, + "share_links_token_hash_chk": { + "name": "share_links_token_hash_chk", + "value": "token_hash ~ '^[0-9a-f]{64}$'" + }, + "share_links_subject_chk": { + "name": "share_links_subject_chk", + "value": "CASE scope_type\n WHEN 'thing' THEN thing_id IS NOT NULL AND place_id IS NULL AND filter IS NULL\n WHEN 'place' THEN place_id IS NOT NULL AND thing_id IS NULL AND filter IS NULL\n ELSE filter IS NOT NULL AND thing_id IS NULL AND place_id IS NULL\n AND title IS NOT NULL END" + }, + "share_links_filter_chk": { + "name": "share_links_filter_chk", + "value": "filter IS NULL OR jsonb_typeof(filter) = 'object'" + }, + "share_links_title_chk": { + "name": "share_links_title_chk", + "value": "title IS NULL OR char_length(title) BETWEEN 1 AND 120" + }, + "share_links_expiry_chk": { + "name": "share_links_expiry_chk", + "value": "expires_at > created_at AND expires_at <= created_at + interval '1 year'" + }, + "share_links_views_chk": { + "name": "share_links_views_chk", + "value": "views >= 0" + } + }, + "isRLSEnabled": false + }, + "public.box_check_lines": { + "name": "box_check_lines", + "schema": "", + "columns": { + "box_check_id": { + "name": "box_check_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expected_qty": { + "name": "expected_qty", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true + }, + "found_qty": { + "name": "found_qty", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "box_check_lines_thing_idx": { + "name": "box_check_lines_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "box_check_lines_check_fk": { + "name": "box_check_lines_check_fk", + "tableFrom": "box_check_lines", + "columnsFrom": [ + "location_id", + "box_check_id" + ], + "tableTo": "box_checks", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "box_check_lines_thing_fk": { + "name": "box_check_lines_thing_fk", + "tableFrom": "box_check_lines", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "box_check_lines_pk": { + "name": "box_check_lines_pk", + "columns": [ + "box_check_id", + "thing_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "box_check_lines_expected_chk": { + "name": "box_check_lines_expected_chk", + "value": "expected_qty >= 0" + }, + "box_check_lines_found_chk": { + "name": "box_check_lines_found_chk", + "value": "found_qty >= 0" + } + }, + "isRLSEnabled": false + }, + "public.box_checks": { + "name": "box_checks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "container_id": { + "name": "container_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checked_by": { + "name": "checked_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "box_checks_container_idx": { + "name": "box_checks_container_idx", + "columns": [ + { + "expression": "container_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "box_checks_location_id_locations_id_fk": { + "name": "box_checks_location_id_locations_id_fk", + "tableFrom": "box_checks", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "box_checks_container_fk": { + "name": "box_checks_container_fk", + "tableFrom": "box_checks", + "columnsFrom": [ + "location_id", + "container_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "box_checks_location_id_uq": { + "name": "box_checks_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legacy_codes": { + "name": "legacy_codes", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_collection": { + "name": "source_collection", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "change_xid": { + "name": "change_xid", + "type": "xid8", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "legacy_codes_code_idx": { + "name": "legacy_codes_code_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "legacy_codes_any_code_idx": { + "name": "legacy_codes_any_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "legacy_codes_thing_idx": { + "name": "legacy_codes_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "legacy_codes_place_idx": { + "name": "legacy_codes_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "legacy_codes_sync_idx": { + "name": "legacy_codes_sync_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_xid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "legacy_codes_location_id_locations_id_fk": { + "name": "legacy_codes_location_id_locations_id_fk", + "tableFrom": "legacy_codes", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "legacy_codes_thing_fk": { + "name": "legacy_codes_thing_fk", + "tableFrom": "legacy_codes", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "legacy_codes_place_fk": { + "name": "legacy_codes_place_fk", + "tableFrom": "legacy_codes", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "legacy_codes_pk": { + "name": "legacy_codes_pk", + "columns": [ + "location_id", + "source", + "source_collection", + "code" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "legacy_codes_source_chk": { + "name": "legacy_codes_source_chk", + "value": "\"source\" IN ('homebox', 'csv', 'own', 'kept')" + }, + "legacy_codes_collection_chk": { + "name": "legacy_codes_collection_chk", + "value": "char_length(source_collection) <= 100" + }, + "legacy_codes_code_chk": { + "name": "legacy_codes_code_chk", + "value": "char_length(code) BETWEEN 1 AND 100 AND code = upper(btrim(code))" + }, + "legacy_codes_target_chk": { + "name": "legacy_codes_target_chk", + "value": "num_nonnulls(thing_id, place_id) = 1" + } + }, + "isRLSEnabled": false + }, + "public.own_code_counters": { + "name": "own_code_counters", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_number": { + "name": "last_number", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "own_code_counters_location_id_locations_id_fk": { + "name": "own_code_counters_location_id_locations_id_fk", + "tableFrom": "own_code_counters", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "own_code_counters_pk": { + "name": "own_code_counters_pk", + "columns": [ + "location_id", + "prefix" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "own_code_counters_number_chk": { + "name": "own_code_counters_number_chk", + "value": "last_number >= 0" + } + }, + "isRLSEnabled": false + }, + "public.own_code_settings": { + "name": "own_code_settings", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "numbering": { + "name": "numbering", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "pad": { + "name": "pad", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 4 + }, + "rule_pattern": { + "name": "rule_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rule_message": { + "name": "rule_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rule_example": { + "name": "rule_example", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "own_code_settings_location_id_locations_id_fk": { + "name": "own_code_settings_location_id_locations_id_fk", + "tableFrom": "own_code_settings", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "own_code_settings_prefix_chk": { + "name": "own_code_settings_prefix_chk", + "value": "char_length(prefix) <= 20 AND prefix = upper(btrim(prefix))" + }, + "own_code_settings_pad_chk": { + "name": "own_code_settings_pad_chk", + "value": "pad BETWEEN 1 AND 8" + }, + "own_code_settings_rule_chk": { + "name": "own_code_settings_rule_chk", + "value": "num_nonnulls(rule_pattern, rule_message, rule_example) IN (0, 3)\n AND char_length(rule_pattern) BETWEEN 1 AND 200\n AND char_length(rule_message) BETWEEN 1 AND 200\n AND char_length(rule_example) BETWEEN 1 AND 100" + } + }, + "isRLSEnabled": false + }, + "public.sync_ops": { + "name": "sync_ops", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "op": { + "name": "op", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_version": { + "name": "payload_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "client_version": { + "name": "client_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "taken_at": { + "name": "taken_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "sync_ops_received_idx": { + "name": "sync_ops_received_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "sync_ops_user_id_user_id_fk": { + "name": "sync_ops_user_id_user_id_fk", + "tableFrom": "sync_ops", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "sync_ops_location_id_locations_id_fk": { + "name": "sync_ops_location_id_locations_id_fk", + "tableFrom": "sync_ops", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "sync_ops_pk": { + "name": "sync_ops_pk", + "columns": [ + "user_id", + "idempotency_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sync_ops_op_chk": { + "name": "sync_ops_op_chk", + "value": "\"op\" IN ('create_thing', 'move', 'log_reading', 'claim_label', 'mark_seen', 'not_here', 'create_area', 'box_check', 'move_box', 'move_box_room', 'scan_session_lines', 'scan_session_finish', 'tag_things')" + }, + "sync_ops_outcome_chk": { + "name": "sync_ops_outcome_chk", + "value": "\"outcome\" IN ('applied', 'needs_review', 'dropped')" + }, + "sync_ops_key_chk": { + "name": "sync_ops_key_chk", + "value": "idempotency_key ~ '^[A-Za-z0-9_.:-]{8,200}$'" + }, + "sync_ops_payload_version_chk": { + "name": "sync_ops_payload_version_chk", + "value": "payload_version > 0" + }, + "sync_ops_client_version_chk": { + "name": "sync_ops_client_version_chk", + "value": "char_length(client_version) BETWEEN 1 AND 40" + }, + "sync_ops_request_hash_chk": { + "name": "sync_ops_request_hash_chk", + "value": "request_hash ~ '^[0-9a-f]{64}$'" + }, + "sync_ops_reason_chk": { + "name": "sync_ops_reason_chk", + "value": "char_length(reason) <= 60" + }, + "sync_ops_result_chk": { + "name": "sync_ops_result_chk", + "value": "jsonb_typeof(result) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.idempotency_keys": { + "name": "idempotency_keys", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "idempotency_keys_user_id_user_id_fk": { + "name": "idempotency_keys_user_id_user_id_fk", + "tableFrom": "idempotency_keys", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "idempotency_keys_user_id_key_pk": { + "name": "idempotency_keys_user_id_key_pk", + "columns": [ + "user_id", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_admins": { + "name": "instance_admins", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "granted_by": { + "name": "granted_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "instance_admins_user_id_user_id_fk": { + "name": "instance_admins_user_id_user_id_fk", + "tableFrom": "instance_admins", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "instance_admins_granted_by_user_id_fk": { + "name": "instance_admins_granted_by_user_id_fk", + "tableFrom": "instance_admins", + "columnsFrom": [ + "granted_by" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "instance_settings_values_chk": { + "name": "instance_settings_values_chk", + "value": "CASE key\n WHEN 'embeddings_source' THEN jsonb_typeof(value) = 'string'\n AND value #>> '{}' IN ('provider', 'local', 'off')\n WHEN 'assistant_thread_days' THEN jsonb_typeof(value) = 'number'\n AND (value #>> '{}')::numeric BETWEEN 7 AND 365\n WHEN 'oidc_config_hash' THEN jsonb_typeof(value) = 'string'\n AND value #>> '{}' ~ '^[0-9a-f]{64}$'\n WHEN 'smtp_config_hash' THEN jsonb_typeof(value) = 'string'\n AND value #>> '{}' ~ '^[0-9a-f]{64}$'\n ELSE true END" + } + }, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_expires_at": { + "name": "membership_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_by": { + "name": "accepted_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_email": { + "name": "claimed_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "invites_location_idx": { + "name": "invites_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invites_location_id_locations_id_fk": { + "name": "invites_location_id_locations_id_fk", + "tableFrom": "invites", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invites_created_by_user_id_fk": { + "name": "invites_created_by_user_id_fk", + "tableFrom": "invites", + "columnsFrom": [ + "created_by" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "invites_accepted_by_user_id_fk": { + "name": "invites_accepted_by_user_id_fk", + "tableFrom": "invites", + "columnsFrom": [ + "accepted_by" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invites_token_hash_unique": { + "name": "invites_token_hash_unique", + "columns": [ + "token_hash" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "invites_role_chk": { + "name": "invites_role_chk", + "value": "\"role\" IN ('admin', 'member', 'viewer')" + } + }, + "isRLSEnabled": false + }, + "public.location_modules": { + "name": "location_modules", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "enabled_at": { + "name": "enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "location_modules_location_id_locations_id_fk": { + "name": "location_modules_location_id_locations_id_fk", + "tableFrom": "location_modules", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "location_modules_location_id_module_pk": { + "name": "location_modules_location_id_module_pk", + "columns": [ + "location_id", + "module" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "location_modules_module_chk": { + "name": "location_modules_module_chk", + "value": "module ~ '^[a-z][a-z0-9_]*$'" + } + }, + "isRLSEnabled": false + }, + "public.locations": { + "name": "locations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "owner_account_id": { + "name": "owner_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": true + }, + "languages": { + "name": "languages", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "address": { + "name": "address", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "longitude": { + "name": "longitude", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "suggest_radius_m": { + "name": "suggest_radius_m", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 150 + }, + "preset": { + "name": "preset", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'household'" + }, + "money_visible_to_viewers": { + "name": "money_visible_to_viewers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "require_2fa": { + "name": "require_2fa", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "long_unseen_months": { + "name": "long_unseen_months", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 12 + }, + "keep_room_photos": { + "name": "keep_room_photos", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "successor_user_id": { + "name": "successor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "purge_after": { + "name": "purge_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "locations_one_personal_uq": { + "name": "locations_one_personal_uq", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "kind = 'personal'", + "concurrently": false + }, + "locations_owner_account_idx": { + "name": "locations_owner_account_idx", + "columns": [ + { + "expression": "owner_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "locations_owner_account_id_owner_accounts_id_fk": { + "name": "locations_owner_account_id_owner_accounts_id_fk", + "tableFrom": "locations", + "columnsFrom": [ + "owner_account_id" + ], + "tableTo": "owner_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "locations_currency_currencies_code_fk": { + "name": "locations_currency_currencies_code_fk", + "tableFrom": "locations", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "locations_successor_user_id_user_id_fk": { + "name": "locations_successor_user_id_user_id_fk", + "tableFrom": "locations", + "columnsFrom": [ + "successor_user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "locations_id_owner_account_uq": { + "name": "locations_id_owner_account_uq", + "columns": [ + "id", + "owner_account_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "locations_kind_chk": { + "name": "locations_kind_chk", + "value": "\"kind\" IN ('personal', 'home', 'apartment', 'garage', 'storage_unit', 'office', 'vacation_home', 'custom')" + }, + "locations_preset_chk": { + "name": "locations_preset_chk", + "value": "\"preset\" IN ('essentials', 'household', 'complete')" + }, + "locations_latitude_chk": { + "name": "locations_latitude_chk", + "value": "latitude BETWEEN -90 AND 90" + }, + "locations_longitude_chk": { + "name": "locations_longitude_chk", + "value": "longitude BETWEEN -180 AND 180" + }, + "locations_suggest_radius_chk": { + "name": "locations_suggest_radius_chk", + "value": "suggest_radius_m > 0" + }, + "locations_long_unseen_chk": { + "name": "locations_long_unseen_chk", + "value": "long_unseen_months > 0" + }, + "locations_purge_needs_delete_chk": { + "name": "locations_purge_needs_delete_chk", + "value": "purge_after IS NULL OR deleted_at IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.memberships": { + "name": "memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memberships_one_owner_uq": { + "name": "memberships_one_owner_uq", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "role = 'owner'", + "concurrently": false + }, + "memberships_user_idx": { + "name": "memberships_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "memberships_location_id_locations_id_fk": { + "name": "memberships_location_id_locations_id_fk", + "tableFrom": "memberships", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "memberships_user_id_user_id_fk": { + "name": "memberships_user_id_user_id_fk", + "tableFrom": "memberships", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "memberships_invited_by_user_id_fk": { + "name": "memberships_invited_by_user_id_fk", + "tableFrom": "memberships", + "columnsFrom": [ + "invited_by" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "memberships_location_user_uq": { + "name": "memberships_location_user_uq", + "columns": [ + "location_id", + "user_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "memberships_role_chk": { + "name": "memberships_role_chk", + "value": "\"role\" IN ('owner', 'admin', 'member', 'viewer')" + }, + "memberships_owner_no_expiry_chk": { + "name": "memberships_owner_no_expiry_chk", + "value": "role <> 'owner' OR expires_at IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.owner_accounts": { + "name": "owner_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "owner_accounts_user_id_user_id_fk": { + "name": "owner_accounts_user_id_user_id_fk", + "tableFrom": "owner_accounts", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "owner_accounts_user_id_unique": { + "name": "owner_accounts_user_id_unique", + "columns": [ + "user_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_tombstones": { + "name": "sync_tombstones", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_key": { + "name": "entity_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "change_xid": { + "name": "change_xid", + "type": "xid8", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sync_tombstones_change_seq_idx": { + "name": "sync_tombstones_change_seq_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sync_tombstones_sync_idx": { + "name": "sync_tombstones_sync_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_xid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "sync_tombstones_location_id_locations_id_fk": { + "name": "sync_tombstones_location_id_locations_id_fk", + "tableFrom": "sync_tombstones", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "sync_tombstones_location_id_entity_type_entity_id_pk": { + "name": "sync_tombstones_location_id_entity_type_entity_id_pk", + "columns": [ + "location_id", + "entity_type", + "entity_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sync_tombstones_key_chk": { + "name": "sync_tombstones_key_chk", + "value": "(entity_type IN ('code', 'legacy_code')) = (entity_key IS NOT NULL)\n AND (entity_key IS NULL\n OR (char_length(entity_key) <= 400 AND entity_id = md5(entity_key)::uuid))" + } + }, + "isRLSEnabled": false + }, + "public.user_hidden_modules": { + "name": "user_hidden_modules", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_hidden_modules_user_id_user_id_fk": { + "name": "user_hidden_modules_user_id_user_id_fk", + "tableFrom": "user_hidden_modules", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_hidden_modules_location_id_locations_id_fk": { + "name": "user_hidden_modules_location_id_locations_id_fk", + "tableFrom": "user_hidden_modules", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "user_hidden_modules_user_id_location_id_module_pk": { + "name": "user_hidden_modules_user_id_location_id_module_pk", + "columns": [ + "user_id", + "location_id", + "module" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_hidden_modules_module_chk": { + "name": "user_hidden_modules_module_chk", + "value": "module ~ '^[a-z][a-z0-9_]*$'" + } + }, + "isRLSEnabled": false + }, + "public.user_profiles": { + "name": "user_profiles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'en'" + }, + "units": { + "name": "units", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'metric'" + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "digits": { + "name": "digits", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'western'" + }, + "suggest_location": { + "name": "suggest_location", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hide_personal": { + "name": "hide_personal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "digest_time": { + "name": "digest_time", + "type": "time", + "primaryKey": false, + "notNull": false + }, + "quiet_from": { + "name": "quiet_from", + "type": "time", + "primaryKey": false, + "notNull": false + }, + "quiet_to": { + "name": "quiet_to", + "type": "time", + "primaryKey": false, + "notNull": false + }, + "managed": { + "name": "managed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_in_location_id": { + "name": "created_in_location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_profiles_user_id_user_id_fk": { + "name": "user_profiles_user_id_user_id_fk", + "tableFrom": "user_profiles", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_profiles_created_by_user_id_user_id_fk": { + "name": "user_profiles_created_by_user_id_user_id_fk", + "tableFrom": "user_profiles", + "columnsFrom": [ + "created_by_user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "user_profiles_created_in_location_id_locations_id_fk": { + "name": "user_profiles_created_in_location_id_locations_id_fk", + "tableFrom": "user_profiles", + "columnsFrom": [ + "created_in_location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_profiles_units_chk": { + "name": "user_profiles_units_chk", + "value": "\"units\" IN ('metric', 'imperial')" + }, + "user_profiles_theme_chk": { + "name": "user_profiles_theme_chk", + "value": "\"theme\" IN ('system', 'light', 'dark')" + }, + "user_profiles_digits_chk": { + "name": "user_profiles_digits_chk", + "value": "\"digits\" IN ('western', 'eastern')" + } + }, + "isRLSEnabled": false + }, + "public.short_ids": { + "name": "short_ids", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "char(6)", + "primaryKey": true, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'assigned'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "printed_at": { + "name": "printed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "change_xid": { + "name": "change_xid", + "type": "xid8", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "short_ids_primary_thing_uq": { + "name": "short_ids_primary_thing_uq", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_primary AND state = 'assigned'", + "concurrently": false + }, + "short_ids_primary_place_uq": { + "name": "short_ids_primary_place_uq", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_primary AND state = 'assigned'", + "concurrently": false + }, + "short_ids_thing_idx": { + "name": "short_ids_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "short_ids_place_idx": { + "name": "short_ids_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "short_ids_location_idx": { + "name": "short_ids_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "short_ids_sync_idx": { + "name": "short_ids_sync_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_xid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "short_ids_state_chk": { + "name": "short_ids_state_chk", + "value": "\"state\" IN ('blank', 'assigned', 'retired')" + }, + "short_ids_code_chk": { + "name": "short_ids_code_chk", + "value": "code ~ '^[0-9A-HJKMNP-TV-Z]{6}$'" + }, + "short_ids_target_chk": { + "name": "short_ids_target_chk", + "value": "CASE state WHEN 'blank' THEN num_nonnulls(thing_id, place_id) = 0\n WHEN 'assigned' THEN num_nonnulls(thing_id, place_id) = 1\n ELSE num_nonnulls(thing_id, place_id) <= 1 END" + } + }, + "isRLSEnabled": false + }, + "public.thing_links": { + "name": "thing_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_thing_id": { + "name": "from_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_thing_id": { + "name": "to_thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thing_links_to_idx": { + "name": "thing_links_to_idx", + "columns": [ + { + "expression": "to_thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "thing_links_from_fk": { + "name": "thing_links_from_fk", + "tableFrom": "thing_links", + "columnsFrom": [ + "location_id", + "from_thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + }, + "thing_links_to_fk": { + "name": "thing_links_to_fk", + "tableFrom": "thing_links", + "columnsFrom": [ + "location_id", + "to_thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "thing_links_uq": { + "name": "thing_links_uq", + "columns": [ + "from_thing_id", + "to_thing_id", + "kind" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "thing_links_kind_chk": { + "name": "thing_links_kind_chk", + "value": "\"kind\" IN ('accessory_of', 'spare_part_for', 'consumable_for', 'bundled_with', 'replaces', 'related')" + }, + "thing_links_not_self_chk": { + "name": "thing_links_not_self_chk", + "value": "from_thing_id <> to_thing_id" + } + }, + "isRLSEnabled": false + }, + "public.thing_tags": { + "name": "thing_tags", + "schema": "", + "columns": { + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thing_tags_tag_idx": { + "name": "thing_tags_tag_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "thing_tags_tag_id_tags_id_fk": { + "name": "thing_tags_tag_id_tags_id_fk", + "tableFrom": "thing_tags", + "columnsFrom": [ + "tag_id" + ], + "tableTo": "tags", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "thing_tags_thing_fk": { + "name": "thing_tags_thing_fk", + "tableFrom": "thing_tags", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "thing_tags_pk": { + "name": "thing_tags_pk", + "columns": [ + "thing_id", + "tag_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.things": { + "name": "things", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "container_id": { + "name": "container_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type_id": { + "name": "type_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand_id": { + "name": "brand_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serial": { + "name": "serial", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "barcode": { + "name": "barcode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "colour": { + "name": "colour", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric(12, 3)", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "belongs_to_person_id": { + "name": "belongs_to_person_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_line_id": { + "name": "purchase_line_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "manual_url": { + "name": "manual_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_on": { + "name": "expires_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "expiry_lead_days": { + "name": "expiry_lead_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_use'" + }, + "ended_on": { + "name": "ended_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "ended_price": { + "name": "ended_price", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "ended_currency": { + "name": "ended_currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "ended_to": { + "name": "ended_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ended_notes": { + "name": "ended_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_from": { + "name": "acquired_from", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provenance_notes": { + "name": "provenance_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rack_units": { + "name": "rack_units", + "type": "numeric(3, 1)", + "primaryKey": false, + "notNull": false + }, + "rack_at": { + "name": "rack_at", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "location_uncertain": { + "name": "location_uncertain", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "custom": { + "name": "custom", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_custom": { + "name": "archived_custom", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "field_status": { + "name": "field_status", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'confirmed'" + }, + "created_via": { + "name": "created_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'app'" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "split_from_id": { + "name": "split_from_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "place_path": { + "name": "place_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "search_tsv": { + "name": "search_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trash_batch_id": { + "name": "trash_batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capture_batch_id": { + "name": "capture_batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "merged_into_id": { + "name": "merged_into_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cover_file_id": { + "name": "cover_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "meter_version": { + "name": "meter_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "state_version": { + "name": "state_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "change_xid": { + "name": "change_xid", + "type": "xid8", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "things_purchase_line_idx": { + "name": "things_purchase_line_idx", + "columns": [ + { + "expression": "purchase_line_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_place_idx": { + "name": "things_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_container_idx": { + "name": "things_container_idx", + "columns": [ + { + "expression": "container_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_type_idx": { + "name": "things_type_idx", + "columns": [ + { + "expression": "type_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_brand_idx": { + "name": "things_brand_idx", + "columns": [ + { + "expression": "brand_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_person_idx": { + "name": "things_person_idx", + "columns": [ + { + "expression": "belongs_to_person_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_barcode_idx": { + "name": "things_barcode_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "barcode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_serial_exact_idx": { + "name": "things_serial_exact_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "things_last_seen_idx": { + "name": "things_last_seen_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "lifecycle = 'in_use' AND deleted_at IS NULL", + "concurrently": false + }, + "things_uncertain_idx": { + "name": "things_uncertain_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "location_uncertain", + "concurrently": false + }, + "things_draft_idx": { + "name": "things_draft_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "review_state = 'draft'", + "concurrently": false + }, + "things_expires_idx": { + "name": "things_expires_idx", + "columns": [ + { + "expression": "expires_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "expires_on IS NOT NULL", + "concurrently": false + }, + "things_trash_idx": { + "name": "things_trash_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "deleted_at IS NOT NULL", + "concurrently": false + }, + "things_trash_purge_idx": { + "name": "things_trash_purge_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "deleted_at IS NOT NULL", + "concurrently": false + }, + "things_created_by_idx": { + "name": "things_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "deleted_at IS NULL", + "concurrently": false + }, + "things_capture_batch_idx": { + "name": "things_capture_batch_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "capture_batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "deleted_at IS NULL", + "concurrently": false + }, + "things_merged_into_idx": { + "name": "things_merged_into_idx", + "columns": [ + { + "expression": "merged_into_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "merged_into_id IS NOT NULL", + "concurrently": false + }, + "things_sync_idx": { + "name": "things_sync_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "change_xid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "things_location_id_locations_id_fk": { + "name": "things_location_id_locations_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "things_type_id_types_id_fk": { + "name": "things_type_id_types_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "type_id" + ], + "tableTo": "types", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "things_brand_id_brands_id_fk": { + "name": "things_brand_id_brands_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "brand_id" + ], + "tableTo": "brands", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "things_belongs_to_person_id_people_id_fk": { + "name": "things_belongs_to_person_id_people_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "belongs_to_person_id" + ], + "tableTo": "people", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "things_ended_currency_currencies_code_fk": { + "name": "things_ended_currency_currencies_code_fk", + "tableFrom": "things", + "columnsFrom": [ + "ended_currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "things_split_from_id_things_id_fk": { + "name": "things_split_from_id_things_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "split_from_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "things_merged_into_id_things_id_fk": { + "name": "things_merged_into_id_things_id_fk", + "tableFrom": "things", + "columnsFrom": [ + "merged_into_id" + ], + "tableTo": "things", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "things_place_fk": { + "name": "things_place_fk", + "tableFrom": "things", + "columnsFrom": [ + "location_id", + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "no action" + }, + "things_container_fk": { + "name": "things_container_fk", + "tableFrom": "things", + "columnsFrom": [ + "location_id", + "container_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "no action" + }, + "things_purchase_line_fk": { + "name": "things_purchase_line_fk", + "tableFrom": "things", + "columnsFrom": [ + "purchase_line_id" + ], + "tableTo": "purchase_lines", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "things_location_id_uq": { + "name": "things_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "things_lifecycle_chk": { + "name": "things_lifecycle_chk", + "value": "\"lifecycle\" IN ('in_use', 'archived', 'sold', 'given_away', 'lost', 'disposed', 'stolen', 'destroyed', 'returned_to_owner')" + }, + "things_condition_chk": { + "name": "things_condition_chk", + "value": "\"condition\" IN ('new', 'good', 'fair', 'poor', 'broken')" + }, + "things_review_state_chk": { + "name": "things_review_state_chk", + "value": "\"review_state\" IN ('draft', 'confirmed')" + }, + "things_created_via_chk": { + "name": "things_created_via_chk", + "value": "\"created_via\" IN ('app', 'mcp', 'assistant', 'import', 'email')" + }, + "things_one_parent_chk": { + "name": "things_one_parent_chk", + "value": "num_nonnulls(place_id, container_id) = 1" + }, + "things_not_own_container_chk": { + "name": "things_not_own_container_chk", + "value": "container_id IS NULL OR container_id <> id" + }, + "things_named_chk": { + "name": "things_named_chk", + "value": "name IS NOT NULL OR review_state = 'draft'" + }, + "things_name_chk": { + "name": "things_name_chk", + "value": "name IS NULL OR char_length(name) BETWEEN 1 AND 200" + }, + "things_model_chk": { + "name": "things_model_chk", + "value": "char_length(model) <= 120" + }, + "things_serial_chk": { + "name": "things_serial_chk", + "value": "char_length(serial) <= 100" + }, + "things_barcode_chk": { + "name": "things_barcode_chk", + "value": "char_length(barcode) <= 64" + }, + "things_colour_chk": { + "name": "things_colour_chk", + "value": "char_length(colour) <= 60" + }, + "things_notes_chk": { + "name": "things_notes_chk", + "value": "char_length(notes) <= 5000" + }, + "things_manual_url_chk": { + "name": "things_manual_url_chk", + "value": "char_length(manual_url) <= 2000" + }, + "things_ended_to_chk": { + "name": "things_ended_to_chk", + "value": "char_length(ended_to) <= 200" + }, + "things_ended_notes_chk": { + "name": "things_ended_notes_chk", + "value": "char_length(ended_notes) <= 5000" + }, + "things_acquired_from_chk": { + "name": "things_acquired_from_chk", + "value": "char_length(acquired_from) <= 200" + }, + "things_provenance_notes_chk": { + "name": "things_provenance_notes_chk", + "value": "char_length(provenance_notes) <= 5000" + }, + "things_quantity_chk": { + "name": "things_quantity_chk", + "value": "quantity >= 0" + }, + "things_expiry_lead_chk": { + "name": "things_expiry_lead_chk", + "value": "expiry_lead_days BETWEEN 0 AND 3650" + }, + "things_ended_price_chk": { + "name": "things_ended_price_chk", + "value": "ended_price >= 0" + }, + "things_ended_money_chk": { + "name": "things_ended_money_chk", + "value": "(ended_price IS NULL) = (ended_currency IS NULL)" + }, + "things_in_use_chk": { + "name": "things_in_use_chk", + "value": "lifecycle <> 'in_use' OR (ended_on IS NULL AND ended_price IS NULL AND ended_to IS NULL)" + }, + "things_aliases_chk": { + "name": "things_aliases_chk", + "value": "jsonb_typeof(aliases) = 'object'" + }, + "things_custom_chk": { + "name": "things_custom_chk", + "value": "jsonb_typeof(custom) = 'object'" + }, + "things_archived_custom_chk": { + "name": "things_archived_custom_chk", + "value": "jsonb_typeof(archived_custom) = 'object'" + }, + "things_field_status_chk": { + "name": "things_field_status_chk", + "value": "jsonb_typeof(field_status) = 'object'" + }, + "things_rack_units_chk": { + "name": "things_rack_units_chk", + "value": "rack_units BETWEEN 0 AND 60 AND rack_units * 2 = trunc(rack_units * 2)" + }, + "things_rack_at_chk": { + "name": "things_rack_at_chk", + "value": "rack_at IS NULL OR jsonb_typeof(rack_at) = 'object'" + } + }, + "isRLSEnabled": false + }, + "public.api_tokens": { + "name": "api_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lookup": { + "name": "lookup", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_with_mfa": { + "name": "created_with_mfa", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_tokens_lookup_uq": { + "name": "api_tokens_lookup_uq", + "columns": [ + { + "expression": "lookup", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "api_tokens_oauth_uq": { + "name": "api_tokens_oauth_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oauth_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "kind = 'oauth' AND revoked_at IS NULL", + "concurrently": false + }, + "api_tokens_user_idx": { + "name": "api_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "api_tokens_user_id_user_id_fk": { + "name": "api_tokens_user_id_user_id_fk", + "tableFrom": "api_tokens", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "api_tokens_kind_chk": { + "name": "api_tokens_kind_chk", + "value": "\"kind\" IN ('personal', 'oauth')" + }, + "api_tokens_scope_chk": { + "name": "api_tokens_scope_chk", + "value": "\"scope\" IN ('read', 'write')" + }, + "api_tokens_revoked_reason_chk": { + "name": "api_tokens_revoked_reason_chk", + "value": "\"revoked_reason\" IN ('user', 'membership_ended', 'role_lost', 'expired', 'admin', 'client_revoked', 'account_claimed')" + }, + "api_tokens_name_chk": { + "name": "api_tokens_name_chk", + "value": "char_length(name) BETWEEN 1 AND 80" + }, + "api_tokens_lookup_chk": { + "name": "api_tokens_lookup_chk", + "value": "lookup ~ '^[A-Za-z0-9]{8}$'" + }, + "api_tokens_hash_chk": { + "name": "api_tokens_hash_chk", + "value": "hash ~ '^[0-9a-f]{64}$'" + }, + "api_tokens_oauth_client_chk": { + "name": "api_tokens_oauth_client_chk", + "value": "char_length(oauth_client_id) <= 400" + }, + "api_tokens_kind_fields_chk": { + "name": "api_tokens_kind_fields_chk", + "value": "CASE kind WHEN 'personal'\n THEN lookup IS NOT NULL AND hash IS NOT NULL AND oauth_client_id IS NULL\n ELSE lookup IS NULL AND hash IS NULL AND oauth_client_id IS NOT NULL END" + }, + "api_tokens_revoked_chk": { + "name": "api_tokens_revoked_chk", + "value": "(revoked_at IS NULL) = (revoked_reason IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.token_locations": { + "name": "token_locations", + "schema": "", + "columns": { + "token_id": { + "name": "token_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "token_locations_location_idx": { + "name": "token_locations_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "token_locations_token_id_api_tokens_id_fk": { + "name": "token_locations_token_id_api_tokens_id_fk", + "tableFrom": "token_locations", + "columnsFrom": [ + "token_id" + ], + "tableTo": "api_tokens", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "token_locations_location_id_locations_id_fk": { + "name": "token_locations_location_id_locations_id_fk", + "tableFrom": "token_locations", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "token_locations_pk": { + "name": "token_locations_pk", + "columns": [ + "token_id", + "location_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.token_rate_windows": { + "name": "token_rate_windows", + "schema": "", + "columns": { + "token_id": { + "name": "token_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "minute": { + "name": "minute", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "token_rate_windows_token_id_api_tokens_id_fk": { + "name": "token_rate_windows_token_id_api_tokens_id_fk", + "tableFrom": "token_rate_windows", + "columnsFrom": [ + "token_id" + ], + "tableTo": "api_tokens", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "token_rate_windows_pk": { + "name": "token_rate_windows_pk", + "columns": [ + "token_id", + "minute", + "kind" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "token_rate_windows_kind_chk": { + "name": "token_rate_windows_kind_chk", + "value": "\"kind\" IN ('read', 'write')" + }, + "token_rate_windows_count_chk": { + "name": "token_rate_windows_count_chk", + "value": "count >= 0" + } + }, + "isRLSEnabled": false + }, + "public.place_folds": { + "name": "place_folds", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "place_id": { + "name": "place_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "place_folds_place_idx": { + "name": "place_folds_place_idx", + "columns": [ + { + "expression": "place_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "place_folds_user_id_user_id_fk": { + "name": "place_folds_user_id_user_id_fk", + "tableFrom": "place_folds", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "place_folds_place_id_places_id_fk": { + "name": "place_folds_place_id_places_id_fk", + "tableFrom": "place_folds", + "columnsFrom": [ + "place_id" + ], + "tableTo": "places", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "place_folds_pk": { + "name": "place_folds_pk", + "columns": [ + "user_id", + "place_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_view_prefs": { + "name": "saved_view_prefs", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_view_id": { + "name": "default_view_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + } + }, + "indexes": {}, + "foreignKeys": { + "saved_view_prefs_user_id_user_id_fk": { + "name": "saved_view_prefs_user_id_user_id_fk", + "tableFrom": "saved_view_prefs", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "saved_view_prefs_default_view_id_saved_views_id_fk": { + "name": "saved_view_prefs_default_view_id_saved_views_id_fk", + "tableFrom": "saved_view_prefs", + "columnsFrom": [ + "default_view_id" + ], + "tableTo": "saved_views", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": { + "saved_view_prefs_pk": { + "name": "saved_view_prefs_pk", + "columns": [ + "user_id", + "surface" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "saved_view_prefs_surface_chk": { + "name": "saved_view_prefs_surface_chk", + "value": "surface ~ '^[a-z][a-z-]{0,31}$'" + }, + "saved_view_prefs_pinned_chk": { + "name": "saved_view_prefs_pinned_chk", + "value": "cardinality(pinned) <= 20" + } + }, + "isRLSEnabled": false + }, + "public.saved_views": { + "name": "saved_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "shared": { + "name": "shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'search'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "saved_views_user_idx": { + "name": "saved_views_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "saved_views_location_idx": { + "name": "saved_views_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "shared", + "concurrently": false + }, + "saved_views_user_surface_idx": { + "name": "saved_views_user_surface_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "surface", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "saved_views_user_id_user_id_fk": { + "name": "saved_views_user_id_user_id_fk", + "tableFrom": "saved_views", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "saved_views_location_id_locations_id_fk": { + "name": "saved_views_location_id_locations_id_fk", + "tableFrom": "saved_views", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "saved_views_name_chk": { + "name": "saved_views_name_chk", + "value": "char_length(name) BETWEEN 1 AND 80" + }, + "saved_views_surface_chk": { + "name": "saved_views_surface_chk", + "value": "surface ~ '^[a-z][a-z-]{0,31}$'" + }, + "saved_views_query_chk": { + "name": "saved_views_query_chk", + "value": "jsonb_typeof(query) = 'object'" + }, + "saved_views_shared_chk": { + "name": "saved_views_shared_chk", + "value": "NOT shared OR location_id IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.user_hints": { + "name": "user_hints", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hint_key": { + "name": "hint_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seen_at": { + "name": "seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dismissed": { + "name": "dismissed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_hints_user_id_user_id_fk": { + "name": "user_hints_user_id_user_id_fk", + "tableFrom": "user_hints", + "columnsFrom": [ + "user_id" + ], + "tableTo": "user", + "schemaTo": "auth", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "user_hints_pk": { + "name": "user_hints_pk", + "columns": [ + "user_id", + "hint_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_hints_key_chk": { + "name": "user_hints_key_chk", + "value": "hint_key ~ '^[a-z0-9_.:-]{1,64}$'" + } + }, + "isRLSEnabled": false + }, + "public.claims": { + "name": "claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "warranty_id": { + "name": "warranty_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "incident_id": { + "name": "incident_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "opened_on": { + "name": "opened_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "reference": { + "name": "reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "cost": { + "name": "cost", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "covered_amount": { + "name": "covered_amount", + "type": "numeric(16, 4)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "closed_on": { + "name": "closed_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "claims_one_repair_uq": { + "name": "claims_one_repair_uq", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status = 'in_repair'", + "concurrently": false + }, + "claims_thing_idx": { + "name": "claims_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "claims_warranty_idx": { + "name": "claims_warranty_idx", + "columns": [ + { + "expression": "warranty_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "claims_incident_idx": { + "name": "claims_incident_idx", + "columns": [ + { + "expression": "incident_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "claims_vendor_idx": { + "name": "claims_vendor_idx", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "claims_location_id_locations_id_fk": { + "name": "claims_location_id_locations_id_fk", + "tableFrom": "claims", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "claims_currency_currencies_code_fk": { + "name": "claims_currency_currencies_code_fk", + "tableFrom": "claims", + "columnsFrom": [ + "currency" + ], + "tableTo": "currencies", + "columnsTo": [ + "code" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "claims_thing_fk": { + "name": "claims_thing_fk", + "tableFrom": "claims", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "claims_location_id_uq": { + "name": "claims_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "claims_status_chk": { + "name": "claims_status_chk", + "value": "\"status\" IN ('open', 'in_repair', 'resolved', 'rejected')" + }, + "claims_reference_chk": { + "name": "claims_reference_chk", + "value": "char_length(reference) <= 100" + }, + "claims_cost_chk": { + "name": "claims_cost_chk", + "value": "cost >= 0" + }, + "claims_covered_amount_chk": { + "name": "claims_covered_amount_chk", + "value": "covered_amount >= 0" + }, + "claims_notes_chk": { + "name": "claims_notes_chk", + "value": "char_length(notes) <= 5000" + }, + "claims_money_chk": { + "name": "claims_money_chk", + "value": "(cost IS NULL AND covered_amount IS NULL) OR currency IS NOT NULL" + }, + "claims_closed_chk": { + "name": "claims_closed_chk", + "value": "(status IN ('resolved', 'rejected')) = (closed_on IS NOT NULL)" + }, + "claims_closed_on_chk": { + "name": "claims_closed_on_chk", + "value": "closed_on IS NULL OR closed_on >= opened_on" + } + }, + "isRLSEnabled": false + }, + "public.warranties": { + "name": "warranties", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thing_id": { + "name": "thing_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "starts_on": { + "name": "starts_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "ends_on": { + "name": "ends_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "term_months": { + "name": "term_months", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lifetime": { + "name": "lifetime", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "effective_ends_on": { + "name": "effective_ends_on", + "type": "date", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "CASE WHEN lifetime THEN NULL\n WHEN ends_on IS NOT NULL THEN ends_on\n ELSE (starts_on + make_interval(months => term_months))::date - 1 END" + } + }, + "lead_days": { + "name": "lead_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "claim_contact": { + "name": "claim_contact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registered": { + "name": "registered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "registration_deadline": { + "name": "registration_deadline", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "warranties_thing_idx": { + "name": "warranties_thing_idx", + "columns": [ + { + "expression": "thing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "warranties_ends_idx": { + "name": "warranties_ends_idx", + "columns": [ + { + "expression": "effective_ends_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "warranties_location_id_locations_id_fk": { + "name": "warranties_location_id_locations_id_fk", + "tableFrom": "warranties", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "warranties_thing_fk": { + "name": "warranties_thing_fk", + "tableFrom": "warranties", + "columnsFrom": [ + "location_id", + "thing_id" + ], + "tableTo": "things", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "warranties_location_id_uq": { + "name": "warranties_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "warranties_kind_chk": { + "name": "warranties_kind_chk", + "value": "\"kind\" IN ('manufacturer', 'extended', 'store', 'credit_card', 'insurance')" + }, + "warranties_provider_chk": { + "name": "warranties_provider_chk", + "value": "char_length(provider) <= 120" + }, + "warranties_term_months_chk": { + "name": "warranties_term_months_chk", + "value": "term_months BETWEEN 1 AND 600" + }, + "warranties_lead_days_chk": { + "name": "warranties_lead_days_chk", + "value": "lead_days BETWEEN 0 AND 365" + }, + "warranties_claim_contact_chk": { + "name": "warranties_claim_contact_chk", + "value": "char_length(claim_contact) <= 300" + }, + "warranties_term_chk": { + "name": "warranties_term_chk", + "value": "num_nonnulls(ends_on, term_months) + lifetime::int = 1" + }, + "warranties_ends_chk": { + "name": "warranties_ends_chk", + "value": "ends_on IS NULL OR ends_on >= starts_on" + } + }, + "isRLSEnabled": false + }, + "public.webhook_deliveries": { + "name": "webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "http_status": { + "name": "http_status", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_deliveries_webhook_idx": { + "name": "webhook_deliveries_webhook_idx", + "columns": [ + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "webhook_deliveries_due_idx": { + "name": "webhook_deliveries_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status IN ('pending', 'failed')", + "concurrently": false + }, + "webhook_deliveries_location_idx": { + "name": "webhook_deliveries_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhook_deliveries_webhook_fk": { + "name": "webhook_deliveries_webhook_fk", + "tableFrom": "webhook_deliveries", + "columnsFrom": [ + "location_id", + "webhook_id" + ], + "tableTo": "webhooks", + "columnsTo": [ + "location_id", + "id" + ], + "onUpdate": "cascade", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "webhook_deliveries_event_uq": { + "name": "webhook_deliveries_event_uq", + "columns": [ + "webhook_id", + "event_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "webhook_deliveries_status_chk": { + "name": "webhook_deliveries_status_chk", + "value": "\"status\" IN ('pending', 'delivered', 'failed', 'gave_up')" + }, + "webhook_deliveries_event_id_chk": { + "name": "webhook_deliveries_event_id_chk", + "value": "event_id ~ '^evt_[A-Za-z0-9]{10,40}$'" + }, + "webhook_deliveries_event_chk": { + "name": "webhook_deliveries_event_chk", + "value": "event IN ('thing.created', 'thing.updated', 'thing.moved', 'thing.trashed', 'thing.restored', 'thing.lifecycle_changed', 'reading.logged', 'reminder.due', 'ping')" + }, + "webhook_deliveries_attempts_chk": { + "name": "webhook_deliveries_attempts_chk", + "value": "attempts BETWEEN 0 AND 50" + }, + "webhook_deliveries_http_status_chk": { + "name": "webhook_deliveries_http_status_chk", + "value": "http_status BETWEEN 100 AND 599" + } + }, + "isRLSEnabled": false + }, + "public.webhooks": { + "name": "webhooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuidv7()" + }, + "location_id": { + "name": "location_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_ciphertext": { + "name": "secret_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "key_version": { + "name": "key_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "events": { + "name": "events", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failing_since": { + "name": "failing_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "row_version": { + "name": "row_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_seq": { + "name": "change_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "webhooks_location_idx": { + "name": "webhooks_location_idx", + "columns": [ + { + "expression": "location_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhooks_location_id_locations_id_fk": { + "name": "webhooks_location_id_locations_id_fk", + "tableFrom": "webhooks", + "columnsFrom": [ + "location_id" + ], + "tableTo": "locations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "webhooks_location_id_uq": { + "name": "webhooks_location_id_uq", + "columns": [ + "location_id", + "id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "webhooks_disabled_reason_chk": { + "name": "webhooks_disabled_reason_chk", + "value": "\"disabled_reason\" IN ('creator_lost_role', 'failing', 'admin')" + }, + "webhooks_url_chk": { + "name": "webhooks_url_chk", + "value": "url ~ '^https?://' AND char_length(url) <= 500" + }, + "webhooks_events_chk": { + "name": "webhooks_events_chk", + "value": "cardinality(events) BETWEEN 1 AND 8 AND events <@ ARRAY['thing.created', 'thing.updated', 'thing.moved', 'thing.trashed', 'thing.restored', 'thing.lifecycle_changed', 'reading.logged', 'reminder.due']::text[]" + }, + "webhooks_key_version_chk": { + "name": "webhooks_key_version_chk", + "value": "key_version >= 1" + }, + "webhooks_disabled_chk": { + "name": "webhooks_disabled_chk", + "value": "active OR disabled_reason IS NOT NULL" + } + }, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": { + "auth": "auth" + }, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/server/migrations/meta/_journal.json b/apps/server/migrations/meta/_journal.json index 1a2dc010..8f81afd1 100644 --- a/apps/server/migrations/meta/_journal.json +++ b/apps/server/migrations/meta/_journal.json @@ -1240,6 +1240,13 @@ "when": 1794309650737, "tag": "0176_meter_unit_keeps_reminders", "breakpoints": true + }, + { + "idx": 177, + "version": "7", + "when": 1794489790206, + "tag": "0177_counts_module", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/server/src/locations/locations.test.ts b/apps/server/src/locations/locations.test.ts index 9491e519..bdc1c974 100644 --- a/apps/server/src/locations/locations.test.ts +++ b/apps/server/src/locations/locations.test.ts @@ -1,4 +1,4 @@ -import { newId, presetModules } from '@kept/shared'; +import { MODULE_IDS, newId, presetModules } from '@kept/shared'; import { v7 } from 'uuid'; import { beforeEach, describe, expect, it } from 'vitest'; import type { TestApp } from '../../test/app.js'; @@ -51,7 +51,7 @@ describe('POST /api/v1/locations', () => { pendingInviteCount: 0, modules: [...presetModules('essentials')], // The AI modules need a provider: switched on, not effective. - effectiveModules: ['labels'], + effectiveModules: ['labels', 'counts'], providerResolved: false, rowVersion: 1, }); @@ -71,7 +71,10 @@ describe('POST /api/v1/locations', () => { [loc.id], ), ); - expect(switches.rows[0]).toEqual({ n: 14, on: presetModules('essentials').size }); + expect(switches.rows[0]).toEqual({ + n: MODULE_IDS.length, + on: presetModules('essentials').size, + }); expect(await auditOf(db, loc.id)).toEqual([ expect.objectContaining({ action: 'location.create', @@ -139,7 +142,7 @@ describe('GET /api/v1/locations and /:id', () => { ['personal', 'owner'], ['home', 'owner'], ]); - expect(mine[1]).toMatchObject({ memberCount: 2, effectiveModules: ['labels'] }); + expect(mine[1]).toMatchObject({ memberCount: 2, effectiveModules: ['labels', 'counts'] }); const theirs = (await call(t, '/api/v1/locations', { as: viewer })).json().locations; expect(theirs.map((l: { id: string; role: string }) => [l.id, l.role])).toEqual([ diff --git a/apps/server/src/schedules/service.ts b/apps/server/src/schedules/service.ts index 1ea95fb3..ac363ff1 100644 --- a/apps/server/src/schedules/service.ts +++ b/apps/server/src/schedules/service.ts @@ -443,7 +443,11 @@ export async function createSchedule(ctx: Ctx, body: CreateScheduleInput): Promi dueOn: body.dueOn ?? null, }); const kind = body.kind ?? 'task'; - if (kind === 'count') await checkCount(client, subject, body); + if (kind === 'count') { + // A planned count needs Counts on too (#506). + await requireModule(ctx, subject.locationId, 'counts', 'write'); + await checkCount(client, subject, body); + } const today = await todayIn(client, subject.locationId); const anchorOn = body.anchorOn ?? today; if (anchorOn > today) @@ -540,6 +544,7 @@ export async function updateSchedule( meterId: meterId ? meterId.toLowerCase() : null, dueOn: pick('dueOn', before.due_on), }); + if (before.kind === 'count') await requireModule(ctx, before.location_id, 'counts', 'write'); if (before.kind === 'count' && pick('active', before.active)) { await checkCount( client, diff --git a/apps/server/src/sessions/counts-module.test.ts b/apps/server/src/sessions/counts-module.test.ts new file mode 100644 index 00000000..854f40b9 --- /dev/null +++ b/apps/server/src/sessions/counts-module.test.ts @@ -0,0 +1,228 @@ +import { newId, type QueueItem } from '@kept/shared'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import type { TestApp } from '../../test/app.js'; +import { type TestDb, testDb } from '../../test/db.js'; +import { call, join, type Person, peopleApp, person } from '../../test/people.js'; +import { op, sent } from '../../test/sync-ops.js'; +import { + createLocation, + createThing, + type Json, + type Loc, + ok, + own, + place, +} from '../../test/things.js'; + +// Counts as a module a location can turn off (#506), like Labels and Lending: on in every preset, +// so a location keeps its counts; off, the counts' reads are 404 `module_off`, their writes 409, +// the phone's count ops are dropped, and a planned count leaves the agenda. A pack or a move, +// the other kinds of scan session, don't need it. + +let db: TestDb; +let t: TestApp; +let ibrahim: Person; // owner of Home +let louis: Person; // member of Home +let home: Loc; + +const HOUR = 3_600_000; +const ago = (ms: number) => new Date(Date.now() - ms).toISOString(); + +type Plain = Record; + +const session = (mode: 'count' | 'pack' | 'move', target: Plain) => ({ + id: newId(), + mode, + target, + startedAt: ago(2 * HOUR), +}); + +function walk(s: ReturnType, lines: Plain[]): QueueItem[] { + const lineOp = op( + 'scan_session_lines', + home.id, + { session: s, lines }, + { idempotencyKey: `scan-lines:${s.id}:0` }, + ); + return [ + lineOp, + op( + 'scan_session_finish', + home.id, + { sessionId: s.id, expected: lines.length }, + { idempotencyKey: `scan-finish:${s.id}`, dependsOn: [lineOp.idempotencyKey] }, + ), + ]; +} + +const switchCounts = async (enabled: boolean) => + ok( + await call(t, `/api/v1/locations/${home.id}/modules`, { + as: ibrahim, + body: { module: 'counts', enabled }, + }), + ); + +const status = async (as: Person, url: string, body?: unknown) => { + const res = await call(t, url, { as, ...(body === undefined ? {} : { body }) }); + const code = res.headers['content-type']?.toString().includes('json') + ? (res.json() as { code?: string }).code + : undefined; + return { status: res.statusCode, code }; +}; + +const agendaHas = async (scheduleId: string) => { + const r = ok(await call(t, '/api/v1/agenda?sourceType=schedule', { as: ibrahim })); + return (r.items as Json[]).some((x) => x.sourceId === scheduleId); +}; + +beforeAll(async () => { + db = await testDb(); + await db.reset(); + t = await peopleApp(db); + ibrahim = await person(t, db, 'ibrahim'); + louis = await person(t, db, 'louis'); + home = await createLocation(t, db, ibrahim, 'household'); + await join(db, home.id, louis.userId, 'member'); +}); + +afterAll(async () => { + await t.app.close(); +}); + +describe('Counts, a module a location can turn off (#506)', () => { + it('is on in a new location of every preset', async () => { + for (const preset of ['essentials', 'household', 'complete'] as const) { + const loc = await createLocation(t, db, ibrahim, preset, `Counts ${preset}`); + const view = ok(await call(t, `/api/v1/locations/${loc.id}`, { as: ibrahim })); + expect(view.modules, preset).toContain('counts'); + expect((await status(ibrahim, `/api/v1/locations/${loc.id}/counts`)).status, preset).toBe( + 200, + ); + } + }); + + it('off: its reads are 404 module_off, its writes 409, and it comes back as it was', async () => { + const study = await place(db, home, 'Study'); + const lamp = await createThing(t, ibrahim, home, { name: 'Lamp', placeId: study }); + const before = session('count', { placeId: study, inside: true }); + const results = await sent( + t, + louis, + walk(before, [{ thingId: lamp.id, readAt: ago(HOUR), action: 'seen' }]), + ); + expect(results.map((r) => r.outcome)).toEqual(['applied', 'applied']); + const open = session('count', { placeId: study, inside: true }); + ok( + await call(t, '/api/v1/scan-sessions', { + as: louis, + body: { id: open.id, target: open.target }, + }), + 201, + ); + + await switchCounts(false); + const view = ok(await call(t, `/api/v1/locations/${home.id}`, { as: ibrahim })); + expect(view.modules).not.toContain('counts'); + + const off404 = { status: 404, code: 'module_off' }; + expect(await status(louis, `/api/v1/locations/${home.id}/counts`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/${before.id}`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/${before.id}/result`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/${before.id}/result.csv`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/${open.id}/lines`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/open?placeId=${study}`)).toEqual(off404); + expect(await status(louis, `/api/v1/scan-sessions/open?locationId=${home.id}`)).toEqual(off404); + const fresh = session('count', { placeId: study, inside: true }); + expect( + await status(louis, '/api/v1/scan-sessions', { id: fresh.id, target: fresh.target }), + ).toEqual({ status: 409, code: 'module_off' }); + + // A count saved on a phone while it was off is dropped, the open one can't be finished. + const offline = session('count', { placeId: study, inside: true }); + const dropped = await sent( + t, + louis, + walk(offline, [{ thingId: lamp.id, readAt: ago(HOUR), action: 'seen' }]), + ); + expect(dropped.map((r) => r.outcome)).toEqual(['dropped', 'dropped']); + const finish = await sent(t, louis, [ + op('scan_session_finish', home.id, { sessionId: open.id, expected: 0 }), + ]); + expect(finish[0]?.outcome).toBe('dropped'); + expect( + await own(db, 'SELECT finished_at FROM public.scan_sessions WHERE id = $1', [open.id]), + ).toEqual([{ finished_at: null }]); + + // Back on: nothing was deleted. + await switchCounts(true); + const r = ok(await call(t, `/api/v1/scan-sessions/${before.id}/result`, { as: louis })); + expect(r.session).toMatchObject({ seen: 1 }); + const list = ok(await call(t, `/api/v1/locations/${home.id}/counts`, { as: louis })); + expect((list.items as Json[]).map((x) => x.id)).toContain(before.id); + }); + + it('off: a pack or a move is still a scan session of its own', async () => { + const hall = await place(db, home, 'Hall'); + const shelf = await place(db, home, 'Shelf'); + const tape = await createThing(t, ibrahim, home, { name: 'Tape', placeId: hall }); + await switchCounts(false); + try { + const s = session('move', { placeId: shelf, inside: false }); + const results = await sent( + t, + louis, + walk(s, [{ thingId: tape.id, readAt: ago(HOUR), action: 'move', to: { placeId: shelf } }]), + ); + expect(results.map((r) => r.outcome)).toEqual(['applied', 'applied']); + expect((await status(louis, `/api/v1/scan-sessions/${s.id}`)).status).toBe(200); + } finally { + await switchCounts(true); + } + }); + + it('off: a count can’t be planned, and a planned count leaves the agenda', async () => { + const loft = await place(db, home, 'Loft'); + const attic = await place(db, home, 'Attic'); + const made = ok( + await call(t, '/api/v1/schedules', { + as: ibrahim, + body: { subject: { placeId: loft }, name: 'Count the loft', kind: 'count', everyMonths: 6 }, + }), + 201, + ); + await own( + db, + `UPDATE public.schedules SET base_on = current_date - 200, anchor_on = current_date - 200 + WHERE id = $1`, + [made.id], + ); + expect(await agendaHas(made.id as string)).toBe(true); + + await switchCounts(false); + try { + expect(await agendaHas(made.id as string)).toBe(false); + expect( + await status(ibrahim, '/api/v1/schedules', { + subject: { placeId: attic }, + name: 'Count the attic', + kind: 'count', + everyMonths: 3, + }), + ).toEqual({ status: 409, code: 'module_off' }); + // Any other schedule still can be. + expect( + ( + await status(ibrahim, '/api/v1/schedules', { + subject: { placeId: attic }, + name: 'Air the attic', + everyMonths: 3, + }) + ).status, + ).toBe(201); + } finally { + await switchCounts(true); + } + expect(await agendaHas(made.id as string)).toBe(true); + }); +}); diff --git a/apps/server/src/sessions/counts.ts b/apps/server/src/sessions/counts.ts index 7eee620e..bbec8eb7 100644 --- a/apps/server/src/sessions/counts.ts +++ b/apps/server/src/sessions/counts.ts @@ -2,7 +2,7 @@ import { SCAN_SESSION_ACTIONS, type ScanSessionTarget } from '@kept/shared'; import type pg from 'pg'; import { z } from 'zod'; import { encodeCursor, type PageRequest } from '../http/conventions.js'; -import { invalid, notFound } from '../http/errors.js'; +import { AppError, invalid, notFound } from '../http/errors.js'; import { callerMembership } from '../locations/access.js'; // Counts that keep every result (1.14, #172): the reads behind the Counts page, a count's page @@ -166,6 +166,23 @@ async function requireVisible(client: pg.ClientBase, locationId: string): Promis if (!(await callerMembership(client, locationId))) throw notFound(); } +/** + * Counts is a module (#506): where it is off, a count's read is 404 `module_off` and its write + * 409, as http/modules.ts answers for a gated route. kept.module_on() is effectiveModules() in + * SQL (Counts needs no provider and no other module). A pack or a move doesn't ask. + */ +export async function requireCountsOn( + client: pg.ClientBase, + locationId: string, + mode: 'read' | 'write', +): Promise { + const { rows } = await client.query<{ on: boolean }>( + `SELECT kept.module_on($1, 'counts') AS on`, + [locationId], + ); + if (rows[0]?.on !== true) throw new AppError('module_off', mode === 'read' ? 404 : 409); +} + const UNDONE = (event: string, location: string) => `EXISTS (SELECT 1 FROM public.audit_events u WHERE u.undo_of = ${event} AND u.location_id = ${location})`; @@ -192,6 +209,7 @@ export async function listCounts( page: PageRequest<[string, string]>, ): Promise> { await requireVisible(client, locationId); + await requireCountsOn(client, locationId, 'read'); const after = page.after; if (after && (typeof after[0] !== 'string' || typeof after[1] !== 'string')) throw invalid('The cursor is not valid; start again from the first page.'); @@ -327,6 +345,7 @@ async function countHead(client: pg.ClientBase, id: string): Promise( + `SELECT location_id FROM public.places WHERE id = $1 + UNION ALL SELECT location_id FROM public.things WHERE id = $2 + UNION ALL SELECT id FROM public.locations WHERE id = $3`, + [place, box, loc], + ); + if (!at[0]) return { session: null }; + await requireCountsOn(client, at[0].location_id, 'read'); const { rows } = await client.query<{ id: string; target: z.infer; @@ -619,15 +647,17 @@ export async function sessionLines( id: string, ): Promise> { const { rows: head } = await client.query<{ + location_id: string; finished_at: Date | null; counted_place_ids: string[] | null; }>( - `SELECT finished_at, counted_place_ids FROM public.scan_sessions + `SELECT location_id, finished_at, counted_place_ids FROM public.scan_sessions WHERE id = $1 AND mode = 'count'`, [id], ); const s = head[0]; if (!s) throw notFound(); + await requireCountsOn(client, s.location_id, 'read'); const { rows } = await client.query<{ thing_id: string; action: ResultLine['result']; diff --git a/apps/server/src/sessions/service.ts b/apps/server/src/sessions/service.ts index bd46a6da..74ec7b8c 100644 --- a/apps/server/src/sessions/service.ts +++ b/apps/server/src/sessions/service.ts @@ -24,6 +24,7 @@ import { forbidden, invalid, notFound, toErrorReply } from '../http/errors.js'; import { callerMembership } from '../locations/access.js'; import { moveBackForUndo, moveThings } from '../things/move.js'; import { type Ctx, splitThing } from '../things/service.js'; +import { requireCountsOn } from './counts.js'; // Scan sessions (1.12, #168): one walk with the camera, many labels, one mode. // @@ -162,6 +163,7 @@ export async function startCount(ctx: Ctx, body: StartCountBody): Promise<{ id: const id = assertClientId(body.id.toLowerCase()); const locationId = await targetLocation(client, body.target); await requireAllowed(client, locationId, needs('count')); + await requireCountsOn(client, locationId, 'write'); const found = await sessionRow(client, id); if (found) { if (found.started_by !== scope.userId || found.mode !== 'count') throw notFound(); @@ -230,6 +232,7 @@ export async function addSessionLines( throw invalid("The session's target is in another location."); } await requireAllowed(client, locationId, needs(head.mode)); + if (head.mode === 'count') await requireCountsOn(client, locationId, 'write'); const counted = head.countedPlaceIds ? head.countedPlaceIds.map((x) => x.toLowerCase()) : null; const found = await sessionRow(client, id, true); @@ -420,6 +423,7 @@ export async function finishSession( if (!s || (s.started_by !== scope.userId && s.mode !== 'count')) throw notFound(); if (s.finished_at) throw invalid('This session is already saved.'); await requireAllowed(client, s.location_id, needs(s.mode)); + if (s.mode === 'count') await requireCountsOn(client, s.location_id, 'write'); if (s.mode === 'count') { // What is lent, in repair or trashed now, and the areas counted by everyone who joined. await client.query( @@ -711,6 +715,7 @@ export async function sessionView(client: pg.ClientBase, id: string): Promise ({ // Consumables on: the AA batteries keep at least 16 and run low. modules: [ 'labels', + 'counts', 'money', 'warranties', 'schedules', @@ -186,7 +196,7 @@ const garage = (): LocationDetail => ({ // Essentials, with Lending turned on over the preset: tools get lent from the garage (the // step-4 drill lent to Murdock). Money, warranties, schedules and paperwork stay off, the // module-off variant step-4 screens test against. - modules: ['labels', 'lending'], + modules: ['labels', 'counts', 'lending'], providerResolved: false, }); diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx index 9489c08a..c9a242fe 100644 --- a/apps/web/src/components/app-shell.tsx +++ b/apps/web/src/components/app-shell.tsx @@ -231,7 +231,14 @@ export function useNavEntries(): NavEntry[] { to: '/labels', group: 'tools', }), - { key: 'counts', label: t`Counts`, icon: , to: '/counts', group: 'tools' }, + // A module since #506: shown while Counts is on in one of your locations, as Labels is. + ...gated(modulesOn.has('counts'), { + key: 'counts', + label: t`Counts`, + icon: , + to: '/counts', + group: 'tools', + }), { key: 'activity', label: t`Activity`, diff --git a/apps/web/src/components/counts/counts-list.tsx b/apps/web/src/components/counts/counts-list.tsx index 9a89dce9..f544ea0b 100644 --- a/apps/web/src/components/counts/counts-list.tsx +++ b/apps/web/src/components/counts/counts-list.tsx @@ -3,6 +3,8 @@ * standard (surface `counts`: search by what was counted; filters kind, place, who, had missing * and date; grouped by place, person or month; paged; all in the URL). One location at a time: * the location filter picks it (the first one by default). Each row opens the count's page. + * Counts is a module (#506): only the locations with it on are offered, and with it off in every + * one the list says so. */ import { plural } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; @@ -16,6 +18,7 @@ import type { FilterDef } from '@/components/filters/types'; import { ActivityIcon, AlertIcon, BoxIcon, QrIcon } from '@/components/icons'; import { ListSurface } from '@/components/list-surface'; import { EmptyState, IconTile, Pill, Row } from '@/components/page'; +import { accessOf } from '@/components/schedules/access'; import { oneValue } from '@/components/schedules/list-query'; import { useFormat } from '@/lib/format'; import { useLocationName } from '@/lib/labels'; @@ -28,7 +31,7 @@ export function CountsList() { const f = useFilterRegistry(); const locations = useLocationsInView(); const locationName = useLocationName(); - const all = locations.data ?? []; + const all = (locations.data ?? []).filter((l) => accessOf(l).moduleOn('counts')); const locationId = oneValue(list, 'location') ?? all[0]?.id ?? null; const kind = oneValue(list, 'kind'); @@ -82,6 +85,13 @@ export function CountsList() { f.date('when', t`Counted`), ]; + if (locations.data && all.length === 0) + return ( + } title={Counts are off}> + An owner or admin turns them on in a location's What to track. + + ); + return ( label={t`Counts`} diff --git a/apps/web/src/components/hints/tours.test.ts b/apps/web/src/components/hints/tours.test.ts index e41e06d5..4cef30f1 100644 --- a/apps/web/src/components/hints/tours.test.ts +++ b/apps/web/src/components/hints/tours.test.ts @@ -102,6 +102,7 @@ describe('tour gating', () => { const EVERYTHING: ModuleId[] = [ 'ai_capture', 'labels', + 'counts', 'moving', 'lending', 'schedules', @@ -131,6 +132,8 @@ describe('tour gating', () => { ]); // Labels is one step of Search & labels, not its own tour. expect(stepIds('search', none)).toEqual(['field', 'views', 'select', 'scan']); + // Counts is the last step of Scan sessions (#506). + expect(stepIds('scanning', none)).toEqual(['start', 'modes', 'target', 'finish']); }); it('follows a module that is on in any one location', () => { diff --git a/apps/web/src/components/hints/tours.ts b/apps/web/src/components/hints/tours.ts index 8f9b1209..4c916242 100644 --- a/apps/web/src/components/hints/tours.ts +++ b/apps/web/src/components/hints/tours.ts @@ -188,7 +188,7 @@ export const TOURS: readonly Tour[] = [ { id: 'target', to: '/scan-session', anchors: ['session-target'] }, { id: 'finish', to: '/scan-session', anchors: ['session-finish'] }, // 1.14 (#172): every count is kept, with what was missing. - { id: 'counts', to: '/counts', anchors: ['counts-list'] }, + { id: 'counts', to: '/counts', anchors: ['counts-list'], modules: ['counts'] }, ], }, { diff --git a/apps/web/src/components/scan/session/session-screen.test.tsx b/apps/web/src/components/scan/session/session-screen.test.tsx index cc404d58..a96b43db 100644 --- a/apps/web/src/components/scan/session/session-screen.test.tsx +++ b/apps/web/src/components/scan/session/session-screen.test.tsx @@ -7,7 +7,14 @@ * The camera is a fake stream and the decode loop is replaced: the test feeds what "the scanner * read". The phone's store is the real `MemoryStore`; the server's batch resolve is a stub. */ -import type { Role, SnapLocation, SnapPlace, SnapshotPage, SnapThing } from '@kept/shared'; +import type { + ModuleId, + Role, + SnapLocation, + SnapPlace, + SnapshotPage, + SnapThing, +} from '@kept/shared'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -81,7 +88,7 @@ const code = (c: string, thingId: string) => ({ isPrimary: true, }); -async function phone(role: Role = 'member') { +async function phone(role: Role = 'member', modules: ModuleId[] = ['labels', 'counts']) { const store = new MemoryStore(); const location: SnapLocation = { id: HOME, @@ -90,7 +97,7 @@ async function phone(role: Role = 'member') { timezone: 'Africa/Cairo', languages: ['en'], role, - effectiveModules: ['labels'], + effectiveModules: modules, unplacedPlaceId: UNPLACED, suggestRadiusM: 150, }; @@ -442,7 +449,7 @@ describe('a hidden Personal', () => { timezone: 'Africa/Cairo', languages: ['en'], role: 'owner', - effectiveModules: ['labels'], + effectiveModules: ['labels', 'counts'], unplacedPlaceId: PERSONAL_UNPLACED, suggestRadiusM: 150, }, @@ -482,6 +489,31 @@ describe('a hidden Personal', () => { }); }); +describe('Counts off (#506)', () => { + it('offers packing and moving, and no count', async () => { + await render({ store: await phone('member', ['labels']) }); + await screen.findByRole('heading', { name: 'Start a session' }); + expect(await screen.findByRole('radio', { name: /^Pack into/ })).toBeChecked(); + expect(screen.getByRole('radio', { name: /^Move to/ })).toBeInTheDocument(); + expect(screen.queryByRole('radio', { name: /^Count/ })).not.toBeInTheDocument(); + }); + + it('won’t start a count from a link to one', async () => { + await render({ + store: await phone('member', ['labels']), + start: { mode: 'count', target: { kind: 'place', id: STUDY } }, + }); + await screen.findByRole('heading', { name: 'Start a session' }); + expect(screen.getByRole('button', { name: 'Start' })).toBeDisabled(); + }); + + it('tells a viewer there is nothing to do', async () => { + await render({ store: await phone('viewer', ['labels']) }); + expect(await screen.findByText('Counts are off')).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Start a session' })).not.toBeInTheDocument(); + }); +}); + describe('a shared count', () => { const OPEN = '01926f00-0000-7000-8000-00000000e0aa'; const LOUIS = '01926f00-0000-7000-8000-00000000e0bb'; diff --git a/apps/web/src/components/scan/session/session-screen.tsx b/apps/web/src/components/scan/session/session-screen.tsx index 06bd42e2..fe67fca9 100644 --- a/apps/web/src/components/scan/session/session-screen.tsx +++ b/apps/web/src/components/scan/session/session-screen.tsx @@ -47,7 +47,8 @@ import { appDetect, type FrameDecoder } from '@/camera/scanner'; import type { CameraEnv } from '@/camera/session'; import { shownLocations } from '@/components/capture/target'; import { ShowMe } from '@/components/hints/show-me'; -import { XIcon } from '@/components/icons'; +import { QrIcon, XIcon } from '@/components/icons'; +import { EmptyState } from '@/components/page'; import { usePlaceName } from '@/components/places/labels'; import { Button } from '@/components/ui/button'; import { useConfirm } from '@/components/ui/confirm'; @@ -250,7 +251,11 @@ export function SessionScreen({ const tgt = res?.outcome === 'open' ? res.target : null; const thing = tgt?.kind === 'thing' ? await nameOfThing(tgt.id) : undefined; const role = tgt ? roles(tgt.locationId) : null; - const allowed = !!role && (mode === 'count' || can(role, 'things.edit')); + // Counts is a module (#506): a count only where it is on. + const countsHere = !!world?.locations + .find((l) => l.id === tgt?.locationId) + ?.effectiveModules.includes('counts'); + const allowed = !!role && (mode === 'count' ? countsHere : can(role, 'things.edit')); const kindOk = tgt?.kind === 'place' ? mode !== 'pack' : thing?.isContainer === true; if (!tgt || !allowed || !kindOk) { feel('grey'); @@ -964,7 +969,15 @@ function SessionSetup({ const hidden = useHiddenLocationId(); const shown = useMemo(() => shownLocations(world.locations, hidden), [world, hidden]); const anyEdit = shown.some((l) => canEdit(l.id)) || (!!fixed && canEdit(fixed.locationId)); - const [mode, setMode] = useState(start?.mode ?? 'count'); + // Counts is a module (#506): counting is offered where it is on, and nowhere else. + const countsOn = useCallback( + (id: string) => !!world.locations.find((l) => l.id === id)?.effectiveModules.includes('counts'), + [world], + ); + const countable = shown.some((l) => countsOn(l.id)) || (!!fixed && countsOn(fixed.locationId)); + const [mode, setMode] = useState( + start?.mode ?? (countable || !anyEdit ? 'count' : 'pack'), + ); const [inside, setInside] = useState(true); const [contents, setContents] = useState(false); const [picked, setPicked] = useState(null); @@ -974,6 +987,7 @@ function SessionSetup({ const out: TargetOption[] = []; for (const l of shown) { if (mode !== 'count' && !canEdit(l.id)) continue; + if (mode === 'count' && !countsOn(l.id)) continue; if (mode === 'count') out.push({ key: `l:${l.id}`, @@ -1016,7 +1030,7 @@ function SessionSetup({ }); } return out; - }, [world, shown, mode, t, placeName, canEdit, locName]); + }, [world, shown, mode, t, placeName, canEdit, locName, countsOn]); const chosen: SessionTarget | null = fixed && @@ -1026,7 +1040,10 @@ function SessionSetup({ : (options.find((o) => o.key === picked)?.target ?? null); const target = chosen?.kind === 'place' ? { ...chosen, inside } : chosen; const allowed = - !!target && (mode === 'count' ? role(target.locationId) !== null : canEdit(target.locationId)); + !!target && + (mode === 'count' + ? role(target.locationId) !== null && countsOn(target.locationId) + : canEdit(target.locationId)); const counting = mode === 'count' && !!target; const counts = !!target && can(role(target.locationId) ?? 'viewer', 'things.mark-seen'); const openKey = target ? `${target.kind}:${target.id}` : ''; @@ -1054,13 +1071,17 @@ function SessionSetup({ const cov = coverage.data?.things ? coverage.data : null; const modes = [ - { - id: 'count' as const, - title: t`Count`, - body: anyEdit - ? t`Walk a place or a box: what you scan is seen; the summary shows what's missing.` - : t`Check a place or a box: see what's there and what's missing. Viewers' counts aren't saved.`, - }, + ...(countable + ? [ + { + id: 'count' as const, + title: t`Count`, + body: anyEdit + ? t`Walk a place or a box: what you scan is seen; the summary shows what's missing.` + : t`Check a place or a box: see what's there and what's missing. Viewers' counts aren't saved.`, + }, + ] + : []), ...(anyEdit ? [ { @@ -1077,6 +1098,16 @@ function SessionSetup({ : []), ]; + // A viewer where Counts is off everywhere: nothing a session could do. + if (modes.length === 0) + return ( +
+ } title={Counts are off}> + An owner or admin turns them on in a location's What to track. + +
+ ); + return (
diff --git a/apps/web/src/components/things/action-menu.tsx b/apps/web/src/components/things/action-menu.tsx index e115fba9..2446e0c1 100644 --- a/apps/web/src/components/things/action-menu.tsx +++ b/apps/web/src/components/things/action-menu.tsx @@ -117,7 +117,7 @@ function useItems(extra: Item[] = []): Item[] { if (thing.isContainer && can('things.mark-seen')) items.push({ id: 'box-check', label: t`Box check`, icon: }); // Scan sessions (#168): count a box's labels in one walk (a viewer's count is a check), or pack. - if (thing.isContainer && can('content.view')) + if (thing.isContainer && can('content.view') && moduleOn('counts')) items.push({ id: 'scan-count', label: t`Count this box`, icon: }); if (thing.isContainer && edit) items.push({ id: 'scan-pack', label: t`Pack into this box`, icon: }); diff --git a/apps/web/src/lib/labels.ts b/apps/web/src/lib/labels.ts index deb06d77..9efaf104 100644 --- a/apps/web/src/lib/labels.ts +++ b/apps/web/src/lib/labels.ts @@ -88,6 +88,7 @@ export function useModuleLabels(): Record { const { t } = useLingui(); return { labels: t`Labels and QR codes`, + counts: t`Counts`, money: t`Money and spending`, warranties: t`Receipts and warranties`, schedules: t`Reminders`, diff --git a/apps/web/src/map/card.tsx b/apps/web/src/map/card.tsx index 35a0ddce..45d600f5 100644 --- a/apps/web/src/map/card.tsx +++ b/apps/web/src/map/card.tsx @@ -74,6 +74,8 @@ export type CardProps = { level: Level | undefined; focused: string | null; arranging: boolean; + /** Counts is on in the map's location (#506): the room card offers "Count this room". */ + countsOn?: boolean; /** A thing picked from an Inside list, shown as its own card. */ inner: string | null; setInner: (id: string | null) => void; @@ -592,9 +594,11 @@ export function MapCard(p: CardProps) { Photograph this room ) : null} - - Count this room - + {p.countsOn ? ( + + Count this room + + ) : null}
{p.arranging ? ( <> diff --git a/apps/web/src/map/map-view.tsx b/apps/web/src/map/map-view.tsx index 2b1ebf09..682615cc 100644 --- a/apps/web/src/map/map-view.tsx +++ b/apps/web/src/map/map-view.tsx @@ -42,6 +42,7 @@ import type { } from '@/api/map/types'; import { useOfferUndo } from '@/components/history/undo'; import { ErrorState, LoadingRows, Notice, useErrorText } from '@/components/page'; +import { useLocationAccess } from '@/components/schedules/access'; import { buttonClass } from '@/components/ui/button'; import { useConfirm } from '@/components/ui/confirm'; import { Switch } from '@/components/ui/switch'; @@ -218,6 +219,7 @@ function MapBody({ const f = useFormat(); const lengths = useLengths(); const qc = useQueryClient(); + const access = useLocationAccess(); const confirm = useConfirm(); const errorText = useErrorText(); const offerUndo = useOfferUndo(); @@ -1359,6 +1361,7 @@ function MapBody({ sel={sel} plan={plan} view={data} + countsOn={access(data.locationId).moduleOn('counts')} contents={contents} level={plan.levels.find((l) => l.key === es.levelKey)} focused={es.focused} diff --git a/apps/web/src/routes/_app/loc.$id.tsx b/apps/web/src/routes/_app/loc.$id.tsx index 598524a5..0c3c01a7 100644 --- a/apps/web/src/routes/_app/loc.$id.tsx +++ b/apps/web/src/routes/_app/loc.$id.tsx @@ -202,15 +202,18 @@ function LocationBody({ location }: { location: LocationDetail }) { Incidents ) : null} - {/* 1.14 (#172): every count and box check of the location, everyone's to read. */} - - - Counts - + {/* 1.14 (#172): every count and box check of the location, everyone's to read, while + Counts is on here (#506). */} + {access.moduleOn('counts') ? ( + + + Counts + + ) : null} {/* 1.x (D235): move-in and move-out condition reports, everyone's to read. */} {location.kind === 'personal' ? null : ( - {/* Scan sessions (#168): every label here in one walk. A viewer's count is a check. */} - - } - title={Count here} - subtitle={Scan every label in {name} in one walk} - onPress={() => - void navigate({ to: '/scan-session', search: { mode: 'count', place: place.id } }) - } - /> - {/* 1.14 (#172): a planned count, and every past count of this place. */} - {place.isUnplaced || !accessOf(location).moduleOn('schedules') ? null : ( - + } + title={Count here} + subtitle={Scan every label in {name} in one walk} + onPress={() => + void navigate({ to: '/scan-session', search: { mode: 'count', place: place.id } }) + } /> - )} - } - title={Past counts} - subtitle={Every count of {name}, with what was missing} - onPress={() => - void navigate({ - to: '/counts', - search: { 'f.location': location.id, 'f.place': place.id }, - }) - } - /> - + {/* 1.14 (#172): a planned count, and every past count of this place. */} + {place.isUnplaced || !accessOf(location).moduleOn('schedules') ? null : ( + + )} + } + title={Past counts} + subtitle={Every count of {name}, with what was missing} + onPress={() => + void navigate({ + to: '/counts', + search: { 'f.location': location.id, 'f.place': place.id }, + }) + } + /> + + ) : null} {/* #238: furniture's compartments, as it stands: turned, merged or split here. */} {map || graph || !place.furniture ? null : ( diff --git a/apps/web/src/test/screens/counts-module.test.tsx b/apps/web/src/test/screens/counts-module.test.tsx new file mode 100644 index 00000000..cf352cd3 --- /dev/null +++ b/apps/web/src/test/screens/counts-module.test.tsx @@ -0,0 +1,87 @@ +/** + * Counts as a module a location can turn off (#506), against the mock server: with it on, a + * place's page has Count here and Past counts, a box's menu Count this box, a location's page its + * Counts link, and Tools has Counts; with it off, none of them shows, and /counts says so. + */ +import { screen, within } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { INV_IDS } from '@/api/inventory/mock/fixtures'; +import { type MockState, ownerScenario } from '@/api/mock/fixtures'; +import { findHeading, renderApp } from '@/test/app'; + +vi.setConfig({ testTimeout: 15_000 }); + +const P = INV_IDS.place; +const T = INV_IDS.thing; + +/** The owner scenario with Counts off in every location. */ +function countsOff(): MockState { + const state = ownerScenario(); + for (const l of state.locations) { + l.modules = l.modules.filter((m) => m !== 'counts'); + if (l.effectiveModules) l.effectiveModules = l.effectiveModules.filter((m) => m !== 'counts'); + } + return state; +} + +const mainNav = () => + screen + .getAllByRole('navigation', { name: 'Main' }) + .find((n) => within(n).queryByText('Activity')) as HTMLElement; + +describe('Counts on (every preset has it)', () => { + it('shows Count here and Past counts on a place, and Counts in Tools', async () => { + await renderApp(`/p/${P.livingRoom}`); + await findHeading('Living room'); + expect(await screen.findByRole('button', { name: /^Count here/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /^Past counts/ })).toBeInTheDocument(); + expect(within(mainNav()).getByRole('link', { name: 'Counts' })).toBeInTheDocument(); + }); + + it('offers Count this box in a box’s menu', async () => { + const { user } = await renderApp(`/t/${T.cableBox}`); + await findHeading('Cable box'); + await user.click(screen.getByRole('button', { name: 'Actions' })); + expect(await screen.findByRole('menuitem', { name: 'Count this box' })).toBeInTheDocument(); + }); +}); + +describe('Counts off', () => { + it('hides Count here, Count regularly and Past counts on a place, and Counts in Tools', async () => { + await renderApp(`/p/${P.livingRoom}`, { state: countsOff() }); + await findHeading('Living room'); + // The page has loaded its rows: Select is one of them. + await screen.findByRole('heading', { name: 'Living room', level: 1 }); + expect(screen.queryByRole('button', { name: /^Count here/ })).toBeNull(); + expect(screen.queryByRole('button', { name: /^Count regularly/ })).toBeNull(); + expect(screen.queryByRole('button', { name: /^Past counts/ })).toBeNull(); + expect(within(mainNav()).queryByRole('link', { name: 'Counts' })).toBeNull(); + expect(within(mainNav()).getByRole('link', { name: 'Labels' })).toBeInTheDocument(); + }); + + it('leaves Count this box out of a box’s menu, and keeps Box check', async () => { + const { user } = await renderApp(`/t/${T.cableBox}`, { state: countsOff() }); + await findHeading('Cable box'); + await user.click(screen.getByRole('button', { name: 'Actions' })); + expect(await screen.findByRole('menuitem', { name: 'Box check' })).toBeInTheDocument(); + expect(screen.queryByRole('menuitem', { name: 'Count this box' })).toBeNull(); + expect(screen.getByRole('menuitem', { name: 'Pack into this box' })).toBeInTheDocument(); + }); + + it('leaves the Counts link off a location’s page', async () => { + const state = countsOff(); + const home = state.locations.find((l) => l.name === 'Home'); + await renderApp(`/loc/${home?.id}`, { state }); + await findHeading('Home'); + await screen.findByRole('link', { name: 'Incidents' }); + expect(screen.queryByRole('link', { name: 'Counts' })).toBeNull(); + }); + + it('says so on /counts', async () => { + await renderApp('/counts', { state: countsOff() }); + expect(await screen.findByText('Counts are off')).toBeInTheDocument(); + expect( + screen.getByText("An owner or admin turns them on in a location's What to track."), + ).toBeInTheDocument(); + }); +}); diff --git a/packages/shared/src/modules.test.ts b/packages/shared/src/modules.test.ts index 039bbeea..5e94e0a8 100644 --- a/packages/shared/src/modules.test.ts +++ b/packages/shared/src/modules.test.ts @@ -21,6 +21,7 @@ describe('the module registry', () => { 'ai_assistant', 'ai_capture', 'consumables', + 'counts', 'fuel', 'labels', 'lending', @@ -59,19 +60,27 @@ describe('the module registry', () => { }); describe('presets', () => { - it('Essentials is Labels, plus AI that follows the provider', () => { + it('Essentials is Labels and Counts, plus AI that follows the provider', () => { expect([...presetModules('essentials')].sort()).toEqual([ 'ai_assistant', 'ai_capture', + 'counts', 'labels', ]); }); + it('Counts is in every preset, so a location keeps its counts until it turns them off (#506)', () => { + for (const preset of PRESETS) expect(presetModules(preset).has('counts'), preset).toBe(true); + expect(MODULES.counts.deps).toEqual([]); + expect(MODULES.counts.nav).toEqual([]); + }); + it('Household includes vehicles', () => { expect([...presetModules('household')].sort()).toEqual( [ 'ai_assistant', 'ai_capture', + 'counts', 'labels', 'lending', 'money', diff --git a/packages/shared/src/modules.ts b/packages/shared/src/modules.ts index b98423c5..016d6053 100644 --- a/packages/shared/src/modules.ts +++ b/packages/shared/src/modules.ts @@ -22,6 +22,7 @@ export type Preset = (typeof PRESETS)[number]; export const MODULE_IDS = [ 'labels', + 'counts', 'money', 'warranties', 'schedules', @@ -101,6 +102,8 @@ const def = ( export const MODULES: Readonly> = Object.freeze({ labels: def('labels', 'Labels & QR', E), + // 1.14.x (#506): in every preset, so a location keeps its counts until someone turns them off. + counts: def('counts', 'Counts', E), money: def('money', 'Money', H), warranties: def('warranties', 'Warranties & claims', H), schedules: def('schedules', 'Schedules & reminders', H), From ede226a1d4d0bf1dd6b4a0d84df5fd15a1472efa Mon Sep 17 00:00:00 2001 From: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:07:09 +0200 Subject: [PATCH 3/4] i18n(web): the Counts list with Counts off, in every catalogue One string for a location with Counts off, whose Counts list shows its box checks only (#506). Signed-off-by: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> --- apps/web/src/locales/ar/messages.po | 4 ++++ apps/web/src/locales/de/messages.po | 4 ++++ apps/web/src/locales/en/messages.po | 4 ++++ apps/web/src/locales/es/messages.po | 4 ++++ apps/web/src/locales/fr/messages.po | 4 ++++ apps/web/src/locales/it/messages.po | 4 ++++ apps/web/src/locales/uk/messages.po | 4 ++++ apps/web/src/locales/vi/messages.po | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/apps/web/src/locales/ar/messages.po b/apps/web/src/locales/ar/messages.po index 442b90e9..b5537b90 100644 --- a/apps/web/src/locales/ar/messages.po +++ b/apps/web/src/locales/ar/messages.po @@ -29316,3 +29316,7 @@ msgstr "العدّ متوقف" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "يفعّله المالك أو المشرف من «ما الذي تتابعه» في الموقع." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "العدّ متوقف هنا: تعرض هذه القائمة فحوص الصناديق." diff --git a/apps/web/src/locales/de/messages.po b/apps/web/src/locales/de/messages.po index 0b6f57c3..88b2ec79 100644 --- a/apps/web/src/locales/de/messages.po +++ b/apps/web/src/locales/de/messages.po @@ -29316,3 +29316,7 @@ msgstr "Zählungen sind aus" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Ein Eigentümer oder Admin schaltet sie unter „Was erfasst wird“ eines Standorts ein." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Zählungen sind hier aus: Diese Liste zeigt die Kistenchecks." diff --git a/apps/web/src/locales/en/messages.po b/apps/web/src/locales/en/messages.po index 857c6499..463f93fb 100644 --- a/apps/web/src/locales/en/messages.po +++ b/apps/web/src/locales/en/messages.po @@ -29316,3 +29316,7 @@ msgstr "Counts are off" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "An owner or admin turns them on in a location's What to track." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Counts are off here: this lists the box checks." diff --git a/apps/web/src/locales/es/messages.po b/apps/web/src/locales/es/messages.po index fe016ba5..ed8332e3 100644 --- a/apps/web/src/locales/es/messages.po +++ b/apps/web/src/locales/es/messages.po @@ -29316,3 +29316,7 @@ msgstr "Los conteos están desactivados" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Un propietario o administrador los activa en «Qué seguir» de una ubicación." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Los conteos están desactivados aquí: esta lista muestra las revisiones de caja." diff --git a/apps/web/src/locales/fr/messages.po b/apps/web/src/locales/fr/messages.po index 9f656ecd..ac6cebe9 100644 --- a/apps/web/src/locales/fr/messages.po +++ b/apps/web/src/locales/fr/messages.po @@ -29316,3 +29316,7 @@ msgstr "Les comptages sont désactivés" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Un propriétaire ou un administrateur les active dans « Ce que vous suivez » d'un lieu." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Les comptages sont désactivés ici : cette liste montre les vérifications de boîtes." diff --git a/apps/web/src/locales/it/messages.po b/apps/web/src/locales/it/messages.po index f6789aa4..04783e38 100644 --- a/apps/web/src/locales/it/messages.po +++ b/apps/web/src/locales/it/messages.po @@ -29316,3 +29316,7 @@ msgstr "I conteggi sono disattivati" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Un proprietario o un amministratore li attiva in «Cosa monitorare» di un luogo." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "I conteggi sono disattivati qui: questo elenco mostra i controlli scatola." diff --git a/apps/web/src/locales/uk/messages.po b/apps/web/src/locales/uk/messages.po index afb98391..371d4d0f 100644 --- a/apps/web/src/locales/uk/messages.po +++ b/apps/web/src/locales/uk/messages.po @@ -31299,3 +31299,7 @@ msgstr "Підрахунки вимкнено" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Власник або адміністратор вмикає їх у розділі «Що відстежувати» локації." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Підрахунки тут вимкнено: цей список показує перевірки коробок." diff --git a/apps/web/src/locales/vi/messages.po b/apps/web/src/locales/vi/messages.po index c6555177..203c85c2 100644 --- a/apps/web/src/locales/vi/messages.po +++ b/apps/web/src/locales/vi/messages.po @@ -21364,3 +21364,7 @@ msgstr "Kiểm kê đang tắt" #: src/components/scan/session/session-screen.tsx msgid "An owner or admin turns them on in a location's What to track." msgstr "Chủ sở hữu hoặc quản trị viên bật nó trong mục Theo dõi gì của một địa điểm." + +#: src/components/counts/counts-list.tsx +msgid "Counts are off here: this lists the box checks." +msgstr "Kiểm kê đang tắt ở đây: danh sách này hiện các lần kiểm tra hộp." From d5606aac40de7c2696c5d2dae2ba4119fc198e14 Mon Sep 17 00:00:00 2001 From: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> Date: Fri, 13 Nov 2026 02:07:10 +0200 Subject: [PATCH 4/4] fix(counts): what Counts off keeps, and an export that keeps it Review of #541 (#506): - an export names a location's modules from its switch rows, and existing locations had none for Counts, so an import turned Counts off. 0177 now gives every location its Counts row, on; an export also names the modules it knew (modulesKnown), and an import sets only those, so one made before Counts leaves the target's own switch as it is; - Check this kit shows only while Counts is on; - with Counts off the Counts list (server, mock and app) lists the box checks only, with a line saying so, and a box check's Back lands on its location's list; a count's page says Counts are off instead of an error; /counts opens on a location with Counts on; - a planned count with Counts off can be paused, resumed or deleted, but not done, snoozed or skipped (409 module_off; tool_unavailable through MCP and the assistant), and its row offers none of those; - docs: Turning Counts off and the kits line in every language, and the es and it wording for planned counts. Signed-off-by: Ibrahim Roshdy <22573766+ibrahimroshdy@users.noreply.github.com> --- apps/docs/src/content/docs/ar/users/counts.md | 2 +- apps/docs/src/content/docs/ar/users/kits.md | 2 +- apps/docs/src/content/docs/de/users/counts.md | 6 +- apps/docs/src/content/docs/de/users/kits.md | 2 +- apps/docs/src/content/docs/es/users/counts.md | 6 +- apps/docs/src/content/docs/es/users/kits.md | 3 +- apps/docs/src/content/docs/fr/users/counts.md | 6 +- apps/docs/src/content/docs/fr/users/kits.md | 2 +- apps/docs/src/content/docs/it/users/counts.md | 6 +- apps/docs/src/content/docs/it/users/kits.md | 2 +- apps/docs/src/content/docs/uk/users/counts.md | 4 +- apps/docs/src/content/docs/uk/users/kits.md | 2 +- apps/docs/src/content/docs/users/counts.md | 6 +- apps/docs/src/content/docs/users/kits.md | 2 +- apps/docs/src/content/docs/vi/users/counts.md | 2 +- apps/docs/src/content/docs/vi/users/kits.md | 2 +- apps/server/migrations/0177_counts_module.sql | 12 +- apps/server/src/exports/data.ts | 5 + apps/server/src/exports/format.ts | 6 +- apps/server/src/imports/kept/apply.ts | 19 ++- .../src/imports/kept/kept-import.test.ts | 93 +++++++++++++ apps/server/src/schedules/service.ts | 16 ++- apps/server/src/schedules/services.ts | 2 + .../server/src/sessions/counts-module.test.ts | 121 +++++++++++++++- apps/server/src/sessions/counts.ts | 17 ++- apps/web/src/api/counts/mock.ts | 6 +- .../src/components/counts/box-check-page.tsx | 5 +- apps/web/src/components/counts/count-page.tsx | 23 ++- .../web/src/components/counts/counts-list.tsx | 131 ++++++++++-------- apps/web/src/components/kits/kit-section.tsx | 5 +- apps/web/src/components/page.tsx | 9 +- .../src/components/schedules/schedule-row.tsx | 9 +- .../src/test/screens/counts-module.test.tsx | 95 ++++++++++++- apps/web/src/test/screens/kits.test.tsx | 23 +++ 34 files changed, 544 insertions(+), 108 deletions(-) diff --git a/apps/docs/src/content/docs/ar/users/counts.md b/apps/docs/src/content/docs/ar/users/counts.md index 9a288eab..8aaa1cb9 100644 --- a/apps/docs/src/content/docs/ar/users/counts.md +++ b/apps/docs/src/content/docs/ar/users/counts.md @@ -67,4 +67,4 @@ description: "يُحفظ كل عدّ وفحص للصناديق، مع ما وُ ## إيقاف العدّ -العدّ وحدة مفعّلة في كل قالب جاهز. يوقفها المالك أو المشرف من **إعدادات الموقع → ما الذي تتابعه** (**عمليات العدّ**). حين تتوقف في موقع، لا تظهر في أماكنه **عُدّ هنا** ولا **عُدّ بانتظام** ولا **عمليات العدّ السابقة**، ولا **عُدّ هذا الصندوق** في صناديقه، ولا **عُدّ هذه الغرفة** في غرفه على الخريطة، ولا يعرض **ابدأ جلسة** العدّ فيه. وتختفي **عمليات العدّ** من الأدوات متى توقفت في كل مواقعك. وتتوقف تذكيرات العدّ المخطط له. أما **فحص الصندوق** فيبقى. لا يُحذف شيء: أعِد تفعيلها تجد كل عدّ في مكانه. +العدّ وحدة مفعّلة في كل قالب جاهز. يوقفها المالك أو المشرف من **إعدادات الموقع → ما الذي تتابعه** (**عمليات العدّ**). حين تتوقف في موقع، لا تظهر في أماكنه **عُدّ هنا** ولا **عُدّ بانتظام** ولا **عمليات العدّ السابقة**، ولا **عُدّ هذا الصندوق** في صناديقه، ولا **عُدّ هذه الغرفة** في غرفه على الخريطة، ولا يعرض **ابدأ جلسة** العدّ فيه. وتختفي **عمليات العدّ** من الأدوات متى توقفت في كل مواقعك. ولا يظهر **افحص هذا الطقم** في الأطقم. وتتوقف تذكيرات العدّ المخطط له: يمكن إيقافه مؤقتًا واستئنافه وحذفه، لا إتمامه ولا تأجيله ولا تخطّيه. أما **فحص الصندوق** فيبقى، وتعرض قائمة عمليات العدّ في الموقع فحوص الصناديق وحدها. لا يُحذف شيء: أعِد تفعيلها تجد كل عدّ في مكانه. diff --git a/apps/docs/src/content/docs/ar/users/kits.md b/apps/docs/src/content/docs/ar/users/kits.md index 20d7fd53..98f6b63b 100644 --- a/apps/docs/src/content/docs/ar/users/kits.md +++ b/apps/docs/src/content/docs/ar/users/kits.md @@ -47,7 +47,7 @@ Kept يذكرك قبل يومين من موعد العودة، وحين تتأخ - **الأجزاء → اجعله طقمًا** على حاوية: محتوياتها محدّدة كأجزاء؛ أضف المزيد بالبحث. الشيء جزء من طقم واحد على الأكثر، في موقع الطقم. - تبويب **الأجزاء** يقول **إنجاز**، "جزآن غير موجودين في الطقم" أو **مع سام منذ ٣ تشرين الأول**، وكل جزء: في الطقم، في مكان آخر (مع **أرجعه**)، مفقود، مع شخص، أو في المهملات. -- **افحص هذا الطقم** يعدّه بجلسة [مسح](/ar/users/scanning-sessions/): قائمته هي الأجزاء، أينما كانوا. +- **افحص هذا الطقم** يعدّه بجلسة [مسح](/ar/users/scanning-sessions/): قائمته هي الأجزاء، أينما كانوا. ويظهر ما دام العدّ مفعّلًا في الموقع. - **عدّل الأجزاء** و **لم يعد طقمًا** يمكن التراجع عنهما لمدة ٧ أيام. - البحث والقوائم تصفّى بـ **الأطقم** و **أطقم ناقصة**. diff --git a/apps/docs/src/content/docs/de/users/counts.md b/apps/docs/src/content/docs/de/users/counts.md index 81ded500..e097540a 100644 --- a/apps/docs/src/content/docs/de/users/counts.md +++ b/apps/docs/src/content/docs/de/users/counts.md @@ -96,5 +96,7 @@ unter **Standorteinstellungen → Was erfasst wird** (**Zählungen**) aus. Sind aus, haben seine Orte kein **Hier zählen**, **Regelmäßig zählen** oder **Frühere Zählungen**, seine Kisten kein **Diese Kiste zählen**, seine Räume auf der Karte kein **Diesen Raum zählen**, und **Sitzung starten** bietet dort kein Zählen an. **Zählungen** unter Werkzeuge verschwindet, sobald -sie an allen deinen Standorten aus sind. Geplante Zählungen erinnern nicht mehr. Der -**Kistencheck** bleibt. Nichts wird gelöscht: wieder eingeschaltet, ist jede Zählung wieder da. +sie an allen deinen Standorten aus sind. Sets haben kein **Dieses Set prüfen**. Geplante +Zählungen erinnern nicht mehr: sie lassen sich pausieren, fortsetzen oder löschen, aber nicht +erledigen, zurückstellen oder überspringen. Der **Kistencheck** bleibt, und die Zählungsliste des +Standorts zeigt nur seine Kistenchecks. Nichts wird gelöscht: wieder eingeschaltet, ist jede Zählung wieder da. diff --git a/apps/docs/src/content/docs/de/users/kits.md b/apps/docs/src/content/docs/de/users/kits.md index aade04d4..5bb56527 100644 --- a/apps/docs/src/content/docs/de/users/kits.md +++ b/apps/docs/src/content/docs/de/users/kits.md @@ -47,7 +47,7 @@ Ein **Gesetzt** ist ein Behälter mit einer Teileliste: was darin sein sollte. E - **Teile → Zu einem Set machen** auf einem Behälter: sein Inhalt wird als Teile angehakt; mehr über Suche hinzufügen. Ein Teil gehört zu höchstens einem Set, am Standort des Sets. - Der **Teile**-Tab sagt **Erledigen**, „2 Teile nicht im Set“ oder **Mit Sam seit 3. Okt.**, und jedes Teil: Im Set, Sonstwo (mit **Zurücklegen**), Vermisst, Bei jemandem, oder Im Papierkorb. -- **Dieses Set prüfen** zählt es mit einer [Scan-Session](/de/users/scanning-sessions/): ihre Liste sind die Teile, egal wo sie sind. +- **Dieses Set prüfen** zählt es mit einer [Scan-Session](/de/users/scanning-sessions/): ihre Liste sind die Teile, egal wo sie sind. Es erscheint, solange Zählungen am Standort eingeschaltet sind. - **Teile bearbeiten** und **Kein Set mehr** können für 7 Tage rückgängig gemacht werden. - Suche und Listen filtern nach **Sets** und **Unvollständige Sets**. diff --git a/apps/docs/src/content/docs/es/users/counts.md b/apps/docs/src/content/docs/es/users/counts.md index c670f0a0..002493fc 100644 --- a/apps/docs/src/content/docs/es/users/counts.md +++ b/apps/docs/src/content/docs/es/users/counts.md @@ -72,5 +72,7 @@ en **Configuración de ubicación → Qué seguir** (**Conteos**). Desactivado e lugares no tienen **Contar aquí**, **Contar regularmente** ni **Conteos pasados**, sus cajas no tienen **Contar esta caja**, sus habitaciones en el mapa no tienen **Contar esta habitación**, e **Iniciar una sesión** no ofrece contar allí. **Conteos** en Herramientas desaparece cuando está -desactivado en todas tus ubicaciones. Los conteos planificados dejan de recordarse. La **Revisión -de caja** se queda. No se borra nada: al volver a activarlo, cada conteo sigue ahí. +desactivado en todas tus ubicaciones. Los kits no tienen **Revisar este kit**. Los conteos +planificados dejan de enviar recordatorios: se pueden pausar, reanudar o eliminar, pero no +completar, posponer ni saltar. La **Revisión de caja** se queda, y la lista de conteos de la +ubicación muestra solo sus revisiones de caja. No se borra nada: al volver a activarlo, cada conteo sigue ahí. diff --git a/apps/docs/src/content/docs/es/users/kits.md b/apps/docs/src/content/docs/es/users/kits.md index 3be19621..46ed8365 100644 --- a/apps/docs/src/content/docs/es/users/kits.md +++ b/apps/docs/src/content/docs/es/users/kits.md @@ -66,7 +66,8 @@ pieza, préstarla o perderla, y el kit sigue sabiendo. desde el 3 oct**, y cada pieza: En el kit, En otro sitio (con **Devolver**), Falta, Con alguien, o En la papelera. - **Revisar este kit** lo cuenta con una [sesión de escaneo](/es/users/scanning-sessions/): - su lista son las piezas, estén donde estén. + su lista son las piezas, estén donde estén. Aparece mientras los conteos estén activos en la + ubicación. - **Editar piezas** y **Ya no es un kit** se pueden deshacer durante 7 días. - La búsqueda y las listas filtran por **Kits** y **Kits incompletos**. diff --git a/apps/docs/src/content/docs/fr/users/counts.md b/apps/docs/src/content/docs/fr/users/counts.md index bf5fb412..6f184632 100644 --- a/apps/docs/src/content/docs/fr/users/counts.md +++ b/apps/docs/src/content/docs/fr/users/counts.md @@ -72,5 +72,7 @@ désactive dans **Paramètres du lieu → Ce que vous suivez** (**Comptages**). ses emplacements n'ont plus **Compter ici**, **Compter régulièrement** ni **Comptages passés**, ses boîtes plus **Compter cette boîte**, ses pièces sur la carte plus **Compter cette pièce**, et **Démarrer une session** n'y propose plus de comptage. **Comptages** sous Outils disparaît dès -qu'il est désactivé dans tous vos lieux. Les comptages planifiés ne rappellent plus rien. -**Vérifier une boîte** reste. Rien n'est supprimé : réactivé, chaque comptage est de nouveau là. +qu'il est désactivé dans tous vos lieux. Les kits n'ont plus **Vérifier ce kit**. Les comptages +planifiés ne rappellent plus rien : on peut les mettre en pause, les reprendre ou les supprimer, +mais pas les terminer, les reporter ni les sauter. **Vérifier une boîte** reste, et la liste des +comptages du lieu n'affiche que ses vérifications de boîtes. Rien n'est supprimé : réactivé, chaque comptage est de nouveau là. diff --git a/apps/docs/src/content/docs/fr/users/kits.md b/apps/docs/src/content/docs/fr/users/kits.md index d6e1ceab..4b31e775 100644 --- a/apps/docs/src/content/docs/fr/users/kits.md +++ b/apps/docs/src/content/docs/fr/users/kits.md @@ -47,7 +47,7 @@ Un **kit** est un conteneur avec une liste de pièces : ce qui devrait s'y trouv - **Pièces → En faire un kit** sur un conteneur : son contenu est coché comme pièces ; ajoutez-en d'autres par recherche. Un objet peut faire partie d'au plus un kit, dans le lieu du kit. - L'onglet **Pièces** affiche **Terminer**, « 2 pièces hors du kit » ou **Avec Sam depuis le 3 oct**, et chaque pièce : Dans le kit, Ailleurs (avec **Remettre**), Manquant, Chez quelqu'un, ou Dans la corbeille. -- **Vérifier ce kit** la compte avec une [session de numérisation](/fr/users/scanning-sessions/) : sa liste est les pièces, où qu'elles soient. +- **Vérifier ce kit** la compte avec une [session de numérisation](/fr/users/scanning-sessions/) : sa liste est les pièces, où qu'elles soient. Il s'affiche tant que les comptages sont activés dans le lieu. - **Modifier les pièces** et **Plus un kit** peuvent être annulés pendant 7 jours. - Recherche et listes filtrent par **Kits** et **Kits incomplets**. diff --git a/apps/docs/src/content/docs/it/users/counts.md b/apps/docs/src/content/docs/it/users/counts.md index 0cdc1a48..204ff10e 100644 --- a/apps/docs/src/content/docs/it/users/counts.md +++ b/apps/docs/src/content/docs/it/users/counts.md @@ -72,5 +72,7 @@ Conteggi è un modulo, attivo in ogni preset. Un proprietario o un amministrator non hanno più **Conta qui**, **Conta regolarmente** né **Conteggi passati**, le sue scatole non hanno **Conta questa scatola**, le sue stanze sulla mappa non hanno **Conta questa stanza**, e **Avvia una sessione** non vi propone il conteggio. **Conteggi** sotto Strumenti sparisce quando è -disattivato in tutti i tuoi luoghi. I conteggi pianificati smettono di ricordarsi. Il **Controllo -scatola** resta. Non si cancella nulla: riattivato, ogni conteggio è di nuovo lì. +disattivato in tutti i tuoi luoghi. I kit non hanno **Controlla questo kit**. I conteggi +pianificati non mandano più promemoria: si possono mettere in pausa, riprendere o eliminare, ma non +completare, rimandare o saltare. Il **Controllo scatola** resta, e l'elenco dei conteggi del luogo +mostra solo i suoi controlli scatola. Non si cancella nulla: riattivato, ogni conteggio è di nuovo lì. diff --git a/apps/docs/src/content/docs/it/users/kits.md b/apps/docs/src/content/docs/it/users/kits.md index f9c44c66..23c4825a 100644 --- a/apps/docs/src/content/docs/it/users/kits.md +++ b/apps/docs/src/content/docs/it/users/kits.md @@ -48,7 +48,7 @@ Un **kit** è un contenitore con un elenco di parti: ciò che dovrebbe esserci. - **Parti → Rendilo un kit** su un contenitore: i suoi contenuti sono spuntati come parti; aggiungine altre tramite ricerca. - Una cosa è parte di al massimo un kit, nel luogo del kit. - La scheda **Parti** dice **Completo**, "2 parti non nel kit" oppure **Con Sam dal 3 Oct**, e ogni parte: Nel kit, Altrove (con **Rimetti**), Mancante, Con qualcuno, oppure Nel cestino. -- **Controlla questo kit** lo conta con una [sessione di scansione](/it/users/scanning-sessions/): il suo elenco è composto dalle parti, ovunque si trovino. +- **Controlla questo kit** lo conta con una [sessione di scansione](/it/users/scanning-sessions/): il suo elenco è composto dalle parti, ovunque si trovino. Compare finché i conteggi sono attivi nel luogo. - **Modifica le parti** e **Non più un kit** possono essere annullati per 7 giorni. - La ricerca e gli elenchi filtrano per **Kit** e **Kit incompleti**. diff --git a/apps/docs/src/content/docs/uk/users/counts.md b/apps/docs/src/content/docs/uk/users/counts.md index 8f2736ec..a0754f72 100644 --- a/apps/docs/src/content/docs/uk/users/counts.md +++ b/apps/docs/src/content/docs/uk/users/counts.md @@ -96,5 +96,7 @@ description: Кожен підрахунок і перевірка коробк місцях немає **Порахувати тут**, **Підраховувати регулярно** чи **Минулі підрахунки**, у коробках немає **Порахувати цю коробку**, у кімнатах на мапі немає **Порахувати цю кімнату**, а **Почати сеанс** не пропонує там підрахунку. **Підрахунки** в Інструментах зникають, коли модуль вимкнено в -усіх ваших локаціях. Заплановані підрахунки більше не нагадують. **Перевірка коробки** лишається. +усіх ваших локаціях. У наборах немає **Перевірити цей набір**. Заплановані підрахунки більше не +нагадують: їх можна призупинити, відновити чи видалити, але не виконати, відкласти чи пропустити. +**Перевірка коробки** лишається, а список підрахунків локації показує лише її перевірки коробок. Нічого не видаляється: після ввімкнення всі підрахунки знову на місці. diff --git a/apps/docs/src/content/docs/uk/users/kits.md b/apps/docs/src/content/docs/uk/users/kits.md index 3060e129..65daf621 100644 --- a/apps/docs/src/content/docs/uk/users/kits.md +++ b/apps/docs/src/content/docs/uk/users/kits.md @@ -47,7 +47,7 @@ Kept нагадує за два дні до терміну повернення, - **Частини → Зробити це набором** на контейнері: його вміст позначений як частини; додайте більше пошуком. Річ є частиною максимум одного набору, в локації набору. - Вкладка **Частини** каже **Завершити**, "2 частини не в наборі" або **З Семом з 3 жов**, і кожна частина: В наборі, Деінде (з **Повернути**), Пропала, У когось, або В Коші. -- **Перевірити цей набір** рахує його [скануванням сесії](/uk/users/scanning-sessions/): його список — це частини, де б вони не були. +- **Перевірити цей набір** рахує його [скануванням сесії](/uk/users/scanning-sessions/): його список — це частини, де б вони не були. Показується, поки в локації увімкнено підрахунки. - **Редагувати частини** і **Більше не набір** можна скасувати на 7 днів. - Пошук і списки фільтрують за **Набори** та **Неповні набори**. diff --git a/apps/docs/src/content/docs/users/counts.md b/apps/docs/src/content/docs/users/counts.md index 55902730..30ee0004 100644 --- a/apps/docs/src/content/docs/users/counts.md +++ b/apps/docs/src/content/docs/users/counts.md @@ -95,5 +95,7 @@ Counts is a module, on in every preset. An owner or an admin turns it off in **L → What to track** (**Counts**). Off in a location, its places have no **Count here**, **Count regularly** or **Past counts**, its boxes no **Count this box**, its rooms on the map no **Count this room**, and **Start a session** offers no count there. **Counts** under Tools goes once it -is off in all your locations. Planned counts stop reminding. **Box check** stays. Nothing is -deleted: turned back on, every count is there again. +is off in all your locations. Kits have no **Check this kit**. Planned counts stop reminding: +they can be paused, resumed or deleted, but not done, snoozed or skipped. **Box check** stays, +and the location's Counts list shows its box checks only. Nothing is deleted: turned back on, +every count is there again. diff --git a/apps/docs/src/content/docs/users/kits.md b/apps/docs/src/content/docs/users/kits.md index d7cf7f08..5a91c28c 100644 --- a/apps/docs/src/content/docs/users/kits.md +++ b/apps/docs/src/content/docs/users/kits.md @@ -60,7 +60,7 @@ it, and the kit still knows. - The **Parts** tab says **Complete**, "2 parts not in the kit" or **With Sam since 3 Oct**, and each part: In the kit, Elsewhere (with **Put back**), Missing, With someone, or In the Trash. - **Check this kit** counts it with a [scan session](/users/scanning-sessions/): its list is the - parts, wherever they are. + parts, wherever they are. It shows while Counts is on in the location. - **Edit parts** and **No longer a kit** can be undone for 7 days. - Search and lists filter by **Kits** and **Incomplete kits**. diff --git a/apps/docs/src/content/docs/vi/users/counts.md b/apps/docs/src/content/docs/vi/users/counts.md index 5061c1fe..91611c54 100644 --- a/apps/docs/src/content/docs/vi/users/counts.md +++ b/apps/docs/src/content/docs/vi/users/counts.md @@ -67,4 +67,4 @@ Trên trang của một vị trí, **Kiểm kê định kỳ** lên kế hoạch ## Tắt kiểm kê -Kiểm kê là một mô-đun, bật trong mọi mẫu cài đặt. Chủ sở hữu hoặc quản trị viên tắt nó trong **Cài đặt địa điểm → Theo dõi gì** (**Kiểm kê**). Khi tắt ở một địa điểm, các vị trí của nó không còn **Kiểm kê tại đây**, **Kiểm kê định kỳ** hay **Các lần kiểm kê trước**, các hộp không còn **Kiểm kê hộp này**, các phòng trên bản đồ không còn **Kiểm kê phòng này**, và **Bắt đầu phiên** không cho kiểm kê ở đó. **Kiểm kê** trong Công cụ biến mất khi nó tắt ở mọi địa điểm của bạn. Các lần kiểm kê dự định ngừng nhắc. **Kiểm tra hộp** vẫn còn. Không có gì bị xóa: bật lại, mọi lần kiểm kê vẫn còn đó. +Kiểm kê là một mô-đun, bật trong mọi mẫu cài đặt. Chủ sở hữu hoặc quản trị viên tắt nó trong **Cài đặt địa điểm → Theo dõi gì** (**Kiểm kê**). Khi tắt ở một địa điểm, các vị trí của nó không còn **Kiểm kê tại đây**, **Kiểm kê định kỳ** hay **Các lần kiểm kê trước**, các hộp không còn **Kiểm kê hộp này**, các phòng trên bản đồ không còn **Kiểm kê phòng này**, và **Bắt đầu phiên** không cho kiểm kê ở đó. **Kiểm kê** trong Công cụ biến mất khi nó tắt ở mọi địa điểm của bạn. Các bộ không còn **Kiểm tra bộ này**. Các lần kiểm kê dự định ngừng nhắc: có thể tạm dừng, tiếp tục hoặc xóa, nhưng không thể hoàn tất, hoãn hay bỏ qua. **Kiểm tra hộp** vẫn còn, và danh sách kiểm kê của địa điểm chỉ hiện các lần kiểm tra hộp. Không có gì bị xóa: bật lại, mọi lần kiểm kê vẫn còn đó. diff --git a/apps/docs/src/content/docs/vi/users/kits.md b/apps/docs/src/content/docs/vi/users/kits.md index 8376652c..16f765e9 100644 --- a/apps/docs/src/content/docs/vi/users/kits.md +++ b/apps/docs/src/content/docs/vi/users/kits.md @@ -60,7 +60,7 @@ ra, cho mượn nó hoặc mất nó, và bộ vẫn biết. - Tab **Bộ phận** nói **Hoàn thành**, "2 bộ phận không nằm trong bộ" hoặc **Đang ở chỗ Sam từ 3 thg 10**, và mỗi bộ phận: Trong bộ, Ở nơi khác (với **Đặt lại**), Còn thiếu, Ở chỗ ai đó, hoặc Trong Thùng rác. - **Kiểm tra bộ này** đếm nó bằng một [phiên quét](/vi/users/scanning-sessions/): danh sách của nó là - các bộ phận, bất kể chúng ở đâu. + các bộ phận, bất kể chúng ở đâu. Nó hiện khi Kiểm kê đang bật ở địa điểm. - **Sửa bộ phận** và **Không còn là bộ** có thể hoàn tác trong 7 ngày. - Tìm kiếm và danh sách lọc theo **Các bộ** và **Các bộ chưa đầy đủ**. diff --git a/apps/server/migrations/0177_counts_module.sql b/apps/server/migrations/0177_counts_module.sql index 34bd48c7..bb4f33bf 100644 --- a/apps/server/migrations/0177_counts_module.sql +++ b/apps/server/migrations/0177_counts_module.sql @@ -8,8 +8,12 @@ -- 2. The agenda: a planned count (a schedule of kind `count`) is on it only while Counts is on -- too, so with Counts off it reminds no one and leaves Home's Needs you. The rest of the view is -- 0152's. --- Additive: both are CREATE OR REPLACE with the same signature and columns; the release before --- this one never asks about 'counts', and a planned count it reads stays on its agenda. +-- 3. Every location gets its Counts switch as a row, on: an export names the modules a location +-- has from these rows, so without one an export, imported, would turn Counts off. A location +-- made from now on gets every module's row when it is created. +-- Additive: 1 and 2 are CREATE OR REPLACE with the same signature and columns, and 3 only adds +-- rows; the release before this one never asks about 'counts' (an unknown module id is ignored), +-- and a planned count it reads stays on its agenda. CREATE OR REPLACE FUNCTION kept.module_enabled(p_location uuid, p_module text) RETURNS boolean LANGUAGE sql STABLE SET search_path = pg_catalog, public AS $$ @@ -171,3 +175,7 @@ SELECT 'stock', r.id, r.location_id, r.thing_id, NULL::uuid, 'due', JOIN public.things t ON t.id = r.thing_id WHERE loc.on_consumables AND r.low_since IS NOT NULL AND t.quantity < r.min_quantity AND t.deleted_at IS NULL AND t.lifecycle = 'in_use'; +--> statement-breakpoint +INSERT INTO public.location_modules (location_id, module, enabled, enabled_at) +SELECT l.id, 'counts', true, now() FROM public.locations l +ON CONFLICT (location_id, module) DO NOTHING; diff --git a/apps/server/src/exports/data.ts b/apps/server/src/exports/data.ts index 8d537ffe..952cdde3 100644 --- a/apps/server/src/exports/data.ts +++ b/apps/server/src/exports/data.ts @@ -1,3 +1,4 @@ +import { MODULE_IDS } from '@kept/shared'; import type pg from 'pg'; import { type EntityDef, type Field, fieldsOf } from './registry.js'; @@ -69,6 +70,9 @@ export function pageQuery(def: EntityDef, ctx: ReadContext, after: boolean): Bui if (def.entity === 'location') { select.push(`ARRAY(SELECT m.module FROM public.location_modules m WHERE m.location_id = x.id AND m.enabled ORDER BY m.module) AS "modules"`); + // Every module this server has (#506): the importer then knows a module missing from + // `modules` is off, not one this export never heard of. The ids are the registry's own. + select.push(`ARRAY[${MODULE_IDS.map((m) => `'${m}'`).join(', ')}]::text[] AS "modulesKnown"`); } if (def.entity === 'types') select.push(`x.owner_account_id IS NULL AS "builtin"`); def.key.forEach((k, i) => { @@ -110,6 +114,7 @@ function toWire(row: Record, built: Built, showMoney: boolean): out[f.name] = v instanceof Date ? v.toISOString() : (v ?? null); } if (row.modules !== undefined) out.modules = row.modules; + if (row.modulesKnown !== undefined) out.modulesKnown = row.modulesKnown; if (row.builtin !== undefined) out.builtin = row.builtin; if (!showMoney && built.money.length > 0) out.moneyHidden = true; return out; diff --git a/apps/server/src/exports/format.ts b/apps/server/src/exports/format.ts index a85839aa..faa68ea6 100644 --- a/apps/server/src/exports/format.ts +++ b/apps/server/src/exports/format.ts @@ -48,7 +48,11 @@ function schemaOf(def: EntityDef): z.ZodObject { shape[f.name] = KIND_SCHEMA[f.kind].nullable().optional(); } if (def.money?.length) shape.moneyHidden = z.literal(true).optional(); - if (def.entity === 'location') shape.modules = z.array(z.string()); + if (def.entity === 'location') { + shape.modules = z.array(z.string()); + // #506: the modules the exporting server had. Absent from an export made before Counts. + shape.modulesKnown = z.array(z.string()).optional(); + } if (def.entity === 'types') shape.builtin = z.boolean(); // An export without secrets names the secret fields whose value it left out (exports/withheld.ts). if (def.entity === 'things' || def.entity === 'places') { diff --git a/apps/server/src/imports/kept/apply.ts b/apps/server/src/imports/kept/apply.ts index 6a4a358a..1fe525e2 100644 --- a/apps/server/src/imports/kept/apply.ts +++ b/apps/server/src/imports/kept/apply.ts @@ -1,4 +1,4 @@ -import { type ExportEntity, type ImportEntityType, MODULE_IDS } from '@kept/shared'; +import { type ExportEntity, type ImportEntityType, MODULE_IDS, type ModuleId } from '@kept/shared'; import type pg from 'pg'; import { type EntityDef, entityDef, type Field, fieldsOf } from '../../exports/registry.js'; import { adoptCode, type CodeOutcome, type ImportedCode } from './codes.js'; @@ -451,6 +451,9 @@ async function applyPersonContact(c: ApplyCtx, def: EntityDef, row: Row): Promis return writeGeneric(c, def, row); } +/** Modules added after exports began, before exports named the modules they knew (#506). */ +const MODULES_SINCE_EXPORTS_KNEW: readonly ModuleId[] = ['counts']; + /** The location row: its settings onto the new location (what kept_app may change), and its * modules (the row's `modules`). Name, kind, timezone and currency were chosen with the target. */ async function applyLocation(c: ApplyCtx, def: EntityDef, row: Row): Promise { @@ -485,9 +488,15 @@ async function applyLocation(c: ApplyCtx, def: EntityDef, row: Row): Promise 0) { await c.client.query(`UPDATE public.locations SET ${sets.join(', ')} WHERE id = $1`, vals); } - const modules = Array.isArray(row.modules) - ? row.modules.filter((m): m is string => (MODULE_IDS as readonly string[]).includes(String(m))) - : null; + const ours = (list: unknown[]) => + list.filter((m): m is string => (MODULE_IDS as readonly string[]).includes(String(m))); + const modules = Array.isArray(row.modules) ? ours(row.modules) : null; + // Only the modules the export knew about are set (#506): one it never heard of keeps the + // target's own switch (or its preset). An export made before `modulesKnown` knew every module + // but Counts. + const known = Array.isArray(row.modulesKnown) + ? ours(row.modulesKnown) + : MODULE_IDS.filter((m) => !MODULES_SINCE_EXPORTS_KNEW.includes(m)); if (modules) { await c.client.query( `INSERT INTO public.location_modules (location_id, module, enabled, enabled_at) @@ -498,7 +507,7 @@ async function applyLocation(c: ApplyCtx, def: EntityDef, row: Row): Promise { expect(back?.deleted).toBe(false); }); }); + +describe('modules (#506)', () => { + const countsOn = async (locationId: string) => + (await own<{ on: boolean }>(db, `SELECT kept.module_on($1, 'counts') AS on`, [locationId]))[0] + ?.on; + const setCounts = (locationId: string, enabled: boolean) => + own( + db, + `INSERT INTO public.location_modules (location_id, module, enabled, enabled_at) + VALUES ($1, 'counts', $2, CASE WHEN $2 THEN now() END) + ON CONFLICT (location_id, module) DO UPDATE SET enabled = excluded.enabled`, + [locationId, enabled], + ); + const importInto = async (forged: Awaited>, name: string) => { + const target = await newTarget(name); + return { target, run: () => runInto(forged, target) }; + }; + async function runInto(forged: Awaited>, target: Loc) { + const runId = await stageKeptRun(db, files, forged, { + userId: alfred.userId, + locationId: target.id, + }); + await runKeptImport(deps(), scopeOf(alfred), runId); + expect(await runOf(runId)).toMatchObject({ status: 'done', error: null }); + } + + it('carries Counts off, and Counts on, to the location it is imported into', async () => { + await setCounts(home.id, false); + try { + const off = await forgeKeptExport(db, { userId: ibrahim.userId, locationId: home.id, files }); + try { + const { target, run } = await importInto(off, 'Home, counts off'); + expect(await countsOn(target.id)).toBe(true); + await run(); + expect(await countsOn(target.id)).toBe(false); + } finally { + await off.cleanup(); + } + } finally { + await setCounts(home.id, true); + } + const on = await forgeKeptExport(db, { userId: ibrahim.userId, locationId: home.id, files }); + try { + const { target, run } = await importInto(on, 'Home, counts on'); + await setCounts(target.id, false); + await run(); + expect(await countsOn(target.id)).toBe(true); + } finally { + await on.cleanup(); + } + }); + + it('leaves Counts as the target has it when the export was made before Counts was a module', async () => { + // As Kept before #506 wrote it: the modules on, with no `counts` and no `modulesKnown`. + const older = await forgeKeptExport(db, { + userId: ibrahim.userId, + locationId: home.id, + files, + mutate: (m, entries) => { + m.location.modules = m.location.modules.filter((x) => x !== 'counts'); + const name = EXPORT_PATHS.data('location'); + const lines = (entries.get(name)?.toString('utf8') ?? '').split('\n').filter(Boolean); + const out = lines.map((l) => { + const row = JSON.parse(l) as { modules?: string[]; modulesKnown?: string[] }; + row.modules = (row.modules ?? []).filter((x) => x !== 'counts'); + delete row.modulesKnown; + return `${JSON.stringify(row)}\n`; + }); + entries.set(name, Buffer.from(out.join(''))); + }, + }); + try { + const kept = await importInto(older, 'Home, before counts'); + await kept.run(); + expect(await countsOn(kept.target.id)).toBe(true); + + const off = await importInto(older, 'Home, before counts, off'); + await setCounts(off.target.id, false); + await off.run(); + expect(await countsOn(off.target.id)).toBe(false); + // The modules it did know about still come across: Money is on in the export. + expect( + ( + await own<{ on: boolean }>(db, `SELECT kept.module_on($1, 'money') AS on`, [ + kept.target.id, + ]) + )[0]?.on, + ).toBe(true); + } finally { + await older.cleanup(); + } + }); +}); diff --git a/apps/server/src/schedules/service.ts b/apps/server/src/schedules/service.ts index ac363ff1..a0dd2caa 100644 --- a/apps/server/src/schedules/service.ts +++ b/apps/server/src/schedules/service.ts @@ -77,6 +77,17 @@ export async function requireModule( } } +/** + * A planned count (kind `count`) is done, snoozed or skipped only while Counts is on in its + * location (#506): 409 `module_off` otherwise. Pausing, resuming and deleting it don't ask. + */ +export async function requireCountsFor( + ctx: Pick, + row: { kind: string; location_id: string }, +): Promise { + if (row.kind === 'count') await requireModule(ctx, row.location_id, 'counts', 'write'); +} + /** Today in the location's zone, `YYYY-MM-DD` ("today" is the location's). */ export async function todayIn(client: pg.ClientBase, locationId: string): Promise { const { rows } = await client.query<{ today: string }>( @@ -544,7 +555,8 @@ export async function updateSchedule( meterId: meterId ? meterId.toLowerCase() : null, dueOn: pick('dueOn', before.due_on), }); - if (before.kind === 'count') await requireModule(ctx, before.location_id, 'counts', 'write'); + // With Counts off a planned count can still be paused or resumed (#506); anything else waits. + if (fields.some((f) => f !== 'active')) await requireCountsFor(ctx, before); if (before.kind === 'count' && pick('active', before.active)) { await checkCount( client, @@ -615,6 +627,7 @@ export async function snoozeSchedule( ): Promise { const { client } = ctx; const before = await writableSchedule(ctx, id); + await requireCountsFor(ctx, before); await requireVersion(client, before, expected, Object.keys(body)); await requireNotDone(client, before); let untilDate: string | null = null; @@ -671,6 +684,7 @@ export async function unsnoozeSchedule(ctx: Ctx, id: string, expected: number): export async function skipSchedule(ctx: Ctx, id: string, expected: number): Promise { const { client } = ctx; const before = await writableSchedule(ctx, id); + await requireCountsFor(ctx, before); await requireVersion(client, before, expected, ['skipNext']); if (before.every_months === null && before.every_units === null) { throw invalid('A one-off date has no interval to skip; snooze it or change the date.'); diff --git a/apps/server/src/schedules/services.ts b/apps/server/src/schedules/services.ts index 4b61063c..7c6db44f 100644 --- a/apps/server/src/schedules/services.ts +++ b/apps/server/src/schedules/services.ts @@ -22,6 +22,7 @@ import { requireRole } from '../things/service.js'; import { moneyOff, requireCurrencies } from '../things/validate.js'; import { type Ctx, + requireCountsFor, requireModule, requireNotDone, type SubjectInput, @@ -562,6 +563,7 @@ export async function completeSchedule( body: CompleteInput, ): Promise<{ serviceRecord: ServiceRecord; schedule: Schedule }> { const s = await writableSchedule(ctx, id); + await requireCountsFor(ctx, s); if (s.row_version !== expected) { const by = await lastChangedBy(ctx.client, s.location_id, { type: 'schedule', id }); checkVersion( diff --git a/apps/server/src/sessions/counts-module.test.ts b/apps/server/src/sessions/counts-module.test.ts index 854f40b9..a43bbf95 100644 --- a/apps/server/src/sessions/counts-module.test.ts +++ b/apps/server/src/sessions/counts-module.test.ts @@ -5,6 +5,7 @@ import { type TestDb, testDb } from '../../test/db.js'; import { call, join, type Person, peopleApp, person } from '../../test/people.js'; import { op, sent } from '../../test/sync-ops.js'; import { + builtinType, createLocation, createThing, type Json, @@ -13,6 +14,7 @@ import { own, place, } from '../../test/things.js'; +import { runTool } from '../tools/context.js'; // Counts as a module a location can turn off (#506), like Labels and Lending: on in every preset, // so a location keeps its counts; off, the counts' reads are 404 `module_off`, their writes 409, @@ -126,7 +128,6 @@ describe('Counts, a module a location can turn off (#506)', () => { expect(view.modules).not.toContain('counts'); const off404 = { status: 404, code: 'module_off' }; - expect(await status(louis, `/api/v1/locations/${home.id}/counts`)).toEqual(off404); expect(await status(louis, `/api/v1/scan-sessions/${before.id}`)).toEqual(off404); expect(await status(louis, `/api/v1/scan-sessions/${before.id}/result`)).toEqual(off404); expect(await status(louis, `/api/v1/scan-sessions/${before.id}/result.csv`)).toEqual(off404); @@ -162,6 +163,36 @@ describe('Counts, a module a location can turn off (#506)', () => { expect((list.items as Json[]).map((x) => x.id)).toContain(before.id); }); + it('off: a box check still works, and the Counts list keeps the box checks, only them', async () => { + const box = await createThing(t, ibrahim, home, { + name: 'Crate042', + typeId: await builtinType(db, 'box_bin'), + }); + const cable = await createThing(t, ibrahim, home, { name: 'Cable', containerId: box.id }); + await switchCounts(false); + try { + const checkId = newId(); + ok( + await call(t, `/api/v1/things/${box.id}/box-check`, { + as: louis, + body: { id: checkId, lines: [{ thingId: cable.id, expectedQty: '1', foundQty: '1' }] }, + }), + ); + const list = ok(await call(t, `/api/v1/locations/${home.id}/counts`, { as: louis })); + const items = list.items as Json[]; + expect(items.map((x) => x.id)).toContain(checkId); + expect(new Set(items.map((x) => x.kind))).toEqual(new Set(['box_check'])); + // Asking for counts gets none either. + const counts = ok( + await call(t, `/api/v1/locations/${home.id}/counts?kind=count`, { as: louis }), + ); + expect((counts.items as Json[]).map((x) => x.kind)).not.toContain('count'); + expect((await status(louis, `/api/v1/box-checks/${checkId}`)).status).toBe(200); + } finally { + await switchCounts(true); + } + }); + it('off: a pack or a move is still a scan session of its own', async () => { const hall = await place(db, home, 'Hall'); const shelf = await place(db, home, 'Shelf'); @@ -226,3 +257,91 @@ describe('Counts, a module a location can turn off (#506)', () => { expect(await agendaHas(made.id as string)).toBe(true); }); }); + +describe('a planned count while Counts is off (#506)', () => { + const plan = async (name: string) => { + const loft = await place(db, home, name); + const made = ok( + await call(t, '/api/v1/schedules', { + as: ibrahim, + body: { + subject: { placeId: loft }, + name: `Count the ${name}`, + kind: 'count', + everyMonths: 6, + }, + }), + 201, + ); + return { id: made.id as string, version: () => versionOf(made.id as string) }; + }; + const versionOf = async (id: string) => + ( + await own<{ v: number }>(db, 'SELECT row_version AS v FROM public.schedules WHERE id = $1', [ + id, + ]) + )[0]?.v as number; + const write = async ( + method: 'DELETE' | 'PATCH' | 'POST', + url: string, + version: number, + body?: unknown, + ) => { + const res = await call(t, url, { + as: ibrahim, + method, + headers: { 'if-match': String(version) }, + ...(body === undefined ? {} : { body }), + }); + const code = res.headers['content-type']?.toString().includes('json') + ? (res.json() as { code?: string }).code + : undefined; + return { status: res.statusCode, code }; + }; + const tool = (name: 'complete_schedule' | 'snooze_schedule', args: Record) => + runTool( + { + deps: { pools: db.pools, jobs: null, files: null, providerResolved: async () => true }, + principal: { userId: ibrahim.userId, mfa: false, scope: 'write' }, + locale: 'en', + requestId: `counts-module-${name}`, + via: 'assistant', + }, + name, + args, + ) as Promise<{ error?: string }>; + const off409 = { status: 409, code: 'module_off' }; + + it('pauses, resumes and is deleted, but is not done, snoozed, skipped or changed', async () => { + const a = await plan('Box room'); + const b = await plan('Cellar store'); + await switchCounts(false); + try { + const url = `/api/v1/schedules/${a.id}`; + expect((await write('PATCH', url, await a.version(), { active: false })).status).toBe(200); + expect((await write('PATCH', url, await a.version(), { active: true })).status).toBe(200); + expect(await write('PATCH', url, await a.version(), { everyMonths: 3 })).toEqual(off409); + expect(await write('POST', `${url}/complete`, await a.version(), {})).toEqual(off409); + expect( + await write('POST', `${url}/snooze`, await a.version(), { untilDate: '2099-01-01' }), + ).toEqual(off409); + expect(await write('POST', `${url}/skip`, await a.version(), {})).toEqual(off409); + // Through MCP and the assistant: the same refusal, as any module that is off. + expect(await tool('complete_schedule', { schedule_id: a.id })).toMatchObject({ + error: 'tool_unavailable', + }); + expect( + await tool('snooze_schedule', { schedule_id: a.id, until_date: '2099-01-01' }), + ).toMatchObject({ error: 'tool_unavailable' }); + expect((await write('DELETE', `/api/v1/schedules/${b.id}`, await b.version())).status).toBe( + 204, + ); + } finally { + await switchCounts(true); + } + // Back on, it is done as before. + expect( + (await write('POST', `/api/v1/schedules/${a.id}/complete`, await a.version(), {})).status, + ).toBe(200); + }); +}); diff --git a/apps/server/src/sessions/counts.ts b/apps/server/src/sessions/counts.ts index bbec8eb7..f9d117bb 100644 --- a/apps/server/src/sessions/counts.ts +++ b/apps/server/src/sessions/counts.ts @@ -176,11 +176,17 @@ export async function requireCountsOn( locationId: string, mode: 'read' | 'write', ): Promise { + if (!(await countsAreOn(client, locationId))) + throw new AppError('module_off', mode === 'read' ? 404 : 409); +} + +/** Whether Counts is on in the location (#506). */ +export async function countsAreOn(client: pg.ClientBase, locationId: string): Promise { const { rows } = await client.query<{ on: boolean }>( `SELECT kept.module_on($1, 'counts') AS on`, [locationId], ); - if (rows[0]?.on !== true) throw new AppError('module_off', mode === 'read' ? 404 : 409); + return rows[0]?.on === true; } const UNDONE = (event: string, location: string) => @@ -201,7 +207,10 @@ const TARGET_JOINS = ` LEFT JOIN public.things tc ON tc.id = s.target_container_id JOIN public.locations l ON l.id = s.location_id`; -/** GET /api/v1/locations/:id/counts: finished counts and box checks, newest first. */ +/** + * GET /api/v1/locations/:id/counts: finished counts and box checks, newest first. With Counts off + * (#506) it lists the box checks only: a box check is not a count, and this is their one list. + */ export async function listCounts( client: pg.ClientBase, locationId: string, @@ -209,7 +218,7 @@ export async function listCounts( page: PageRequest<[string, string]>, ): Promise> { await requireVisible(client, locationId); - await requireCountsOn(client, locationId, 'read'); + const kind = (await countsAreOn(client, locationId)) ? (query.kind ?? null) : 'box_check'; const after = page.after; if (after && (typeof after[0] !== 'string' || typeof after[1] !== 'string')) throw invalid('The cursor is not valid; start again from the first page.'); @@ -266,7 +275,7 @@ export async function listCounts( LIMIT $11`, [ locationId, - query.kind ?? null, + kind, query.placeId?.toLowerCase() ?? null, query.by?.toLowerCase() ?? null, query.had ?? null, diff --git a/apps/web/src/api/counts/mock.ts b/apps/web/src/api/counts/mock.ts index b120a853..4d2ed3ee 100644 --- a/apps/web/src/api/counts/mock.ts +++ b/apps/web/src/api/counts/mock.ts @@ -3,6 +3,7 @@ * mock's scan sessions saved, and Try Kept's past ones), a box check's lines, and a shared count * (opened, found in progress, its lines so far). Viewers read everything. */ +import { moduleOn } from '../household/mock/db'; import { accessOf, livePlace, liveThing, now } from '../inventory/mock/db'; import { INV_IDS } from '../inventory/mock/fixtures'; import { IDS, type MockState } from '../mock/fixtures'; @@ -256,7 +257,8 @@ export function countsMockRoutes(state: MockState): MockRoute[] { const id = params.id ?? ''; if (!access().visible(id)) return notFound(); const q = (query.get('q') ?? '').toLocaleLowerCase(); - const kind = query.get('kind'); + // As the server: with Counts off (#506), the box checks only. + const kind = moduleOn(state, id, 'counts') ? query.get('kind') : 'box_check'; const had = query.get('had'); const by = query.get('by'); const placeId = query.get('placeId'); @@ -291,6 +293,8 @@ export function countsMockRoutes(state: MockState): MockRoute[] { if (gate) return gate; const r = store().results.find((x) => x.session.id === params.id); if (!r || !access().visible(r.session.locationId)) return notFound(); + if (!moduleOn(state, r.session.locationId, 'counts')) + return err(404, 'module_off', 'That feature is turned off for this location.'); return withCompare(store().results, r); }), diff --git a/apps/web/src/components/counts/box-check-page.tsx b/apps/web/src/components/counts/box-check-page.tsx index feb5dfc4..4ad69f15 100644 --- a/apps/web/src/components/counts/box-check-page.tsx +++ b/apps/web/src/components/counts/box-check-page.tsx @@ -56,7 +56,10 @@ export function BoxCheckPage({ id }: { id: string }) { ); }; return ( - +

{t`${found} of ${of}`}

diff --git a/apps/web/src/components/counts/count-page.tsx b/apps/web/src/components/counts/count-page.tsx index 02d1f489..0c453dbd 100644 --- a/apps/web/src/components/counts/count-page.tsx +++ b/apps/web/src/components/counts/count-page.tsx @@ -10,9 +10,21 @@ import { plural } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; import { Link } from '@tanstack/react-router'; import type { ReactNode } from 'react'; +import { ApiError } from '@/api/client'; import { useCountResult } from '@/api/counts/queries'; import type { CountResult, ResultLine } from '@/api/counts/types'; -import { ErrorState, List, LoadingRows, Notice, Page, Pill, Row, Section } from '@/components/page'; +import { QrIcon } from '@/components/icons'; +import { + EmptyState, + ErrorState, + List, + LoadingRows, + Notice, + Page, + Pill, + Row, + Section, +} from '@/components/page'; import { Button } from '@/components/ui/button'; import { canShareFiles, saveFile, shareFiles } from '@/lib/files'; import { useFormat } from '@/lib/format'; @@ -32,7 +44,14 @@ export function CountPage({ id }: { id: string }) { if (q.isError) return ( - void q.refetch()} /> + {/* Counts turned off in the count's location (#506): say so, not "Couldn't load". */} + {q.error instanceof ApiError && q.error.code === 'module_off' ? ( + } title={Counts are off}> + An owner or admin turns them on in a location's What to track. + + ) : ( + void q.refetch()} /> + )} ); return ; diff --git a/apps/web/src/components/counts/counts-list.tsx b/apps/web/src/components/counts/counts-list.tsx index f544ea0b..7f60ade1 100644 --- a/apps/web/src/components/counts/counts-list.tsx +++ b/apps/web/src/components/counts/counts-list.tsx @@ -3,8 +3,8 @@ * standard (surface `counts`: search by what was counted; filters kind, place, who, had missing * and date; grouped by place, person or month; paged; all in the URL). One location at a time: * the location filter picks it (the first one by default). Each row opens the count's page. - * Counts is a module (#506): only the locations with it on are offered, and with it off in every - * one the list says so. + * Counts is a module (#506): it opens on a location with Counts on, and a location with it off + * lists its box checks only (a box check is not a count, and this is their one list). */ import { plural } from '@lingui/core/macro'; import { Trans, useLingui } from '@lingui/react/macro'; @@ -17,7 +17,7 @@ import { useFilterRegistry } from '@/components/filters/registry'; import type { FilterDef } from '@/components/filters/types'; import { ActivityIcon, AlertIcon, BoxIcon, QrIcon } from '@/components/icons'; import { ListSurface } from '@/components/list-surface'; -import { EmptyState, IconTile, Pill, Row } from '@/components/page'; +import { EmptyState, IconTile, Notice, Pill, Row } from '@/components/page'; import { accessOf } from '@/components/schedules/access'; import { oneValue } from '@/components/schedules/list-query'; import { useFormat } from '@/lib/format'; @@ -31,10 +31,13 @@ export function CountsList() { const f = useFilterRegistry(); const locations = useLocationsInView(); const locationName = useLocationName(); - const all = (locations.data ?? []).filter((l) => accessOf(l).moduleOn('counts')); - const locationId = oneValue(list, 'location') ?? all[0]?.id ?? null; + const all = locations.data ?? []; + const countsOn = (id: string | null) => accessOf(all.find((l) => l.id === id)).moduleOn('counts'); + const locationId = + oneValue(list, 'location') ?? (all.find((l) => countsOn(l.id)) ?? all[0])?.id ?? null; + const boxChecksOnly = !!locations.data && !countsOn(locationId); - const kind = oneValue(list, 'kind'); + const kind = boxChecksOnly ? 'box_check' : oneValue(list, 'kind'); const params: CountsParams = { ...(list.q ? { q: list.q } : {}), ...(kind === 'count' || kind === 'box_check' ? { kind } : {}), @@ -59,20 +62,24 @@ export function CountsList() { }, ] : []), - { - key: 'kind', - label: t`Kind`, - icon: , - kind: 'single', - negatable: false, - values: { - from: 'static', - options: [ - { value: 'count', label: t`Counts` }, - { value: 'box_check', label: t`Box checks` }, - ], - }, - }, + ...(boxChecksOnly + ? [] + : [ + { + key: 'kind', + label: t`Kind`, + icon: , + kind: 'single' as const, + negatable: false, + values: { + from: 'static' as const, + options: [ + { value: 'count', label: t`Counts` }, + { value: 'box_check', label: t`Box checks` }, + ], + }, + }, + ]), { ...f.place(locationId ? [locationId] : []), kind: 'single', negatable: false }, { ...f.actor(locationId ? [locationId] : []), key: 'by', kind: 'single', negatable: false }, { @@ -85,46 +92,52 @@ export function CountsList() { f.date('when', t`Counted`), ]; - if (locations.data && all.length === 0) - return ( - } title={Counts are off}> - An owner or admin turns them on in a location's What to track. - - ); - return ( - - label={t`Counts`} - search={{ label: t`Search counts`, placeholder: t`Search by what was counted` }} - filters={filters} - surface="counts" - groups={[ - { value: 'none', label: t`None` }, - { value: 'place', label: t`Place`, short: t`by place` }, - { value: 'person', label: t`Person`, short: t`by person` }, - { value: 'month', label: t`Month`, short: t`by month` }, - ]} - query={query} - getKey={(c) => c.id} - groupOf={(c, by) => - by === 'place' - ? { key: c.target.id, label: {c.target.name} } - : by === 'person' - ? { key: c.by.id ?? '', label: {c.by.displayName} } - : by === 'month' - ? { key: c.at.slice(0, 7), label: } - : null - } - renderRow={(c) => } - empty={ - } title={No counts yet}> - - Count a place or a box from Scan: every count and box check is kept here, with what was - found and what was missing. - - - } - /> +
+ {boxChecksOnly ? ( + + Counts are off here: this lists the box checks. + + ) : null} + + label={t`Counts`} + search={{ label: t`Search counts`, placeholder: t`Search by what was counted` }} + filters={filters} + surface="counts" + groups={[ + { value: 'none', label: t`None` }, + { value: 'place', label: t`Place`, short: t`by place` }, + { value: 'person', label: t`Person`, short: t`by person` }, + { value: 'month', label: t`Month`, short: t`by month` }, + ]} + query={query} + getKey={(c) => c.id} + groupOf={(c, by) => + by === 'place' + ? { key: c.target.id, label: {c.target.name} } + : by === 'person' + ? { key: c.by.id ?? '', label: {c.by.displayName} } + : by === 'month' + ? { key: c.at.slice(0, 7), label: } + : null + } + renderRow={(c) => } + empty={ + boxChecksOnly ? ( + } title={Counts are off}> + An owner or admin turns them on in a location's What to track. + + ) : ( + } title={No counts yet}> + + Count a place or a box from Scan: every count and box check is kept here, with what + was found and what was missing. + + + ) + } + /> +
); } diff --git a/apps/web/src/components/kits/kit-section.tsx b/apps/web/src/components/kits/kit-section.tsx index 889d5117..b3e62f06 100644 --- a/apps/web/src/components/kits/kit-section.tsx +++ b/apps/web/src/components/kits/kit-section.tsx @@ -82,7 +82,7 @@ export function KitSection() { } function KitParts({ kit, onEdit }: { kit: Kit; onEdit: () => void }) { - const { thing, can } = useThingCtx(); + const { thing, can, moduleOn } = useThingCtx(); const { t } = useLingui(); const fmt = useFormat(); const navigate = useNavigate(); @@ -199,7 +199,8 @@ function KitParts({ kit, onEdit }: { kit: Kit; onEdit: () => void }) { })}
- {can('things.mark-seen') || !edit ? ( + {/* A count of the kit: only while Counts is on in its location (#506). */} + {moduleOn('counts') && (can('things.mark-seen') || !edit) ? (
{canManage ? (
- {done ? null : ( + {done || countsOff ? null : (