feat(categorize): dimension bags end-to-end + runtime template learning (#1273)

* feat(categorize): dimension bags end-to-end + runtime template learning

The categorize path could not tag: categorize-core accepted a dimensions
bag but no route or UI ever passed one, and runtime template learning
dropped the bag entirely (only SIE import produced dimension-carrying
patterns).

- CategorizeTransactionSchema gains dimensions; the dashboard route, v1
  single and v1 batch-categorize apply it to the mapping result's
  business lines (explicit bag wins over a learned counterparty bag).
- categorization_templates.default_dimensions (migration 20260728091000)
  records the bag of the latest tagged booking; latest-explicit-wins, an
  untagged booking never erases it. Applied on the legacy single-line
  template path and the mirrored-refund path; multi-line SIE patterns
  keep their authoritative per-entry bags.
- QuickReviewDialog gets a LineDimensionFields picker (dimensions_enabled
  gate, same as BulkBookDialog), prefilled from the counterparty
  suggestion's learned bag; hidden for multi-line patterns whose per-line
  bags would ignore an edit.

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

* Renumber migration above 20260728120000 (out-of-order vs prod after #1271)

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-29 09:39:23 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 951b33363b
commit 222e581476
16 changed files with 403 additions and 9 deletions
+4
View File
@@ -1293,6 +1293,10 @@ export const CategorizeTransactionSchema = z
vat_treatment: VatTreatmentSchema.optional(),
account_override: accountNumber.optional(),
counterparty_template_id: z.string().uuid().optional(),
// Dimensions bag {sie_dim_no: code} applied to the business lines of the
// generated verifikat (bank/VAT legs stay untagged). Wins over a learned
// counterparty-template bag when both are present.
dimensions: DimensionsBagSchema.optional(),
user_description: z.string().max(500).optional(),
inbox_item_id: z.string().uuid().optional(),
confirm_no_match: z.boolean().optional(),