Skip to content

feat: smart payee & merchant alias management (#114)#472

Open
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/merchant-aliases
Open

feat: smart payee & merchant alias management (#114)#472
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/merchant-aliases

Conversation

@qiridigital
Copy link

Summary

Implements smart payee & merchant alias management — allowing users to define canonical merchant names and map multiple raw payee strings (as they appear in bank statements or expense notes) to a single canonical identity.

What's included

Backend (packages/backend/)

  • app/models.py — Two new models:
    • Merchant (id, user_id, canonical_name, created_at)
    • MerchantAlias (id, merchant_id, alias, created_at) with cascade delete
  • app/routes/merchants.py — Full CRUD + merge + suggest:
    • GET /merchants?q= — list + search by canonical name (case-insensitive)
    • POST /merchants — create with optional comma-separated aliases
    • GET /merchants/:id — fetch single merchant with aliases
    • PATCH /merchants/:id — rename canonical name
    • DELETE /merchants/:id — delete merchant + all aliases (cascade)
    • POST /merchants/:id/aliases — add alias (duplicate-safe)
    • DELETE /merchants/:id/aliases/:aid — remove specific alias
    • POST /merchants/merge — merge source into target: moves all source aliases to target, adds source canonical name as an alias, then deletes source
    • GET /merchants/suggest?q=&limit= — scans expense notes for distinct payee strings to seed new merchants
  • app/routes/__init__.pymerchants_bp registered at /merchants
  • app/db/schema.sqlmerchants + merchant_aliases tables with unique indexes
  • tests/test_merchants.py — 19 pytest tests

Frontend (app/src/)

  • api/merchants.ts — TypeScript client with full typed interfaces
  • pages/Merchants.tsx — Dashboard page:
    • Live search + "Suggest from expenses" to auto-populate new merchant name
    • Create form with optional comma-separated aliases input
    • Merchant cards with inline alias add (click "+ Alias") and alias pill × remove
    • Merge UI: source/target dropdowns → merge button
  • App.tsx/merchants route
  • Navbar.tsx — "Merchants" nav link

Closes

Closes #114

Allows users to define canonical merchant names and map multiple raw
payee strings (bank statement descriptions) to them, enabling clean
merchant deduplication and consistent categorisation.

- Merchant model with canonical_name; MerchantAlias model for aliases
- Full CRUD for merchants and aliases:
  - GET  /merchants?q=         list + search by canonical name
  - POST /merchants             create merchant with optional aliases
  - GET  /merchants/:id         get single merchant
  - PATCH /merchants/:id        rename canonical name
  - DELETE /merchants/:id       delete merchant + all aliases (cascade)
  - POST /merchants/:id/aliases        add alias to merchant
  - DELETE /merchants/:id/aliases/:aid remove specific alias
- POST /merchants/merge         merge source into target:
  moves all source aliases to target, adds source canonical as alias,
  deletes source merchant
- GET  /merchants/suggest?q=   scans expense notes for payee suggestions
  (up to 20 distinct notes, de-duplicated case-insensitively)
- Update schema.sql with merchants + merchant_aliases tables + indexes
  (unique index on user+canonical_name, unique alias per merchant)
- React Merchants page:
  - Live search + suggest-from-expenses button
  - Create form with comma-separated aliases
  - Inline alias add/remove per merchant card
  - Merge UI (source → target dropdowns)
  - Alias pill tags with × remove button
- TypeScript API client (merchants.ts) with full typed interfaces
- Route /merchants added to App.tsx + Merchants nav link in Navbar
- 19 pytest tests: CRUD, duplicate detection, alias add/remove/dedupe,
  merge (aliases transferred, source deleted), suggest, auth

/claim rohitdash08#114
Copilot AI review requested due to automatic review settings March 16, 2026 21:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements merchant canonicalization and alias management so users can map multiple raw payee strings to a single canonical merchant, with backend CRUD/merge/suggest endpoints and a new frontend management page.

Changes:

  • Added Merchant / MerchantAlias data model + DB schema (with uniqueness constraints and cascade deletes).
  • Implemented /merchants backend routes (list/search, CRUD, alias add/remove, merge, suggest from expenses) and added pytest coverage.
  • Added frontend merchants API client and a /merchants UI page wired into routing + navbar.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/backend/app/models.py Adds Merchant and MerchantAlias SQLAlchemy models with alias relationship + cascade.
packages/backend/app/db/schema.sql Creates merchants / merchant_aliases tables and supporting indexes.
packages/backend/app/routes/merchants.py Implements merchant CRUD, alias ops, merge, and suggestions endpoints.
packages/backend/app/routes/__init__.py Registers the merchants blueprint at /merchants.
packages/backend/tests/test_merchants.py Adds endpoint tests for merchants/aliases/merge/suggest behavior.
app/src/api/merchants.ts Adds typed frontend API client for the merchants endpoints.
app/src/pages/Merchants.tsx Adds merchant management UI: search, create, aliases, merge, suggest.
app/src/App.tsx Adds protected /merchants route.
app/src/components/layout/Navbar.tsx Adds “Merchants” navigation entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

assert "suggestions" in r.get_json()


import pytest
Comment on lines +115 to +116
const res = await suggestMerchants(search, 10);
setSuggestions(res.suggestions);
body = request.get_json(silent=True) or {}
new_name = (body.get("canonical_name") or "").strip()
if not new_name:
return jsonify({"error": "canonical_name is required"}), 400
Comment on lines +178 to +181
target_id = body.get("target_id")
source_id = body.get("source_id")
if not target_id or not source_id:
return jsonify({"error": "target_id and source_id are required"}), 400
Comment on lines +189 to +204
for alias in list(source.aliases):
# Avoid duplicates
exists = MerchantAlias.query.filter_by(
merchant_id=target.id, alias=alias.alias
).first()
if not exists:
alias.merchant_id = target.id

# Add source canonical name as an alias on target (if not already there)
canon_as_alias = MerchantAlias.query.filter_by(
merchant_id=target.id, alias=source.canonical_name
).first()
if not canon_as_alias:
db.session.add(
MerchantAlias(merchant_id=target.id, alias=source.canonical_name)
)
Comment on lines +1 to +3
"""Tests for merchant & payee alias management endpoints."""
import pytest

Comment on lines +75 to +79
for raw_alias in body.get("aliases", []):
alias_str = (raw_alias or "").strip()
if alias_str:
db.session.add(MerchantAlias(merchant_id=merchant.id, alias=alias_str))

import {
listMerchants,
createMerchant,
updateMerchant,
def suggest_merchants():
uid = int(get_jwt_identity())
q = (request.args.get("q") or "").strip()
limit = min(20, max(1, int(request.args.get("limit", 10))))
Comment on lines 19 to +22
app.register_blueprint(categories_bp, url_prefix="/categories")
app.register_blueprint(docs_bp, url_prefix="/docs")
app.register_blueprint(dashboard_bp, url_prefix="/dashboard")
app.register_blueprint(merchants_bp, url_prefix="/merchants")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Smart payee & merchant alias management

2 participants