ExpenseFlow is a production-minded reimbursement management system built for the Reimbursement Management problem statement. It replaces slow, error-prone manual reimbursement handling with OCR-assisted receipt capture, role-based approval routing, audit visibility, real-time currency normalization, and a cleaner enterprise UI in a single Next.js 14 application.
Our team built ExpenseFlow with a strong focus on:
- dynamic runtime data instead of static JSON
- responsive and clean UI
- robust validation and role-based access control
- intuitive navigation
- local-first and offline-friendly behavior where practical
- clean repository structure and clear documentation
The fastest way to run ExpenseFlow is with Docker Compose.
docker compose up --buildAfter the container is healthy, open:
http://localhost:3000
If port 3000 is already in use on your machine, Compose also supports a one-line override:
APP_PORT=3001 docker compose up --buildPowerShell:
$env:APP_PORT="3001"
docker compose up --buildNo separate PostgreSQL setup is required for the Docker path. The compose workflow runs the app in a single container with an embedded PostgreSQL instance, applies the Prisma schema automatically, seeds the demo dataset on first boot, and serves the application.
- Admin:
admin@expenseflow.demo/password123 - Manager:
manager1@expenseflow.demo/password123 - Employee:
employee1@expenseflow.demo/password123
Companies often struggle with manual expense reimbursement processes that are:
- time-consuming
- error-prone
- hard to audit
- unclear for employees waiting on approvals
- inconsistent when multi-level approval rules are involved
Typical pain points include:
- no simple way to define approval flows based on thresholds
- no flexible support for multi-level or conditional approvals
- manual receipt handling
- manual currency conversion for cross-border spending
- weak status visibility for submitters and approvers
ExpenseFlow centralizes the full reimbursement flow:
- New companies are created automatically on first signup.
- The company currency is derived from the selected country using the Rest Countries API.
- Employees can create draft expenses, upload receipts, run OCR extraction, review the extracted values, and submit expenses for approval.
- Managers and approvers receive approval requests in sequence or in parallel depending on rule settings.
- Admins can manage users, reporting lines, approval rules, and override stuck or exceptional requests.
- The product stores both original submitted currency values and company-base currency values using live exchange data with cache fallbacks.
- Audit logs and in-app notifications make each workflow step visible.
- Credentials authentication with NextAuth.js v5
- Signup flow with:
- full name
- conditional company name field for public email domains such as Gmail or Outlook
- password
- confirm password
- country selection
- Automatic company creation on first signup
- Automatic admin user creation on first signup
- Company base currency derived from live country/currency data
- Forgot password flow with temporary password generation
- SMTP-backed email sending when configured
- In-app fallback for temporary passwords during local development when SMTP is not configured
ADMIN- manage users
- resend passwords for employees and managers from the users table
- view the generated password in a persistent admin dialog and copy it directly
- safely delete users who are not tied to historical workflow records
- configure approval rules
- view all company expenses
- override any expense
MANAGER- review assigned approvals
- approve or reject with comments
- view direct-report expenses
EMPLOYEE- create draft expenses
- upload receipts and use OCR
- submit claims
- view only their own expense history and approval trail
- manager-first routing when enabled
- sequential approval routing
- parallel approval routing
- minimum approval percentage rules
- required approver override rules
- hybrid workflows that combine sequence and conditional logic
- approval audit logs
- admin override route for final approval or rejection
- draft and submit flows
- OCR receipt extraction with Tesseract.js
- PDF first-page conversion before OCR
- OCR extraction for:
- amount
- date
- inferred category
- currency
- manual correction after OCR
- in-form receipt preview while editing
- click-to-enlarge receipt and PDF review
- receipt preview in the detail view
- receipt access directly from employee, manager, and admin list views
- read-only lock after submission
- live exchange lookup using
https://api.exchangerate-api.com/v4/latest/{BASE_CURRENCY} - cached FX responses in memory
- persisted disk-backed FX cache for local resilience
- original submitted currency preserved
- converted company-base amount stored on the expense
- warning fallback if live conversion is unavailable
- professional enterprise-style dashboard shell
- persistent desktop sidebar
- mobile bottom navigation
- sticky glassmorphism header
- notification bell with unread state
- sortable tables
- responsive layouts
- breadcrumb navigation
- subtle motion and entry animations
- clean status badges and workflow stepper
- glass surface treatment without over-styling or novelty visuals
- shared read-only expense detail route for managers, admins, and employees
- in-app notification center in the top bar
- new approval assignment notifications for approvers
- approval progress notifications for employees
- final approval or rejection notifications for employees
- mark-one and mark-all-as-read behavior
- persisted notification history in the database
- Zod validation on client and server
- strict TypeScript
- authenticated OCR route
- authenticated FX route
- company-scoped queries
- self-manager and simple reporting-cycle protection
- transaction-based workflow state changes
- indexed Prisma models for common list and status queries
- disk-backed country and FX caches for local/offline resilience
- security headers in Next.js config
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js App Router | 14.2.35 |
| Language | TypeScript | 5.9.3 |
| Styling | TailwindCSS | 3.4.18 |
| Authentication | next-auth | 5.0.0-beta.30 |
| ORM | Prisma | 6.17.1 |
| Database | PostgreSQL | 14+ |
| OCR | tesseract.js | 6.0.1 |
| PDF conversion | pdf-to-png-converter | 3.14.0 |
| Forms | React Hook Form + Zod | 7.72.0 / 4.3.6 |
| Data fetching | SWR | 2.4.1 |
| Nodemailer | 7.0.13 |
app/(auth)contains login, signup, and forgot-password flowsapp/(dashboard)contains the authenticated role-based product shellapp/api/*contains API routes for auth, users, approvals, rules, expenses, notifications, and dashboard summariescomponents/*contains UI primitives and domain-specific moduleslib/*contains shared backend logic including auth, OCR, caching, email, workflow logic, and notification helpersprisma/*contains the schema and seed script
lib/approvalEngine.ts is the core workflow module. It handles:
- expense submission initialization
- creation of approval requests
- manager-first routing
- sequential and parallel fan-out
- percentage threshold checks
- required approver overrides
- final expense state changes
- audit log creation
- email notifications
- in-app notification creation
Route handlers only validate input, authorize access, and delegate workflow logic to the engine.
POST /api/expenses/ocr:
- validates the uploaded file and type
- stores the file locally in the configured uploads directory
- converts PDFs to an image buffer
- runs Tesseract OCR with a 30-second timeout
- extracts likely structured fields with receipt-focused heuristics
- returns OCR suggestions plus the stored receipt URL so the uploaded file can be reviewed immediately in the form
ExpenseFlow uses both in-memory and disk-backed caches:
- countries:
- live source: Rest Countries API
- memory cache: 24 hours
- disk cache fallback:
.cache/expenseflow/countries.json
- exchange rates:
- live source: ExchangeRate API
- memory cache: 1 hour per base currency
- disk cache fallback:
.cache/expenseflow/fx-<BASE>.json
This improves startup resilience and supports a more local-first setup instead of depending entirely on cloud availability.
The notification system is intentionally lightweight and role-aware:
- approvers receive notifications when a request is assigned
- employees receive progress updates when an approver acts
- employees receive a final notification when the expense is approved or rejected
- notifications remain visible in the top-bar bell interface until marked read
- Node.js 18+
- PostgreSQL 14+
- Git
- Clone the repository.
- Install dependencies.
npm install- Copy
.env.exampleto.env. - Fill the required values.
- Sync the database schema.
npm run db:push- Seed the demo data.
npm run db:seed- Start the development server.
npm run dev- Open
http://localhost:3000.
docker compose up --buildOptional port override when 3000 is busy:
APP_PORT=3001 docker compose up --buildPowerShell:
$env:APP_PORT="3001"
docker compose up --build- builds the app image
- starts a single container
- boots an embedded PostgreSQL server inside that container
- applies the Prisma schema automatically
- seeds the demo dataset on first run
- serves the Next.js production build on port
3000
- no external database is required for the compose path
- uploads and database state are persisted through named volumes
- SMTP is optional; leave SMTP env values blank for local setup
- the compose workflow is designed to keep setup simple for team handoff, mentor access, and demo runs
| Variable | Required for Local Dev | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string for Prisma |
NEXTAUTH_SECRET |
Yes | Session signing secret |
NEXTAUTH_URL |
No | Optional fixed public app base URL. Leave blank to let Auth.js use the incoming host dynamically, which works well with ngrok. |
SMTP_HOST |
No | SMTP host for real outgoing mail |
SMTP_PORT |
No | SMTP port |
SMTP_USER |
No | SMTP username |
SMTP_PASS |
No | SMTP password |
SMTP_FROM |
No | Sender identity |
UPLOAD_DIR |
Yes | Local uploads directory |
These are optional when using the provided docker-compose.yml because defaults are already included:
POSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORDPGDATAAPP_PORT
-
POST /api/auth/register- Auth: public
- Body:
{ fullName, email, companyName?, password, confirmPassword, country } - Response: company metadata and success message
-
POST /api/auth/forgot-password- Auth: public
- Body:
{ email } - Response: success message, or a temporary password when SMTP is not configured
-
GET /api/users- Auth: Admin
- Response: company users with manager info
-
POST /api/users- Auth: Admin
- Body:
{ name, email, role, managerId } - Response: user id, message, email status, and generated temporary password
-
PATCH /api/users/:id- Auth: Admin
- Body:
{ name, role, managerId } - Response: success message
-
DELETE /api/users/:id- Auth: Admin
- Response: success message
- Notes: protected for users tied to expense, approval, audit, or rule history
-
POST /api/users/:id/send-password- Auth: Admin
- Response: success message and the generated temporary password
-
GET /api/countries- Auth: public
- Response: country list with currency metadata
-
GET /api/currencies?base=INR- Auth: authenticated
- Response: live/cached FX payload and source
-
GET /api/expenses- Auth: authenticated
- Scope:
- Employee: own expenses
- Manager: direct-report expenses
- Admin: all company expenses
-
POST /api/expenses- Auth: Employee
- Body:
{ description, category, amount, currency, date, remarks, receiptUrl } - Response: created expense id and optional conversion warning
-
GET /api/expenses/:id- Auth: authenticated with role-based scope checks
- Response: expense, approval requests, and audit log
-
PATCH /api/expenses/:id- Auth: Employee owner
- Requirement: expense must be
DRAFT
-
POST /api/expenses/:id/submit- Auth: Employee owner
- Requirement: expense must be
DRAFT - Side effects: audit log, approval requests, notifications, emails
-
POST /api/expenses/ocr- Auth: authenticated
- Body: multipart upload with
file - Response: OCR extraction payload for amount, date, currency, and category plus the stored receipt URL
-
GET /api/approvals- Auth: Manager or Admin
- Response: approvals assigned to the signed-in approver
-
PATCH /api/approvals/:id- Auth: Manager or Admin assignee
- Body:
{ decision, comments } - Response: success message
-
GET /api/approval-rules- Auth: Admin
- Response: all approval rules for the company
-
POST /api/approval-rules- Auth: Admin
- Body:
{ name, description, isManagerApprover, isSequential, minimumApprovalPercentage, approvers[] }
-
PATCH /api/approval-rules/:id- Auth: Admin
- Body: same as create
-
DELETE /api/approval-rules/:id- Auth: Admin
-
GET /api/notifications- Auth: authenticated
- Response: latest notifications plus unread count
-
PATCH /api/notifications/:id- Auth: authenticated owner
- Response: marks one notification as read
-
PATCH /api/notifications/read-all- Auth: authenticated
- Response: marks all notifications as read
POST /api/admin/expenses/:id/override- Auth: Admin
- Body:
{ decision, comments } - Response: success message
GET /api/dashboard/summary- Auth: authenticated
- Response: role-scoped totals and counts
PATCH /api/profile/password- Auth: authenticated
- Body:
{ currentPassword, newPassword, confirmPassword } - Response: success message
Employee submits expense
|
v
Manager-first enabled?
| yes | no
v v
Direct manager request Rule approvers start
created as sequence 0 immediately
|
v
Sequential rule? ------------------------------+
| yes | no
v v
Create next approver only Create all approvers together
after previous action and evaluate after each action
|
v
Evaluate after every approval/rejection:
1. Required approver rejected
=> reject immediately
2. Required approver approved
=> approve immediately
3. Minimum approval percentage reached
=> approve immediately
4. No percentage rule and every approver approved
=> approve
5. Threshold can no longer be met
=> reject
6. Otherwise
=> continue workflow
The seed script creates:
- Company:
Acme Corp - Country:
India - Currency:
INR - 1 Admin user
- 2 Managers
- 3 Employees
- 1 approval rule
- 5 demo expenses across draft, submitted, approved, and rejected states
- demo in-app notifications
This repository is prepared as our hackathon submission codebase.
Recommended submission checklist:
- Push the repository to the team GitHub repo.
- Add the mentor as a collaborator on the repository.
- Confirm the repo is accessible.
- Provide the video demo link on the hackathon platform.
- Share the Docker Compose run command:
docker compose up --build
- Odoo x VIT Pune Hackathon 26
- Vishwakarma Institute of Technology (VIT), Pune
- Team Leader: Maitri
- Team Member: Vyas Devgna
- Mentor: Jay Patel (
jayp) - GitHub:
jayp-odoo
- We thank the Odoo x VIT Pune Hackathon 26 organizers for the platform and opportunity to build this solution.
- We are grateful to our mentor Jay Patel for the guidance and technical direction throughout the challenge.
- This submission was prepared by our team with Maitri as Team Leader and Vyas Devgna as Team Member.
- OCR accuracy still depends on receipt quality, orientation, and lighting
- The current rule selector uses the latest company rule; category- or amount-specific rule targeting would be a strong next step
- SMTP is optional in the default local and Docker setup; production email delivery requires real SMTP credentials
- The Docker setup is optimized for demo and submission convenience rather than horizontal production scaling
The project should be validated with:
npx tsc --noEmit
npm run lint
npm run buildFor container verification:
docker compose up --buildMIT