feat: smart payee & merchant alias management (#114)#472
Open
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
Open
feat: smart payee & merchant alias management (#114)#472qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
Conversation
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
There was a problem hiding this comment.
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/MerchantAliasdata model + DB schema (with uniqueness constraints and cascade deletes). - Implemented
/merchantsbackend routes (list/search, CRUD, alias add/remove, merge, suggest from expenses) and added pytest coverage. - Added frontend merchants API client and a
/merchantsUI 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") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 deleteapp/routes/merchants.py— Full CRUD + merge + suggest:GET /merchants?q=— list + search by canonical name (case-insensitive)POST /merchants— create with optional comma-separated aliasesGET /merchants/:id— fetch single merchant with aliasesPATCH /merchants/:id— rename canonical nameDELETE /merchants/:id— delete merchant + all aliases (cascade)POST /merchants/:id/aliases— add alias (duplicate-safe)DELETE /merchants/:id/aliases/:aid— remove specific aliasPOST /merchants/merge— merge source into target: moves all source aliases to target, adds source canonical name as an alias, then deletes sourceGET /merchants/suggest?q=&limit=— scans expense notes for distinct payee strings to seed new merchantsapp/routes/__init__.py—merchants_bpregistered at/merchantsapp/db/schema.sql—merchants+merchant_aliasestables with unique indexestests/test_merchants.py— 19 pytest testsFrontend (
app/src/)api/merchants.ts— TypeScript client with full typed interfacespages/Merchants.tsx— Dashboard page:App.tsx—/merchantsrouteNavbar.tsx— "Merchants" nav linkCloses
Closes #114