@dooer/dooer-test-env 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/bin/index.js +7 -0
  2. package/discovery-router/Dockerfile +18 -0
  3. package/discovery-router/README.md +99 -0
  4. package/discovery-router/package.json +13 -0
  5. package/discovery-router/registry.example.json +5 -0
  6. package/discovery-router/server.js +272 -0
  7. package/lib/account.js +120 -0
  8. package/lib/auth-dev-keys.js +12 -0
  9. package/lib/bankid.js +130 -0
  10. package/lib/cli.js +27 -0
  11. package/lib/command/bankid.js +45 -0
  12. package/lib/command/customer.js +108 -0
  13. package/lib/command/db.js +156 -0
  14. package/lib/command/env.js +114 -0
  15. package/lib/command/logs.js +143 -0
  16. package/lib/command/measure.js +81 -0
  17. package/lib/command/service.js +166 -0
  18. package/lib/command/setup.js +92 -0
  19. package/lib/command/shred.js +60 -0
  20. package/lib/compose/README.md +98 -0
  21. package/lib/compose/generate.js +375 -0
  22. package/lib/compose/manifests.js +108 -0
  23. package/lib/db/roles.js +118 -0
  24. package/lib/discovery/client.js +40 -0
  25. package/lib/engine/GUIDE.md +176 -0
  26. package/lib/engine/PROCESS.md +571 -0
  27. package/lib/engine/dbbuild.js +325 -0
  28. package/lib/engine/gen-schema-map.js +479 -0
  29. package/lib/engine/purge.js +137 -0
  30. package/lib/engine/schema-map.json +11016 -0
  31. package/lib/engine/seed.js +1045 -0
  32. package/lib/obc.js +72 -0
  33. package/lib/registry.js +123 -0
  34. package/lib/runtime.js +101 -0
  35. package/lib/service-token.js +40 -0
  36. package/lib/shred/README.md +118 -0
  37. package/lib/shred/audit.js +128 -0
  38. package/lib/shred/faker.js +545 -0
  39. package/lib/shred/index.js +126 -0
  40. package/lib/shred/scripts/base-partner-emails.sql +9 -0
  41. package/lib/shred/scripts/dev-accounts.sql +195 -0
  42. package/lib/shred/scripts/emails.sql +48 -0
  43. package/lib/shred/scripts/institution-browser.sql +3 -0
  44. package/lib/shred/scripts/notification-targets.sql +5 -0
  45. package/lib/shred/scripts/partners.sql +2 -0
  46. package/lib/shred/scripts/passwords.sql +8 -0
  47. package/lib/shred/scripts/personal-numbers.sql +177 -0
  48. package/lib/shred/scripts/phone-numbers.sql +22 -0
  49. package/lib/shred/scripts/salary-spec-reports.sql +5 -0
  50. package/lib/shred/scripts/service-activity-tracker-data.sql +4 -0
  51. package/lib/shred/scripts/service-core-objects.sql +19 -0
  52. package/lib/shred/scripts/service-event-stream.sql +2 -0
  53. package/lib/shred/scripts/service-integrations.sql +4 -0
  54. package/lib/shred/scripts/template.sql +4 -0
  55. package/lib/shred/scripts/x-service-billing.sql +34 -0
  56. package/lib/shred/scripts/xxx-history-tables.sql +25 -0
  57. package/lib/stub.js +8 -0
  58. package/local-postgres/Dockerfile +11 -0
  59. package/package.json +46 -0
  60. package/readme.md +92 -0
@@ -0,0 +1,571 @@
1
+ # Seeding a test account — how this works (the full process)
2
+
3
+ Goal: make an **exact copy of one Dooer organization's data into another** organization (a test
4
+ entity), so a real company's documents / transactions / matchings / vouchers / settings / subscriptions
5
+ appear in a test org — with all personal email addresses scrubbed so the real customer gets no mail.
6
+
7
+ This document explains how the tool was built and why, so it can be maintained and re-run. The
8
+ companion **GUIDE.md** is the how-to-run cheatsheet.
9
+
10
+ > **Provenance — how this was built (full session transcript):**
11
+ > - Chat log: [`_chats/20260826-1157-jimmy-service-ai-agents…61cb10fe….md`](_chats/20260826-1157-jimmy-service-ai-agents.unknown-session-61cb10fe-a00a-4e97-9a69-fe91db17ba8e.md)
12
+ > - Prompts only: [`_chats/…61cb10fe….prompts.md`](_chats/20260826-1157-jimmy-service-ai-agents.unknown-session-61cb10fe-a00a-4e97-9a69-fe91db17ba8e.prompts.md)
13
+ >
14
+ > The transcript walks through the whole method — measuring the schema, the source/target direction
15
+ > mix-up caught by row counts, and the iterative discovery of each uniqueness-collision class
16
+ > (jsonb, unique uuids, unique ints, composite text keys) that shaped the per-column strategies.
17
+
18
+ ---
19
+
20
+ ## 1. The core idea — org identity is ONE uuid
21
+
22
+ Every service in the Dooer database stores customer data under an **organization id** (a uuid). The
23
+ column is almost always called `organizationId`; a few legacy tables call it `fk_companies_pk`,
24
+ `company_id`, or `companyId`. Crucially, the root row `service_accounts.companies.companies_pk` is
25
+ **that same uuid** — so `organizationId == companies.companies_pk`. Copy therefore boils down to:
26
+
27
+ > Take every row whose org column = SOURCE, write it back with the org column = TARGET, and give every
28
+ > copied row a fresh primary key so nothing collides — while keeping references between the copied rows
29
+ > pointing at each other (not back at the source).
30
+
31
+ Because primary keys are overwhelmingly **uuids** (282 of 297 org-scoped tables), this "rewrite the
32
+ uuids" approach rewires intra-org references automatically. To stay memory-lean on a big org (millions
33
+ of rows), we don't store an old→new map: pass 1 collects only the **set of copied source uuids**, and
34
+ the fresh target uuid is **derived deterministically** from the source uuid (a per-run-salted v5 hash),
35
+ so any reference recomputes to the same new value. References to rows we did **not** copy (users, global
36
+ chart-of-accounts templates) are left untouched because their uuids are not in the set.
37
+
38
+ ## 2. Measure first, then generate the map
39
+
40
+ Nothing about the table list is hand-written — it is **measured** from the live database (read-only)
41
+ and compiled into `schema-map.json`. The raw measurements live in `_measure/` (see `FINDINGS.md`).
42
+ The key queries (run against `dooer-staging`; identical shape on any namespace):
43
+
44
+ ```sql
45
+ -- (m2) every table with a company/organization column = the copy set
46
+ SELECT table_schema, table_name, column_name FROM information_schema.columns
47
+ WHERE column_name ~* '(compan(y|ies)|organi[sz]ation)' AND column_name ~* '(_pk$|_id$|id$|pk$)'
48
+ AND table_schema !~ '_history$';
49
+
50
+ -- (m11) every single-column UNIQUE column + its sequence (drives the uniqueness strategy)
51
+ SELECT n.nspname, c.relname, a.attname, t.typname,
52
+ pg_get_serial_sequence(quote_ident(n.nspname)||'.'||quote_ident(c.relname), a.attname)
53
+ FROM pg_index i JOIN pg_class c ON c.oid=i.indrelid JOIN pg_namespace n ON n.oid=c.relnamespace
54
+ JOIN pg_attribute a ON a.attrelid=c.oid AND a.attnum=ANY(i.indkey) JOIN pg_type t ON t.oid=a.atttypid
55
+ WHERE i.indisunique AND array_length(i.indkey,1)=1;
56
+
57
+ -- (m4) the FK graph (used to find which columns REFERENCE a remapped key)
58
+ -- (m5) email columns to scrub · (m12) composite-unique constraints
59
+ ```
60
+
61
+ Run `node gen-schema-map.js` to compile `_measure/*.tsv` → `schema-map.json`. Re-run the queries and
62
+ the generator whenever the schema changes.
63
+
64
+ ## 3. What `schema-map.json` contains (the config object — the script hardcodes nothing)
65
+
66
+ - `tables[]` — all 297 org-scoped tables. Each has `action: "copy" | "ignore"`, an `ignoreReason`, the
67
+ `filterColumn` (which org column selects the org's rows), the `pk`, and `emailColumns`.
68
+ **Default is copy;** a table is only `ignore` with a stated reason (see §4).
69
+ - `uniquenessStrategies` — a **per-column** strategy for every unique column, so a copy never collides:
70
+ - `remap-uuid` — fresh uuid, applied consistently everywhere the value appears (all 318 uuid uniques).
71
+ - `sequence` — draw `nextval(sequenceName)` on the target; also rewrite the columns in `references`.
72
+ (Only `service_core_objects.transactions.transactions_pk`, whose junctions carry an int FK to it.)
73
+ - `max-offset` — target `max(col)+1,+2,…` for app-assigned unique ints (`invoiceNumber`, `transactionId`).
74
+ - `text-suffix` — append `-seedcopy-<rand>` for text dedup keys, including the composite-unique
75
+ members `task.uniquenessKey`, `checkFinding.dedupeKey`, `dynamicQuestion.uniquenessKey`,
76
+ `entity.externalId`.
77
+ - `preserve` — keep the value (only when a collision is impossible).
78
+ Edit a column's `strategy` to change behaviour — no code change.
79
+ - `options.remapTextStoredUuids` — remap a text value that equals a copied uuid (polymorphic ids stored
80
+ as text), so those references follow the copy too.
81
+ - `emailScrub` — the address every email column is overwritten with (`testcustomer@dooer.com`).
82
+
83
+ ## 4. What is NOT copied, and why
84
+
85
+ Copy-by-default; the `ignore` set (22 tables) each carries a reason:
86
+ - **`service_institution_browser.*`** — bank-scraping data, security-sensitive (Dennis/Jimmy).
87
+ - **Identity / user / auth tables** (`companies`, `companies2users`, `user_roles_at_companies`,
88
+ `invites`, `signup*`, `bankIdSession`, `tokenInvalidation`, …) — rule: **never create or copy
89
+ users.** The target org already exists with its own owner; we keep it and just remap the org id.
90
+ - **`service_outgoing_email.email`** — outbound queue; avoid any send from a test org.
91
+ - **`service_search.searchIndex`** — derived/rebuildable full-text index (Jimmy 2026-08-24).
92
+ - **Audit/history mirrors** — `*_history` schemas (already excluded by the measurement) and `*__history`
93
+ tables (`task__history`, `taskEventAction__history`, `workOrder__history`): not source-of-truth,
94
+ regenerated by the history triggers, and large/slow to copy (Jimmy 2026-08-26).
95
+ - Tables with **no owner-filter column** we could not safely scope (reviewed individually).
96
+
97
+ ## 5. The copy algorithm (`seed-test-account.js`)
98
+
99
+ 1. Connect to the source and target `dooer-database` via its **ClusterIP** (needs VPN), credentials
100
+ read from the Zalando `postgres.*.credentials` secret with `kubectl`. Source and target namespaces
101
+ can differ (e.g. read `dooer-production`, write `dooer-staging`).
102
+ - `--target <uuid>` copies into an existing org; `--name "…" --owner-user <uuid>` instead **creates**
103
+ the target — cloning the source `companies` row (fresh id / name / unique short_name / api_uuid)
104
+ and linking the owner with role `Owner`. The owner **user must already exist**; users are never
105
+ created or copied.
106
+ 2. **Pass 1 (plan):** for every copy table, count the source-org rows and collect every unique-uuid
107
+ value → generate a fresh uuid (the remap). Prints the plan.
108
+ 3. **Dry-run by default** ("dummy" mode) stops here and writes nothing.
109
+ 4. **Pass 2 (execute, one transaction):** `SET session_replication_role='replica'` (disables FK checks
110
+ and triggers for a clean bulk load), build the non-uuid remap maps per their config strategy, then
111
+ for each table stream the source rows into the target with **`COPY … FROM STDIN`** (far faster than
112
+ per-row INSERT), applying: org column → target, every uuid/text-uuid in the remap → its fresh value,
113
+ transaction ints → their sequence values (reserved in one `nextval` batch), unique text keys →
114
+ suffixed, email columns → the scrub address, jsonb → re-serialised with embedded uuids remapped.
115
+ Date/timestamp columns are read as their raw Postgres text (no JS `Date` round-trip) so COPY writes
116
+ them back with no timezone drift. Any error rolls the **entire** copy back.
117
+ 5. **Files (S3), after the DB commit** — on by default, `--skip-files` opts out. The file bytes live in
118
+ Ceph RGW keyed by `service_file_storage.fileData.id`. We copy `fileData` with fresh ids, then copy
119
+ each S3 object from the SOURCE env bucket to the TARGET env bucket at the new key, so the copy owns
120
+ its own files. See §5b and the caveats in §8.
121
+
122
+ ### 5b. Tables with no `organizationId` — orphan/child + reverse-reference + files
123
+
124
+ Many rows belong to an org only *through a foreign key* and carry no `organizationId`, so the org-column
125
+ scan misses them. These are handled explicitly:
126
+ - **Orphan child tables** — discovered from the FK graph (`_measure/m13`) by a **transitive closure**
127
+ (`gen-schema-map.js`): any table with no org column that reaches an org-rooted table through a chain of
128
+ FKs (matches `documents2transactions`/`payments2transactions`/`reports2transactions`/
129
+ `transactions2transactions`; task children `link`/`assignee`/`taskEvent`/…; `voucherJournalLine`,
130
+ `ledger.transaction`; `document_files`/`report_files`; …). Each is copied by filtering its FK into the
131
+ copied parent's org rows (`fkFilter` in the map).
132
+ - **Multi-level orphans (child-of-an-orphan)** carry a **nested `parentFilter`** so the whole chain
133
+ roots at an org-scoped ancestor. The one that matters most is
134
+ **`document_files_converted`** (the per-page converted images that HQ's booking view renders as
135
+ *pages* — `to-v1.js`/`get-thumbnail.js` read them, ordered by `page_number`): it hangs off
136
+ `document_files` → `documents`, i.e. it is a child of an orphan, so a one-level-only scan missed it
137
+ and **the booking view showed no pages** even though the primary file copied fine. `report_files_converted`
138
+ is the same for reports. (Superseded the static one-level `m14.orphan-fk.json`; fix 2026-08-27.)
139
+ Its `storage_id` (page image `fileEntry`) and `scaled_images` (a jsonb map of scaled-variant
140
+ `fileEntry`s) are ordinary uuid refs → they remap to the copied `fileEntry`s automatically, and those
141
+ are org-owned so their `fileData` + S3 objects are copied too.
142
+ - **Reverse-reference parents** — `service_file_storage.fileData` (no org column, but referenced by the
143
+ copied `fileEntry`): copied by the reverse FK, with fresh ids.
144
+ - **Files (S3)** — `fileData.id` is the S3 object key. After the DB copy, the tool reads the source
145
+ env's file-storage pod for the RGW endpoint (ClusterIP, reachable over VPN), bucket and credentials
146
+ (primary **and** fallback bucket), fetches each object from the source and PUTs it into the target
147
+ env's bucket at the copied `fileData.id`. `document_files.storage_uuid` → `fileEntry.id` and
148
+ `fileEntry.fileDataId` → `fileData.id` are ordinary uuid references, so they remap to the copied rows
149
+ automatically — the whole document → page → file → S3 chain resolves in the target.
150
+
151
+ ### 5c. Referenced users (copied on demand, customer emails anonymized)
152
+
153
+ Copied rows reference **users** through soft uuid columns (`createdByUserId`, `fk_users_pk`, `updatedByUserId`, …) that carry no FK constraint. In a **cross-namespace** copy (prod → staging) those user ids don't exist in the target, so resolvers like `ledgerDocuments … createdByUser` fail with *"Could not find Users"*. Handling (on by default; `--skip-users` to disable, config in `schema-map.json` → `userTable` / `userRefColumns`):
154
+
155
+ 1. **Discover** — collect the distinct ids in every configured `userRefColumns` entry (uuid columns, in the copy set, matching user-ref name patterns + declared FKs — measured into `_measure/m15`).
156
+ 2. **Match to the target** — for each referenced user:
157
+ - already present by `users_pk` → leave the ref as-is (nothing copied);
158
+ - else, for **non-customer** roles, match by the natural unique key (`email`,role)/(`id_number`,role) — the *same person across namespaces* has a different pk — and **pin the ref to the existing target pk** (`explicitRemap`), so we never create a duplicate that would clash on the `(email, fk_user_roles_at_dooer_pk)` / `(id_number, …)` unique indexes;
159
+ - else **copy** the user with a fresh derived id (added to the remap set so the refs follow).
160
+ 3. **Anonymize by role** — role is `users.fk_user_roles_at_dooer_pk` (text: `admin`/`customer`/`hi`/`hiab`). Only **`customer`** is anonymized: its email columns (`email`, `contactEmail`, `conversationEmailAddress`) become a **unique** `testcustomer+u<newid>@dooer.com` (unique per the `(email, role)` key) and `id_number` + `password_hash` are nulled (PII / credentials). Every other role (`hi`/`hiab`/`admin`) is copied verbatim. (Jimmy 2026-08-27)
161
+
162
+ We never copy `companies2users`, so a copied user has **no membership** in the test org — it resolves for display (`createdByUser`) but can't log in.
163
+
164
+ ## 6. Safety model
165
+
166
+ - **Dry-run is the default**; `--execute` is required to write.
167
+ - Writing to **`dooer-production` requires `--confirm-production`** (reads from prod are always allowed).
168
+ - The whole copy is **one transaction** — a failure leaves the target exactly as it was.
169
+ - `--if-target-nonempty refuse` (default) aborts if a copy table already holds target-org rows.
170
+ - The tool **never deletes** and **never touches users** — the target account and its owners are kept.
171
+
172
+ ## 7. Verified (staging, 2026-08-24)
173
+
174
+ A same-DB copy of a small org reproduced exact row counts (documents/transactions/vouchers/tasks/sales
175
+ all matched), zero dangling FKs, `transactions_pk` fully remapped (no source/target overlap), all email
176
+ columns scrubbed, and the composite dedup keys suffixed. Dry-run of a large org (ghost-inspector)
177
+ planned 819,739 rows / 466k uuid remaps with no writes.
178
+
179
+ ## 8. Known limitations
180
+
181
+ - **Performance:** writes use `COPY` and the `nextval` reservation is batched, so the bottleneck is
182
+ pass 1 reading + row remapping (some big shared tables have no per-org index). Watch the `[pass1 n/N]`
183
+ and `[copy n/N]` progress on stderr. The `*__history` audit tables (the biggest) are ignored (§4).
184
+ - **The S3 file copy is NOT transactional and runs AFTER the DB commit.** The DB copy is atomic (one
185
+ transaction); the file copy is best-effort on top of it. If it is interrupted, the DB is already
186
+ committed and only some files are in the target — just **re-run with the same source/target** to
187
+ re-copy (PUT is idempotent: same key overwritten). Because it copies actual bytes, cross-environment
188
+ works (files land in the target env's own bucket) and there is **no sharing** — deleting a file in the
189
+ copy cannot affect the source. It runs from the laptop over VPN, reading the source env's S3 and
190
+ writing the target env's S3, so it needs kubectl access to both `service-file-storage` pods and the
191
+ `rook-ceph` RGW ClusterIPs. A file with no object in either the primary or fallback source bucket is
192
+ reported as `missing-in-source` and skipped (a deleted/absent file).
193
+ - **Live source drift:** copying a *live* production org, its counts can move slightly during the run
194
+ (e.g. a few tasks created mid-copy) — the copy is a point-in-time snapshot, not a lock.
195
+ - **Memory:** the uuid remap is a Set + on-demand derivation (not an old→new map), and the run script
196
+ `seed-test-account.sh` raises Node's heap (`SEED_MAX_HEAP_MB`, default 8192). A multi-million-row org
197
+ still holds a large uuid set; bump the heap if needed. (A very large org could be made constant-memory
198
+ by streaming pass 2 with a server-side cursor — not needed at current sizes.)
199
+ - **user references inside copied rows** (`createdByUserId`, …) keep pointing at the source org's real
200
+ users — we do not copy or invent users. Fine for a test org; the owner is the target account's owner.
201
+ - **Cross-DB vs same-DB:** cross-namespace copies (prod→staging) never collide on unique constraints;
202
+ same-DB copies (prod→prod) rely on the uniqueness strategies in §3, which are validated for the
203
+ measured constraint set. If the schema gains a new unique constraint, re-measure (m11/m12) and
204
+ regenerate.
205
+ - **Composite uniques** are handled where a member is the org, a remapped uuid, or a configured
206
+ text/int key; a genuinely new composite of un-remapped columns would need a new config entry.
207
+
208
+ ---
209
+
210
+ ## Appendix A — the 329 tables copied
211
+
212
+ Generated from `schema-map.json` (`action: "copy"`). Filter = the org column, or `fk:<col>→parent` for an orphan/child table scoped through a FK; PK = primary key type; Notes = email-scrub / uniqueness strategy / S3-blob.
213
+
214
+ | # | table | filter | PK | notes |
215
+ |---|---|---|---|---|
216
+ | 1 | `service_accounting_checks.check` | `organizationId` | uuid | active-task filter |
217
+ | 2 | `service_accounting_checks.transactionsBalanceInconsistencyCheck` | `organizationId` | uuid | |
218
+ | 3 | `service_accounting_investigation.investigation` | `organizationId` | uuid | |
219
+ | 4 | `service_accounting_onboarding.accountingOnboarding` | `organizationId` | uuid | |
220
+ | 5 | `service_accounting_onboarding.accountingOnboardingCheck` | `organizationId` | uuid | |
221
+ | 6 | `service_accounting_onboarding.accountingOnboardingCheckGroup` | `organizationId` | uuid | |
222
+ | 7 | `service_accounting_onboarding.accountingOnboardingConfirmedBookkeepingStartDate` | `organizationId` | uuid | |
223
+ | 8 | `service_accounting_onboarding.accountingOnboardingPhaseStatus` | `organizationId` | uuid | |
224
+ | 9 | `service_accounting_onboarding.syncedPreOffer` | `organizationId` | uuid | scrub: ownerEmail |
225
+ | 10 | `service_accounting_resolution.resolution` | `organizationId` | uuid | |
226
+ | 11 | `service_accounts.incoming_email_whitelists` | `fk_companies_pk` | uuid | scrub: email |
227
+ | 12 | `service_accounts.offboarding_status_change_log` | `fk_companies_pk` | uuid | |
228
+ | 13 | `service_accounts.organizationAddress` | `organizationId` | — | staging-only |
229
+ | 14 | `service_accounts.organizationFeature` | `organizationId` | uuid | |
230
+ | 15 | `service_accounts.partnerOrganization` | `organizationId` | uuid | |
231
+ | 16 | `service_accounts.subscriptionCancellation` | `organizationId` | uuid | |
232
+ | 17 | `service_accounts.subscriptionItem` | `organizationId` | uuid | |
233
+ | 18 | `service_accounts.subscriptionOffer` | `organizationId` | uuid | |
234
+ | 19 | `service_accounts.subscriptionOfferExpiredEvent` | fk:`subscriptionOfferId`→subscriptionOffer | uuid | |
235
+ | 20 | `service_accounts.subscriptions` | `fk_companies_pk` | uuid | |
236
+ | 21 | `service_accounts.systemMessageRecipient` | `organizationId` | uuid | |
237
+ | 22 | `service_accounts.temporalKeyValue` | `organizationId` | uuid | |
238
+ | 23 | `service_accounts.tosAcceptance` | `organizationId` | uuid | |
239
+ | 24 | `service_activity_log.groupedUserActivity` | `organizationId` | uuid | |
240
+ | 25 | `service_activity_log.groupedUserActivityLogLink` | `organizationId` | uuid | |
241
+ | 26 | `service_activity_log.userActivity` | `organizationId` | uuid | |
242
+ | 27 | `service_activity_tracker_data.billingTimeTrackingEntry` | `organizationId` | uuid | |
243
+ | 28 | `service_activity_tracker_data.billingTimeTrackingProject` | `organizationId` | uuid | uniquenessKey:text-suffix |
244
+ | 29 | `service_activity_tracker_data.salesActivity` | `organizationId` | uuid | |
245
+ | 30 | `service_activity_tracker_data.trackedEvent` | `organizationId` | uuid | |
246
+ | 31 | `service_ai_agents.aiFinding` | `organizationId` | uuid | |
247
+ | 32 | `service_ai_agents.aiQueueSnapshot` | `organizationId` | uuid | |
248
+ | 33 | `service_ai_agents.aiStepResult` | `organizationId` | uuid | |
249
+ | 34 | `service_ai_agents.aiSubtaskRun` | `organizationId` | uuid | |
250
+ | 35 | `service_ai_agents.aiTaskRun` | `organizationId` | uuid | |
251
+ | 36 | `service_ai_data_studio.tmpSupplierDb` | `organizationId` | uuid | |
252
+ | 37 | `service_annual_report.annualReport` | `organizationId` | uuid | |
253
+ | 38 | `service_annual_report.annualReport_annualReportPhase` | `organizationId` | uuid | |
254
+ | 39 | `service_annual_report.annualReportFile` | `organizationId` | uuid | |
255
+ | 40 | `service_annual_report.annualReportPrice` | `organizationId` | uuid | |
256
+ | 41 | `service_annual_report.annualReportStepStatus` | `organizationId` | uuid | |
257
+ | 42 | `service_annual_report.bolagsverketEvent` | `organizationId` | uuid | |
258
+ | 43 | `service_annual_report.bolagsverketEventSubscription` | `organizationId` | uuid | |
259
+ | 44 | `service_annual_report.bolagsverketFileSubmission` | `organizationId` | uuid | scrub: emailAddresses, receiptEmailAddresses |
260
+ | 45 | `service_annual_report.bolagsverketToken` | `organizationId` | uuid | |
261
+ | 46 | `service_authorization.access_rule` | `company_id` | uuid | |
262
+ | 47 | `service_authorization.company_relation` | special | uuid | |
263
+ | 48 | `service_authorization.role` | `company_id` | uuid | |
264
+ | 49 | `service_automatic_booking.bookingTask` | `organizationId` | uuid | |
265
+ | 50 | `service_automatic_matching.attempt` | `organizationId` | uuid | |
266
+ | 51 | `service_automatic_matching.match` | fk:`attemptId`→attempt | uuid | |
267
+ | 52 | `service_automatic_monthly_closing.check` | `organizationId` | uuid | |
268
+ | 53 | `service_automatic_monthly_closing.postMonthlyClosing` | `organizationId` | uuid | |
269
+ | 54 | `service_automatic_monthly_closing.preMonthlyClosing` | `organizationId` | uuid | |
270
+ | 55 | `service_bill_clients.calculatedUsageRecord` | `organizationId` | uuid | |
271
+ | 56 | `service_bill_clients.creditInvoice` | `organizationId` | uuid | |
272
+ | 57 | `service_bill_clients.invoice` | `organizationId` | uuid | |
273
+ | 58 | `service_bill_clients.invoiceSpecification` | `organizationId` | uuid | |
274
+ | 59 | `service_bill_clients.invoiceSpecificationLine` | fk:`invoiceSpecificationId`→invoiceSpecification | uuid | |
275
+ | 60 | `service_billing.companyInformation` | `organizationId` | uuid | scrub: email |
276
+ | 61 | `service_billing.customer` | `organizationId` | uuid | |
277
+ | 62 | `service_billing.customerAddress` | `organizationId` | uuid | |
278
+ | 63 | `service_billing.customerContact` | `organizationId` | uuid | scrub: email |
279
+ | 64 | `service_billing.invoice` | `organizationId` | uuid | |
280
+ | 65 | `service_billing.invoiceAccess` | `organizationId` | uuid | |
281
+ | 66 | `service_billing.invoiceAttachment` | `organizationId` | uuid | |
282
+ | 67 | `service_billing.invoiceDelivery` | `organizationId` | uuid | |
283
+ | 68 | `service_billing.invoiceLine` | `organizationId` | uuid | |
284
+ | 69 | `service_billing.invoiceLink` | `organizationId` | uuid | |
285
+ | 70 | `service_billing.item` | `organizationId` | uuid | |
286
+ | 71 | `service_billing.itemTaxDeductionSettings` | `organizationId` | uuid | |
287
+ | 72 | `service_billing.organizationFeature` | `organizationId` | uuid | |
288
+ | 73 | `service_billing.organizationFeatureOverride` | `organizationId` | uuid | |
289
+ | 74 | `service_billing.payment` | `organizationId` | uuid | |
290
+ | 75 | `service_billing.paymentIntention` | `organizationId` | uuid | |
291
+ | 76 | `service_billing.quote` | `organizationId` | uuid | |
292
+ | 77 | `service_billing.quoteAccess` | `organizationId` | uuid | |
293
+ | 78 | `service_billing.quoteAttachment` | `organizationId` | uuid | |
294
+ | 79 | `service_billing.quoteDelivery` | `organizationId` | uuid | |
295
+ | 80 | `service_billing.quoteInvoice` | `organizationId` | uuid | |
296
+ | 81 | `service_billing.quoteLine` | `organizationId` | uuid | |
297
+ | 82 | `service_billing.reminder` | `organizationId` | uuid | |
298
+ | 83 | `service_billing.taxDeductionDecision` | `organizationId` | uuid | |
299
+ | 84 | `service_booking_review.auditItem` | `organizationId` | uuid | |
300
+ | 85 | `service_booking_review.bookingReviewLine` | `organizationId` | uuid | |
301
+ | 86 | `service_business_entities.entity` | `organizationId` | uuid | scrub: emailAddress; externalId:text-suffix |
302
+ | 87 | `service_business_entities.entityLink` | fk:`entityId`→entity | uuid | |
303
+ | 88 | `service_business_entities.entityMatchRule` | `organizationId` | uuid | |
304
+ | 89 | `service_business_entities.entityReferenceWord` | fk:`entityId`→entity | uuid | |
305
+ | 90 | `service_business_entities.transaction` | `organizationId` | uuid | transactionId:max-offset |
306
+ | 91 | `service_business_entities.transactionReferenceWord` | fk:`transactionId`→transaction | uuid | |
307
+ | 92 | `service_calendar.calendar` | `organizationId` | uuid | |
308
+ | 93 | `service_calendar.calendarEvent` | fk:`calendarId`→calendar | uuid | |
309
+ | 94 | `service_calendar.calendarEventAttendee` | `organizationId` | uuid | scrub: email |
310
+ | 95 | `service_calendar.calendarEventLink` | fk:`calendarId`→calendar | uuid | |
311
+ | 96 | `service_calendar.calendarMeetingRequest` | `organizationId` | uuid | |
312
+ | 97 | `service_checks_engine.checkCoverage` | `organizationId` | uuid | |
313
+ | 98 | `service_checks_engine.checkFinding` | `organizationId` | uuid | dedupeKey:text-suffix |
314
+ | 99 | `service_checks_engine.checkRun` | `organizationId` | uuid | |
315
+ | 100 | `service_closing.action` | `organizationId` | uuid | |
316
+ | 101 | `service_closing.check` | `organizationId` | uuid | |
317
+ | 102 | `service_closing.period` | `organizationId` | uuid | |
318
+ | 103 | `service_closing.periodPhase` | `organizationId` | uuid | |
319
+ | 104 | `service_closing.periodStatus` | `organizationId` | uuid | |
320
+ | 105 | `service_comments.attachment` | `organizationId` | uuid | |
321
+ | 106 | `service_comments.checklist` | `organizationId` | uuid | |
322
+ | 107 | `service_comments.checklistCategory` | `organizationId` | — | staging-only |
323
+ | 108 | `service_comments.checklistItem` | `organizationId` | uuid | |
324
+ | 109 | `service_comments.commentView` | `organizationId` | — | staging-only |
325
+ | 110 | `service_comments.conversation` | `organizationId` | uuid | scrub: startedByEmail, startedByEmailForRecipient |
326
+ | 111 | `service_comments.conversationReply` | `organizationId` | uuid | scrub: createdByEmail |
327
+ | 112 | `service_comments.conversationReplyReadAt` | fk:`conversationId`→conversation | uuid | |
328
+ | 113 | `service_comments.link` | `organizationId` | uuid | |
329
+ | 114 | `service_comments.message` | `organizationId` | uuid | |
330
+ | 115 | `service_comments.note` | `organizationId` | uuid | |
331
+ | 116 | `service_core_objects.accounts_at_institution` | `fk_companies_pk` | uuid | |
332
+ | 117 | `service_core_objects.accounts_at_institution2users` | fk:`fk_accounts_at_institution_pk`→accounts_at_institution | uuid | |
333
+ | 118 | `service_core_objects.document_files` | fk:`fk_documents_pk`→documents | uuid | |
334
+ | 119 | `service_core_objects.document_resort_history` | fk:`fk_documents_pk`→documents | uuid | |
335
+ | 120 | `service_core_objects.documents` | `fk_companies_pk` | uuid | |
336
+ | 121 | `service_core_objects.documents2transactions` | fk:`fk_transactions_pk_uuid`→transactions | uuid | |
337
+ | 122 | `service_core_objects.filter` | `organizationId` | uuid | |
338
+ | 123 | `service_core_objects.ocr_data` | fk:`fk_documents_pk`→documents | uuid | |
339
+ | 124 | `service_core_objects.organizationInstitution` | `organizationId` | — | staging-only |
340
+ | 125 | `service_core_objects.payments` | `fk_companies_pk` | uuid | |
341
+ | 126 | `service_core_objects.payments2transactions` | fk:`fk_transactions_pk_uuid`→transactions | uuid | |
342
+ | 127 | `service_core_objects.report_files` | fk:`fk_reports_pk`→reports | uuid | |
343
+ | 128 | `service_core_objects.reports` | `fk_companies_pk` | uuid | |
344
+ | 129 | `service_core_objects.reports2transactions` | fk:`fk_transactions_pk_uuid`→transactions | uuid | |
345
+ | 130 | `service_core_objects.transactions` | `organizationId` | integer | transactions_pk:sequence |
346
+ | 131 | `service_core_objects.transactions_fetch_logs` | fk:`fk_accounts_at_institution_pk`→accounts_at_institution | uuid | |
347
+ | 132 | `service_core_objects.transactions2transactions` | fk:`fk_transactions_pk_a_uuid`→transactions | uuid | |
348
+ | 133 | `service_customer_questions.dynamicQuestion` | `organizationId` | uuid | |
349
+ | 134 | `service_customer_questions.question` | `organizationId` | uuid | |
350
+ | 135 | `service_customer_questions.questionVariableValue` | fk:`questionId`→question | uuid | |
351
+ | 136 | `service_customer_questions.scheduledDynamicQuestion` | `organizationId` | — | |
352
+ | 137 | `service_document_processing.cursor` | fk:`resultId`→result | uuid | |
353
+ | 138 | `service_document_processing.documentField` | fk:`ocrDataId`→ocrData | uuid | |
354
+ | 139 | `service_document_processing.ocrData` | `organizationId` | uuid | |
355
+ | 140 | `service_document_processing.processRun` | `organizationId` | uuid | |
356
+ | 141 | `service_document_processing.result` | `organizationId` | uuid | |
357
+ | 142 | `service_document_processing.resultField` | fk:`resultId`→result | uuid | |
358
+ | 143 | `service_document_processing.v2poc_documentInformationExtractedField` | fk:`tokenId`→v2poc_token | uuid | |
359
+ | 144 | `service_document_processing.v2poc_documentInformationExtraction` | `organizationId` | uuid | |
360
+ | 145 | `service_document_processing.v2poc_token` | `organizationId` | uuid | |
361
+ | 146 | `service_documents.documentMeta` | `organizationId` | uuid | |
362
+ | 147 | `service_employees_legacy.employee` | `organizationId` | uuid | scrub: email |
363
+ | 148 | `service_employees_legacy.employeeAddress` | fk:`employeeId`→employee | uuid | |
364
+ | 149 | `service_employees_legacy.employeeBenefit` | `organizationId` | uuid | |
365
+ | 150 | `service_employees_legacy.employeePaymentCard` | `organizationId` | uuid | |
366
+ | 151 | `service_employees_legacy.employeeVacationDaysChange` | `organizationId` | uuid | |
367
+ | 152 | `service_employees_legacy.organizationDefault` | `organizationId` | uuid | |
368
+ | 153 | `service_employees.employee` | `organizationId` | uuid | scrub: email |
369
+ | 154 | `service_employees.employeeAddress` | fk:`employeeId`→employee | uuid | |
370
+ | 155 | `service_employees.employeeBenefit` | `organizationId` | uuid | |
371
+ | 156 | `service_employees.employeePaymentCard` | `organizationId` | uuid | |
372
+ | 157 | `service_employees.employeeVacationBalance` | `organizationId` | uuid | |
373
+ | 158 | `service_employees.employeeVacationBalanceChange` | `organizationId` | uuid | |
374
+ | 159 | `service_employees.employeeVacationDaysChange` | `organizationId` | uuid | |
375
+ | 160 | `service_employees.employeeWorkRate` | `organizationId` | uuid | |
376
+ | 161 | `service_employees.organizationDefault` | `organizationId` | uuid | |
377
+ | 162 | `service_employees.vacationYear` | `organizationId` | uuid | |
378
+ | 163 | `service_export.dooerExport` | `organizationId` | uuid | |
379
+ | 164 | `service_feedback.feedbackQuestion` | `organizationId` | uuid | |
380
+ | 165 | `service_feedback.feedbackResult` | `organizationId` | uuid | |
381
+ | 166 | `service_file_storage.fileData` | fk:`id`→fileEntry | uuid | S3 blob |
382
+ | 167 | `service_file_storage.fileEntry` | `organizationId` | uuid | |
383
+ | 168 | `service_integrations.applyReceipt` | fk:`syncedObjectId`→syncedObject | uuid | |
384
+ | 169 | `service_integrations.applyRun` | fk:`integrationId`→integration | — | |
385
+ | 170 | `service_integrations.integration` | `organizationId` | uuid | |
386
+ | 171 | `service_integrations.integrationSync` | `organizationId` | uuid | |
387
+ | 172 | `service_integrations.processedEvent` | `organizationId` | uuid | |
388
+ | 173 | `service_integrations.remoteSnapshot` | fk:`syncedObjectId`→syncedObject | uuid | |
389
+ | 174 | `service_integrations.syncedObject` | `organizationId` | uuid | staging-only |
390
+ | 175 | `service_invoices.balance` | `organizationId` | uuid | |
391
+ | 176 | `service_invoices.invoice` | `organizationId` | uuid | scrub: billingEmail, companyEmail; invoiceNumber:max-offset |
392
+ | 177 | `service_jumanji.experimentalAccountOverride` | `organizationId` | uuid | |
393
+ | 178 | `service_jumanji.experimentalAccountPlan` | `organizationId` | uuid | |
394
+ | 179 | `service_jumanji.experimentalInputToAccountingEventsRule` | `organizationId` | uuid | |
395
+ | 180 | `service_jumanji.experimentalRule` | `organizationId` | uuid | |
396
+ | 181 | `service_jumanji.experimentalSupplier` | `organizationId` | uuid | |
397
+ | 182 | `service_jumanji.testExample` | `organizationId` | uuid | |
398
+ | 183 | `service_ledger_onboarding.audit` | `organizationId` | uuid | |
399
+ | 184 | `service_ledger.account` | `companyId` | uuid | |
400
+ | 185 | `service_ledger.accountSpecificationRow` | `organizationId` | uuid | |
401
+ | 186 | `service_ledger.asset` | `organizationId` | uuid | |
402
+ | 187 | `service_ledger.balanceSheetAudit` | `organizationId` | uuid | |
403
+ | 188 | `service_ledger.balanceSheetAuditAccount` | `organizationId` | uuid | |
404
+ | 189 | `service_ledger.bookingTemplate` | `organizationId` | uuid | |
405
+ | 190 | `service_ledger.bookingTemplateGroup` | `organizationId` | uuid | |
406
+ | 191 | `service_ledger.bookingTemplateGroup_bookingTemplate` | fk:`bookingTemplateGroupId`→bookingTemplateGroup | uuid | |
407
+ | 192 | `service_ledger.bookingTemplateTemplateUsageLogEntry` | `organizationId` | uuid | |
408
+ | 193 | `service_ledger.bookingTemplateUsageLogEntry` | `organizationId` | uuid | |
409
+ | 194 | `service_ledger.bookingTemplateVariable` | `organizationId` | uuid | |
410
+ | 195 | `service_ledger.chartOfAccounts` | `organizationId` | uuid | |
411
+ | 196 | `service_ledger.chartOfAccountsAccount` | fk:`chartOfAccountsId`→chartOfAccounts | uuid | |
412
+ | 197 | `service_ledger.deferral` | `company_id` | uuid | |
413
+ | 198 | `service_ledger.deferred_verification` | fk:`deferral_id`→deferral | uuid | |
414
+ | 199 | `service_ledger.dimension` | `company_id` | uuid | |
415
+ | 200 | `service_ledger.dimension_object` | `company_id` | uuid | |
416
+ | 201 | `service_ledger.dimension_object__transaction` | fk:`dimension_object_id`→dimension_object | uuid | |
417
+ | 202 | `service_ledger.fiscal_year` | `company_id` | uuid | |
418
+ | 203 | `service_ledger.ledgerDocumentClassification` | `organizationId` | uuid | |
419
+ | 204 | `service_ledger.link` | `organizationId` | uuid | |
420
+ | 205 | `service_ledger.otherBookable` | `organizationId` | uuid | |
421
+ | 206 | `service_ledger.otherBookablePayment` | `organizationId` | uuid | |
422
+ | 207 | `service_ledger.receipt` | `organizationId` | uuid | |
423
+ | 208 | `service_ledger.salesInvoice` | `organizationId` | uuid | |
424
+ | 209 | `service_ledger.salesInvoicePayment` | `organizationId` | uuid | |
425
+ | 210 | `service_ledger.sieImport` | `organizationId` | uuid | |
426
+ | 211 | `service_ledger.supplierBookingTemplateTemplate` | `organizationId` | uuid | |
427
+ | 212 | `service_ledger.supplierInvoice` | `organizationId` | uuid | |
428
+ | 213 | `service_ledger.supplierInvoicePayment` | `organizationId` | uuid | |
429
+ | 214 | `service_ledger.transaction` | fk:`account_id`→account | uuid | |
430
+ | 215 | `service_ledger.verification` | `company_id` | uuid | |
431
+ | 216 | `service_ledger.verification_series` | `company_id` | uuid | |
432
+ | 217 | `service_ledger.voucherJournal` | `organizationId` | uuid | |
433
+ | 218 | `service_ledger.voucherJournalAsset` | `organizationId` | uuid | |
434
+ | 219 | `service_ledger.voucherJournalDeferral` | `organizationId` | uuid | |
435
+ | 220 | `service_ledger.voucherJournalLine` | fk:`voucherJournalId`→voucherJournal | uuid | |
436
+ | 221 | `service_matching_tasks.checks` | `organizationId` | uuid | |
437
+ | 222 | `service_matching_tasks.lastMatchedDocument` | `organizationId` | uuid | |
438
+ | 223 | `service_matching_tasks.lastMatchedTransaction` | `organizationId` | uuid | |
439
+ | 224 | `service_matching_tasks.matchingTask` | `organizationId` | uuid | |
440
+ | 225 | `service_notifications.notification` | `organizationId` | uuid | |
441
+ | 226 | `service_onboarding.status` | `organizationId` | uuid | |
442
+ | 227 | `service_onboarding.step` | `organizationId` | uuid | |
443
+ | 228 | `service_orders.order` | `organizationId` | uuid | |
444
+ | 229 | `service_organizations.organizationMeta` | `organizationId` | uuid | |
445
+ | 230 | `service_reconciliation.comment` | `organizationId` | uuid | |
446
+ | 231 | `service_reconciliation.reconciliation` | `organizationId` | uuid | |
447
+ | 232 | `service_representatives.representative` | `organizationId` | uuid | |
448
+ | 233 | `service_salaries_legacy.payroll` | `organizationId` | uuid | |
449
+ | 234 | `service_salaries_legacy.payrollStatus` | `organizationId` | uuid | |
450
+ | 235 | `service_salaries_legacy.salary` | `organizationId` | uuid | |
451
+ | 236 | `service_salaries_legacy.salaryAddon` | `organizationId` | uuid | |
452
+ | 237 | `service_salaries_legacy.salaryBenefit` | `organizationId` | uuid | |
453
+ | 238 | `service_salaries_legacy.salaryCarExpense` | `organizationId` | uuid | |
454
+ | 239 | `service_salaries_legacy.salaryException` | `organizationId` | uuid | |
455
+ | 240 | `service_salaries_legacy.salaryExpense` | `organizationId` | uuid | |
456
+ | 241 | `service_salaries_legacy.salarySpecification` | `organizationId` | uuid | |
457
+ | 242 | `service_salaries_legacy.salarySpecificationLine` | `organizationId` | uuid | |
458
+ | 243 | `service_salaries_legacy.salaryTravelAllowance` | `organizationId` | uuid | |
459
+ | 244 | `service_salaries.accumulatedBenefitValue` | fk:`employeeAccumulatedValuesId`→employeeAccumulatedValues | uuid | |
460
+ | 245 | `service_salaries.customSalaryLineSetting` | fk:`salarySettingId`→salarySetting | uuid | |
461
+ | 246 | `service_salaries.customSalaryLineToOrganization` | `organizationId` | uuid | |
462
+ | 247 | `service_salaries.employeeAccumulatedValues` | `organizationId` | uuid | |
463
+ | 248 | `service_salaries.employeeVacationDebt` | `organizationId` | uuid | |
464
+ | 249 | `service_salaries.employeeVacationDebtData` | fk:`employeeVacationDebtId`→employeeVacationDebt | uuid | |
465
+ | 250 | `service_salaries.employeeVacationDebtVariableEntry` | `organizationId` | uuid | |
466
+ | 251 | `service_salaries.organizationMigration` | `organizationId` | uuid | |
467
+ | 252 | `service_salaries.orgs` | `organizationId` | — | |
468
+ | 253 | `service_salaries.orgs_old` | `organizationId` | — | |
469
+ | 254 | `service_salaries.payeReport` | `organizationId` | uuid | |
470
+ | 255 | `service_salaries.payroll` | `organizationId` | uuid | |
471
+ | 256 | `service_salaries.payrollPeriod` | `organizationId` | — | staging-only |
472
+ | 257 | `service_salaries.payrollStatus` | `organizationId` | uuid | |
473
+ | 258 | `service_salaries.payrollSubmitTask` | fk:`payrollId`→payroll | uuid | |
474
+ | 259 | `service_salaries.payrollSubmitTaskError` | fk:`payrollId`→payroll | uuid | |
475
+ | 260 | `service_salaries.periodicTask` | `organizationId` | uuid | |
476
+ | 261 | `service_salaries.periodicTaskError` | fk:`taskId`→periodicTask | uuid | |
477
+ | 262 | `service_salaries.salary` | `organizationId` | uuid | |
478
+ | 263 | `service_salaries.salaryAddon` | `organizationId` | uuid | |
479
+ | 264 | `service_salaries.salaryBasicLine` | `organizationId` | uuid | |
480
+ | 265 | `service_salaries.salaryBenefit` | `organizationId` | uuid | |
481
+ | 266 | `service_salaries.salaryCarExpense` | `organizationId` | uuid | |
482
+ | 267 | `service_salaries.salaryException` | `organizationId` | uuid | |
483
+ | 268 | `service_salaries.salaryExpense` | `organizationId` | uuid | |
484
+ | 269 | `service_salaries.salaryLine` | `organizationId` | uuid | |
485
+ | 270 | `service_salaries.salarySetting` | `organizationId` | uuid | scrub: payeContactEmail, sendSalarySpecificationByEmail |
486
+ | 271 | `service_salaries.salarySettingsGroup` | `organizationId` | uuid | |
487
+ | 272 | `service_salaries.salarySpecification` | `organizationId` | uuid | scrub: employeeEmail |
488
+ | 273 | `service_salaries.salarySpecificationLine` | `organizationId` | uuid | |
489
+ | 274 | `service_salaries.salaryTravelAllowance` | `organizationId` | uuid | |
490
+ | 275 | `service_sales.customVendor` | `organizationId` | uuid | |
491
+ | 276 | `service_sales.invoice` | `organizationId` | uuid | |
492
+ | 277 | `service_sales.invoiceLine` | `organizationId` | uuid | |
493
+ | 278 | `service_sales.invoicePayment` | `organizationId` | uuid | |
494
+ | 279 | `service_sales.invoiceReminder` | `organizationId` | uuid | |
495
+ | 280 | `service_sales.paymentMethodSetting` | `organizationId` | uuid | |
496
+ | 281 | `service_sales.payout` | `organizationId` | uuid | |
497
+ | 282 | `service_sales.payoutFee` | `organizationId` | uuid | |
498
+ | 283 | `service_sales.revenueVendorSetting` | `organizationId` | uuid | |
499
+ | 284 | `service_sales.salesArea` | `organizationId` | uuid | |
500
+ | 285 | `service_sales.salesAreaProcess` | `organizationId` | uuid | |
501
+ | 286 | `service_sales.salesAreaProcess_salesSystem` | `organizationId` | uuid | |
502
+ | 287 | `service_sales.salesReportDocument` | `organizationId` | uuid | |
503
+ | 288 | `service_sales.salesReportDocumentLink` | `organizationId` | uuid | |
504
+ | 289 | `service_sales.salesReportSettings` | `organizationId` | uuid | scrub: responsibleEmailAddress |
505
+ | 290 | `service_sales.salesSettings` | `organizationId` | uuid | |
506
+ | 291 | `service_sales.salesSystem` | `organizationId` | uuid | |
507
+ | 292 | `service_sales.salesSystemLink` | `organizationId` | uuid | |
508
+ | 293 | `service_sales.scheduledDynamicQuestionAuditLog` | `organizationId` | uuid | |
509
+ | 294 | `service_sales.verificationVendorSetting` | `organizationId` | uuid | |
510
+ | 295 | `service_scheduled_tasks.scheduledTask` | `taskOrganizationId` | uuid | |
511
+ | 296 | `service_shareholder_registers.shareholder` | `organizationId` | uuid | |
512
+ | 297 | `service_shareholder_registers.shareholderK10` | `organizationId` | uuid | |
513
+ | 298 | `service_shareholder_registers.shareholderRegister` | `organizationId` | uuid | |
514
+ | 299 | `service_shareholder_registers.shareholderRegisterEvent` | `organizationId` | uuid | |
515
+ | 300 | `service_shareholder_registers.shareholderRegisterRow` | `organizationId` | uuid | |
516
+ | 301 | `service_shareholder_registers.shareholderRegisterSeries` | `organizationId` | uuid | |
517
+ | 302 | `service_suppliers.bookingTag` | `organizationId` | uuid | |
518
+ | 303 | `service_suppliers.customAttribute` | `organizationId` | uuid | |
519
+ | 304 | `service_suppliers.supplierDocument` | `organizationId` | uuid | |
520
+ | 305 | `service_suppliers.supplierRelation` | `organizationId` | uuid | |
521
+ | 306 | `service_suppliers.supplierTransaction` | `organizationId` | uuid | |
522
+ | 307 | `service_task_engine.assignee` | fk:`taskId`→task | uuid | |
523
+ | 308 | `service_task_engine.assignment` | fk:`taskId`→task | uuid | |
524
+ | 309 | `service_task_engine.deadline` | fk:`taskId`→task | uuid | |
525
+ | 310 | `service_task_engine.link` | fk:`taskId`→task | uuid | |
526
+ | 311 | `service_task_engine.pause` | fk:`taskId`→task | uuid | |
527
+ | 312 | `service_task_engine.result` | fk:`taskId`→task | uuid | |
528
+ | 313 | `service_task_engine.task` | `organizationId` | uuid | uniquenessKey:text-suffix |
529
+ | 314 | `service_task_engine.taskAction` | fk:`taskId`→task | uuid | |
530
+ | 315 | `service_task_engine.taskDependency` | fk:`taskId`→task | uuid | |
531
+ | 316 | `service_task_engine.taskEvent` | fk:`taskId`→task | uuid | |
532
+ | 317 | `service_task_engine.taskEventAction` | `organizationId` | uuid | active-task filter |
533
+ | 318 | `service_task_engine.workOrder` | `organizationId` | uuid | |
534
+ | 319 | `service_task_feeder.failedTaskAssignment` | fk:`taskBucketTaskId`→taskBucketTask | uuid | |
535
+ | 320 | `service_task_feeder.taskBucketTask` | `organizationId` | uuid | |
536
+ | 321 | `service_taxonomies.reportInstance` | `organizationId` | uuid | |
537
+ | 322 | `service_taxonomies.reportInstanceElement` | `organizationId` | uuid | |
538
+ | 323 | `service_vat_report.vatReport` | `organizationId` | uuid | |
539
+ | 324 | `service_vat_report.vatReportLine` | fk:`vatReportId`→vatReport | uuid | |
540
+ | 325 | `service_voice.call` | `customerOrganizationId` | uuid | |
541
+ | 326 | `service_workflow.eventNode` | `organizationId` | uuid | |
542
+ | 327 | `service_workflow.mergeNode` | fk:`runId`→run | uuid | |
543
+ | 328 | `service_workflow.run` | `organizationId` | uuid | |
544
+ | 329 | `service_workflow.runJob` | `organizationId` | uuid | |
545
+
546
+ ## Appendix B — the 22 tables NOT copied (with reason)
547
+
548
+ | table | reason |
549
+ |---|---|
550
+ | `service_accounts.bankIdSession` | BankID auth sessions — security, user-specific, transient. |
551
+ | `service_accounts.companies` | Org identity row already exists in the target; keep it, remap org id. |
552
+ | `service_accounts.companies2users` | Owner/user links — rule #4: keep the target account owners, do not copy users. |
553
+ | `service_accounts.company_types` | No owner-filter column found (only company_types_pk:nonOrg) — cannot scope rows to the org. Review before enabling. |
554
+ | `service_accounts.invites` | User invites — user/onboarding data, not part of accounting state. |
555
+ | `service_accounts.notification_settings` | No owner-filter column found (only fk_companies2users_pk:nonOrg) — cannot scope rows to the org. Review before enabling. |
556
+ | `service_accounts.partnerTeamOrganization` | No owner-filter column found (only partnerOrganizationId:crossOrg) — cannot scope rows to the org. Review before enabling. |
557
+ | `service_accounts.signup` | Signup/onboarding user data. |
558
+ | `service_accounts.signupConnectAccount` | Signup/onboarding user data. |
559
+ | `service_accounts.tokenInvalidation` | Auth token state — security, user-specific. |
560
+ | `service_accounts.user_privileges` | Per-user privileges — user data, kept as-is on target. |
561
+ | `service_accounts.user_roles_at_companies` | User role assignments — user data, kept as-is on target. |
562
+ | `service_accounts.user_roles_at_companies_privileges` | User role privileges — user data, kept as-is on target. |
563
+ | `service_documents.duplicateInfo` | Derived/rebuildable duplicate-detection metadata; a CHECK constraint (documentIds_ordered: documentId1<documentId2) is broken when both uuids are remapped and their order flips. Rebuilt by the dedup pass. (2026-08-26) |
564
+ | `service_institution_browser.browserNotification` | Excluded by Dennis/Jimmy: bank-scraping/institution data, security-sensitive, irrelevant to AI booking. |
565
+ | `service_institution_browser.institution_browser_run_logs` | Excluded by Dennis/Jimmy: bank-scraping/institution data, security-sensitive, irrelevant to AI booking. |
566
+ | `service_institution_browser.skatteverket_sync_run_company` | Excluded by Dennis/Jimmy: bank-scraping/institution data, security-sensitive, irrelevant to AI booking. |
567
+ | `service_outgoing_email.email` | Outbound email queue — avoid any send from a test org; historical, not accounting state. |
568
+ | `service_search.searchIndex` | Derived/rebuildable full-text search index; its composite (entityType, entityId) unique would collide in a same-DB copy and it rebuilds itself from the copied source data. (Jimmy 2026-08-24) |
569
+ | `service_task_engine.task__history` | Audit/history mirror — not source-of-truth state, regenerated by history triggers, large/slow to copy. |
570
+ | `service_task_engine.taskEventAction__history` | Audit/history mirror — not source-of-truth state, regenerated by history triggers, large/slow to copy. |
571
+ | `service_task_engine.workOrder__history` | Audit/history mirror — not source-of-truth state, regenerated by history triggers, large/slow to copy. |