README
ΒΆ
ExpenseFlow
Track and report professional expenses ExpenseFlow is an app designed to help professionals track and report their expenses. It simplifies the process of managing receipts, categorizing expenses, and generating reports, all from a single platform.
π§ Features π§
- Session-based expense tracking: Log expenses against a session (client or mission).
- Expense details: Track the reason, value, date, and time of each expense.
- Receipt capture: Upload photos of receipts.
- Reports: Generate reports in a specific format.
- Cross-platform support: Available on Android, iOS, Windows, Mac, Linux and Online.
Tech Stack
- Backend: Go (Golang)
- Frontend: Flutter
- Database: SQLite
- License: Apache 2.0
Roadmap
-
Backend Development (Go)
- Define data models (expenses, sessions, reasons, etc.)
- Set up API routes for expense tracking:
- POST
/expenses: Add a new expense - GET
/expenses: Get a list of all expenses - POST
/receipts: Upload a receipt - GET
/reports: Generate a report
- POST
- Set up error handling, logging, and unit testing
- Write documentation for the backend API (OpenAPI/Swagger)
-
Frontend Development (Flutter)
- Create basic UI for inputting expenses and sessions
- Add functionality to upload receipt photos
- Implement a report viewer
- Handle authentication (connect to backend)
- Perform user testing
-
Deployment
- Set up web hosting (use platforms like Firebase, DigitalOcean, etc.)
- Prepare Android/iOS builds for Google Play and the Apple App Store
- Ensure cross-platform compatibility for desktop versions
-
Future Features
- Push notifications for expense reminders
- Multi-language support
- Integration with other tools (e.g., Google Drive for backup)
- Customizable filters/presentation for the report
- Export in CSV/PDF/...
- DB backup system
- Show a lock design for sessions reported, warning when try to edit after report
Development Journal
First impressions
1. Roadmap Details
-
Data Models (Go):
type Expense struct { ID int SessionID int Reason string Value float64 DateTime time.Time ReceiptURL string } type Session struct { ID int Client string Mission string } -
API Design: Plan API endpoints. Need routes for adding expenses, uploading receipts, and generating reports.
-
Authentication: Can skip authentication early on, but eventually, want to handle user accounts. JWT (JSON Web Tokens) could be a good fit.
-
Testing: Write tests for the API routes as I go. Go has built-in testing functionality (
go test).
2. Frontend Focus (Flutter)
Once the backend is functional, shift focus to Flutter. Keep it simple at first:
- A form to add expenses
- A file picker for receipt images
- A report generator that formats data nicely
Tools
Install
Modify path in /config/config.go
Running:
go run ./cmd/expenseflow
### Dev
Use git hooks
```bash
git config core.hooksPath .githooks
### Testing:
```bash
go test ./tests
Change go test timeout in VS Code:
{
"go.testTimeout": "10s"
}
Reminders
- Handle overlap of a mission in reports from one week to another
- Create INDEX for every FK
- Create users(name, distance unit, date_display, week_start, language, standard_model, car_expense_rate_by_km)
- Session start/end only need date not datetime
- [front] Intercept the UNIQUE error of adding CarTrip.DateOnly and propose the user to add the new one to the existing one
- [front] Check that Flutter handle the display of img with wrong extension but correct file header (if not, just add extension validation in back-end on top of header validation)
- [front] Show sessions affected by the change of the value of an entity used as FK by another one. Example: session affected by the change of the name of a client.
- Option to clear logs, limit size max
Important decisions
Choice of stack: Go / Flutter / SQLite:
Go is fast, simple and clear. After some courses on boot.dev I got attracted to it and wanted to make a full project to really compare to my other experiences in Python, my language of choice for many years, but also PHP, Java, TypeScript...
Flutter is my default choice in frontend. It's cross-platform most of all. I need more experience in it.
SQLite is also my default choice for database. Lightweight, it integrates well in mobile. ExpenseFlow doesn't have a huge amount of complex data to handle.
Codebase architecture
ExpenseFlow/
β
βββ cmd/
β βββ expenseflow/
β βββ main.go # Entry point of the application
β
βββ internal/
β βββ db/
β β βββ models.go # Database models (e.g., Expense, Session)
β β βββ queries.go # Database query functions
β β βββ init.go # Database connection setup
β βββ handlers/
β β βββ expenses.go # API route handlers (e.g., for adding expenses)
β β βββ sessions.go # API route handlers (e.g., for sessions)
β βββ services/
β βββ report.go # Business logic (e.g., generating reports)
β
βββ pkg/
β βββ middleware/ # Any reusable middleware (e.g., authentication, logging)
β
βββ api/
β βββ routes.go # Routes definition (e.g., registering API endpoints)
β
βββ config/
β βββ config.go # Configuration (e.g., environment variables, app settings)
β
βββ .gitignore
βββ go.mod
βββ README.md
Date/Time
Everything will use the standard library "time" and will record UTC timestamps. Making the user able travel in different timezone and not confuse any logic. Flutter will be the one taking care of the preferred display for the user.
Receipt handling
I chose to have non-nullable in the DB. A placeholder IMG. So when I test Expense for validation it's not the same as when I CheckReceipt().
The latter will make sure the file exist and is not the placeholder.
I think the user could add the picture later in their workflow when adding expense.
Hard limiting Float (for Amount.Value operations)
After creating some test to identify when Add or Sum were creating a float > math.MaxFloat64. I realized they were weird behaviors. You can't subtract a small float from a giant one, the result is unchanged. So I decided to hard code an unrealistic max in /config/config.go at 1_000_000_000.0
Datatype for IDs
sqlite3 drivers are returning int64 for ID columns. I decided to stick with int datatype in go (32 or 64 depending on the machine running.) It's very unlikely that I'll ever need int64, but I added a validation with Fatal if it ever occurs.
Logging
I will use the tag [info] for what's not fatal/error. I'll try to not crowd the logs with useless logic event. But for now any change to the DB is logged. And to avoid any security/privacy breach I'll only log IDs for information.
Problems and solutions
π§
Design choices
π§
Q/A to end users
- Do I need a currency converter? Does the report show multiple totals depending on currency?
-
Multiple reports in case of multiple currencies. - What are the models/properties I'm missing?
-
Standard categories -
Additional expense comment: Observation -
Taxes by categories -
Expenses can have Session = NULL -
KM by session -
opt VILLE > MISSION > opt VILLE