@accounter/server
Version:
Accounter GraphQL server
771 lines (672 loc) • 471 kB
Markdown
# @accounter/server
## 0.2.0-alpha-20260723123038-e4259efcbe5b68fcd11939a1a7f40b694d568acb
### Minor Changes
- [#3851](https://github.com/Urigo/accounter-fullstack/pull/3851)
[`a91e4a0`](https://github.com/Urigo/accounter-fullstack/commit/a91e4a0726fed53dda22dfb57b6919411bbeaba3)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Add the Charge Matching Review Screen: a
guided side-by-side UI for pairing document-based and transaction-based charges and merging them
one by one.
- Server: new `chargesAwaitingMatchQueue` GraphQL query that returns a paginated queue of
unmatched base charges with on-the-fly match suggestions, filterable by business, date range and
mode (`DOC_BASE`/`TRANSACTION_BASE`) and sortable `BY_DATE` or `BY_SCORE` (score evaluation
capped at the 100 most recent unmatched charges).
- Client: new `/charges/matching` screen with a filter/sort header, collapsible queue sidebar with
per-item pending/matched/skipped status, side-by-side base-vs-suggestion comparison cards,
alternative-suggestion switching, and accept (merge) / skip actions.
- [#3761](https://github.com/Urigo/accounter-fullstack/pull/3761)
[`837d95c`](https://github.com/Urigo/accounter-fullstack/commit/837d95c0985ade04cf31cc7f956d409a1a7463e5)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Deprecate the legacy gmail-listener email
path now that the v2 multi-tenant `@accounter/email-ingestion-gateway` pipeline (Cloudflare →
Gateway → Server) covers it.
- The server GraphQL fields `businessEmailConfig` (query) and `insertEmailDocuments` (mutation)
are marked `@deprecated` (non-breaking — the legacy listener keeps working for rollback) and
scheduled for removal after cutover. Their resolvers carry matching `@deprecated` JSDoc.
- `@accounter/gmail-listener` is marked deprecated (README banner + package description) and
superseded by the gateway. It is kept only for rollback during the cutover.
- Docs updated to acknowledge the migration (root `CLAUDE.md`, `packages/server/CLAUDE.md`).
The `gmail_listener` auth role and the package itself are intentionally retained for rollback;
hard removal (and `npm deprecate` of the published package) is a follow-up once the cutover is
complete.
### Patch Changes
- [#3763](https://github.com/Urigo/accounter-fullstack/pull/3763)
[`606c8d7`](https://github.com/Urigo/accounter-fullstack/commit/606c8d7ba94f272659341b4baadd03593d363d5e)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency [`auth0@5.13.0` ↗︎](https://www.npmjs.com/package/auth0/v/5.13.0) (from
`5.12.0`, in `dependencies`)
- [#3768](https://github.com/Urigo/accounter-fullstack/pull/3768)
[`7a7dc71`](https://github.com/Urigo/accounter-fullstack/commit/7a7dc71ba2dc9a660f150340f2352704d571f367)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@3.0.86` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/3.0.86) (from
`3.0.85`, in `dependencies`)
- Updated dependency [`ai@6.0.209` ↗︎](https://www.npmjs.com/package/ai/v/6.0.209) (from `6.0.208`,
in `dependencies`)
- Updated dependency [`playwright@1.61.1` ↗︎](https://www.npmjs.com/package/playwright/v/1.61.1)
(from `1.61.0`, in `dependencies`)
- [#3776](https://github.com/Urigo/accounter-fullstack/pull/3776)
[`b3761d9`](https://github.com/Urigo/accounter-fullstack/commit/b3761d9fa853bd7bb26b41796fdff96af5f45863)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@3.0.87` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/3.0.87) (from
`3.0.86`, in `dependencies`)
- Updated dependency [`ai@6.0.210` ↗︎](https://www.npmjs.com/package/ai/v/6.0.210) (from `6.0.209`,
in `dependencies`)
- [#3778](https://github.com/Urigo/accounter-fullstack/pull/3778)
[`02dc27e`](https://github.com/Urigo/accounter-fullstack/commit/02dc27e172538c66f4f9af228ea2d419d0e81141)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.1` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.1) (from
`3.0.86`, in `dependencies`)
- Updated dependency [`ai@7.0.2` ↗︎](https://www.npmjs.com/package/ai/v/7.0.2) (from `6.0.209`, in
`dependencies`)
- [#3789](https://github.com/Urigo/accounter-fullstack/pull/3789)
[`e26256c`](https://github.com/Urigo/accounter-fullstack/commit/e26256c2e4cd3ba51042446a4617b996d49a9e0c)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.4` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.4) (from
`4.0.1`, in `dependencies`)
- Updated dependency
[`@graphql-tools/utils@11.1.1` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/11.1.1)
(from `11.1.0`, in `dependencies`)
- Updated dependency [`ai@7.0.9` ↗︎](https://www.npmjs.com/package/ai/v/7.0.9) (from `7.0.2`, in
`dependencies`)
- [#3790](https://github.com/Urigo/accounter-fullstack/pull/3790)
[`1f016b8`](https://github.com/Urigo/accounter-fullstack/commit/1f016b8739573311dc0d56ee4fe1a3570ba65428)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.1` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.1) (from
`3.0.87`, in `dependencies`)
- Updated dependency [`ai@7.0.2` ↗︎](https://www.npmjs.com/package/ai/v/7.0.2) (from `6.0.210`, in
`dependencies`)
- [#3825](https://github.com/Urigo/accounter-fullstack/pull/3825)
[`cc16f94`](https://github.com/Urigo/accounter-fullstack/commit/cc16f9472b79c3991d1b8fd186a1a75d2b9e26e1)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@opentelemetry/exporter-trace-otlp-http@0.220.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.220.0)
(from `0.219.0`, in `dependencies`)
- Updated dependency
[`@opentelemetry/resources@2.9.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/resources/v/2.9.0)
(from `2.8.0`, in `dependencies`)
- Updated dependency
[`@opentelemetry/sdk-node@0.220.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.220.0)
(from `0.219.0`, in `dependencies`)
- [#3832](https://github.com/Urigo/accounter-fullstack/pull/3832)
[`bf8ed3b`](https://github.com/Urigo/accounter-fullstack/commit/bf8ed3b88f5e2abdb045361b7d33ead0a10c352f)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@opentelemetry/auto-instrumentations-node@0.78.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.78.0)
(from `0.77.0`, in `dependencies`)
- [#3834](https://github.com/Urigo/accounter-fullstack/pull/3834)
[`77222b7`](https://github.com/Urigo/accounter-fullstack/commit/77222b7052ffb094db5df5c9065ed93e9dc21bf2)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.7` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.7) (from
`4.0.4`, in `dependencies`)
- Updated dependency [`ai@7.0.14` ↗︎](https://www.npmjs.com/package/ai/v/7.0.14) (from `7.0.9`, in
`dependencies`)
- [#3836](https://github.com/Urigo/accounter-fullstack/pull/3836)
[`059b638`](https://github.com/Urigo/accounter-fullstack/commit/059b638702116701a5a0062a7ac3a58a28eeb004)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.7` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.7) (from
`4.0.4`, in `dependencies`)
- Updated dependency
[`@opentelemetry/auto-instrumentations-node@0.78.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.78.0)
(from `0.77.0`, in `dependencies`)
- Updated dependency [`ai@7.0.14` ↗︎](https://www.npmjs.com/package/ai/v/7.0.14) (from `7.0.9`, in
`dependencies`)
- [#3837](https://github.com/Urigo/accounter-fullstack/pull/3837)
[`465eefe`](https://github.com/Urigo/accounter-fullstack/commit/465eefe4062076ecdf9f0a447637ddc797039572)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency
[`@opentelemetry/exporter-trace-otlp-http@0.220.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.220.0)
(from `0.219.0`, in `dependencies`)
- Updated dependency
[`@opentelemetry/resources@2.9.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/resources/v/2.9.0)
(from `2.8.0`, in `dependencies`)
- Updated dependency
[`@opentelemetry/sdk-node@0.220.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.220.0)
(from `0.219.0`, in `dependencies`)
- [#3846](https://github.com/Urigo/accounter-fullstack/pull/3846)
[`d382eb2`](https://github.com/Urigo/accounter-fullstack/commit/d382eb20cf27b76ef80d235080bead1ba66b1e77)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.8` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.8) (from
`4.0.7`, in `dependencies`)
- Updated dependency
[`@graphql-hive/yoga@0.48.2` ↗︎](https://www.npmjs.com/package/@graphql-hive/yoga/v/0.48.2) (from
`0.48.1`, in `dependencies`)
- Updated dependency [`ai@7.0.16` ↗︎](https://www.npmjs.com/package/ai/v/7.0.16) (from `7.0.14`, in
`dependencies`)
- Updated dependency [`iconv-lite@0.7.3` ↗︎](https://www.npmjs.com/package/iconv-lite/v/0.7.3)
(from `0.7.2`, in `dependencies`)
- [#3853](https://github.com/Urigo/accounter-fullstack/pull/3853)
[`a17149a`](https://github.com/Urigo/accounter-fullstack/commit/a17149a90ce064eae93bb88143732174e5875c4c)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@graphql-tools/utils@11.2.0` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/11.2.0)
(from `11.1.1`, in `dependencies`)
- [#3866](https://github.com/Urigo/accounter-fullstack/pull/3866)
[`dd31186`](https://github.com/Urigo/accounter-fullstack/commit/dd311865528c83422d9c5805ebe75005260b765c)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@opentelemetry/semantic-conventions@1.42.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.42.0)
(from `1.41.1`, in `dependencies`)
- [#3872](https://github.com/Urigo/accounter-fullstack/pull/3872)
[`a809750`](https://github.com/Urigo/accounter-fullstack/commit/a809750d9028f7a3305d4e5fccc1be247341fac3)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.9` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.9) (from
`4.0.8`, in `dependencies`)
- Updated dependency [`ai@7.0.17` ↗︎](https://www.npmjs.com/package/ai/v/7.0.17) (from `7.0.16`, in
`dependencies`)
- [#3873](https://github.com/Urigo/accounter-fullstack/pull/3873)
[`246b1e4`](https://github.com/Urigo/accounter-fullstack/commit/246b1e4d41a6424c4b9f251ff0b8cf36c774ab30)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.10` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.10) (from
`4.0.8`, in `dependencies`)
- Updated dependency
[`@opentelemetry/semantic-conventions@1.43.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.43.0)
(from `1.42.0`, in `dependencies`)
- Updated dependency [`ai@7.0.18` ↗︎](https://www.npmjs.com/package/ai/v/7.0.18) (from `7.0.16`, in
`dependencies`)
- Updated dependency [`auth0@5.14.0` ↗︎](https://www.npmjs.com/package/auth0/v/5.14.0) (from
`5.13.0`, in `dependencies`)
- [#3885](https://github.com/Urigo/accounter-fullstack/pull/3885)
[`41b8dc2`](https://github.com/Urigo/accounter-fullstack/commit/41b8dc269929e2e7132ea1d043a6eb227de916ba)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.10` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.10) (from
`4.0.9`, in `dependencies`)
- Updated dependency [`ai@7.0.18` ↗︎](https://www.npmjs.com/package/ai/v/7.0.18) (from `7.0.17`, in
`dependencies`)
- [#3906](https://github.com/Urigo/accounter-fullstack/pull/3906)
[`8f87877`](https://github.com/Urigo/accounter-fullstack/commit/8f87877fc2aec3a37d1b61836d2454803626ef58)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@opentelemetry/semantic-conventions@1.43.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.43.0)
(from `1.42.0`, in `dependencies`)
- [#3909](https://github.com/Urigo/accounter-fullstack/pull/3909)
[`8458c4a`](https://github.com/Urigo/accounter-fullstack/commit/8458c4aa5b7b6a3733360553fc056882a77e6e7b)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency [`auth0@5.14.0` ↗︎](https://www.npmjs.com/package/auth0/v/5.14.0) (from
`5.13.0`, in `dependencies`)
- [#3913](https://github.com/Urigo/accounter-fullstack/pull/3913)
[`6d29a1e`](https://github.com/Urigo/accounter-fullstack/commit/6d29a1ecfc84e1519165e7775aaa90ed3cb4071f)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.12` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.12) (from
`4.0.10`, in `dependencies`)
- Updated dependency [`ai@7.0.22` ↗︎](https://www.npmjs.com/package/ai/v/7.0.22) (from `7.0.18`, in
`dependencies`)
- [#3928](https://github.com/Urigo/accounter-fullstack/pull/3928)
[`ee7df50`](https://github.com/Urigo/accounter-fullstack/commit/ee7df5089190a89094aa36013ab9d1d02828c096)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.14` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.14) (from
`4.0.12`, in `dependencies`)
- Updated dependency [`ai@7.0.26` ↗︎](https://www.npmjs.com/package/ai/v/7.0.26) (from `7.0.22`, in
`dependencies`)
- [#3936](https://github.com/Urigo/accounter-fullstack/pull/3936)
[`5b2ffbb`](https://github.com/Urigo/accounter-fullstack/commit/5b2ffbbec76719fb0b6cd17dbe535755a830287d)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.15` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.15) (from
`4.0.14`, in `dependencies`)
- Updated dependency [`ai@7.0.28` ↗︎](https://www.npmjs.com/package/ai/v/7.0.28) (from `7.0.26`, in
`dependencies`)
- [#3943](https://github.com/Urigo/accounter-fullstack/pull/3943)
[`e61ed55`](https://github.com/Urigo/accounter-fullstack/commit/e61ed5561353766e72c180a0da5cd6c8b0a25d99)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency [`auth0@6.0.0` ↗︎](https://www.npmjs.com/package/auth0/v/6.0.0) (from
`5.14.0`, in `dependencies`)
- [#3955](https://github.com/Urigo/accounter-fullstack/pull/3955)
[`bdcc700`](https://github.com/Urigo/accounter-fullstack/commit/bdcc7001ddaa78524310952919ebf30d331447eb)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency [`ai@7.0.29` ↗︎](https://www.npmjs.com/package/ai/v/7.0.29) (from `7.0.28`, in
`dependencies`)
- [#3964](https://github.com/Urigo/accounter-fullstack/pull/3964)
[`49cd281`](https://github.com/Urigo/accounter-fullstack/commit/49cd281e9aa63832cd1eda409472d6074ad83ba4)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - dependencies updates:
- Updated dependency [`ai@7.0.29` ↗︎](https://www.npmjs.com/package/ai/v/7.0.29) (from `7.0.28`, in
`dependencies`)
- Updated dependency [`auth0@6.0.0` ↗︎](https://www.npmjs.com/package/auth0/v/6.0.0) (from
`5.14.0`, in `dependencies`)
- [#3965](https://github.com/Urigo/accounter-fullstack/pull/3965)
[`24cb271`](https://github.com/Urigo/accounter-fullstack/commit/24cb271b6c7a62d1e97e6e591fdf0c9bf4863e95)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@4.0.16` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/4.0.16) (from
`4.0.15`, in `dependencies`)
- Updated dependency
[`@graphql-tools/utils@11.2.2` ↗︎](https://www.npmjs.com/package/@graphql-tools/utils/v/11.2.2)
(from `11.2.0`, in `dependencies`)
- Updated dependency [`ai@7.0.31` ↗︎](https://www.npmjs.com/package/ai/v/7.0.31) (from `7.0.29`, in
`dependencies`)
- [#3979](https://github.com/Urigo/accounter-fullstack/pull/3979)
[`e3c844b`](https://github.com/Urigo/accounter-fullstack/commit/e3c844b49da2a7bec4094c7ed8f5052fe71504dc)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Exclude charge types that never require
document matching from the awaiting-match queue. Only COMMON, BUSINESS_TRIP and untyped charges
(which resolve to one of those) are expected to hold both documents and transactions;
BANK_DEPOSIT, CREDITCARD_BANK, DIVIDEND, FOREIGN_SECURITIES, INTERNAL, VAT, PAYROLL, CONVERSION
and FINANCIAL charges are now skipped.
- [#3983](https://github.com/Urigo/accounter-fullstack/pull/3983)
[`8ce1562`](https://github.com/Urigo/accounter-fullstack/commit/8ce1562f19af6cf35ec36a9a9c1be85d8e62f9f0)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Score charge-match candidates in parallel.
`findMatches` previously scored candidates in a sequential `await` loop, which serialized the
per-candidate client and issued-document-status DataLoader lookups inside `scoreMatch` into an
N+1. Scoring now runs with `Promise.all` so those loads batch. Ordering is unaffected (results are
sorted afterwards), so match output is unchanged.
- [#3982](https://github.com/Urigo/accounter-fullstack/pull/3982)
[`b97de0e`](https://github.com/Urigo/accounter-fullstack/commit/b97de0e153fcfa35ef598c9a99a908a934afc0ec)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Speed up the `chargesAwaitingMatchQueue`
query by building the match candidate pool once per request instead of once per queued charge.
`ChargesMatcherProvider` now exposes a batch `findMatchesForCharges`, which loads and classifies
the shared candidate pool a single time and scores every source charge against it in memory (the
per-source ±12-month window is still applied in-memory, so results are unchanged). This removes
the previous per-charge re-query and re-hydration of the candidate pool — the dominant cost,
especially for `BY_SCORE` which evaluated up to 100 charges.
Candidate charges are also filtered by `chargeRequiresMatch` before hydration, so charge types
that never require a document match (e.g. `BANK_DEPOSIT`, `CREDITCARD_BANK`, `VAT`) are no longer
loaded or offered as suggestions — mirroring the same filter already applied to the queue's source
charges.
- [#3816](https://github.com/Urigo/accounter-fullstack/pull/3816)
[`6963a16`](https://github.com/Urigo/accounter-fullstack/commit/6963a166897f794d86fad9bed173a31879e3e0db)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Enrich the charges `freeText` filter
(`allCharges` query) so it matches more than just text descriptions. In addition to the existing
`user_description` / transaction / document description matches, the `search_matches` CTE in
`ChargesProvider.getChargesByFilters` now also matches:
- **Amounts** — transaction `amount` and document `total_amount` / `vat_amount`. Both the raw and
`ABS()` values are compared so signed and unsigned inputs match. Thousands separators are
stripped from the search term (via a resolver-computed `freeTextNumeric` param) so both
`1,234.56` and `1234.56` match the plain value stored in the DB.
- **Counterparty business names** — by joining `financial_entities` on the business ids referenced
by the charge's transactions (`business_id`) and documents (`creditor_id`, `debtor_id`). The
document match is split into separate creditor/debtor `UNION` branches so the planner can use
the foreign-key indexes.
- [#3794](https://github.com/Urigo/accounter-fullstack/pull/3794)
[`ffcbb95`](https://github.com/Urigo/accounter-fullstack/commit/ffcbb9576241d6c31f4db2461b1ec12ca85b8b94)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Contract-generated document descriptions
now reflect the billing cycle: monthly contracts keep the billed-month label (`… - May 2026`),
while annual contracts use the contract's start & end dates
(`… January 15th, 2025 → January 14th, 2026`). Also parse `issueMonth` as local midnight to avoid
an off-by-one-month shift in timezones west of UTC.
- [#3850](https://github.com/Urigo/accounter-fullstack/pull/3850)
[`8d765c8`](https://github.com/Urigo/accounter-fullstack/commit/8d765c883a4457b2f22de5be0f95d0bd457d24f7)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - **Conversion charges with multiple
transactions per side**: conversion ledger generation no longer requires exactly two main
transactions. A single conversion can now be split across two or more transactions on the base or
quote side (for example two ILS credits against one USD debit).
- Main transactions are grouped by sign; each side is validated to be single-currency and the two
sides to differ in currency, with failures recorded as ledger errors instead of aborting
generation.
- Each side is aggregated into one representative entry (`aggregateConversionSideEntries`) that
feeds the existing fee and exchange-revaluation calculations, so the classic two-transaction
case is unchanged.
- [#3863](https://github.com/Urigo/accounter-fullstack/pull/3863)
[`35b1125`](https://github.com/Urigo/accounter-fullstack/commit/35b112532514b1447d615826943d192ed8a0d19f)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Add a "Pull Deel Documents" button to the
user nav menu, next to "Add Balance Charge", that triggers the `fetchDeelDocuments` mutation on
demand.
Also improves the underlying Deel invoice fetch/matching logic:
- Distinguishes newly-seen invoices from previously-fetched invoices that are still unmatched, so
a later run can pick up a payment match for an invoice it already recorded (via a new
`updateDeelInvoiceRecords` update path) instead of only ever inserting.
- Reuses an already-matched charge for a given receipt when creating charges from payment
breakdowns, avoiding duplicate charges for invoices that share a receipt.
- Extends the Deel invoice schema/table with `billing_type` and `document_type`, and refines
several payment-receipt and payment-breakdown Zod schemas to match the Deel API more closely.
- [#3873](https://github.com/Urigo/accounter-fullstack/pull/3873)
[`246b1e4`](https://github.com/Urigo/accounter-fullstack/commit/246b1e4d41a6424c4b9f251ff0b8cf36c774ab30)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **New helper function
`isSelfIssuedSenderEvidence()`** in `email-ingestion-issuer.helper.ts`: Detects when an email's
only determinable issuer is a known provider (Morning, Sumit) or the tenant's own forwarding
address, indicating no external counterparty. Reuses existing `selectIssuerEmail()` logic to
ensure consistency with issuer resolution.
- **Self-issued binding in control resolver** (`email-ingestion-control.resolver.ts`): When
`isSelfIssuedSenderEvidence()` returns true, the grant's `businessId` is set to the tenant's own
ID instead of the recognized business. This signals to the ingest step that the issuer is the
tenant itself. Also returns `null` for `businessEmailConfig` to spare the gateway needless
document work (link-fetch, body→PDF conversion).
- **Early short-circuit in ingest provider** (`email-ingestion-ingest.provider.ts`): After grant
validation, checks if `grant.businessId === tenantId`. If true, returns `DUPLICATE` outcome with
new `SELF_ISSUED` reason code, skipping all document processing (Cloudinary upload, OCR, dedup
queries, insert queries). Runs before `prepareDocuments()` to avoid unnecessary work.
- **New `IngestReasonCode.SELF_ISSUED`** constant in both `contracts.ts` files (server and
gateway): Distinguishes self-issued skips from content re-deliveries in duplicate outcomes.
- **Comprehensive test coverage**: Added test suites for the new helper function and ingest
behavior, verifying that self-issued emails are detected correctly, grants are consumed, and no
document work or database writes occur.
- [#3938](https://github.com/Urigo/accounter-fullstack/pull/3938)
[`2a0c041`](https://github.com/Urigo/accounter-fullstack/commit/2a0c041b0dc6104039304598f932e7990e84c2c0)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Automatically degrade a charge's accountant
approval (`APPROVED` → `PENDING`) whenever its underlying data changes, so an already-approved
charge is re-flagged for accountant review.
A shared `degradeChargesAccountantApproval` helper (built on
`AccountantApprovalProvider.degradeChargeAccountantApproval`) is now invoked from every
charge-mutating operation:
- **Documents** — upload / batch-upload / Google-Drive batch-upload / insert / update (former and
new charge) / delete / charge-spread.
- **Transactions** — `updateTransaction` and `updateTransactions`, degrading both the former and
the new charge.
- **Misc expenses** — insert / bulk-insert / update (former and new charge) / delete.
- **Charges** — `updateCharge`, `batchUpdateCharges` and `mergeCharges`. Tag-only changes and
explicit accountant-approval changes are intentionally excluded.
- **Ledger** — `regenerateLedgerRecords`.
- **Cron** — `mergeChargesByTransactionReference`.
The helper de-duplicates ids and ignores empty / `EMPTY_UUID` values (degrading a non-approved
charge is a no-op), and returns the freshly-degraded charges so mutations respond with the
up-to-date `PENDING` status rather than a stale one.
- [#3948](https://github.com/Urigo/accounter-fullstack/pull/3948)
[`f933c8c`](https://github.com/Urigo/accounter-fullstack/commit/f933c8cdc8a9dc5378ebb24d332af11079835c1b)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Pre-fill financial fields when switching an
"other"/"unprocessed" document to a financial type in the edit-document modal.
A document can be inserted carrying full financial data (date, amount, VAT, serial number,
debtor/creditor, etc.) while still categorized as `OTHER`/`UNPROCESSED`. Previously, editing such
a document and switching its type to a financial one (e.g. invoice) showed all extended attribute
fields empty — the stored values only appeared after saving and reopening the modal, once the
document re-resolved as a `FinancialDocument`.
The financial fields were only exposed on the `FinancialDocument` GraphQL types, so the edit query
could never fetch the already-stored values for `Unprocessed`/`OtherDocument`. These fields
(`vat`, `serialNumber`, `date`, `amount`, `vatReportDateOverride`, `noVatAmount`,
`allocationNumber`, `exchangeRateOverride`, `debtor`, `creditor`) are now exposed on the
`Unprocessed` and `OtherDocument` types and fetched by the client, and the edit form reads its
financial defaults from the document regardless of its current type — so switching to a financial
type immediately pre-fills the stored values.
- [#3836](https://github.com/Urigo/accounter-fullstack/pull/3836)
[`059b638`](https://github.com/Urigo/accounter-fullstack/commit/059b638702116701a5a0062a7ac3a58a28eeb004)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - Add `exemptDealer`, `optionalVAT`,
`isReceiptEnough`, `isDocumentsOptional` and `isActive` to `BatchUpdateBusinessInput` in
`financial-entities/typeDefs/businesses.graphql.ts`.
- No resolver change is needed: these fields are already handled by the shared
`updateSingleBusiness` helper (`helpers/update-business.helper.ts`), and
`BatchUpdateBusinessInput` remains a strict structural subset of `UpdateBusinessInput`, so it is
still passed straight through. `isActive` flows to the core financial-entity update via
`hasFinancialEntitiesCoreProperties`; the rest map to the business row update. Tags and
description were already supported through the existing `suggestions` input.
- [#3809](https://github.com/Urigo/accounter-fullstack/pull/3809)
[`533a7ec`](https://github.com/Urigo/accounter-fullstack/commit/533a7ec00bcbb722759531b3b607ad5a5c109c47)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Recognize the issuing business for
**manually forwarded** invoice emails. The real issuer address often survives only inside the
quoted "Forwarded message" header block (e.g. the original `Reply-To`), which the gateway
previously ignored. The gateway now also recovers `From`/`Reply-To`/`Sender` addresses from
forwarded-header blocks, and the server tries every candidate (case-insensitively) until one
matches a business — so a forwarded Cloudflare invoice is attributed to the right business and its
email-processing config (e.g. "ignore the body") is applied instead of falling back to default
treatment.
- [#3778](https://github.com/Urigo/accounter-fullstack/pull/3778)
[`02dc27e`](https://github.com/Urigo/accounter-fullstack/commit/02dc27e172538c66f4f9af228ea2d419d0e81141)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Fix false `GRANT_INVALID` rejections in v2
email ingestion. A slow Cloudinary upload could make the gateway's ingest call time out and retry
after the server had already consumed the single-use grant, so the retry was rejected even though
the first attempt had succeeded. The server now performs an early idempotency check before
consuming the grant (returning `DUPLICATE` on retry), the gateway no longer retries ingest on
timeout, and the ingest timeout was raised from 10s to 30s.
- [#3762](https://github.com/Urigo/accounter-fullstack/pull/3762)
[`50ca939`](https://github.com/Urigo/accounter-fullstack/commit/50ca939661d9eb5b31e134c54d12015e524fac1c)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Email ingestion: set a descriptive charge
description matching the legacy gmail-listener —
`Email documents: <subject> (from: <sender>, <date>)` — instead of the bare
`email-ingestion: <messageId>`. The email subject, sender, and received date are threaded through
`IngestEmailInput` (new optional `subject`/`sender`/`receivedAt` fields) → the `ingestEmail`
resolver → `EmailIngestionIngestProvider`, with graceful fallback to the message id when any field
is missing. The received date is formatted in UTC so the same email produces a stable description
across dev/CI/prod.
- [#3896](https://github.com/Urigo/accounter-fullstack/pull/3896)
[`36f8d49`](https://github.com/Urigo/accounter-fullstack/commit/36f8d49c1fff95afea466a23b33eba7c0db004a8)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **Server**:
`recordsByFinancialEntityIdAndSortCodeValidations`
(`packages/server/src/modules/reports/helpers/misc.helper.ts`) now collects the contributing
ledger records (deduplicated by record ID) alongside the per-entity amount aggregation.
- **GraphQL schema**: `ReportCommentarySubRecord` gains a `ledgerRecords: [LedgerRecord!]!` field,
so the details are delegated to the client. This applies to both the Profit & Loss and Tax
reports, which share these commentary types.
- [#3812](https://github.com/Urigo/accounter-fullstack/pull/3812)
[`c8d5560`](https://github.com/Urigo/accounter-fullstack/commit/c8d5560b0210d4a0095a4254db406f9d402a8e4b)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **A1 — `sortCode: Int`**: new field on
`LtdFinancialEntity`. The value already rides on the joined `financial_entities` row
(`sort_code`), so the resolver is a direct mapping.
- **A2 — `taxCategory` returns `null` when unmatched**: the field is nullable, but the resolver
previously threw a `GraphQLError` when a business had no tax category match. The management
screen lists every business (including uncategorized, auto-generated ones), so one missing match
would fail the whole query. Now returns `null`.
- **A3 — `isClient: Boolean!` and `isAdmin: Boolean!`**: simple boolean columns for the client and
admin/owner extension tags, reusing the loaders that already back `clientInfo`
(`ClientsProvider.getClientByIdLoader`) and `adminInfo`
(`AdminBusinessesProvider.getAdminBusinessByIdLoader`).
- **B1 — `BusinessUsageProvider`**: new `@Injectable()` provider
(`providers/businesses-usage.provider.ts`) exposing
`getUsageByBusinessIds(ids): Map<id, { transactions, documents, miscExpenses, ledgerRecords }>`.
One grouped count query per source. For sources where a business can appear in more than one
column, each id-bearing column is projected into a common `business_id` via `UNION ALL` before
grouping, so counts attribute to the correct id (a single `OR … GROUP BY business_id` would
not):
- `transactions` → `business_id`
- `documents` → `debtor_id` ∪ `creditor_id`
- `misc_expenses` → `debtor_id` ∪ `creditor_id`
- `ledger_records` → `debit_entity1/2` ∪ `credit_entity1/2`
Every requested id defaults to zero so unused businesses are represented. Column names confirmed
against the `replaceBusiness` merge query and the misc-expenses provider.
- **B2 — `businessesUsage` query**: new typeDef + resolver returning `[BusinessUsage!]!`, wired
into the financial-entities module (typeDef, resolver, provider registration, pgtyped types
re-export).
```graphql
type BusinessUsage {
businessId: UUID!
totalTransactions: Int!
totalDocuments: Int!
totalMiscExpenses: Int!
totalLedgerRecords: Int!
}
extend type Query {
businessesUsage(ids: [UUID!]!): [BusinessUsage!]! @requiresAuth
}
```
- **C1 — `deleteBusiness` mutation**: exposes `deleteBusiness(businessId: UUID!): Boolean!`
(`@requiresAuth`, `@requiresAnyRole(["business_owner", "accountant"])`) wrapping the existing
`BusinessesOperationProvider.deleteBusinessById`. That provider already validates the business
is not an employee / pension-or-training fund / business-trip attendee / dividends receiver
(throwing per case) and cleans up its dependent rows (unbalanced-ledger businesses,
balance-cancellation, green-invoice client, tax-category match) before deleting the `businesses`
row.
The resolver returns `true` on success; the provider's guard errors propagate as GraphQL errors
with their original messages. No new guards are added — the provider is the hard backstop, and
the screen disables delete when usage > 0 (Phase I).
- **D1 — extract `updateSingleBusiness` helper** (refactor, no schema change): the per-business
body of the `updateBusiness` mutation (core financial-entity fields, business fields,
suggestions, tax-category match, green-invoice sync) moves into
`helpers/update-business.helper.ts`. It returns the refreshed business and throws on failure.
The `updateBusiness` resolver now wraps the helper and maps thrown errors to `CommonError`,
preserving observable behavior (the success path is byte-for-byte the same; failures keep the
`Failed to update business ID="…": …` format).
- **D2 — `batchUpdateBusinesses` mutation**:
```graphql
input BatchUpdateBusinessInput {
country: CountryCode
city: String
zipCode: String
sortCode: Int
taxCategory: UUID
irsCode: Int
pcn874RecordType: Pcn874RecordType
suggestions: SuggestionsInput
}
extend type Mutation {
batchUpdateBusinesses(businessIds: [UUID!]!, fields: BatchUpdateBusinessInput!): [Business!]!
@requiresAuth
@requiresAnyRole(roles: ["business_owner", "accountant"])
}
```
The input is the subset of fields safe to apply to many businesses at once (locality,
categorization, suggestions). The resolver resolves the owner from the admin context and applies
`updateSingleBusiness` to each id, returning the updated businesses; a failure on any id rejects
the whole mutation. `BatchUpdateBusinessInput` is a strict structural subset of
`UpdateBusinessInput`, so it is passed straight to the shared helper.
- [#3773](https://github.com/Urigo/accounter-fullstack/pull/3773)
[`2f1e974`](https://github.com/Urigo/accounter-fullstack/commit/2f1e9746533d297cac8bd4d80c668c2c9d9fe823)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **VAT extraction**: adds
`issuerVatNumber` / `recipientVatNumber` to the Claude extraction schema so VAT/registration
numbers are pulled from the document in the same LLM call (near-zero extra cost).
- **Server-side matching**: new pure helper `business-matcher.helper.ts` resolves extracted
names/VAT numbers to business UUIDs in three priority tiers: (1) VAT exact match, (2)
name/Hebrew-name substring, (3) `suggestion_data.phrases` sorted by priority.
- **LLM fallback**: for financial document types (INVOICE, RECEIPT, INVOICE_RECEIPT,
CREDIT_INVOICE, PROFORMA) where one or both sides remain unmatched after server-side matching, a
second LLM call reuses the existing conversation context — no file re-send — and asks Claude to
pick the closest business from the full businesses list.
- **UUID-based side resolution**: `getDocumentFromFile` now resolves `isOwnerIssuer` and
`counterpartyId` from the UUID matches, replacing the previous hardcoded `"the guild"` text
check.
- **Graceful fallback**: `fetchBusinessesForMatching` wraps the `BusinessesProvider` call in
try/catch, so email-ingestion paths (gateway auth, no tenant context) continue to work
unchanged.
- [#3847](https://github.com/Urigo/accounter-fullstack/pull/3847)
[`3109f03`](https://github.com/Urigo/accounter-fullstack/commit/3109f03cb36df30f01ec76d2a7016e833d743fb5)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - Replace
`new URL('.', import.meta.url).pathname` with `fileURLToPath(new URL('.', import.meta.url))`
everywhere it's used to resolve `__dirname` in ESM.
- `.pathname` breaks on Windows (prepends an extra leading slash before the drive letter, e.g.
`/C:/...`) and doesn't decode URL-encoded characters (e.g. `%20` for spaces). `fileURLToPath`
from the built-in `url` module is the standard, cross-platform way to do this.
- Applied across all 34 GraphQL server modules' `index.ts` files, the root and server
`vitest.config.ts`, and the `graphql-module` skill's scaffold template (so newly generated
modules follow the same convention).
- [#4028](https://github.com/Urigo/accounter-fullstack/pull/4028)
[`5fd5993`](https://github.com/Urigo/accounter-fullstack/commit/5fd5993d7213c1dc6b947f2e49dd8bf05f07467b)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - Update charge meta helpers to accept
either a charge id or a loaded charge row (`ChargeRef`) and use an enriched fast path when
available.
- Short-circuit multiple GraphQL field resolvers to use precomputed aggregates already present on
enriched charge rows.
- Extend the filters SQL query to return additional precomputed aggregates and prime the
charge-by-id DataLoader with enriched rows.
- [#4026](https://github.com/Urigo/accounter-fullstack/pull/4026)
[`d7614eb`](https://github.com/Urigo/accounter-fullstack/commit/d7614eb90aea120846a51558767923a07218411c)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Make `TenantAwareDBClient` hold one pooled
connection and one open transaction per GraphQL request instead of per query.
Previously every stand-alone query paid `BEGIN` → RLS `set_config` → query → `COMMIT` — four to
five round trips for one logical read. The client now opens a session lazily on the first query,
sets the RLS variables once, and reuses it for the rest of the request, collapsing that to roughly
one round trip per query. On the AllCharges screen, which issues ~90 logical queries per request,
this is the dominant cost. Queries are still serialized through the existing mutex (one connection
per request), so the win is fewer round trips, not parallel execution.
Semantics are preserved:
- Data-modifying stand-alone queries and the outermost explicit `transaction()` scope commit
immediately, so a mutation response always reflects durable state.
- Nested `transaction()` scopes keep their SAVEPOINT isolation.
- A failed statement rolls the session back, and the next query starts a fresh one.
- `dbCleanupPlugin` disposes the client once the response — including any `@defer`/`@stream` tail
— is fully sent, committing any open read session and releasing the connection.
Clients constructed outside the GraphQL request lifecycle (no `CONTEXT` injection — test
harnesses, scripts) default to the previous commit-and-release-per-operation behavior, since
nothing would ever call `dispose()` and the held connection would otherwise leak from the pool.
- [#3775](https://github.com/Urigo/accounter-fullstack/pull/3775)
[`7e07190`](https://github.com/Urigo/accounter-fullstack/commit/7e07190447f277b1b744f1d83d19d0a78c8007da)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - automated business matching for extracted
document data using local heuristics (VAT, name, and phrase matching) and an LLM fallback
- [#3903](https://github.com/Urigo/accounter-fullstack/pull/3903)
[`fda1331`](https://github.com/Urigo/accounter-fullstack/commit/fda13312351a0edce6b531bbf52fa601d5c4ccc0)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **Refactored Merge Logic**: Extracted the
complex merge planning logic for charges by transaction reference into a dedicated helper
function, improving maintainability and testability.
- **Added Dry Run Support**: Introduced a `dryRun` parameter to the
`mergeChargesByTransactionReference` mutation, allowing users to preview merge plans before
execution.
- **Enhanced Transaction Data**: Updated the SQL query to include `origin_user_description` by
joining additional transaction tables, providing more context for matching logic.
- **Comprehensive Test Suite**: Added a new test file covering various scenarios, including
recurring payments, foreign securities, and fee associations, to ensure robust merge planning.
- [#3914](https://github.com/Urigo/accounter-fullstack/pull/3914)
[`35a6c4d`](https://github.com/Urigo/accounter-fullstack/commit/35a6c4de58fbaea67d466b53403c232270328557)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **Schema updates**: Added `sortCode: Int`
field to the `DynamicReportNodeData` GraphQL type and Zod validation schema
- **GraphQL query**: Updated the dynamic report query to fetch the `sortCode` field from template
data
- **Tests**: Added comprehensive test coverage for parsing and validating sort-code-branch nodes
with sortCode fields, plus a test verifying sortCode is read from template data (not derived
from UUID IDs)
- [#3880](https://github.com/Urigo/accounter-fullstack/pull/3880)
[`02362ec`](https://github.com/Urigo/accounter-fullstack/commit/02362ecad8f161285d98a238fbd4fef374cebfd3)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - * **VAT Suggestion Logic**: Implemented a
new suggestion resolver that identifies missing VAT charge descriptions by comparing transaction
amounts against monthly VAT reports.
- **Ledger Validation**: Added validation logic to the ledger generation process to ensure monthly
VAT charges have valid descriptions and that the VAT amount matches the transaction sum.
- **Helper Utilities**: Introduced helper functions to calculate monthly VAT totals and verify
them against transaction amounts within a defined tolerance.
- [#3837](https://github.com/Urigo/accounter-fullstack/pull/3837)
[`465eefe`](https://github.com/Urigo/accounter-fullstack/commit/465eefe4062076ecdf9f0a447637ddc797039572)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - **Wildcard business-recognition emails**:
`suggestion_data.emails` now accepts wildcard patterns (e.g. `*@cloudflare.com`) alongside
concrete addresses, so suppliers that send invoices from a unique address per invoice (such as
`qr45uf@cloudflare.com`) can be recognized by a single entry.
- The suggestion-data schema validates wildcard patterns (`*` allowed anywhere in an email-shaped
entry) while still rejecting over-broad values like a bare `*`.
- Both recognition lookups (`BusinessesProvider.getBusinessByEmail` and the email-ingestion
control provider) translate `*` to a `LIKE` wildcard in SQL, escaping `_`, `%` and `\` so
literal entries keep matching exactly. Matching stays case-insensitive.
- New `email-pattern.helper.ts` is the source of truth for the pattern shape and provides an
in-process matcher used by tests.
- [#3793](https://github.com/Urigo/accounter-fullstack/pull/3793)
[`0ffbebd`](https://github.com/Urigo/accounter-fullstack/commit/0ffbebde245e8d1c12c76202bdaf1d889e0b4b40)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - - **sort-codes.provider.ts**: Modified
`updateSortCode` to retrieve the verified admin context and pass the `ownerId` to the query via
`reassureOwnerIdExists` helper
- **sort-codes.provider.test.ts**: Added comprehensive test coverage for both `updateSortCode` and
`addSortCode` methods, including:
- Regression test verifying `owner_id` is included in update query values
- Test confirming all parameters (key, name, defaultIrsCode, ownerId) are forwarded correctly
- Test verifying cache is cleared after updates
- Test for `addSortCode` ensuring `owner_id` is injected into insert queries
- Updated dependencies
[[`d382eb2`](https://github.com/Urigo/accounter-fullstack/commit/d382eb20cf27b76ef80d235080bead1ba66b1e77),
[`d382eb2`](https://github.com/Urigo/accounter-fullstack/commit/d382eb20cf27b76ef80d235080bead1ba66b1e77)]:
- @accounter/shaam-uniform-format-generator@0.2.7-alpha-20260723123038-e4259efcbe5b68fcd11939a1a7f40b694d568acb
- @accounter/shaam6111-generator@0.1.9-alpha-20260723123038-e4259efcbe5b68fcd11939a1a7f40b694d568acb
## 0.1.0
### Minor Changes
- [#3743](https://github.com/Urigo/accounter-fullstack/pull/3743)
[`7cc5c7d`](https://github.com/Urigo/accounter-fullstack/commit/7cc5c7d10015fafec12c3c3fe1e8c6d4d04b19c9)
Thanks [@gilgardosh](https://github.com/gilgardosh)! - Email ingestion v2 — document bytes
transport & persistence (Workstream D).
The gateway now inlines each treated document's bytes (base64, Option B) into the `ingestEmail`
mutation, and the server ingest step persists them: under the grant tenant's RLS context it
uploads each document to Cloudinary, creates a charge, and inserts the documents — attributed to
the recognized issuing business (read back from the grant's `business_id`, never trusted from
gateway input) as the document counterparty, with the legacy per-document hash dedup skip.
Documents are stored as `UNPROCESSED` (classification/OCR happens later via the normal flow).
Because the gateway control-plane caller has no auth session, persistence uses inline,
tenant-pinned SQL rather than the auth-coupled `DocumentsProvider` / `ChargesProvider` /
`getDocumentFromFile` (the same constraint behind the existing idempotency/dedup/quarantine
writes). Metadata-only ingest calls (no inline bytes) remain a no-op for persistence.
### Patch Changes
- [#3276](https://github.com/Urigo/accounter-fullstack/pull/3276)
[`6edc9b2`](https://github.com/Urigo/accounter-fullstack/commit/6edc9b27187a9b891b58ae08abdbddff9dcb1fde)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@graphql-yoga/plugin-defer-stream@3.21.0` ↗︎](https://www.npmjs.com/package/@graphql-yoga/plugin-defer-stream/v/3.21.0)
(from `3.18.1`, in `dependencies`)
- Updated dependency
[`graphql-yoga@5.21.0` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/5.21.0) (from `5.18.1`,
in `dependencies`)
- [#3277](https://github.com/Urigo/accounter-fullstack/pull/3277)
[`a584921`](https://github.com/Urigo/accounter-fullstack/commit/a58492135ab1cac146b0a04885bc968b5c5579e8)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency [`dotenv@17.4.1` ↗︎](https://www.npmjs.com/package/dotenv/v/17.4.1) (from
`17.3.1`, in `dependencies`)
- [#3281](https://github.com/Urigo/accounter-fullstack/pull/3281)
[`917f31e`](https://github.com/Urigo/accounter-fullstack/commit/917f31e956d267704f6d74fb7f3a22d366062135)
Thanks [@renovate](https://github.com/apps/renovate)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@3.0.66` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/3.0.66) (from
`3.0.64`, in `dependencies`)
- Updated dependency [`ai@6.0.146` ↗︎](https://www.npmjs.com/package/ai/v/6.0.146) (from `6.0.141`,
in `dependencies`)
- Updated dependency [`playwright@1.59.1` ↗︎](https://www.npmjs.com/package/playwright/v/1.59.1)
(from `1.59.0`, in `dependencies`)
- [`623ed2b`](https://github.com/Urigo/accounter-fullstack/commit/623ed2b39788764e206241a9c3a628dfec25889e)
Thanks [@renovate[bot]](https://github.com/renovate%5Bbot%5D)! - dependencies updates:
- Updated dependency
[`@ai-sdk/anthropic@3.0.68` ↗︎](https://www.npmjs.com/package/@ai-sdk/anthropic/v/3.0.68) (from
`3.0.66`, in