Commit Graph
3 Commits
Author SHA1 Message Date
c40e63e3f7 fix(parties): skip companies frozen by a migration reset in the party backfill (#2176)
* fix(parties): skip companies frozen by a migration reset in the party backfill

Second prod failure of the substrate backfill: suppliers and customers in
a company archived by a migration reset are immutable
(block_migration_reset_source_mutation), so even setting party_id is
refused. Nine suppliers and eleven customers on prod. They are skipped;
the archive stays untouched by design.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(parties): pin why the party backfill skips migration-reset archives

A supplier in a company archived by a migration reset cannot take a
party_id: block_migration_reset_source_mutation refuses the UPDATE. The
backfill skip rule exists because of this trigger.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci(pg-upgrade): seed the rows that broke the party backfill on prod

A supplier and a customer with an empty name, and a company archived by a
migration reset whose rows are immutable. The substrate migration passed
the upgrade job and then failed twice on prod for exactly these shapes;
any migration that updates every supplier or customer now meets them in
CI first. The archived company is guarded on the table existing at the
merge-base.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 18:14:32 +02:00
a24982463b fix(parties): skip nameless suppliers and customers in the party backfill (#2174)
* fix(parties): skip nameless suppliers and customers in the party backfill

The substrate migration failed on prod at the backfill: three rows (one
supplier, two customers) have an empty name and ensure_party refuses a
nameless party. The file never applied there, so it is corrected in place
rather than chased with a migration that could not run before it. Rows
without a name keep party_id NULL; the suggestion pipeline names them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parties): ensure_party writes only under the caller's own identity

Authenticated callers must pass their own user id; the service role
(auth.uid() NULL: migrations, MCP, cron) may act for another user. Same
guard as apply_party_suggestions and decide_parties. Superagent P2 on
#2172.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 17:57:34 +02:00
daeab67dca feat(parties): phase 1 substrate, one party per counterpart (#2162)
* feat(parties): phase 1 substrate, one party per counterpart

Adds the identity layer above customers and suppliers, which keep their
tables and every foreign key and gain a nullable party_id.

- parties: company-scoped identity with status (suggested | confirmed),
  kind, alias keys, origin and merged_into. One live party per org number
  and company, enforced by a partial unique index; merged losers leave the
  index so a merge can be undone. This is the unique key the
  duplicate-invoice guard has lacked, since suppliers never had one.
- party_facts: statements with a source, a rank (preferred | normal |
  deprecated) and two time axes, never overwritten.
- party_identities: bankgiro, plusgiro, IBAN and friends per party, with
  seen and paid counts and a known | unverified status.
- party_decisions: every human action on a party as a labelled example.
- normalize_org_number(text): SQL mirror of lib/invariants/org-number.ts
  (strip separators, drop the century on 12 digits, Luhn check, 10 digits).
- ensure_party(): find by org number inside the company, else create.
  Name-only rows never merge at insert time; a name merge is a recorded
  human decision.
- Backfill: one party per existing supplier and customer, merged on org
  number, suppliers first so both roles land on one party.
- Archive contract: the four tables are master data in the full archive.

Observed parties (keys derived from voucher and bank text) are not stored;
they stay computed by the ledger-context RPC. No posted entry is touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(parties): tenant-safe composite foreign keys on every party link

Facts, identities, decisions, customers.party_id, suppliers.party_id and
parties.merged_into now reference parties(id, company_id), so a row can
only point at a party in its own company. ON DELETE SET NULL names
party_id so role rows keep their company_id. Adds a pg-real test that
rejects every cross-company link and checks company_id survives a party
delete.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 17:43:06 +02:00