@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.
- package/bin/index.js +7 -0
- package/discovery-router/Dockerfile +18 -0
- package/discovery-router/README.md +99 -0
- package/discovery-router/package.json +13 -0
- package/discovery-router/registry.example.json +5 -0
- package/discovery-router/server.js +272 -0
- package/lib/account.js +120 -0
- package/lib/auth-dev-keys.js +12 -0
- package/lib/bankid.js +130 -0
- package/lib/cli.js +27 -0
- package/lib/command/bankid.js +45 -0
- package/lib/command/customer.js +108 -0
- package/lib/command/db.js +156 -0
- package/lib/command/env.js +114 -0
- package/lib/command/logs.js +143 -0
- package/lib/command/measure.js +81 -0
- package/lib/command/service.js +166 -0
- package/lib/command/setup.js +92 -0
- package/lib/command/shred.js +60 -0
- package/lib/compose/README.md +98 -0
- package/lib/compose/generate.js +375 -0
- package/lib/compose/manifests.js +108 -0
- package/lib/db/roles.js +118 -0
- package/lib/discovery/client.js +40 -0
- package/lib/engine/GUIDE.md +176 -0
- package/lib/engine/PROCESS.md +571 -0
- package/lib/engine/dbbuild.js +325 -0
- package/lib/engine/gen-schema-map.js +479 -0
- package/lib/engine/purge.js +137 -0
- package/lib/engine/schema-map.json +11016 -0
- package/lib/engine/seed.js +1045 -0
- package/lib/obc.js +72 -0
- package/lib/registry.js +123 -0
- package/lib/runtime.js +101 -0
- package/lib/service-token.js +40 -0
- package/lib/shred/README.md +118 -0
- package/lib/shred/audit.js +128 -0
- package/lib/shred/faker.js +545 -0
- package/lib/shred/index.js +126 -0
- package/lib/shred/scripts/base-partner-emails.sql +9 -0
- package/lib/shred/scripts/dev-accounts.sql +195 -0
- package/lib/shred/scripts/emails.sql +48 -0
- package/lib/shred/scripts/institution-browser.sql +3 -0
- package/lib/shred/scripts/notification-targets.sql +5 -0
- package/lib/shred/scripts/partners.sql +2 -0
- package/lib/shred/scripts/passwords.sql +8 -0
- package/lib/shred/scripts/personal-numbers.sql +177 -0
- package/lib/shred/scripts/phone-numbers.sql +22 -0
- package/lib/shred/scripts/salary-spec-reports.sql +5 -0
- package/lib/shred/scripts/service-activity-tracker-data.sql +4 -0
- package/lib/shred/scripts/service-core-objects.sql +19 -0
- package/lib/shred/scripts/service-event-stream.sql +2 -0
- package/lib/shred/scripts/service-integrations.sql +4 -0
- package/lib/shred/scripts/template.sql +4 -0
- package/lib/shred/scripts/x-service-billing.sql +34 -0
- package/lib/shred/scripts/xxx-history-tables.sql +25 -0
- package/lib/stub.js +8 -0
- package/local-postgres/Dockerfile +11 -0
- package/package.json +46 -0
- 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. |
|