Skip to content

feat: bulk import validation & preview improvements (#115)#473

Open
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/bulk-import-validation
Open

feat: bulk import validation & preview improvements (#115)#473
qiridigital wants to merge 1 commit intorohitdash08:mainfrom
qiridigital:feat/bulk-import-validation

Conversation

@qiridigital
Copy link

Summary

Enhances the expense import flow with per-row validation, explicit data corrections, and actionable warnings — allowing users to review all issues and auto-corrections before committing transactions to the database.

What's included

Backend (packages/backend/)

  • app/services/expense_import.py — New validate_import_rows() function:
    • Per-row status: valid | warning | invalid
    • Invalid rows (skipped on commit): unparseable date, unparseable amount, empty description
    • Warnings raised for: future dates, dates >10 years old, amounts >1,000,000, possible duplicate (same description + date already in DB), unknown category_id, uncategorised rows
    • Corrections reported for: date format normalisation (MM/DD/YYYY → YYYY-MM-DD, etc.), amount sign/parenthesis normalisation, description truncation to 500 chars, invalid category_id cleared to null, expense_type inferred from keywords/sign when not specified
    • summary: {total, valid, warnings, invalid, duplicates}
  • app/routes/expenses.py — New POST /expenses/import/validate endpoint:
    • Accepts same multipart file upload as /import/preview
    • Loads user's existing expenses for duplicate detection
    • Loads user's category IDs to validate category_id fields
    • Returns full validation result: {rows: [...], summary: {...}}
    • Original /import/preview and /import/commit unchanged (backward compatible)

Frontend (app/src/)

  • api/importValidate.ts — TypeScript client with full typed interfaces
  • pages/BulkImport.tsx — Two-phase import page:
    • Click-to-upload zone (CSV)
    • 5-tile summary bar: Total · Valid · Warnings · Invalid · Duplicates
    • Filter tabs: All | Valid | Warning | Invalid
    • Row cards with expandable warnings (⚠) and corrections (✏) detail inline
    • Sticky "Import N valid transactions" commit button (skips invalid rows)
    • Post-commit success banner with inserted/skipped counts
  • App.tsx/import route added
  • Navbar.tsx — "Import" nav link added

Tests

  • tests/test_import_validate.py — 14 pytest tests: auth, no-file, valid CSV, response structure, bad date, bad amount, empty description, future date, old date, big amount, duplicate detection, corrections in response, summary counts, unknown file type

Closes

Closes #115

Enhances the expense import flow with per-row validation, explicit data
corrections, and actionable warnings before the user commits transactions.

Backend changes:
- services/expense_import.py: New validate_import_rows() function
  - Per-row status: valid | warning | warning | invalid
  - Warnings: future/old dates, large amounts, duplicates, unknown category
  - Corrections: date format, amount normalisation, truncation, inferred type
  - Summary counts: total, valid, warnings, invalid, duplicates
- routes/expenses.py: New POST /expenses/import/validate endpoint
  - Accepts same multipart file upload as /import/preview
  - Loads user's existing expenses for duplicate detection
  - Loads user's category IDs for category validation
  - Returns full validation result (rows + summary)
  - Original /import/preview and /import/commit unchanged

Frontend changes:
- api/importValidate.ts: TypeScript client with full typed interfaces
- pages/BulkImport.tsx: Two-phase import page with summary bar,
  filter tabs (All/Valid/Warning/Invalid), row cards with expandable
  detail, sticky commit button, post-commit success banner
- App.tsx: route /import added
- Navbar.tsx: Import nav link added

Tests: 14 pytest tests in test_import_validate.py

Closes rohitdash08#115
Copilot AI review requested due to automatic review settings March 16, 2026 21:43
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

Adds a bulk import validation/preview flow (issue #115) with per-row validation, warnings, corrections, and duplicate detection before committing transactions.

Changes:

  • New validate_import_rows() service function and /expenses/import/validate endpoint for per-row validation with status, warnings, and corrections
  • New React BulkImport page with upload, summary tiles, filter tabs, row cards, and commit flow
  • 14 pytest tests covering the validation endpoint

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/backend/app/services/expense_import.py New validate_import_rows() with date/amount/description/category validation, duplicate detection, and correction reporting
packages/backend/app/routes/expenses.py New POST /expenses/import/validate endpoint wiring up the validation service
app/src/api/importValidate.ts TypeScript client with typed interfaces for validate and commit APIs
app/src/pages/BulkImport.tsx Two-phase import page with upload, summary, filtering, and commit
app/src/App.tsx Added /import route
app/src/components/layout/Navbar.tsx Added "Import" nav link
packages/backend/tests/test_import_validate.py 14 tests for the validation endpoint

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

summary – counts (total, valid, warnings, invalid, duplicates)
"""
today = date.today()
far_future = date(today.year, today.month, today.day)
def validate_import_rows(
raw_rows: list[dict[str, Any]],
*,
existing_descriptions: set[str] | None = None,
Comment on lines +177 to +178

import pytest
raw_rows: list[dict[str, Any]],
*,
existing_descriptions: set[str] | None = None,
existing_amounts: dict[str, list[Decimal]] | None = None,
Comment on lines +309 to +313
existing = (
db.session.query(Expense.notes, Expense.spent_at)
.filter_by(user_id=uid)
.all()
)
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.

Bulk import validation & preview improvements

2 participants