Constrain a transaction to a single category so spend math has one unambiguous bucket, and add tags for the cross-cutting labels a second category used to stand in for. Today Transaction::categories() is a many-to-many relation (app/Models/Transaction.php:182) and there is no tag concept, so any per-category total has to either pick one of several or double count. Category becomes one-per-transaction (the spend bucket); tags become a separate user-scoped label that never feeds category, income, or expense totals. Web and mobile are blocked on this shape.
Constraint: this is the source of truth; the web and mobile follow-ups depend on the model and endpoints defined here.
Checklist
Acceptance criteria
Constrain a transaction to a single category so spend math has one unambiguous bucket, and add tags for the cross-cutting labels a second category used to stand in for. Today
Transaction::categories()is a many-to-many relation (app/Models/Transaction.php:182) and there is no tag concept, so any per-category total has to either pick one of several or double count. Category becomes one-per-transaction (the spend bucket); tags become a separate user-scoped label that never feeds category, income, or expense totals. Web and mobile are blocked on this shape.Constraint: this is the source of truth; the web and mobile follow-ups depend on the model and endpoints defined here.
Checklist
type(income/expense) intact./statsbuilds the category breakdown from the single category; tags never contribute to any total.Acceptance criteria
/statscategory totals sum to total expense (and income) with no double counting.