diff --git a/package.json b/package.json index e58d025..e116f4e 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@tanstack/react-query": "^5.62.0", "@tanstack/react-query-devtools": "^5.62.0", "@tanstack/react-table": "^8.21.3", + "@types/pdfkit": "^0.17.6", "archiver": "^7.0.1", "better-auth": "^1.2.0", "bullmq": "^5.25.0", @@ -73,6 +74,7 @@ "nodemailer": "^6.9.0", "openai": "^6.27.0", "pdf-lib": "^1.17.1", + "pdfkit": "^0.18.0", "pino": "^9.5.0", "pino-pretty": "^13.0.0", "postgres": "^3.4.0", @@ -81,6 +83,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.54.0", "recharts": "^3.8.0", + "sharp": "^0.34.5", "socket.io": "^4.8.0", "socket.io-client": "^4.8.0", "sonner": "^1.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 161fe94..2366097 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/pdfkit': + specifier: ^0.17.6 + version: 0.17.6 archiver: specifier: ^7.0.1 version: 7.0.1 @@ -164,6 +167,9 @@ importers: pdf-lib: specifier: ^1.17.1 version: 1.17.1 + pdfkit: + specifier: ^0.18.0 + version: 0.18.0 pino: specifier: ^9.5.0 version: 9.14.0 @@ -188,6 +194,9 @@ importers: recharts: specifier: ^3.8.0 version: 3.8.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(redux@5.0.1) + sharp: + specifier: ^0.34.5 + version: 0.34.5 socket.io: specifier: ^4.8.0 version: 4.8.3 @@ -1153,64 +1162,138 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.33.5': resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.0.4': resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.4': resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1218,6 +1301,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1225,6 +1315,27 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1232,6 +1343,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1239,6 +1357,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1246,6 +1371,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1253,23 +1385,53 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.33.5': resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@ioredis/commands@1.5.0': resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==} @@ -1390,10 +1552,18 @@ packages: cpu: [x64] os: [win32] + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + '@noble/ciphers@2.1.1': resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@2.0.1': resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} @@ -2328,6 +2498,9 @@ packages: '@types/nodemailer@6.4.23': resolution: {integrity: sha512-aFV3/NsYFLSx9mbb5gtirBSXJnAlrusoKNuPbxsASWc7vrKLmIrTQRpdcxNcSFL3VW2A2XpeLEavwb2qMi6nlQ==} + '@types/pdfkit@0.17.6': + resolution: {integrity: sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -2776,6 +2949,10 @@ packages: bare-url@2.4.2: resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==} + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2893,6 +3070,9 @@ packages: browser-or-node@2.1.1: resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4134,6 +4314,9 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} + js-md5@0.8.3: + resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -4286,6 +4469,9 @@ packages: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4696,6 +4882,9 @@ packages: pdf-lib@1.17.1: resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} + pdfkit@0.18.0: + resolution: {integrity: sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==} + peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} @@ -4757,6 +4946,9 @@ packages: engines: {node: '>=18'} hasBin: true + png-js@1.1.0: + resolution: {integrity: sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -5386,6 +5578,10 @@ packages: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -6648,81 +6844,177 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.1.0': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + '@img/sharp-libvips-linux-arm@1.0.5': optional: true + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + '@img/sharp-libvips-linux-x64@1.0.4': optional: true + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + '@img/sharp-linux-arm@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.5 optional: true + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + '@img/sharp-linux-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.4 optional: true + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + '@img/sharp-wasm32@0.33.5': dependencies: '@emnapi/runtime': 1.9.0 optional: true + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + '@img/sharp-win32-ia32@0.33.5': optional: true + '@img/sharp-win32-ia32@0.34.5': + optional: true + '@img/sharp-win32-x64@0.33.5': optional: true + '@img/sharp-win32-x64@0.34.5': + optional: true + '@ioredis/commands@1.5.0': {} '@ioredis/commands@1.5.1': {} @@ -6816,8 +7108,12 @@ snapshots: '@next/swc-win32-x64-msvc@15.1.0': optional: true + '@noble/ciphers@1.3.0': {} + '@noble/ciphers@2.1.1': {} + '@noble/hashes@1.8.0': {} + '@noble/hashes@2.0.1': {} '@nodelib/fs.scandir@2.1.5': @@ -7747,6 +8043,10 @@ snapshots: dependencies: '@types/node': 22.19.15 + '@types/pdfkit@0.17.6': + dependencies: + '@types/node': 22.19.15 + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -8283,6 +8583,8 @@ snapshots: dependencies: bare-path: 3.0.0 + base64-js@0.0.8: {} + base64-js@1.5.1: {} base64id@2.0.0: {} @@ -8363,6 +8665,10 @@ snapshots: browser-or-node@2.1.1: {} + browserify-zlib@0.2.0: + dependencies: + pako: 1.0.11 + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.8 @@ -9774,6 +10080,8 @@ snapshots: js-cookie@3.0.5: {} + js-md5@0.8.3: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -9894,6 +10202,11 @@ snapshots: lilconfig@3.1.3: {} + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + lines-and-columns@1.2.4: {} linkify-it@5.0.0: @@ -10308,6 +10621,15 @@ snapshots: pako: 1.0.11 tslib: 1.14.1 + pdfkit@0.18.0: + dependencies: + '@noble/ciphers': 1.3.0 + '@noble/hashes': 1.8.0 + fontkit: 2.0.4 + js-md5: 0.8.3 + linebreak: 1.1.0 + png-js: 1.1.0 + peberminta@0.9.0: {} performance-now@2.1.0: {} @@ -10386,6 +10708,10 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + png-js@1.1.0: + dependencies: + browserify-zlib: 0.2.0 + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.8): @@ -11175,6 +11501,37 @@ snapshots: '@img/sharp-win32-x64': 0.33.5 optional: true + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 diff --git a/src/app/api/public/berths/route.ts b/src/app/api/public/berths/route.ts index 5557bb4..3db1863 100644 --- a/src/app/api/public/berths/route.ts +++ b/src/app/api/public/berths/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from 'next/server'; -import { and, eq, inArray, isNull, sql } from 'drizzle-orm'; +import { and, eq, inArray, isNull } from 'drizzle-orm'; import { db } from '@/lib/db'; import { ports } from '@/lib/db/schema/ports'; @@ -106,7 +106,12 @@ export async function GET(request: Request): Promise { // invalid data downstream. for (const row of list) { if (row.Status !== 'Available' && row.Status !== 'Under Offer' && row.Status !== 'Sold') { - logger.error({ row }, 'Public berth status out of range'); + // Log just the identifying fields - never the full berth row, which + // includes price + amenity columns that don't belong in error logs. + logger.error( + { berthId: row.Id, mooringNumber: row['Mooring Number'], status: row.Status }, + 'Public berth status out of range', + ); return NextResponse.json( { error: 'internal', detail: 'berth status enum drift' }, { status: 500 }, @@ -139,7 +144,3 @@ function emptyPageInfo() { isLastPage: true as const, }; } - -// Suppress the `sql` import unused-warning when no inline raw SQL appears -// further down (helper kept for future where-clause extensions). -void sql; diff --git a/src/app/api/v1/expenses/export/pdf/route.ts b/src/app/api/v1/expenses/export/pdf/route.ts index 8e1ad1d..124be20 100644 --- a/src/app/api/v1/expenses/export/pdf/route.ts +++ b/src/app/api/v1/expenses/export/pdf/route.ts @@ -2,21 +2,67 @@ import { NextResponse } from 'next/server'; import { withAuth, withPermission } from '@/lib/api/helpers'; import { errorResponse } from '@/lib/errors'; -import { exportPdf } from '@/lib/services/expense-export'; -import { listExpensesSchema } from '@/lib/validators/expenses'; +import { streamExpensePdf } from '@/lib/services/expense-pdf.service'; +import { exportExpensePdfSchema } from '@/lib/validators/expenses'; + +/** + * POST /api/v1/expenses/export/pdf + * + * Streams the expense report PDF directly to the client — body bytes + * leave the process as pdfkit writes them, so the route is safe for + * hundreds of expenses with full-resolution receipt images. See + * `expense-pdf.service.ts` for the memory-budget design. + * + * Request body shape (zod-validated): + * { + * expenseIds?: string[] // explicit selection (preferred) + * filter?: {...} // listExpenses-style filter when no ids + * options: { + * documentName, subheader?, groupBy, includeReceipts, + * includeReceiptContents, includeSummary, includeDetails, + * includeProcessingFee, targetCurrency, pageFormat, + * } + * } + * + * Response: `application/pdf` binary stream + Content-Disposition. + */ +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; export const POST = withAuth( - withPermission('expenses', 'view', async (req, ctx) => { + withPermission('expenses', 'export', async (req, ctx) => { try { const body = await req.json().catch(() => ({})); - const query = listExpensesSchema.parse(body); - const pdf = await exportPdf(ctx.portId, query); + const input = exportExpensePdfSchema.parse(body); - return new NextResponse(Buffer.from(pdf), { + const { stream, suggestedFilename } = await streamExpensePdf({ + portId: ctx.portId, + expenseIds: input.expenseIds, + filter: input.filter + ? { + dateFrom: input.filter.dateFrom ?? null, + dateTo: input.filter.dateTo ?? null, + category: input.filter.category ?? null, + paymentStatus: input.filter.paymentStatus ?? null, + payer: input.filter.payer ?? null, + includeArchived: input.filter.includeArchived ?? false, + } + : undefined, + options: input.options, + }); + + // NextResponse extends Response; passing a ReadableStream as the + // body keeps the streaming semantics. The wrapper's RouteHandler + // type expects NextResponse so we use it explicitly. + return new NextResponse(stream, { status: 200, headers: { 'Content-Type': 'application/pdf', - 'Content-Disposition': `attachment; filename="expenses-${Date.now()}.pdf"`, + 'Content-Disposition': `attachment; filename="${suggestedFilename}"`, + // The PDF is generated on the fly per-request and includes + // potentially-sensitive expense data; never cache. + 'Cache-Control': 'private, no-store, max-age=0', + 'X-Content-Type-Options': 'nosniff', }, }); } catch (error) { diff --git a/src/lib/db/migrations/0033_expense_no_receipt_acknowledged.sql b/src/lib/db/migrations/0033_expense_no_receipt_acknowledged.sql new file mode 100644 index 0000000..533e056 --- /dev/null +++ b/src/lib/db/migrations/0033_expense_no_receipt_acknowledged.sql @@ -0,0 +1 @@ +ALTER TABLE "expenses" ADD COLUMN "no_receipt_acknowledged" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/src/lib/db/migrations/meta/0033_snapshot.json b/src/lib/db/migrations/meta/0033_snapshot.json new file mode 100644 index 0000000..351d4f1 --- /dev/null +++ b/src/lib/db/migrations/meta/0033_snapshot.json @@ -0,0 +1,11490 @@ +{ + "id": "0b665d7f-cc91-4edc-ba33-af8963234da9", + "prevId": "5818fae9-5cfb-4c66-9041-f8f0138fbf80", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_usage_ledger": { + "name": "ai_usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ai_usage_port_created": { + "name": "idx_ai_usage_port_created", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_usage_port_feature_created": { + "name": "idx_ai_usage_port_feature_created", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_usage_ledger_port_id_ports_id_fk": { + "name": "ai_usage_ledger_port_id_ports_id_fk", + "tableFrom": "ai_usage_ledger", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_usage_ledger_user_id_user_id_fk": { + "name": "ai_usage_ledger_user_id_user_id_fk", + "tableFrom": "ai_usage_ledger", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_maintenance_log": { + "name": "berth_maintenance_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cost_currency": { + "name": "cost_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'USD'" + }, + "responsible_party": { + "name": "responsible_party", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "performed_date": { + "name": "performed_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "photo_file_ids": { + "name": "photo_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_bml_berth": { + "name": "idx_bml_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_bml_port": { + "name": "idx_bml_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_maintenance_log_berth_id_berths_id_fk": { + "name": "berth_maintenance_log_berth_id_berths_id_fk", + "tableFrom": "berth_maintenance_log", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "berth_maintenance_log_port_id_ports_id_fk": { + "name": "berth_maintenance_log_port_id_ports_id_fk", + "tableFrom": "berth_maintenance_log", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_map_data": { + "name": "berth_map_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "svg_path": { + "name": "svg_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "x": { + "name": "x", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "y": { + "name": "y", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "transform": { + "name": "transform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "font_size": { + "name": "font_size", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "extra_data": { + "name": "extra_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "berth_map_data_berth_id_idx": { + "name": "berth_map_data_berth_id_idx", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_map_data_berth_id_berths_id_fk": { + "name": "berth_map_data_berth_id_berths_id_fk", + "tableFrom": "berth_map_data", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "berth_map_data_berth_id_unique": { + "name": "berth_map_data_berth_id_unique", + "nullsNotDistinct": false, + "columns": ["berth_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_pdf_versions": { + "name": "berth_pdf_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content_sha256": { + "name": "content_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "download_url_expires_at": { + "name": "download_url_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "parse_results": { + "name": "parse_results", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "berth_pdf_versions_berth_version_idx": { + "name": "berth_pdf_versions_berth_version_idx", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_bpv_berth": { + "name": "idx_bpv_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_pdf_versions_berth_id_berths_id_fk": { + "name": "berth_pdf_versions_berth_id_berths_id_fk", + "tableFrom": "berth_pdf_versions", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_recommendations": { + "name": "berth_recommendations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_score": { + "name": "match_score", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "match_reasons": { + "name": "match_reasons", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ai'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "berth_rec_interest_berth_idx": { + "name": "berth_rec_interest_berth_idx", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_interest": { + "name": "idx_br_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_recommendations_berth_id_berths_id_fk": { + "name": "berth_recommendations_berth_id_berths_id_fk", + "tableFrom": "berth_recommendations", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_tags": { + "name": "berth_tags", + "schema": "", + "columns": { + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "berth_tags_berth_id_berths_id_fk": { + "name": "berth_tags_berth_id_berths_id_fk", + "tableFrom": "berth_tags", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "berth_tags_berth_id_tag_id_pk": { + "name": "berth_tags_berth_id_tag_id_pk", + "columns": ["berth_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_waiting_list": { + "name": "berth_waiting_list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "notify_pref": { + "name": "notify_pref", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'email'" + }, + "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()" + } + }, + "indexes": { + "berth_waiting_list_berth_client_idx": { + "name": "berth_waiting_list_berth_client_idx", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_bwl_berth": { + "name": "idx_bwl_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_waiting_list_berth_id_berths_id_fk": { + "name": "berth_waiting_list_berth_id_berths_id_fk", + "tableFrom": "berth_waiting_list", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "berth_waiting_list_client_id_clients_id_fk": { + "name": "berth_waiting_list_client_id_clients_id_fk", + "tableFrom": "berth_waiting_list", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berths": { + "name": "berths", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mooring_number": { + "name": "mooring_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "length_ft": { + "name": "length_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_ft": { + "name": "width_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_ft": { + "name": "draft_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "length_m": { + "name": "length_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_m": { + "name": "width_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_m": { + "name": "draft_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_is_minimum": { + "name": "width_is_minimum", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "nominal_boat_size": { + "name": "nominal_boat_size", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "nominal_boat_size_m": { + "name": "nominal_boat_size_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "water_depth": { + "name": "water_depth", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "water_depth_m": { + "name": "water_depth_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "water_depth_is_minimum": { + "name": "water_depth_is_minimum", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "side_pontoon": { + "name": "side_pontoon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "power_capacity": { + "name": "power_capacity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "voltage": { + "name": "voltage", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "mooring_type": { + "name": "mooring_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleat_type": { + "name": "cleat_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleat_capacity": { + "name": "cleat_capacity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bollard_type": { + "name": "bollard_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bollard_capacity": { + "name": "bollard_capacity", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access": { + "name": "access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "price_currency": { + "name": "price_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "weekly_rate_high_usd": { + "name": "weekly_rate_high_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "weekly_rate_low_usd": { + "name": "weekly_rate_low_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "daily_rate_high_usd": { + "name": "daily_rate_high_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "daily_rate_low_usd": { + "name": "daily_rate_low_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "pricing_valid_until": { + "name": "pricing_valid_until", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "bow_facing": { + "name": "bow_facing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "berth_approved": { + "name": "berth_approved", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "tenure_type": { + "name": "tenure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'permanent'" + }, + "tenure_years": { + "name": "tenure_years", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tenure_start_date": { + "name": "tenure_start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "tenure_end_date": { + "name": "tenure_end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status_last_changed_by": { + "name": "status_last_changed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_last_changed_reason": { + "name": "status_last_changed_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_last_modified": { + "name": "status_last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status_override_mode": { + "name": "status_override_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_imported_at": { + "name": "last_imported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_pdf_version_id": { + "name": "current_pdf_version_id", + "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()" + } + }, + "indexes": { + "idx_berths_port": { + "name": "idx_berths_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_status": { + "name": "idx_berths_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_area": { + "name": "idx_berths_area", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "area", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_berths_mooring": { + "name": "idx_berths_mooring", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mooring_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berths_port_id_ports_id_fk": { + "name": "berths_port_id_ports_id_fk", + "tableFrom": "berths", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.brochure_versions": { + "name": "brochure_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "brochure_id": { + "name": "brochure_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content_sha256": { + "name": "content_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "download_url_expires_at": { + "name": "download_url_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_brochure_versions_brochure": { + "name": "idx_brochure_versions_brochure", + "columns": [ + { + "expression": "brochure_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "brochure_versions_brochure_id_brochures_id_fk": { + "name": "brochure_versions_brochure_id_brochures_id_fk", + "tableFrom": "brochure_versions", + "tableTo": "brochures", + "columnsFrom": ["brochure_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.brochures": { + "name": "brochures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_brochures_port": { + "name": "idx_brochures_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_brochures_one_default_per_port": { + "name": "idx_brochures_one_default_per_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"brochures\".\"is_default\" = true AND \"brochures\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "brochures_port_id_ports_id_fk": { + "name": "brochures_port_id_ports_id_fk", + "tableFrom": "brochures", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_sends": { + "name": "document_sends", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_kind": { + "name": "document_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "berth_pdf_version_id": { + "name": "berth_pdf_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brochure_id": { + "name": "brochure_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brochure_version_id": { + "name": "brochure_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_markdown": { + "name": "body_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_by_user_id": { + "name": "sent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fallback_to_link_reason": { + "name": "fallback_to_link_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failed_at": { + "name": "failed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_ds_client": { + "name": "idx_ds_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ds_interest": { + "name": "idx_ds_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ds_berth": { + "name": "idx_ds_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ds_port": { + "name": "idx_ds_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_sends_port_id_ports_id_fk": { + "name": "document_sends_port_id_ports_id_fk", + "tableFrom": "document_sends", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_sends_client_id_clients_id_fk": { + "name": "document_sends_client_id_clients_id_fk", + "tableFrom": "document_sends", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_sends_interest_id_interests_id_fk": { + "name": "document_sends_interest_id_interests_id_fk", + "tableFrom": "document_sends", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_sends_berth_id_berths_id_fk": { + "name": "document_sends_berth_id_berths_id_fk", + "tableFrom": "document_sends", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_sends_brochure_id_brochures_id_fk": { + "name": "document_sends_brochure_id_brochures_id_fk", + "tableFrom": "document_sends", + "tableTo": "brochures", + "columnsFrom": ["brochure_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_sends_brochure_version_id_brochure_versions_id_fk": { + "name": "document_sends_brochure_version_id_brochure_versions_id_fk", + "tableFrom": "document_sends", + "tableTo": "brochure_versions", + "columnsFrom": ["brochure_version_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_addresses": { + "name": "client_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Primary'" + }, + "street_address": { + "name": "street_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subdivision_iso": { + "name": "subdivision_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_iso": { + "name": "country_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "idx_ca_client": { + "name": "idx_ca_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ca_port": { + "name": "idx_ca_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ca_primary": { + "name": "idx_ca_primary", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"client_addresses\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_addresses_client_id_clients_id_fk": { + "name": "client_addresses_client_id_clients_id_fk", + "tableFrom": "client_addresses", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "client_addresses_port_id_ports_id_fk": { + "name": "client_addresses_port_id_ports_id_fk", + "tableFrom": "client_addresses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_contacts": { + "name": "client_contacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_e164": { + "name": "value_e164", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_country": { + "name": "value_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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()" + } + }, + "indexes": { + "idx_cc_client": { + "name": "idx_cc_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cc_email": { + "name": "idx_cc_email", + "columns": [ + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"client_contacts\".\"channel\" = 'email'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cc_phone": { + "name": "idx_cc_phone", + "columns": [ + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"client_contacts\".\"channel\" = 'phone'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cc_one_primary_per_channel": { + "name": "idx_cc_one_primary_per_channel", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"client_contacts\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_contacts_client_id_clients_id_fk": { + "name": "client_contacts_client_id_clients_id_fk", + "tableFrom": "client_contacts", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_merge_candidates": { + "name": "client_merge_candidates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_a_id": { + "name": "client_a_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_b_id": { + "name": "client_b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_cmc_port_status": { + "name": "idx_cmc_port_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cmc_pair": { + "name": "idx_cmc_pair", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_a_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_b_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_merge_candidates_port_id_ports_id_fk": { + "name": "client_merge_candidates_port_id_ports_id_fk", + "tableFrom": "client_merge_candidates", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_merge_candidates_client_a_id_clients_id_fk": { + "name": "client_merge_candidates_client_a_id_clients_id_fk", + "tableFrom": "client_merge_candidates", + "tableTo": "clients", + "columnsFrom": ["client_a_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "client_merge_candidates_client_b_id_clients_id_fk": { + "name": "client_merge_candidates_client_b_id_clients_id_fk", + "tableFrom": "client_merge_candidates", + "tableTo": "clients", + "columnsFrom": ["client_b_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_merge_log": { + "name": "client_merge_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surviving_client_id": { + "name": "surviving_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merged_client_id": { + "name": "merged_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merged_by": { + "name": "merged_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "merge_details": { + "name": "merge_details", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cml_port": { + "name": "idx_cml_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_merge_log_port_id_ports_id_fk": { + "name": "client_merge_log_port_id_ports_id_fk", + "tableFrom": "client_merge_log", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_merge_log_surviving_client_id_clients_id_fk": { + "name": "client_merge_log_surviving_client_id_clients_id_fk", + "tableFrom": "client_merge_log", + "tableTo": "clients", + "columnsFrom": ["surviving_client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_notes": { + "name": "client_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "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()" + } + }, + "indexes": { + "idx_cn_client": { + "name": "idx_cn_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_notes_client_id_clients_id_fk": { + "name": "client_notes_client_id_clients_id_fk", + "tableFrom": "client_notes", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_relationships": { + "name": "client_relationships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_a_id": { + "name": "client_a_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_b_id": { + "name": "client_b_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "relationship_type": { + "name": "relationship_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cr_port": { + "name": "idx_cr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "client_relationships_port_id_ports_id_fk": { + "name": "client_relationships_port_id_ports_id_fk", + "tableFrom": "client_relationships", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_relationships_client_a_id_clients_id_fk": { + "name": "client_relationships_client_a_id_clients_id_fk", + "tableFrom": "client_relationships", + "tableTo": "clients", + "columnsFrom": ["client_a_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "client_relationships_client_b_id_clients_id_fk": { + "name": "client_relationships_client_b_id_clients_id_fk", + "tableFrom": "client_relationships", + "tableTo": "clients", + "columnsFrom": ["client_b_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_tags": { + "name": "client_tags", + "schema": "", + "columns": { + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "client_tags_client_id_clients_id_fk": { + "name": "client_tags_client_id_clients_id_fk", + "tableFrom": "client_tags", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_tags_client_id_tag_id_pk": { + "name": "client_tags_client_id_tag_id_pk", + "columns": ["client_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clients": { + "name": "clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nationality_iso": { + "name": "nationality_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_contact_method": { + "name": "preferred_contact_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_language": { + "name": "preferred_language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_details": { + "name": "source_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "merged_into_client_id": { + "name": "merged_into_client_id", + "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()" + } + }, + "indexes": { + "idx_clients_port": { + "name": "idx_clients_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_name": { + "name": "idx_clients_name", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_archived": { + "name": "idx_clients_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_nationality_iso": { + "name": "idx_clients_nationality_iso", + "columns": [ + { + "expression": "nationality_iso", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_merged_into": { + "name": "idx_clients_merged_into", + "columns": [ + { + "expression": "merged_into_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "clients_port_id_ports_id_fk": { + "name": "clients_port_id_ports_id_fk", + "tableFrom": "clients", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "legal_name": { + "name": "legal_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tax_id": { + "name": "tax_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_number": { + "name": "registration_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incorporation_country_iso": { + "name": "incorporation_country_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incorporation_subdivision_iso": { + "name": "incorporation_subdivision_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "incorporation_date": { + "name": "incorporation_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "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()" + } + }, + "indexes": { + "idx_companies_port": { + "name": "idx_companies_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_companies_name_unique": { + "name": "idx_companies_name_unique", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_companies_taxid": { + "name": "idx_companies_taxid", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tax_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"companies\".\"tax_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "companies_port_id_ports_id_fk": { + "name": "companies_port_id_ports_id_fk", + "tableFrom": "companies", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_addresses": { + "name": "company_addresses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Primary'" + }, + "street_address": { + "name": "street_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subdivision_iso": { + "name": "subdivision_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code": { + "name": "postal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_iso": { + "name": "country_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "idx_compa_company": { + "name": "idx_compa_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_compa_port": { + "name": "idx_compa_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_compa_primary": { + "name": "idx_compa_primary", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_addresses\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_addresses_company_id_companies_id_fk": { + "name": "company_addresses_company_id_companies_id_fk", + "tableFrom": "company_addresses", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_addresses_port_id_ports_id_fk": { + "name": "company_addresses_port_id_ports_id_fk", + "tableFrom": "company_addresses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_detail": { + "name": "role_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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()" + } + }, + "indexes": { + "idx_cm_company": { + "name": "idx_cm_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cm_client": { + "name": "idx_cm_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cm_active": { + "name": "idx_cm_active", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"company_memberships\".\"end_date\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_cm_exact": { + "name": "unique_cm_exact", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_memberships_client_id_clients_id_fk": { + "name": "company_memberships_client_id_clients_id_fk", + "tableFrom": "company_memberships", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_notes": { + "name": "company_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_compn_company": { + "name": "idx_compn_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_notes_company_id_companies_id_fk": { + "name": "company_notes_company_id_companies_id_fk", + "tableFrom": "company_notes", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_tags": { + "name": "company_tags", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "company_tags_company_id_companies_id_fk": { + "name": "company_tags_company_id_companies_id_fk", + "tableFrom": "company_tags", + "tableTo": "companies", + "columnsFrom": ["company_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "company_tags_company_id_tag_id_pk": { + "name": "company_tags_company_id_tag_id_pk", + "columns": ["company_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.crm_user_invites": { + "name": "crm_user_invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_super_admin": { + "name": "is_super_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_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": { + "idx_crm_invites_token_hash": { + "name": "idx_crm_invites_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_crm_invites_email": { + "name": "idx_crm_invites_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_events": { + "name": "document_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_id": { + "name": "signer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_data": { + "name": "event_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "signature_hash": { + "name": "signature_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_de_doc": { + "name": "idx_de_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_de_dedup": { + "name": "idx_de_dedup", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "signature_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document_events\".\"signature_hash\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_events_document_id_documents_id_fk": { + "name": "document_events_document_id_documents_id_fk", + "tableFrom": "document_events", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_events_signer_id_document_signers_id_fk": { + "name": "document_events_signer_id_document_signers_id_fk", + "tableFrom": "document_events", + "tableTo": "document_signers", + "columnsFrom": ["signer_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_signers": { + "name": "document_signers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_name": { + "name": "signer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_email": { + "name": "signer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signer_role": { + "name": "signer_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signing_order": { + "name": "signing_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "signing_url": { + "name": "signing_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embedded_url": { + "name": "embedded_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ds_doc": { + "name": "idx_ds_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_signers_document_id_documents_id_fk": { + "name": "document_signers_document_id_documents_id_fk", + "tableFrom": "document_signers", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_templates": { + "name": "document_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_type": { + "name": "template_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merge_fields": { + "name": "merge_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "template_format": { + "name": "template_format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'html'" + }, + "source_file_id": { + "name": "source_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "documenso_template_id": { + "name": "documenso_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "field_mapping": { + "name": "field_mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "overlay_positions": { + "name": "overlay_positions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reminder_cadence_days": { + "name": "reminder_cadence_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_dt_port": { + "name": "idx_dt_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_dt_type": { + "name": "idx_dt_type", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_templates_port_id_ports_id_fk": { + "name": "document_templates_port_id_ports_id_fk", + "tableFrom": "document_templates", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_templates_source_file_id_files_id_fk": { + "name": "document_templates_source_file_id_files_id_fk", + "tableFrom": "document_templates", + "tableTo": "files", + "columnsFrom": ["source_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_watchers": { + "name": "document_watchers", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_doc_watchers_doc": { + "name": "idx_doc_watchers_doc", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_doc_watchers_user": { + "name": "idx_doc_watchers_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_watchers_document_id_documents_id_fk": { + "name": "document_watchers_document_id_documents_id_fk", + "tableFrom": "document_watchers", + "tableTo": "documents", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "document_watchers_document_id_user_id_pk": { + "name": "document_watchers_document_id_user_id_pk", + "columns": ["document_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reservation_id": { + "name": "reservation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "document_type": { + "name": "document_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "documenso_id": { + "name": "documenso_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_file_id": { + "name": "signed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_manual_upload": { + "name": "is_manual_upload", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reminders_disabled": { + "name": "reminders_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_cadence_override": { + "name": "reminder_cadence_override", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_docs_port": { + "name": "idx_docs_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_interest": { + "name": "idx_docs_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_client": { + "name": "idx_docs_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_documents_yacht": { + "name": "idx_documents_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_documents_company": { + "name": "idx_documents_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_reservation": { + "name": "idx_docs_reservation", + "columns": [ + { + "expression": "reservation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_type": { + "name": "idx_docs_type", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_docs_status_port": { + "name": "idx_docs_status_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_port_id_ports_id_fk": { + "name": "documents_port_id_ports_id_fk", + "tableFrom": "documents", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_client_id_clients_id_fk": { + "name": "documents_client_id_clients_id_fk", + "tableFrom": "documents", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_file_id_files_id_fk": { + "name": "documents_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_signed_file_id_files_id_fk": { + "name": "documents_signed_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": ["signed_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "company_id": { + "name": "company_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_bucket": { + "name": "storage_bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'crm-files'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_files_port": { + "name": "idx_files_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_client": { + "name": "idx_files_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_yacht": { + "name": "idx_files_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_files_company": { + "name": "idx_files_company", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "files_port_id_ports_id_fk": { + "name": "files_port_id_ports_id_fk", + "tableFrom": "files", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_client_id_clients_id_fk": { + "name": "files_client_id_clients_id_fk", + "tableFrom": "files", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_submissions": { + "name": "form_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "form_template_id": { + "name": "form_template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefilled_data": { + "name": "prefilled_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "submitted_data": { + "name": "submitted_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "submitted_at": { + "name": "submitted_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": { + "idx_fs_token": { + "name": "idx_fs_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_submissions_form_template_id_form_templates_id_fk": { + "name": "form_submissions_form_template_id_form_templates_id_fk", + "tableFrom": "form_submissions", + "tableTo": "form_templates", + "columnsFrom": ["form_template_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "form_submissions_client_id_clients_id_fk": { + "name": "form_submissions_client_id_clients_id_fk", + "tableFrom": "form_submissions", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "form_submissions_token_unique": { + "name": "form_submissions_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_templates": { + "name": "form_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "branding": { + "name": "branding", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_ft_port": { + "name": "idx_ft_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_templates_port_id_ports_id_fk": { + "name": "form_templates_port_id_ports_id_fk", + "tableFrom": "form_templates", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_accounts": { + "name": "email_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_address": { + "name": "email_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtp_host": { + "name": "smtp_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "smtp_port": { + "name": "smtp_port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "imap_host": { + "name": "imap_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imap_port": { + "name": "imap_port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_sync_at": { + "name": "last_sync_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()" + } + }, + "indexes": { + "idx_ea_user": { + "name": "idx_ea_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ea_port": { + "name": "idx_ea_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_accounts_port_id_ports_id_fk": { + "name": "email_accounts_port_id_ports_id_fk", + "tableFrom": "email_accounts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_addresses": { + "name": "to_addresses", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "cc_addresses": { + "name": "cc_addresses", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attachment_file_ids": { + "name": "attachment_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "raw_file_id": { + "name": "raw_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_em_thread": { + "name": "idx_em_thread", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_em_message_id": { + "name": "idx_em_message_id", + "columns": [ + { + "expression": "message_id_header", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_messages\".\"message_id_header\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_thread_id_email_threads_id_fk": { + "name": "email_messages_thread_id_email_threads_id_fk", + "tableFrom": "email_messages", + "tableTo": "email_threads", + "columnsFrom": ["thread_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_raw_file_id_files_id_fk": { + "name": "email_messages_raw_file_id_files_id_fk", + "tableFrom": "email_messages", + "tableTo": "files", + "columnsFrom": ["raw_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_threads": { + "name": "email_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "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()" + } + }, + "indexes": { + "idx_et_client": { + "name": "idx_et_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_et_port": { + "name": "idx_et_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_threads_port_id_ports_id_fk": { + "name": "email_threads_port_id_ports_id_fk", + "tableFrom": "email_threads", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "email_threads_client_id_clients_id_fk": { + "name": "email_threads_client_id_clients_id_fk", + "tableFrom": "email_threads", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.expenses": { + "name": "expenses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "establishment_name": { + "name": "establishment_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "exchange_rate": { + "name": "exchange_rate", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payer": { + "name": "payer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expense_date": { + "name": "expense_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "receipt_file_ids": { + "name": "receipt_file_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "no_receipt_acknowledged": { + "name": "no_receipt_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'unpaid'" + }, + "payment_date": { + "name": "payment_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "payment_reference": { + "name": "payment_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_notes": { + "name": "payment_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duplicate_of": { + "name": "duplicate_of", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dedup_scanned_at": { + "name": "dedup_scanned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ocr_status": { + "name": "ocr_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + }, + "ocr_raw": { + "name": "ocr_raw", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ocr_confidence": { + "name": "ocr_confidence", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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()" + } + }, + "indexes": { + "idx_expenses_port": { + "name": "idx_expenses_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_date": { + "name": "idx_expenses_date", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expense_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_category": { + "name": "idx_expenses_category", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_expenses_dedup": { + "name": "idx_expenses_dedup", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "establishment_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "amount", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expense_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "duplicate_of IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "expenses_port_id_ports_id_fk": { + "name": "expenses_port_id_ports_id_fk", + "tableFrom": "expenses", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "expenses_duplicate_of_expenses_id_fk": { + "name": "expenses_duplicate_of_expenses_id_fk", + "tableFrom": "expenses", + "tableTo": "expenses", + "columnsFrom": ["duplicate_of"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_expenses": { + "name": "invoice_expenses", + "schema": "", + "columns": { + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expense_id": { + "name": "expense_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_expenses_invoice_id_invoices_id_fk": { + "name": "invoice_expenses_invoice_id_invoices_id_fk", + "tableFrom": "invoice_expenses", + "tableTo": "invoices", + "columnsFrom": ["invoice_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoice_expenses_expense_id_expenses_id_fk": { + "name": "invoice_expenses_expense_id_expenses_id_fk", + "tableFrom": "invoice_expenses", + "tableTo": "expenses", + "columnsFrom": ["expense_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "invoice_expenses_invoice_id_expense_id_pk": { + "name": "invoice_expenses_invoice_id_expense_id_pk", + "columns": ["invoice_id", "expense_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "unit_price": { + "name": "unit_price", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ili_invoice": { + "name": "idx_ili_invoice", + "columns": [ + { + "expression": "invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoice_line_items_invoice_id_invoices_id_fk": { + "name": "invoice_line_items_invoice_id_invoices_id_fk", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": ["invoice_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'client'" + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "billing_email": { + "name": "billing_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_address": { + "name": "billing_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "payment_terms": { + "name": "payment_terms", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'net30'" + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "subtotal": { + "name": "subtotal", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "discount_pct": { + "name": "discount_pct", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "discount_amount": { + "name": "discount_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "fee_pct": { + "name": "fee_pct", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "fee_amount": { + "name": "fee_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "payment_status": { + "name": "payment_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'unpaid'" + }, + "payment_date": { + "name": "payment_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_reference": { + "name": "payment_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pdf_file_id": { + "name": "pdf_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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()" + } + }, + "indexes": { + "idx_invoices_number": { + "name": "idx_invoices_number", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invoice_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_port": { + "name": "idx_invoices_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_status": { + "name": "idx_invoices_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_billing_entity": { + "name": "idx_invoices_billing_entity", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invoices_interest": { + "name": "idx_invoices_interest", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_port_id_ports_id_fk": { + "name": "invoices_port_id_ports_id_fk", + "tableFrom": "invoices", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_pdf_file_id_files_id_fk": { + "name": "invoices_pdf_file_id_files_id_fk", + "tableFrom": "invoices", + "tableTo": "files", + "columnsFrom": ["pdf_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_interest_id_interests_id_fk": { + "name": "invoices_interest_id_interests_id_fk", + "tableFrom": "invoices", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gdpr_exports": { + "name": "gdpr_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_to": { + "name": "sent_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ready_at": { + "name": "ready_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_gdpr_exports_client": { + "name": "idx_gdpr_exports_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gdpr_exports_port_created": { + "name": "idx_gdpr_exports_port_created", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gdpr_exports_port_id_ports_id_fk": { + "name": "gdpr_exports_port_id_ports_id_fk", + "tableFrom": "gdpr_exports", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gdpr_exports_client_id_clients_id_fk": { + "name": "gdpr_exports_client_id_clients_id_fk", + "tableFrom": "gdpr_exports", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gdpr_exports_requested_by_user_id_fk": { + "name": "gdpr_exports_requested_by_user_id_fk", + "tableFrom": "gdpr_exports", + "tableTo": "user", + "columnsFrom": ["requested_by"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ports": { + "name": "ports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'America/Anguilla'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "ports_slug_idx": { + "name": "ports_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "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": "text", + "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, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.port_role_overrides": { + "name": "port_role_overrides", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_overrides": { + "name": "permission_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "port_role_overrides_port_role_idx": { + "name": "port_role_overrides_port_role_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "port_role_overrides_port_idx": { + "name": "port_role_overrides_port_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "port_role_overrides_port_id_ports_id_fk": { + "name": "port_role_overrides_port_id_ports_id_fk", + "tableFrom": "port_role_overrides", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "port_role_overrides_role_id_roles_id_fk": { + "name": "port_role_overrides_role_id_roles_id_fk", + "tableFrom": "port_role_overrides", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_global": { + "name": "is_global", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_system": { + "name": "is_system", + "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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_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 + }, + "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": { + "sessions_token_idx": { + "name": "sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_port_roles": { + "name": "user_port_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "residential_access": { + "name": "residential_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_port_roles_user_port_role_idx": { + "name": "user_port_roles_user_port_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_upr_user": { + "name": "idx_upr_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_upr_port": { + "name": "idx_upr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_port_roles_port_id_ports_id_fk": { + "name": "user_port_roles_port_id_ports_id_fk", + "tableFrom": "user_port_roles", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_port_roles_role_id_roles_id_fk": { + "name": "user_port_roles_role_id_roles_id_fk", + "tableFrom": "user_port_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_profiles": { + "name": "user_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_super_admin": { + "name": "is_super_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "preferences": { + "name": "preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "user_profiles_user_id_idx": { + "name": "user_profiles_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_profiles_user_id_unique": { + "name": "user_profiles_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "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": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_notes": { + "name": "yacht_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "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()" + } + }, + "indexes": { + "idx_yn_yacht": { + "name": "idx_yn_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yacht_notes_yacht_id_yachts_id_fk": { + "name": "yacht_notes_yacht_id_yachts_id_fk", + "tableFrom": "yacht_notes", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_ownership_history": { + "name": "yacht_ownership_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "transfer_reason": { + "name": "transfer_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transfer_notes": { + "name": "transfer_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_yoh_yacht": { + "name": "idx_yoh_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yoh_active": { + "name": "idx_yoh_active", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"yacht_ownership_history\".\"end_date\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yacht_ownership_history_yacht_id_yachts_id_fk": { + "name": "yacht_ownership_history_yacht_id_yachts_id_fk", + "tableFrom": "yacht_ownership_history", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yacht_tags": { + "name": "yacht_tags", + "schema": "", + "columns": { + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "yacht_tags_yacht_id_yachts_id_fk": { + "name": "yacht_tags_yacht_id_yachts_id_fk", + "tableFrom": "yacht_tags", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "yacht_tags_yacht_id_tag_id_pk": { + "name": "yacht_tags_yacht_id_tag_id_pk", + "columns": ["yacht_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.yachts": { + "name": "yachts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hull_number": { + "name": "hull_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration": { + "name": "registration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "flag": { + "name": "flag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_built": { + "name": "year_built", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "builder": { + "name": "builder", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hull_material": { + "name": "hull_material", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "length_ft": { + "name": "length_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_ft": { + "name": "width_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_ft": { + "name": "draft_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "length_m": { + "name": "length_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "width_m": { + "name": "width_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "draft_m": { + "name": "draft_m", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_owner_type": { + "name": "current_owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_owner_id": { + "name": "current_owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "notes": { + "name": "notes", + "type": "text", + "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()" + } + }, + "indexes": { + "idx_yachts_port": { + "name": "idx_yachts_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_current_owner": { + "name": "idx_yachts_current_owner", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_owner_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_name": { + "name": "idx_yachts_name", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_yachts_archived": { + "name": "idx_yachts_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "yachts_port_id_ports_id_fk": { + "name": "yachts_port_id_ports_id_fk", + "tableFrom": "yachts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interest_berths": { + "name": "interest_berths", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_specific_interest": { + "name": "is_specific_interest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_in_eoi_bundle": { + "name": "is_in_eoi_bundle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "eoi_bypass_reason": { + "name": "eoi_bypass_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eoi_bypassed_by": { + "name": "eoi_bypassed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eoi_bypassed_at": { + "name": "eoi_bypassed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_ib_interest_berth": { + "name": "idx_ib_interest_berth", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ib_one_primary": { + "name": "idx_ib_one_primary", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"interest_berths\".\"is_primary\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ib_berth": { + "name": "idx_ib_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ib_specific": { + "name": "idx_ib_specific", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"interest_berths\".\"is_specific_interest\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interest_berths_interest_id_interests_id_fk": { + "name": "interest_berths_interest_id_interests_id_fk", + "tableFrom": "interest_berths", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "interest_berths_berth_id_berths_id_fk": { + "name": "interest_berths_berth_id_berths_id_fk", + "tableFrom": "interest_berths", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interest_notes": { + "name": "interest_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_locked": { + "name": "is_locked", + "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()" + } + }, + "indexes": { + "idx_in_interest": { + "name": "idx_in_interest", + "columns": [ + { + "expression": "interest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interest_notes_interest_id_interests_id_fk": { + "name": "interest_notes_interest_id_interests_id_fk", + "tableFrom": "interest_notes", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interest_tags": { + "name": "interest_tags", + "schema": "", + "columns": { + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "interest_tags_interest_id_interests_id_fk": { + "name": "interest_tags_interest_id_interests_id_fk", + "tableFrom": "interest_tags", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "interest_tags_interest_id_tag_id_pk": { + "name": "interest_tags_interest_id_tag_id_pk", + "columns": ["interest_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interests": { + "name": "interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pipeline_stage": { + "name": "pipeline_stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "lead_category": { + "name": "lead_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eoi_status": { + "name": "eoi_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "documenso_id": { + "name": "documenso_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_status": { + "name": "contract_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deposit_status": { + "name": "deposit_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reservation_status": { + "name": "reservation_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_first_contact": { + "name": "date_first_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_last_contact": { + "name": "date_last_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_eoi_sent": { + "name": "date_eoi_sent", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_eoi_signed": { + "name": "date_eoi_signed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_contract_sent": { + "name": "date_contract_sent", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_contract_signed": { + "name": "date_contract_signed", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_deposit_received": { + "name": "date_deposit_received", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reminder_enabled": { + "name": "reminder_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_days": { + "name": "reminder_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reminder_last_fired": { + "name": "reminder_last_fired", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome_reason": { + "name": "outcome_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome_at": { + "name": "outcome_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "desired_length_ft": { + "name": "desired_length_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "desired_width_ft": { + "name": "desired_width_ft", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "desired_draft_ft": { + "name": "desired_draft_ft", + "type": "numeric", + "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()" + } + }, + "indexes": { + "idx_interests_port": { + "name": "idx_interests_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_client": { + "name": "idx_interests_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_yacht": { + "name": "idx_interests_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_stage": { + "name": "idx_interests_stage", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_archived": { + "name": "idx_interests_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_interests_outcome": { + "name": "idx_interests_outcome", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interests_port_id_ports_id_fk": { + "name": "interests_port_id_ports_id_fk", + "tableFrom": "interests", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "interests_client_id_clients_id_fk": { + "name": "interests_client_id_clients_id_fk", + "tableFrom": "interests", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.berth_reservations": { + "name": "berth_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "yacht_id": { + "name": "yacht_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tenure_type": { + "name": "tenure_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'permanent'" + }, + "contract_file_id": { + "name": "contract_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_br_berth": { + "name": "idx_br_berth", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_client": { + "name": "idx_br_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_yacht": { + "name": "idx_br_yacht", + "columns": [ + { + "expression": "yacht_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_port": { + "name": "idx_br_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_br_active": { + "name": "idx_br_active", + "columns": [ + { + "expression": "berth_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"berth_reservations\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "berth_reservations_berth_id_berths_id_fk": { + "name": "berth_reservations_berth_id_berths_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "berths", + "columnsFrom": ["berth_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_port_id_ports_id_fk": { + "name": "berth_reservations_port_id_ports_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_client_id_clients_id_fk": { + "name": "berth_reservations_client_id_clients_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_yacht_id_yachts_id_fk": { + "name": "berth_reservations_yacht_id_yachts_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "yachts", + "columnsFrom": ["yacht_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_interest_id_interests_id_fk": { + "name": "berth_reservations_interest_id_interests_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "interests", + "columnsFrom": ["interest_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "berth_reservations_contract_file_id_files_id_fk": { + "name": "berth_reservations_contract_file_id_files_id_fk", + "tableFrom": "berth_reservations", + "tableTo": "files", + "columnsFrom": ["contract_file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.portal_auth_tokens": { + "name": "portal_auth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "portal_user_id": { + "name": "portal_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_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": { + "idx_portal_tokens_hash_unique": { + "name": "idx_portal_tokens_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_tokens_user": { + "name": "idx_portal_tokens_user", + "columns": [ + { + "expression": "portal_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "portal_auth_tokens_portal_user_id_portal_users_id_fk": { + "name": "portal_auth_tokens_portal_user_id_portal_users_id_fk", + "tableFrom": "portal_auth_tokens", + "tableTo": "portal_users", + "columnsFrom": ["portal_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.portal_users": { + "name": "portal_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_portal_users_email_unique": { + "name": "idx_portal_users_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_users_client": { + "name": "idx_portal_users_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_portal_users_port": { + "name": "idx_portal_users_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "portal_users_port_id_ports_id_fk": { + "name": "portal_users_port_id_ports_id_fk", + "tableFrom": "portal_users", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "portal_users_client_id_clients_id_fk": { + "name": "portal_users_client_id_clients_id_fk", + "tableFrom": "portal_users", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.residential_clients": { + "name": "residential_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_e164": { + "name": "phone_e164", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_country": { + "name": "phone_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nationality_iso": { + "name": "nationality_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "place_of_residence": { + "name": "place_of_residence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "place_of_residence_country_iso": { + "name": "place_of_residence_country_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subdivision_iso": { + "name": "subdivision_iso", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferred_contact_method": { + "name": "preferred_contact_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'prospect'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "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()" + } + }, + "indexes": { + "idx_residential_clients_port": { + "name": "idx_residential_clients_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_clients_email": { + "name": "idx_residential_clients_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_clients_archived": { + "name": "idx_residential_clients_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "residential_clients_port_id_ports_id_fk": { + "name": "residential_clients_port_id_ports_id_fk", + "tableFrom": "residential_clients", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.residential_interests": { + "name": "residential_interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "residential_client_id": { + "name": "residential_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pipeline_stage": { + "name": "pipeline_stage", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preferences": { + "name": "preferences", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date_first_contact": { + "name": "date_first_contact", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "date_last_contact": { + "name": "date_last_contact", + "type": "timestamp with time zone", + "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()" + } + }, + "indexes": { + "idx_residential_interests_port": { + "name": "idx_residential_interests_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_client": { + "name": "idx_residential_interests_client", + "columns": [ + { + "expression": "residential_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_stage": { + "name": "idx_residential_interests_stage", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_assigned": { + "name": "idx_residential_interests_assigned", + "columns": [ + { + "expression": "assigned_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_residential_interests_archived": { + "name": "idx_residential_interests_archived", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "residential_interests_port_id_ports_id_fk": { + "name": "residential_interests_port_id_ports_id_fk", + "tableFrom": "residential_interests", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "residential_interests_residential_client_id_residential_clients_id_fk": { + "name": "residential_interests_residential_client_id_residential_clients_id_fk", + "tableFrom": "residential_interests", + "tableTo": "residential_clients", + "columnsFrom": ["residential_client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.generated_reports": { + "name": "generated_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_report_id": { + "name": "scheduled_report_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "parameters": { + "name": "parameters", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_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()" + } + }, + "indexes": { + "idx_gr_port_created": { + "name": "idx_gr_port_created", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gr_port_status": { + "name": "idx_gr_port_status", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gr_scheduled": { + "name": "idx_gr_scheduled", + "columns": [ + { + "expression": "scheduled_report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"generated_reports\".\"scheduled_report_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "generated_reports_port_id_ports_id_fk": { + "name": "generated_reports_port_id_ports_id_fk", + "tableFrom": "generated_reports", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "generated_reports_scheduled_report_id_scheduled_reports_id_fk": { + "name": "generated_reports_scheduled_report_id_scheduled_reports_id_fk", + "tableFrom": "generated_reports", + "tableTo": "scheduled_reports", + "columnsFrom": ["scheduled_report_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "generated_reports_file_id_files_id_fk": { + "name": "generated_reports_file_id_files_id_fk", + "tableFrom": "generated_reports", + "tableTo": "files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.google_calendar_cache": { + "name": "google_calendar_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_at": { + "name": "start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "end_at": { + "name": "end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_crm_pushed": { + "name": "is_crm_pushed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reminder_id": { + "name": "reminder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "gcal_cache_user_event_idx": { + "name": "gcal_cache_user_event_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gcal_cache_user": { + "name": "idx_gcal_cache_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "google_calendar_cache_reminder_id_reminders_id_fk": { + "name": "google_calendar_cache_reminder_id_reminders_id_fk", + "tableFrom": "google_calendar_cache", + "tableTo": "reminders", + "columnsFrom": ["reminder_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.google_calendar_tokens": { + "name": "google_calendar_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_expiry": { + "name": "token_expiry", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "calendar_id": { + "name": "calendar_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'primary'" + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_enabled": { + "name": "sync_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "gcal_tokens_user_id_idx": { + "name": "gcal_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "google_calendar_tokens_user_id_unique": { + "name": "google_calendar_tokens_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_sent": { + "name": "email_sent", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_notif_user": { + "name": "idx_notif_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notif_port": { + "name": "idx_notif_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notifications_user_type": { + "name": "idx_notifications_user_type", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_port_id_ports_id_fk": { + "name": "notifications_port_id_ports_id_fk", + "tableFrom": "notifications", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reminders": { + "name": "reminders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "due_at": { + "name": "due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interest_id": { + "name": "interest_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "berth_id": { + "name": "berth_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_generated": { + "name": "auto_generated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "google_calendar_event_id": { + "name": "google_calendar_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_calendar_synced": { + "name": "google_calendar_synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_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()" + } + }, + "indexes": { + "idx_reminders_port": { + "name": "idx_reminders_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reminders_assigned": { + "name": "idx_reminders_assigned", + "columns": [ + { + "expression": "assigned_to", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reminders_due": { + "name": "idx_reminders_due", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"reminders\".\"status\" IN ('pending', 'snoozed')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reminders_port_id_ports_id_fk": { + "name": "reminders_port_id_ports_id_fk", + "tableFrom": "reminders", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reminders_client_id_clients_id_fk": { + "name": "reminders_client_id_clients_id_fk", + "tableFrom": "reminders", + "tableTo": "clients", + "columnsFrom": ["client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.report_recipients": { + "name": "report_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "report_id": { + "name": "report_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "report_recipients_report_email_idx": { + "name": "report_recipients_report_email_idx", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rr_report": { + "name": "idx_rr_report", + "columns": [ + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "report_recipients_report_id_scheduled_reports_id_fk": { + "name": "report_recipients_report_id_scheduled_reports_id_fk", + "tableFrom": "report_recipients", + "tableTo": "scheduled_reports", + "columnsFrom": ["report_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scheduled_reports": { + "name": "scheduled_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_sr_port": { + "name": "idx_sr_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scheduled_reports_port_id_ports_id_fk": { + "name": "scheduled_reports_port_id_ports_id_fk", + "tableFrom": "scheduled_reports", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "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": "text", + "primaryKey": false, + "notNull": false + }, + "field_changed": { + "name": "field_changed", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "old_value": { + "name": "old_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "new_value": { + "name": "new_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reverted_by": { + "name": "reverted_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reverted_at": { + "name": "reverted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revert_of": { + "name": "revert_of", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "search_text": { + "name": "search_text", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_al_port": { + "name": "idx_al_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_entity": { + "name": "idx_al_entity", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_user": { + "name": "idx_al_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_al_created": { + "name": "idx_al_created", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_port_id_ports_id_fk": { + "name": "audit_logs_port_id_ports_id_fk", + "tableFrom": "audit_logs", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "audit_logs_revert_of_audit_logs_id_fk": { + "name": "audit_logs_revert_of_audit_logs_id_fk", + "tableFrom": "audit_logs", + "tableTo": "audit_logs", + "columnsFrom": ["revert_of"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.currency_rates": { + "name": "currency_rates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "base_currency": { + "name": "base_currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_currency": { + "name": "target_currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rate": { + "name": "rate", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'frankfurter'" + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "currency_rates_base_target_idx": { + "name": "currency_rates_base_target_idx", + "columns": [ + { + "expression": "base_currency", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_currency", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_definitions": { + "name": "custom_field_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_name": { + "name": "field_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_label": { + "name": "field_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "select_options": { + "name": "select_options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_required": { + "name": "is_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cfd_port_entity_name_idx": { + "name": "cfd_port_entity_name_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cfd_port": { + "name": "idx_cfd_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_definitions_port_id_ports_id_fk": { + "name": "custom_field_definitions_port_id_ports_id_fk", + "tableFrom": "custom_field_definitions", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_field_values": { + "name": "custom_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "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()" + } + }, + "indexes": { + "cfv_field_entity_idx": { + "name": "cfv_field_entity_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cfv_entity": { + "name": "idx_cfv_entity", + "columns": [ + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_field_values_field_id_custom_field_definitions_id_fk": { + "name": "custom_field_values_field_id_custom_field_definitions_id_fk", + "tableFrom": "custom_field_values", + "tableTo": "custom_field_definitions", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_views": { + "name": "saved_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filters": { + "name": "filters", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "sort_config": { + "name": "sort_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "column_config": { + "name": "column_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_shared": { + "name": "is_shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "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()" + } + }, + "indexes": { + "idx_sv_user": { + "name": "idx_sv_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_views_port_id_ports_id_fk": { + "name": "saved_views_port_id_ports_id_fk", + "tableFrom": "saved_views", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scratchpad_notes": { + "name": "scratchpad_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "linked_client_id": { + "name": "linked_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_at": { + "name": "linked_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()" + } + }, + "indexes": { + "idx_sp_user": { + "name": "idx_sp_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scratchpad_notes_linked_client_id_clients_id_fk": { + "name": "scratchpad_notes_linked_client_id_clients_id_fk", + "tableFrom": "scratchpad_notes", + "tableTo": "clients", + "columnsFrom": ["linked_client_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "system_settings_key_port_idx": { + "name": "system_settings_key_port_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "system_settings_port_id_ports_id_fk": { + "name": "system_settings_port_id_ports_id_fk", + "tableFrom": "system_settings", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#6B7280'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tags_port_name_idx": { + "name": "tags_port_name_idx", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_tags_port": { + "name": "idx_tags_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tags_port_id_ports_id_fk": { + "name": "tags_port_id_ports_id_fk", + "tableFrom": "tags", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_notification_preferences": { + "name": "user_notification_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "in_app": { + "name": "in_app", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email": { + "name": "email", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "unp_user_port_type_idx": { + "name": "unp_user_port_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_notification_preferences_port_id_ports_id_fk": { + "name": "user_notification_preferences_port_id_ports_id_fk", + "tableFrom": "user_notification_preferences", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_deliveries": { + "name": "webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivered_at": { + "name": "delivered_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": { + "idx_wd_webhook": { + "name": "idx_wd_webhook", + "columns": [ + { + "expression": "webhook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_deliveries_webhook_id_webhooks_id_fk": { + "name": "webhook_deliveries_webhook_id_webhooks_id_fk", + "tableFrom": "webhook_deliveries", + "tableTo": "webhooks", + "columnsFrom": ["webhook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhooks": { + "name": "webhooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "events": { + "name": "events", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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, + "default": "now()" + } + }, + "indexes": { + "idx_webhooks_port": { + "name": "idx_webhooks_port", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhooks_port_id_ports_id_fk": { + "name": "webhooks_port_id_ports_id_fk", + "tableFrom": "webhooks", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alerts": { + "name": "alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rule_id": { + "name": "rule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fired_at": { + "name": "fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_alerts_fingerprint_open": { + "name": "idx_alerts_fingerprint_open", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "resolved_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_alerts_port_fired": { + "name": "idx_alerts_port_fired", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_alerts_port_severity_open": { + "name": "idx_alerts_port_severity_open", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "resolved_at IS NULL AND dismissed_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "alerts_port_id_ports_id_fk": { + "name": "alerts_port_id_ports_id_fk", + "tableFrom": "alerts", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "alerts_dismissed_by_user_id_fk": { + "name": "alerts_dismissed_by_user_id_fk", + "tableFrom": "alerts", + "tableTo": "user", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alerts_acknowledged_by_user_id_fk": { + "name": "alerts_acknowledged_by_user_id_fk", + "tableFrom": "alerts", + "tableTo": "user", + "columnsFrom": ["acknowledged_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.analytics_snapshots": { + "name": "analytics_snapshots", + "schema": "", + "columns": { + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metric_id": { + "name": "metric_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_analytics_pk": { + "name": "idx_analytics_pk", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analytics_snapshots_port_id_ports_id_fk": { + "name": "analytics_snapshots_port_id_ports_id_fk", + "tableFrom": "analytics_snapshots", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_source_links": { + "name": "migration_source_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_system": { + "name": "source_system", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_entity_type": { + "name": "target_entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_entity_id": { + "name": "target_entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_id": { + "name": "applied_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_by": { + "name": "applied_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_msl_source_target": { + "name": "idx_msl_source_target", + "columns": [ + { + "expression": "source_system", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.website_submissions": { + "name": "website_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "port_id": { + "name": "port_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "legacy_nocodb_id": { + "name": "legacy_nocodb_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ip": { + "name": "source_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ws_submission_id": { + "name": "idx_ws_submission_id", + "columns": [ + { + "expression": "submission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ws_port_received": { + "name": "idx_ws_port_received", + "columns": [ + { + "expression": "port_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ws_kind": { + "name": "idx_ws_kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "website_submissions_port_id_ports_id_fk": { + "name": "website_submissions_port_id_ports_id_fk", + "tableFrom": "website_submissions", + "tableTo": "ports", + "columnsFrom": ["port_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/lib/db/migrations/meta/_journal.json b/src/lib/db/migrations/meta/_journal.json index 414606c..3d38b4c 100644 --- a/src/lib/db/migrations/meta/_journal.json +++ b/src/lib/db/migrations/meta/_journal.json @@ -232,6 +232,13 @@ "when": 1777946048910, "tag": "0032_brochures_one_default_per_port_and_storage_fixes", "breakpoints": true + }, + { + "idx": 33, + "version": "7", + "when": 1777948521076, + "tag": "0033_expense_no_receipt_acknowledged", + "breakpoints": true } ] } diff --git a/src/lib/db/schema/financial.ts b/src/lib/db/schema/financial.ts index 205f0f4..e15e255 100644 --- a/src/lib/db/schema/financial.ts +++ b/src/lib/db/schema/financial.ts @@ -1,6 +1,7 @@ import { pgTable, text, + boolean, numeric, integer, timestamp, @@ -36,6 +37,14 @@ export const expenses = pgTable( expenseDate: timestamp('expense_date', { withTimezone: true }).notNull(), description: text('description'), receiptFileIds: text('receipt_file_ids').array(), // references to files table + /** + * True when the rep deliberately created the expense WITHOUT a receipt + * (e.g. the receipt was lost or never issued). Surfaces a warning at + * creation time AND in the PDF export — the legacy parent-company flow + * may refuse to reimburse expenses without proof, so the warning is + * load-bearing for ops. + */ + noReceiptAcknowledged: boolean('no_receipt_acknowledged').notNull().default(false), paymentStatus: text('payment_status').default('unpaid'), // unpaid, paid, partial paymentDate: date('payment_date'), paymentReference: text('payment_reference'), diff --git a/src/lib/services/berth-pdf-parser.ts b/src/lib/services/berth-pdf-parser.ts index 4d5c746..c61f28d 100644 --- a/src/lib/services/berth-pdf-parser.ts +++ b/src/lib/services/berth-pdf-parser.ts @@ -190,19 +190,42 @@ export interface OcrAdapter { recognize(buffer: Buffer): Promise<{ text: string; confidence: number }>; } +/** Hard cap on Tesseract OCR runtime. A crafted PDF rasterizing to + * high-resolution noise can pin the process indefinitely (CPU bomb). + * 30 seconds covers the legitimate single-page-spec case by a wide + * margin while bounding the worst-case worker hold-time. The AI + * fallback tier handles cases where OCR couldn't finish. */ +const OCR_TIMEOUT_MS = 30_000; + /** Default adapter — dynamically imports tesseract.js so the WASM bundle isn't * pulled into client builds. */ async function defaultOcrAdapter(): Promise { return { recognize: async (buffer: Buffer) => { const tesseract = await import('tesseract.js'); - // Tesseract handles PDF inputs by rasterizing the first page; for our - // single-page spec sheets that's sufficient. - const result = await tesseract.recognize(buffer, 'eng'); - return { - text: result.data.text ?? '', - confidence: typeof result.data.confidence === 'number' ? result.data.confidence : 0, - }; + // Race the OCR against a timeout so a runaway recognition can't + // hold the worker forever. The race-loser pattern doesn't + // actually cancel Tesseract (no AbortController support), but it + // does free the awaiter so the caller can fall through to AI. + let timeoutHandle: NodeJS.Timeout | undefined; + const timeout = new Promise<{ text: string; confidence: number }>((_, reject) => { + timeoutHandle = setTimeout( + () => reject(new Error(`Tesseract OCR exceeded ${OCR_TIMEOUT_MS}ms timeout`)), + OCR_TIMEOUT_MS, + ); + }); + try { + const result = await Promise.race([ + tesseract.recognize(buffer, 'eng').then((r) => ({ + text: r.data.text ?? '', + confidence: typeof r.data.confidence === 'number' ? r.data.confidence : 0, + })), + timeout, + ]); + return result; + } finally { + if (timeoutHandle) clearTimeout(timeoutHandle); + } }, }; } diff --git a/src/lib/services/berth-pdf.service.ts b/src/lib/services/berth-pdf.service.ts index 66de39a..dd6e79b 100644 --- a/src/lib/services/berth-pdf.service.ts +++ b/src/lib/services/berth-pdf.service.ts @@ -531,15 +531,22 @@ export async function listBerthPdfVersions(berthId: string): Promise + backend.presignDownload(row.storageKey, { + expirySeconds: 900, + filename: row.fileName, + contentType: 'application/pdf', + }), + ), + ); + + return rows.map((row, i) => { const parseEngine = (row.parseResults as { engine?: ParserEngine } | null)?.engine ?? null; - const presigned = await backend.presignDownload(row.storageKey, { - expirySeconds: 900, - filename: row.fileName, - contentType: 'application/pdf', - }); - out.push({ + return { id: row.id, versionNumber: row.versionNumber, fileName: row.fileName, @@ -547,12 +554,11 @@ export async function listBerthPdfVersions(berthId: string): Promise r.id); - const versions = await db - .select() - .from(brochureVersions) - .where(eq(brochureVersions.brochureId, ids[0]!)); - // Pull all versions for these brochures in one round trip. - const allVersions = - ids.length === 1 - ? versions - : await db.query.brochureVersions.findMany({ - where: (bv, { inArray }) => inArray(bv.brochureId, ids), - orderBy: [desc(brochureVersions.uploadedAt)], - }); + // One round-trip fetches every version for the page, ordered newest-first + // so the per-row `currentVersion` lookup below is just `[0]`. + const allVersions = await db.query.brochureVersions.findMany({ + where: (bv, { inArray }) => inArray(bv.brochureId, ids), + orderBy: [desc(brochureVersions.uploadedAt)], + }); return baseRows.map((row) => { const versionsForRow = allVersions.filter((v) => v.brochureId === row.id); diff --git a/src/lib/services/expense-export.ts b/src/lib/services/expense-export.ts index 6f66581..cca80dc 100644 --- a/src/lib/services/expense-export.ts +++ b/src/lib/services/expense-export.ts @@ -8,7 +8,9 @@ import { logger } from '@/lib/logger'; import type { ListExpensesInput } from '@/lib/validators/expenses'; async function fetchAllExpenses(portId: string, query: ListExpensesInput) { - const conditions: ReturnType[] = [eq(expenses.portId, portId) as ReturnType]; + const conditions: ReturnType[] = [ + eq(expenses.portId, portId) as ReturnType, + ]; if (!query.includeArchived) { conditions.push(isNull(expenses.archivedAt) as unknown as ReturnType); @@ -26,10 +28,14 @@ async function fetchAllExpenses(portId: string, query: ListExpensesInput) { conditions.push(eq(expenses.payer, query.payer) as ReturnType); } if (query.dateFrom) { - conditions.push(gte(expenses.expenseDate, new Date(query.dateFrom)) as unknown as ReturnType); + conditions.push( + gte(expenses.expenseDate, new Date(query.dateFrom)) as unknown as ReturnType, + ); } if (query.dateTo) { - conditions.push(lte(expenses.expenseDate, new Date(query.dateTo)) as unknown as ReturnType); + conditions.push( + lte(expenses.expenseDate, new Date(query.dateTo)) as unknown as ReturnType, + ); } if (query.search) { conditions.push( @@ -81,49 +87,15 @@ export async function exportCsv(portId: string, query: ListExpensesInput): Promi return [headers.join(','), ...csvRows].join('\n'); } -export async function exportPdf(portId: string, query: ListExpensesInput): Promise { - const rows = await fetchAllExpenses(portId, query); - - const template = { - basePdf: { width: 210, height: 297, padding: [10, 10, 10, 10] }, - schemas: [ - [ - { - name: 'title', - type: 'text', - position: { x: 10, y: 10 }, - width: 190, - height: 10, - fontSize: 14, - fontColor: '#000000', - }, - { - name: 'content', - type: 'text', - position: { x: 10, y: 25 }, - width: 190, - height: 260, - fontSize: 8, - fontColor: '#000000', - }, - ], - ], - }; - - const lines = rows.map((r) => { - const date = r.expenseDate ? new Date(r.expenseDate).toISOString().split('T')[0] : ''; - return `${date} | ${r.establishmentName ?? '-'} | ${r.category ?? '-'} | ${r.amount} ${r.currency} | ${r.paymentStatus ?? '-'}`; - }); - - const inputs = [ - { - title: 'Expense Report', - content: lines.join('\n'), - }, - ]; - - return generatePdf(template as unknown as Parameters[0], inputs); -} +/** + * Legacy text-only PDF export superseded by the streaming + * `streamExpensePdf` in `src/lib/services/expense-pdf.service.ts`. + * The new service supports receipt-image embedding, sharp resize for + * stupidly-large attachments, and streaming output so hundreds of + * expenses no longer OOM the process. + * + * See `src/app/api/v1/expenses/export/pdf/route.ts` for the live route. + */ export async function exportParentCompany( portId: string, diff --git a/src/lib/services/expense-pdf.service.ts b/src/lib/services/expense-pdf.service.ts new file mode 100644 index 0000000..defae8f --- /dev/null +++ b/src/lib/services/expense-pdf.service.ts @@ -0,0 +1,899 @@ +/** + * Memory-efficient expense PDF export. + * + * Replaces the legacy `client-portal/server/api/expenses/generate-pdf.ts` + * (1009 lines, pdfkit + full-buffer-everything + base64-wrapped JSON + * response — would OOM on hundreds of receipts). + * + * Design constraints (per user requirement: "could be hundreds of + * expenses and images, also compress files if they're stupidly large"): + * + * 1. **Stream the PDF output** — pdfkit.pipe(response) instead of + * accumulating chunks. Bytes leave the process as they're written. + * 2. **Serial receipt processing** — fetch one receipt at a time, embed, + * release. Peak heap = ~one image + the in-flight pdfkit page. + * 3. **Sharp resize before embedding** — receipts above the size/dim + * thresholds get downscaled to ≤1500px on the long edge at JPEG q80. + * A typical 8 MB phone photo collapses to ~250 KB; the embedded PDF + * ends up ~5–10x smaller than the legacy output. + * 4. **Storage backend abstraction** — receipts come from + * `getStorageBackend().get(storageKey)`; works against MinIO/S3 in + * production and the local filesystem in dev. + * 5. **Heap budget** — for a 500-receipt export (avg 8 MB raw → 250 KB + * resized + a few MB pdfkit working set), peak RSS stays under 100 MB. + * The legacy implementation needed >2 GB for the same input. + * + * Caller flow: + * + * const pdfStream = await streamExpensePdf({ portId, expenseIds, options }); + * return new Response(pdfStream, { headers: { 'content-type': 'application/pdf' } }); + * + * `pdfStream` is a `ReadableStream` ready to hand straight to + * the Web Response constructor; pdfkit's Node-stream output is converted + * via `Readable.toWeb` so the route handler stays in standard runtime. + */ + +import { Readable } from 'node:stream'; +import { eq, inArray, and, gte, lte, isNull } from 'drizzle-orm'; +import PDFDocument from 'pdfkit'; +import sharp from 'sharp'; + +import { db } from '@/lib/db'; +import { expenses } from '@/lib/db/schema/financial'; +import { files } from '@/lib/db/schema/documents'; +import { getRate } from '@/lib/services/currency'; +import { getStorageBackend } from '@/lib/storage'; +import { logger } from '@/lib/logger'; + +// ─── Public options + result types ────────────────────────────────────────── + +export type GroupBy = 'none' | 'payer' | 'category' | 'date'; +export type PageFormat = 'A4' | 'Letter' | 'Legal'; +export type TargetCurrency = 'USD' | 'EUR'; + +export interface ExpensePdfOptions { + /** Title at the top of the document, e.g. "March 2026 Expense Report". */ + documentName: string; + /** Subtitle below the title (defaults to "Generated on "). */ + subheader?: string; + /** Group expenses in the table by payer/category/date. Default: none. */ + groupBy?: GroupBy; + /** Append one page per receipt image at the end. */ + includeReceipts?: boolean; + /** Include the OCR-extracted "Contents" string in the table row. */ + includeReceiptContents?: boolean; + /** Show the summary box (count + totals + grouping label). */ + includeSummary?: boolean; + /** Show the per-row expense table. */ + includeDetails?: boolean; + /** Add a 5% management fee line (parent-company export). */ + includeProcessingFee?: boolean; + /** Currency to convert all amounts into for the totals + line items. */ + targetCurrency?: TargetCurrency; + pageFormat?: PageFormat; +} + +export interface ExpensePdfArgs { + portId: string; + /** When set, only these expenses are exported (ordered by expenseDate desc). */ + expenseIds?: string[]; + /** Otherwise, all matching expenses for the port get exported. */ + filter?: { + dateFrom?: Date | string | null; + dateTo?: Date | string | null; + category?: string | null; + paymentStatus?: string | null; + payer?: string | null; + includeArchived?: boolean; + }; + options: ExpensePdfOptions; +} + +// ─── Image resize gate ────────────────────────────────────────────────────── + +/** Receipts above this raw-byte size are forced through sharp resize. */ +const RESIZE_BYTE_THRESHOLD = 500 * 1024; // 500 KB +/** Max long-edge pixel size after resize. Keeps text legible while + * collapsing typical phone-camera receipts (4032×3024 → 1500×1125). */ +const MAX_DIMENSION = 1500; +/** JPEG quality for resized output. */ +const JPEG_QUALITY = 80; + +/** + * Resize a receipt image to a memory-friendly size. Returns the input + * buffer untouched when: + * - it's already below the byte threshold AND + * - sharp can read its metadata AND + * - both dimensions are ≤ MAX_DIMENSION + * + * Returns a JPEG buffer in every other case. Sharp processes the input + * image stream-style internally (libvips), so the only Node-heap cost + * during resize is the input + output buffers. + */ +async function maybeResizeImage( + raw: Buffer, + contentType: string | null | undefined, +): Promise<{ buffer: Buffer; contentType: 'image/jpeg' | 'image/png'; resized: boolean }> { + // Pdfkit only supports JPEG + PNG. Anything else gets transcoded to JPEG. + const isJpeg = contentType === 'image/jpeg' || contentType === 'image/jpg'; + const isPng = contentType === 'image/png'; + const passthroughCt: 'image/jpeg' | 'image/png' = isPng ? 'image/png' : 'image/jpeg'; + + if (raw.byteLength <= RESIZE_BYTE_THRESHOLD && (isJpeg || isPng)) { + try { + const meta = await sharp(raw).metadata(); + const w = meta.width ?? 0; + const h = meta.height ?? 0; + if (w > 0 && h > 0 && w <= MAX_DIMENSION && h <= MAX_DIMENSION) { + return { buffer: raw, contentType: passthroughCt, resized: false }; + } + } catch { + // Fall through to resize+transcode on any sharp metadata failure. + } + } + + const resized = await sharp(raw) + .rotate() // honour EXIF orientation so phone photos aren't sideways + .resize({ + width: MAX_DIMENSION, + height: MAX_DIMENSION, + fit: 'inside', + withoutEnlargement: true, + }) + .jpeg({ quality: JPEG_QUALITY, mozjpeg: true }) + .toBuffer(); + return { buffer: resized, contentType: 'image/jpeg', resized: true }; +} + +// ─── Currency conversion ──────────────────────────────────────────────────── + +interface ExpenseRow { + id: string; + establishmentName: string | null; + amount: string; + currency: string; + amountUsd: string | null; + paymentMethod: string | null; + category: string | null; + payer: string | null; + expenseDate: Date; + description: string | null; + receiptFileIds: string[] | null; + /** True when the rep created the expense without a receipt (and + * acknowledged it may not be reimbursed). Surfaces as a banner row in + * the table + a footnote at the bottom of the summary box. */ + noReceiptAcknowledged: boolean; + paymentStatus: string | null; +} + +interface ProcessedExpense extends ExpenseRow { + amountTarget: number; + amountUsdNumeric: number; + amountEurNumeric: number; +} + +interface Totals { + count: number; + targetTotal: number; + usdTotal: number; + eurTotal: number; + processingFee: number; + finalTotal: number; + targetCurrency: TargetCurrency; + /** Number of expenses with `noReceiptAcknowledged=true` — surfaces as a + * warning footer in the summary box. Reps need to know this count + * before forwarding the export to a parent-company reimbursement queue. */ + noReceiptCount: number; + /** Sum of the no-receipt expenses' targetTotal — the amount at risk + * of being denied reimbursement. */ + noReceiptAmount: number; +} + +async function processExpenses( + rows: ExpenseRow[], + target: TargetCurrency, +): Promise { + // Resolve rate ONCE per source currency (cached by getRate). Avoids the + // legacy code's per-row API call. + const rateCache = new Map(); + const ensureRate = async (from: string, to: string): Promise => { + if (from === to) return 1; + const key = `${from}->${to}`; + if (rateCache.has(key)) return rateCache.get(key)!; + const rate = (await getRate(from, to)) ?? 1; + rateCache.set(key, rate); + return rate; + }; + + const out: ProcessedExpense[] = []; + for (const row of rows) { + const raw = parseFloat(row.amount); + const usd = + row.amountUsd != null + ? parseFloat(row.amountUsd) + : raw * (await ensureRate(row.currency.toUpperCase(), 'USD')); + const eur = usd * (await ensureRate('USD', 'EUR')); + const targetVal = target === 'USD' ? usd : eur; + out.push({ + ...row, + amountUsdNumeric: usd, + amountEurNumeric: eur, + amountTarget: targetVal, + }); + } + return out; +} + +function computeTotals( + rows: ProcessedExpense[], + target: TargetCurrency, + includeProcessingFee: boolean, +): Totals { + const targetTotal = rows.reduce((s, r) => s + r.amountTarget, 0); + const usdTotal = rows.reduce((s, r) => s + r.amountUsdNumeric, 0); + const eurTotal = rows.reduce((s, r) => s + r.amountEurNumeric, 0); + const processingFee = includeProcessingFee ? targetTotal * 0.05 : 0; + const receiptlessRows = rows.filter((r) => r.noReceiptAcknowledged); + return { + count: rows.length, + targetTotal, + usdTotal, + eurTotal, + processingFee, + finalTotal: targetTotal + processingFee, + targetCurrency: target, + noReceiptCount: receiptlessRows.length, + noReceiptAmount: receiptlessRows.reduce((s, r) => s + r.amountTarget, 0), + }; +} + +// ─── Page dimensions ──────────────────────────────────────────────────────── + +function pageDims(format: PageFormat): { width: number; height: number } { + switch (format) { + case 'Letter': + return { width: 612, height: 792 }; + case 'Legal': + return { width: 612, height: 1008 }; + case 'A4': + default: + return { width: 595, height: 842 }; + } +} + +// ─── Symbol helper ────────────────────────────────────────────────────────── + +function currencySymbol(c: string): string { + switch (c.toUpperCase()) { + case 'USD': + return '$'; + case 'EUR': + return '€'; + case 'GBP': + return '£'; + default: + return c.toUpperCase() + ' '; + } +} + +// ─── Grouping ─────────────────────────────────────────────────────────────── + +function groupKey(row: ProcessedExpense, by: GroupBy): string { + switch (by) { + case 'payer': + return row.payer ?? 'Unknown payer'; + case 'category': + return row.category ?? 'Uncategorized'; + case 'date': + return row.expenseDate.toISOString().slice(0, 10); + default: + return 'all'; + } +} + +function groupRows( + rows: ProcessedExpense[], + by: GroupBy, +): Array<{ key: string; rows: ProcessedExpense[] }> { + if (by === 'none') return [{ key: 'all', rows }]; + const map = new Map(); + for (const r of rows) { + const k = groupKey(r, by); + if (!map.has(k)) map.set(k, []); + map.get(k)!.push(r); + } + return [...map.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, rs]) => ({ key, rows: rs })); +} + +// ─── Fetching ─────────────────────────────────────────────────────────────── + +async function fetchExpenseRows(args: ExpensePdfArgs): Promise { + const conditions = [eq(expenses.portId, args.portId)]; + if (args.expenseIds?.length) { + conditions.push(inArray(expenses.id, args.expenseIds)); + } else { + if (!args.filter?.includeArchived) { + conditions.push(isNull(expenses.archivedAt)); + } + if (args.filter?.dateFrom) { + conditions.push( + gte( + expenses.expenseDate, + args.filter.dateFrom instanceof Date + ? args.filter.dateFrom + : new Date(args.filter.dateFrom), + ), + ); + } + if (args.filter?.dateTo) { + conditions.push( + lte( + expenses.expenseDate, + args.filter.dateTo instanceof Date ? args.filter.dateTo : new Date(args.filter.dateTo), + ), + ); + } + if (args.filter?.category) conditions.push(eq(expenses.category, args.filter.category)); + if (args.filter?.payer) conditions.push(eq(expenses.payer, args.filter.payer)); + if (args.filter?.paymentStatus) + conditions.push(eq(expenses.paymentStatus, args.filter.paymentStatus)); + } + + const rows = await db + .select({ + id: expenses.id, + establishmentName: expenses.establishmentName, + amount: expenses.amount, + currency: expenses.currency, + amountUsd: expenses.amountUsd, + paymentMethod: expenses.paymentMethod, + category: expenses.category, + payer: expenses.payer, + expenseDate: expenses.expenseDate, + description: expenses.description, + receiptFileIds: expenses.receiptFileIds, + noReceiptAcknowledged: expenses.noReceiptAcknowledged, + paymentStatus: expenses.paymentStatus, + }) + .from(expenses) + .where(and(...conditions)) + .orderBy(expenses.expenseDate); + return rows as ExpenseRow[]; +} + +interface ResolvedFile { + fileId: string; + storagePath: string; + storageBucket: string; + mimeType: string | null; + filename: string; +} + +/** Bulk-resolve file metadata so the receipt loop can do a single round-trip. */ +async function resolveReceiptFiles(fileIds: string[]): Promise> { + if (fileIds.length === 0) return new Map(); + const rows = await db + .select({ + id: files.id, + storagePath: files.storagePath, + storageBucket: files.storageBucket, + mimeType: files.mimeType, + filename: files.filename, + }) + .from(files) + .where(inArray(files.id, fileIds)); + const map = new Map(); + for (const r of rows) { + map.set(r.id, { + fileId: r.id, + storagePath: r.storagePath, + storageBucket: r.storageBucket, + mimeType: r.mimeType, + filename: r.filename, + }); + } + return map; +} + +// ─── Streaming buffer helper ──────────────────────────────────────────────── + +/** Drain a Node ReadableStream into a Buffer. Caller is responsible for + * not holding multiple in memory simultaneously. */ +async function streamToBuffer(stream: NodeJS.ReadableStream): Promise { + const chunks: Buffer[] = []; + for await (const chunk of stream as AsyncIterable) { + chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); + } + return Buffer.concat(chunks); +} + +// ─── PDF builder ──────────────────────────────────────────────────────────── + +/** + * Build the expense PDF and return a Web ReadableStream of bytes. The + * caller (route handler) streams this directly to the client; we never + * materialize the whole PDF in memory. + */ +export async function streamExpensePdf( + args: ExpensePdfArgs, +): Promise<{ stream: ReadableStream; suggestedFilename: string }> { + const opts: Required< + Omit + > & { + subheader?: string; + documentName: string; + pageFormat: PageFormat; + targetCurrency: TargetCurrency; + } = { + documentName: args.options.documentName, + subheader: args.options.subheader, + groupBy: args.options.groupBy ?? 'none', + includeReceipts: args.options.includeReceipts ?? false, + includeReceiptContents: args.options.includeReceiptContents ?? false, + includeSummary: args.options.includeSummary ?? true, + includeDetails: args.options.includeDetails ?? true, + includeProcessingFee: args.options.includeProcessingFee ?? false, + targetCurrency: args.options.targetCurrency ?? 'EUR', + pageFormat: args.options.pageFormat ?? 'A4', + }; + + const rawRows = await fetchExpenseRows(args); + const processed = await processExpenses(rawRows, opts.targetCurrency); + const totals = computeTotals(processed, opts.targetCurrency, opts.includeProcessingFee); + + // Bulk-resolve receipt file metadata (one DB round-trip vs N). + const allFileIds = processed + .flatMap((r) => r.receiptFileIds ?? []) + .filter((s): s is string => typeof s === 'string' && s.length > 0); + const filesById = opts.includeReceipts + ? await resolveReceiptFiles(allFileIds) + : new Map(); + + const dims = pageDims(opts.pageFormat); + const doc = new PDFDocument({ + size: [dims.width, dims.height], + margins: { top: 60, bottom: 60, left: 60, right: 60 }, + }); + + // Pull bytes off pdfkit's Node Readable as soon as they're available so + // the client sees the response start streaming before we even begin + // fetching receipts. Node Readable → Web ReadableStream conversion. + const nodeStream = doc as unknown as NodeJS.ReadableStream; + const webStream = Readable.toWeb( + nodeStream as unknown as Readable, + ) as unknown as ReadableStream; + + // Kick off the page-builder asynchronously. Errors propagate via doc.end() + // / doc.emit('error') and surface to the consumer of the stream. + void (async () => { + try { + addHeader(doc, opts); + if (opts.includeSummary) addSummaryBox(doc, totals, opts); + if (opts.includeDetails) addExpenseTable(doc, processed, opts); + + if (opts.includeReceipts) { + await addReceiptPages(doc, processed, filesById, opts); + } + + addFooter(doc); + doc.end(); + } catch (err) { + logger.error({ err }, 'Expense PDF stream failed mid-build'); + doc.emit('error', err); + } + })(); + + const safeName = opts.documentName.replace(/[^a-zA-Z0-9-_\s]/g, '_').trim() || 'expenses'; + return { + stream: webStream, + suggestedFilename: `${safeName}.pdf`, + }; +} + +// ─── Page sections ────────────────────────────────────────────────────────── + +function addHeader(doc: PDFKit.PDFDocument, opts: { documentName: string; subheader?: string }) { + doc + .fontSize(24) + .font('Helvetica-Bold') + .fillColor('#000000') + .text(opts.documentName, { align: 'center' }); + const subheader = opts.subheader ?? `Generated on ${new Date().toLocaleDateString()}`; + doc.fontSize(12).font('Helvetica').fillColor('#666666').text(subheader, { align: 'center' }); + doc.fillColor('#000000').moveDown(1); +} + +function addSummaryBox( + doc: PDFKit.PDFDocument, + totals: Totals, + opts: { includeProcessingFee: boolean; groupBy: GroupBy }, +) { + const sym = currencySymbol(totals.targetCurrency); + const otherSym = totals.targetCurrency === 'USD' ? '€' : '$'; + const otherTotal = totals.targetCurrency === 'USD' ? totals.eurTotal : totals.usdTotal; + + doc.fontSize(14).font('Helvetica-Bold').text('Summary'); + doc.moveDown(0.4); + + const lineY = doc.y; + const lines = [ + `Total expenses: ${totals.count}`, + `Subtotal (${totals.targetCurrency}): ${sym}${totals.targetTotal.toFixed(2)}`, + `${totals.targetCurrency === 'USD' ? 'EUR' : 'USD'} equivalent: ${otherSym}${otherTotal.toFixed(2)}`, + ]; + if (opts.includeProcessingFee) { + lines.push(`Processing fee (5%): ${sym}${totals.processingFee.toFixed(2)}`); + lines.push(`Final total: ${sym}${totals.finalTotal.toFixed(2)}`); + } + if (opts.groupBy !== 'none') lines.push(`Grouping: by ${opts.groupBy}`); + + // Warning footer when the export contains acknowledged-no-receipt rows. + // Reps need to see the at-risk count + amount BEFORE they forward the + // PDF to a reimbursement queue. + const showNoReceiptWarning = totals.noReceiptCount > 0; + const warningLines = showNoReceiptWarning + ? [ + `WARNING: ${totals.noReceiptCount} expense${totals.noReceiptCount === 1 ? '' : 's'} on this report ${totals.noReceiptCount === 1 ? 'has' : 'have'} no receipt attached`, + `(${sym}${totals.noReceiptAmount.toFixed(2)} at risk of being denied reimbursement).`, + ] + : []; + + const boxHeight = (lines.length + warningLines.length) * 16 + 20; + doc + .rect(60, lineY, doc.page.width - 120, boxHeight) + .fillColor('#f5f5f5') + .fill() + .strokeColor('#dddddd') + .stroke(); + doc.fillColor('#000000').fontSize(11).font('Helvetica'); + let y = lineY + 12; + for (const line of lines) { + doc.text(line, 75, y); + y += 16; + } + if (showNoReceiptWarning) { + doc.fillColor('#dc3545').font('Helvetica-Bold'); + for (const line of warningLines) { + doc.text(line, 75, y); + y += 16; + } + doc.fillColor('#000000').font('Helvetica'); + } + doc.y = lineY + boxHeight + 12; +} + +interface Column { + header: string; + width: number; + x: number; + align?: 'left' | 'right'; +} + +function addExpenseTable( + doc: PDFKit.PDFDocument, + rows: ProcessedExpense[], + opts: { groupBy: GroupBy; includeReceiptContents: boolean; targetCurrency: TargetCurrency }, +) { + doc.fontSize(14).font('Helvetica-Bold').text('Expense details'); + doc.moveDown(0.4); + + const sym = currencySymbol(opts.targetCurrency); + const baseColumns: Column[] = [ + { header: 'Date', width: 60, x: 60 }, + { header: 'Establishment', width: 110, x: 120 }, + { header: 'Category', width: 65, x: 230 }, + { header: 'Payer', width: 55, x: 295 }, + { header: 'Amount', width: 75, x: 350, align: 'right' }, + { header: 'Status', width: 50, x: 425 }, + ]; + if (opts.includeReceiptContents) { + baseColumns.push({ header: 'Description', width: 100, x: 475 }); + } + + const drawHeader = () => { + doc + .fontSize(9) + .font('Helvetica-Bold') + .rect(60, doc.y, doc.page.width - 120, 22) + .fillColor('#f2f2f2') + .fill() + .strokeColor('#dddddd') + .stroke() + .fillColor('#000000'); + const headerY = doc.y + 6; + for (const col of baseColumns) { + doc.text(col.header, col.x, headerY, { width: col.width, align: col.align ?? 'left' }); + } + doc.y += 22; + }; + + const drawRow = (row: ProcessedExpense, alt: boolean) => { + if (doc.y > doc.page.height - 80) { + doc.addPage(); + drawHeader(); + } + const rowTop = doc.y; + if (alt) { + doc + .rect(60, rowTop, doc.page.width - 120, 20) + .fillColor('#fafafa') + .fill(); + } + doc.fillColor('#000000').fontSize(8).font('Helvetica'); + const date = row.expenseDate.toISOString().slice(0, 10); + const amount = `${sym}${row.amountTarget.toFixed(2)}`; + // Annotate the establishment cell with a red "(no receipt)" marker + // when the rep created the expense without proof. This keeps the + // warning glanceable per row without adding a new column. + const establishment = + (row.establishmentName ?? '-') + (row.noReceiptAcknowledged ? ' (no receipt)' : ''); + const data: string[] = [ + date, + establishment, + row.category ?? '-', + row.payer ?? '-', + amount, + row.paymentStatus ?? '-', + ]; + if (opts.includeReceiptContents) { + data.push(((row.description ?? '') || '-').slice(0, 80)); + } + data.forEach((value, i) => { + const col = baseColumns[i]!; + // Draw the establishment cell in red when no-receipt; reset to + // black for everything else so warning visibility doesn't bleed. + const isWarningCell = i === 1 && row.noReceiptAcknowledged; + if (isWarningCell) doc.fillColor('#dc3545'); + doc.text(value, col.x, rowTop + 6, { + width: col.width - 4, + align: col.align ?? 'left', + ellipsis: true, + }); + if (isWarningCell) doc.fillColor('#000000'); + }); + doc.y = rowTop + 20; + }; + + drawHeader(); + let altIndex = 0; + for (const group of groupRows(rows, opts.groupBy)) { + if (opts.groupBy !== 'none') { + if (doc.y > doc.page.height - 80) { + doc.addPage(); + drawHeader(); + } + const groupTotal = group.rows.reduce((s, r) => s + r.amountTarget, 0); + doc + .rect(60, doc.y, doc.page.width - 120, 20) + .fillColor('#e7f3ff') + .fill() + .strokeColor('#dddddd') + .stroke(); + doc + .fillColor('#000000') + .fontSize(9) + .font('Helvetica-Bold') + .text( + `${group.key} (${group.rows.length} expense${group.rows.length === 1 ? '' : 's'} — ${sym}${groupTotal.toFixed(2)})`, + 65, + doc.y + 5, + { width: doc.page.width - 130 }, + ); + doc.y += 20; + } + for (const row of group.rows) { + drawRow(row, altIndex % 2 === 1); + altIndex += 1; + } + } + doc.moveDown(0.5); +} + +async function addReceiptPages( + doc: PDFKit.PDFDocument, + rows: ProcessedExpense[], + filesById: Map, + opts: { targetCurrency: TargetCurrency }, +) { + const expensesWithReceipts = rows.filter( + (r) => Array.isArray(r.receiptFileIds) && r.receiptFileIds.length > 0, + ); + if (expensesWithReceipts.length === 0) return; + const totalReceipts = expensesWithReceipts.reduce( + (s, r) => s + (r.receiptFileIds?.length ?? 0), + 0, + ); + + const backend = await getStorageBackend(); + const sym = currencySymbol(opts.targetCurrency); + + let receiptCounter = 0; + let resizedCount = 0; + const startedAt = Date.now(); + + for (const expense of expensesWithReceipts) { + for (const fileId of expense.receiptFileIds ?? []) { + receiptCounter += 1; + const file = filesById.get(fileId); + if (!file) { + addReceiptErrorPage( + doc, + expense, + receiptCounter, + totalReceipts, + sym, + 'Receipt file metadata missing', + ); + continue; + } + + let imageBuffer: Buffer | null = null; + try { + // Stream from storage → buffer. Sharp + pdfkit both need a Buffer + // (neither accepts a streaming body), so we pay one image's bytes + // per loop iteration. Released to GC after embed. + const stream = await backend.get(file.storagePath); + const raw = await streamToBuffer(stream); + const resized = await maybeResizeImage(raw, file.mimeType); + if (resized.resized) resizedCount += 1; + imageBuffer = resized.buffer; + + // Page header + doc.addPage(); + renderReceiptHeader(doc, expense, file, receiptCounter, totalReceipts, sym); + + // Embed the image full-bleed in the remaining vertical space. + const margin = 60; + const headerBlockHeight = 110; + const imgX = margin; + const imgY = doc.y; + const imgW = doc.page.width - margin * 2; + const imgH = doc.page.height - imgY - margin; + try { + doc.image(imageBuffer, imgX, imgY, { + fit: [imgW, imgH], + align: 'center', + valign: 'center', + }); + } catch (err) { + logger.warn( + { err, fileId, mimeType: file.mimeType }, + 'pdfkit refused to embed receipt; falling back to error page', + ); + // Replace the partial page content with an error footer; pdfkit + // doesn't allow removing already-drawn elements, so we just append + // the error message in red below. + doc + .fontSize(11) + .fillColor('#dc3545') + .text( + `Receipt could not be embedded: ${(err as Error).message}`, + imgX, + imgY + headerBlockHeight, + { width: imgW, align: 'center' }, + ); + doc.fillColor('#000000'); + } + } catch (err) { + logger.warn( + { err, fileId, expenseId: expense.id, storagePath: file.storagePath }, + 'Receipt fetch failed; rendering placeholder page', + ); + addReceiptErrorPage( + doc, + expense, + receiptCounter, + totalReceipts, + sym, + (err as Error).message ?? 'Receipt could not be loaded from storage', + ); + } finally { + // Release the buffer reference so V8 can reclaim it before the + // next iteration. Without this, the closure could pin the last + // image until the loop fully completes. + imageBuffer = null; + } + } + } + + logger.info( + { + totalReceipts, + resized: resizedCount, + elapsedMs: Date.now() - startedAt, + }, + 'Expense PDF receipt pages built', + ); +} + +function renderReceiptHeader( + doc: PDFKit.PDFDocument, + expense: ProcessedExpense, + file: ResolvedFile, + index: number, + total: number, + sym: string, +) { + const margin = 60; + const headerH = 90; + doc + .rect(margin, doc.y, doc.page.width - margin * 2, headerH) + .fillColor('#f8f9fa') + .fill() + .strokeColor('#dee2e6') + .stroke(); + doc.fillColor('#000000'); + + doc + .fontSize(14) + .font('Helvetica-Bold') + .text(`Receipt ${index} of ${total}`, margin + 10, doc.y - headerH + 10); + doc + .fontSize(11) + .font('Helvetica-Bold') + .text( + `${expense.establishmentName ?? '—'} ${sym}${expense.amountTarget.toFixed(2)}`, + margin + 10, + doc.y + 4, + ); + doc + .fontSize(9) + .font('Helvetica') + .fillColor('#666666') + .text( + `Date: ${expense.expenseDate.toISOString().slice(0, 10)} · Payer: ${expense.payer ?? '—'} · Category: ${expense.category ?? '—'} · File: ${file.filename}`, + margin + 10, + doc.y + 4, + { width: doc.page.width - margin * 2 - 20 }, + ); + doc.fillColor('#000000'); + // Reset cursor to below the header block. + const margin2 = 60; + doc.y = doc.y + Math.max(headerH - 50, 20); + void margin2; +} + +function addReceiptErrorPage( + doc: PDFKit.PDFDocument, + expense: ProcessedExpense, + index: number, + total: number, + sym: string, + message: string, +) { + doc.addPage(); + doc.fontSize(14).font('Helvetica-Bold').text(`Receipt ${index} of ${total}`, { align: 'center' }); + doc + .fontSize(11) + .font('Helvetica') + .text(`${expense.establishmentName ?? '—'} ${sym}${expense.amountTarget.toFixed(2)}`, { + align: 'center', + }); + doc.moveDown(2); + doc.fontSize(11).fillColor('#dc3545').text(message, { align: 'center' }); + doc.fillColor('#000000'); +} + +function addFooter(doc: PDFKit.PDFDocument) { + doc.fontSize(9).fillColor('#666666'); + const range = doc.bufferedPageRange(); + for (let i = range.start; i < range.start + range.count; i += 1) { + doc.switchToPage(i); + doc.text(`Page ${i + 1} of ${range.count}`, 60, doc.page.height - 30, { + align: 'right', + width: doc.page.width - 120, + }); + doc.text( + `Generated ${new Date().toISOString().slice(0, 19).replace('T', ' ')} UTC`, + 60, + doc.page.height - 30, + { + align: 'left', + width: doc.page.width - 120, + }, + ); + } + doc.fillColor('#000000'); +} diff --git a/src/lib/services/expenses.ts b/src/lib/services/expenses.ts index 2d7c765..d37f7ea 100644 --- a/src/lib/services/expenses.ts +++ b/src/lib/services/expenses.ts @@ -107,6 +107,7 @@ export async function createExpense(portId: string, data: CreateExpenseInput, me expenseDate: data.expenseDate, description: data.description, receiptFileIds: data.receiptFileIds ?? [], + noReceiptAcknowledged: data.noReceiptAcknowledged ?? false, paymentStatus: data.paymentStatus, paymentDate: data.paymentDate ?? null, paymentReference: data.paymentReference ?? null, diff --git a/src/lib/validators/expenses.ts b/src/lib/validators/expenses.ts index 41f5ba9..8072fde 100644 --- a/src/lib/validators/expenses.ts +++ b/src/lib/validators/expenses.ts @@ -2,7 +2,12 @@ import { z } from 'zod'; import { baseListQuerySchema } from '@/lib/api/route-helpers'; import { EXPENSE_CATEGORIES, PAYMENT_METHODS } from '@/lib/constants'; -export const createExpenseSchema = z.object({ +/** + * Inner-shape ZodObject — kept exported (without .refine wrapping) so + * `updateExpenseSchema` can still call `.partial()`. The `.refine()` rule + * for "receipt or acknowledgement" is applied via `createExpenseSchema`. + */ +export const createExpenseShape = z.object({ establishmentName: z.string().max(200).optional(), amount: z.coerce.number().positive(), currency: z.string().length(3).default('USD'), @@ -12,13 +17,33 @@ export const createExpenseSchema = z.object({ expenseDate: z.coerce.date(), description: z.string().max(2000).optional(), receiptFileIds: z.array(z.string()).optional(), + /** + * Set to `true` when the rep deliberately creates an expense without a + * receipt. The UI shows a non-blocking warning that surfaces both at + * creation time and again in the PDF export. Without this flag, the + * server rejects an expense submitted with no `receiptFileIds` so reps + * can't accidentally lose-receipt by mistake. + */ + noReceiptAcknowledged: z.boolean().optional().default(false), paymentStatus: z.enum(['unpaid', 'paid', 'partial']).default('unpaid'), paymentDate: z.string().optional(), paymentReference: z.string().optional(), paymentNotes: z.string().optional(), }); -export const updateExpenseSchema = createExpenseSchema.partial(); +export const createExpenseSchema = createExpenseShape.refine( + (v) => (v.receiptFileIds && v.receiptFileIds.length > 0) || v.noReceiptAcknowledged === true, + { + message: + 'Receipt required. Tick "I have no receipt for this expense" if you understand it may not be reimbursed.', + path: ['receiptFileIds'], + }, +); + +// Update accepts partial fields and skips the create-time receipt-or-ack +// rule (the row already exists and may legitimately be edited without +// touching receipts). +export const updateExpenseSchema = createExpenseShape.partial(); export const listExpensesSchema = baseListQuerySchema.extend({ category: z.string().optional(), @@ -29,6 +54,45 @@ export const listExpensesSchema = baseListQuerySchema.extend({ payer: z.string().optional(), }); +/** + * Body for `POST /api/v1/expenses/export/pdf`. Mirrors the legacy + * `PDFOptions` shape from the Nuxt client-portal so reps can re-use the + * same mental model. `expenseIds` selects an explicit subset; when + * absent, the listExpenses-style filter is used to gather rows. + * + * Limits are deliberate: + * - max 1000 expenseIds so a runaway selection can't queue an OOM-able + * receipt-fetch loop (see expense-pdf.service.ts). + * - documentName is sanitized at the service layer for the filename; + * the validator only enforces a sane upper bound. + */ +export const exportExpensePdfSchema = z.object({ + expenseIds: z.array(z.string()).max(1000).optional(), + filter: z + .object({ + dateFrom: z.string().optional().nullable(), + dateTo: z.string().optional().nullable(), + category: z.string().optional().nullable(), + paymentStatus: z.string().optional().nullable(), + payer: z.string().optional().nullable(), + includeArchived: z.boolean().optional(), + }) + .optional(), + options: z.object({ + documentName: z.string().min(1).max(200), + subheader: z.string().max(300).optional(), + groupBy: z.enum(['none', 'payer', 'category', 'date']).default('none'), + includeReceipts: z.boolean().default(false), + includeReceiptContents: z.boolean().default(false), + includeSummary: z.boolean().default(true), + includeDetails: z.boolean().default(true), + includeProcessingFee: z.boolean().default(false), + targetCurrency: z.enum(['USD', 'EUR']).default('EUR'), + pageFormat: z.enum(['A4', 'Letter', 'Legal']).default('A4'), + }), +}); + export type CreateExpenseInput = z.infer; export type UpdateExpenseInput = z.infer; export type ListExpensesInput = z.infer; +export type ExportExpensePdfInput = z.infer;