Add initial files
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# SwiftInvoice
|
||||
|
||||
Cross-platform offline-first invoice app for freelancers and tradespeople. Flutter/Dart, SQLite via Drift ORM, Riverpod state management, Material 3.
|
||||
|
||||
## Spec
|
||||
|
||||
The full implementation plan, database schema, and feature specs live in `docs/SwiftInvoice_Implementation_Plan.md`. **Read it before writing any database or feature code.** It is the single source of truth for this project.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
flutter pub get # Install dependencies
|
||||
flutter run # Run on connected device/emulator
|
||||
flutter test # Run all tests
|
||||
flutter analyze # Static analysis
|
||||
dart run build_runner build # Generate Drift code (run after any table change)
|
||||
dart format . # Format all Dart files
|
||||
```
|
||||
|
||||
## Coding Rules
|
||||
|
||||
- Feature-first folder structure under `lib/features/`. Each feature owns its screens, widgets, and providers.
|
||||
- Drift table classes in `lib/core/database/tables/`, one file per table. DAOs in `lib/core/database/daos/`, one per entity.
|
||||
- No business logic in widgets. Put it in services or Riverpod providers.
|
||||
- Use `ConsumerWidget` / `ConsumerStatefulWidget` with Riverpod. No raw `setState` for shared state.
|
||||
- All monetary values are INTEGER cents. Tax rates are basis points (825 = 8.25%). Never use doubles for money.
|
||||
- All primary keys are TEXT UUIDs. All tables have `created_at` and `updated_at` ISO 8601 timestamps.
|
||||
- Prefer `const` constructors. Use named routes via GoRouter.
|
||||
|
||||
## Git Workflow
|
||||
|
||||
- **Work on a feature branch.** Create a branch (`feat/feature-name`) for each feature or phase. Keep `main` clean.
|
||||
- **Commit in small increments.** One commit per logical unit: a single table, a single screen, a single service. Not one giant commit per feature.
|
||||
- **Use conventional commits:** `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`
|
||||
- **Merge to main only when a feature is complete**, tests pass, and `flutter analyze` is clean.
|
||||
- **Do not ask before committing, branching, or merging.** Just do it.
|
||||
|
||||
## Autonomous Execution
|
||||
|
||||
- **Do not ask clarifying questions.** The implementation plan is the spec. If something is ambiguous, make a reasonable decision, leave a `// TODO:` comment, and keep going.
|
||||
- **Do not ask before running commands.** Run pub get, build_runner, tests, and analyze freely.
|
||||
- **Do not stop to present options or ask for preferences.** The tech stack and patterns are decided.
|
||||
- **Do not ask "should I continue?"** Always continue to the next task.
|
||||
- **If a test fails, fix it.** Only stop if you cannot resolve it after 3 attempts.
|
||||
- **If you need a dependency, add it to pubspec.yaml**, run pub get, and continue.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Drift requires code generation — run `dart run build_runner build` after any table change or it won't compile.
|
||||
- The `pdf` package renders to its own widget tree, not Flutter widgets. They share syntax but are different libraries.
|
||||
- `flutter_local_notifications` needs platform-specific init in `MainActivity.kt` and `AppDelegate.swift`.
|
||||
- RevenueCat needs platform-specific setup in both `android/` and `ios/` — follow their Flutter quickstart.
|
||||
- Free tier limits (3 invoices/month, 2 clients) are enforced at the app layer, not the database.
|
||||
@@ -0,0 +1,685 @@
|
||||
# SwiftInvoice — MVP Implementation Plan
|
||||
|
||||
**Database Schema & Feature Specification**
|
||||
|
||||
Version 1.0 • March 2026
|
||||
Flutter • Dart • SQLite (Drift ORM) • Riverpod
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Executive Summary](#1-executive-summary)
|
||||
2. [Database Schema](#2-database-schema)
|
||||
3. [MVP Feature Specifications](#3-mvp-feature-specifications)
|
||||
4. [Build Timeline](#4-build-timeline)
|
||||
5. [Tech Stack Reference](#5-tech-stack-reference)
|
||||
6. [Recommended Project Structure](#6-recommended-project-structure)
|
||||
7. [Key Risks & Mitigations](#7-key-risks--mitigations)
|
||||
8. [Raw SQL Schema Reference](#8-raw-sql-schema-reference)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
SwiftInvoice is a cross-platform mobile invoicing application built with Flutter, targeting freelancers, tradespeople, and small business owners. The app differentiates itself through offline-first architecture, transparent pricing, and a modern UI that directly addresses the pain points of competitors like Invoice Simple.
|
||||
|
||||
This document defines every MVP feature in implementation-ready detail, provides a production-grade SQLite database schema designed for future extensibility, and outlines the screen-by-screen build plan to take the project from zero to App Store submission in nine weeks.
|
||||
|
||||
### 1.1 Strategic Pillars
|
||||
|
||||
| Pillar | Description |
|
||||
|---|---|
|
||||
| **Offline-First** | Every core feature works without internet. SQLite local storage, local PDF generation, local notifications. |
|
||||
| **Price Transparency** | Flat $3.99/month. Lifetime option available. No retroactive changes, no feature removals. |
|
||||
| **Friction-Based Monetization** | Paywall appears only when the user hits a limit (4th invoice, 3rd client), never on app launch. |
|
||||
| **Modern UX** | Material Design 3, single-screen workflows, one-tap estimate-to-invoice conversion. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Database Schema
|
||||
|
||||
The schema below is designed for the Drift ORM (SQLite) and is intentionally extensible. Every table includes `created_at` and `updated_at` timestamps, UUID-based primary keys for future cloud sync compatibility, and nullable fields where v1.1 features will plug in. The schema supports multi-currency, recurring invoices, and cloud sync without requiring migration-breaking changes.
|
||||
|
||||
### 2.1 Design Principles
|
||||
|
||||
- **UUID primary keys** on all tables to support conflict-free cloud sync in v1.1.
|
||||
- **ISO 4217 `currency_code` column** included from day one (default to user locale, multi-currency UI deferred to v1.1).
|
||||
- **Unified documents table**: invoices and estimates share the same `documents` table via a `document_type` discriminator, eliminating duplication and making estimate-to-invoice conversion trivial.
|
||||
- **Soft deletes** via `is_deleted` flag on client-facing tables, enabling undo and future sync reconciliation.
|
||||
- **All monetary values stored as INTEGER cents** (not floating point) to avoid rounding errors.
|
||||
- **Indexes** on every foreign key and on commonly filtered columns (`status`, `due_date`, `is_deleted`).
|
||||
|
||||
### 2.2 Entity Relationship Overview
|
||||
|
||||
```
|
||||
businesses (1) ──< clients (many)
|
||||
businesses (1) ──< documents (many)
|
||||
clients (1) ──< documents (many)
|
||||
documents (1) ──< line_items (many)
|
||||
documents (1) ──< payments (many)
|
||||
documents (1) ──< documents (self-ref: converted_from_id)
|
||||
```
|
||||
|
||||
### 2.3 Table: `businesses`
|
||||
|
||||
Stores the user's business profile. Single-row table for MVP (multi-business support is a future consideration).
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `id` | TEXT (UUID) | PRIMARY KEY | UUID v4, generated client-side |
|
||||
| `name` | TEXT | NOT NULL | Business display name |
|
||||
| `email` | TEXT | NULLABLE | Contact email shown on invoices |
|
||||
| `phone` | TEXT | NULLABLE | Contact phone |
|
||||
| `address_line1` | TEXT | NULLABLE | Street address line 1 |
|
||||
| `address_line2` | TEXT | NULLABLE | Street address line 2 |
|
||||
| `city` | TEXT | NULLABLE | City |
|
||||
| `state` | TEXT | NULLABLE | State / province |
|
||||
| `postal_code` | TEXT | NULLABLE | Zip / postal code |
|
||||
| `country_code` | TEXT | DEFAULT 'US' | ISO 3166-1 alpha-2 |
|
||||
| `tax_number` | TEXT | NULLABLE | Tax ID / VAT number |
|
||||
| `logo_path` | TEXT | NULLABLE | Local file path to logo image |
|
||||
| `default_currency` | TEXT | DEFAULT 'USD' | ISO 4217 code; drives default for new invoices |
|
||||
| `default_tax_rate` | INTEGER | DEFAULT 0 | Basis points (e.g., 825 = 8.25%) |
|
||||
| `default_payment_terms_days` | INTEGER | DEFAULT 30 | Auto-sets due date on new invoices |
|
||||
| `invoice_prefix` | TEXT | DEFAULT 'INV' | Prefix for auto-numbering (INV-001) |
|
||||
| `estimate_prefix` | TEXT | DEFAULT 'EST' | Prefix for estimate numbering |
|
||||
| `next_invoice_number` | INTEGER | DEFAULT 1 | Counter for auto-numbering |
|
||||
| `next_estimate_number` | INTEGER | DEFAULT 1 | Counter for estimate numbering |
|
||||
| `created_at` | TEXT (ISO 8601) | NOT NULL | Row creation timestamp |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
### 2.4 Table: `clients`
|
||||
|
||||
Stores client/customer records. Free tier enforces a maximum of 2 active clients at the application layer.
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `id` | TEXT (UUID) | PRIMARY KEY | UUID v4 |
|
||||
| `business_id` | TEXT | FK → businesses.id | Owner business |
|
||||
| `name` | TEXT | NOT NULL | Client display name |
|
||||
| `email` | TEXT | NULLABLE | Client email |
|
||||
| `phone` | TEXT | NULLABLE | Client phone |
|
||||
| `address_line1` | TEXT | NULLABLE | Street address |
|
||||
| `address_line2` | TEXT | NULLABLE | Address line 2 |
|
||||
| `city` | TEXT | NULLABLE | City |
|
||||
| `state` | TEXT | NULLABLE | State / province |
|
||||
| `postal_code` | TEXT | NULLABLE | Zip / postal code |
|
||||
| `country_code` | TEXT | NULLABLE | ISO 3166-1 alpha-2 |
|
||||
| `notes` | TEXT | NULLABLE | Internal notes (not shown on invoices) |
|
||||
| `outstanding_balance` | INTEGER | DEFAULT 0 | Cached sum of unpaid invoice totals (cents) |
|
||||
| `is_deleted` | INTEGER | DEFAULT 0 | Soft delete flag (0 = active, 1 = deleted) |
|
||||
| `created_at` | TEXT (ISO 8601) | NOT NULL | Row creation timestamp |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
### 2.5 Table: `documents`
|
||||
|
||||
Unified table for both invoices and estimates, distinguished by the `document_type` column. This avoids schema duplication and makes estimate-to-invoice conversion a simple status change plus a new row copy.
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `id` | TEXT (UUID) | PRIMARY KEY | UUID v4 |
|
||||
| `business_id` | TEXT | FK → businesses.id | Owner business |
|
||||
| `client_id` | TEXT | FK → clients.id | Associated client |
|
||||
| `document_type` | TEXT | NOT NULL | 'invoice' or 'estimate' |
|
||||
| `document_number` | TEXT | NOT NULL, UNIQUE | e.g., INV-001 or EST-003 |
|
||||
| `status` | TEXT | NOT NULL | See status enum below |
|
||||
| `issue_date` | TEXT (ISO 8601) | NOT NULL | Date document was created/issued |
|
||||
| `due_date` | TEXT (ISO 8601) | NULLABLE | Payment due date (invoices) or expiry (estimates) |
|
||||
| `currency_code` | TEXT | DEFAULT 'USD' | ISO 4217; copied from business default at creation |
|
||||
| `subtotal` | INTEGER | DEFAULT 0 | Sum of line items (cents), auto-calculated |
|
||||
| `tax_rate` | INTEGER | DEFAULT 0 | Basis points (825 = 8.25%) |
|
||||
| `tax_amount` | INTEGER | DEFAULT 0 | Calculated tax in cents |
|
||||
| `discount_type` | TEXT | NULLABLE | 'percentage' or 'fixed' |
|
||||
| `discount_value` | INTEGER | DEFAULT 0 | Percentage (basis points) or fixed (cents) |
|
||||
| `discount_amount` | INTEGER | DEFAULT 0 | Resolved discount in cents |
|
||||
| `total` | INTEGER | DEFAULT 0 | Final total in cents (subtotal + tax - discount) |
|
||||
| `amount_paid` | INTEGER | DEFAULT 0 | Sum of linked payments (cents) |
|
||||
| `amount_due` | INTEGER | DEFAULT 0 | total - amount_paid (cents) |
|
||||
| `notes` | TEXT | NULLABLE | Notes/terms shown on document footer |
|
||||
| `converted_from_id` | TEXT | NULLABLE FK → documents.id | Links invoice back to source estimate |
|
||||
| `is_deleted` | INTEGER | DEFAULT 0 | Soft delete flag |
|
||||
| `created_at` | TEXT (ISO 8601) | NOT NULL | Row creation timestamp |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
#### Document Status Enum Values
|
||||
|
||||
| document_type | Valid Statuses | Description |
|
||||
|---|---|---|
|
||||
| invoice | `draft` | Invoice created but not yet sent |
|
||||
| invoice | `sent` | Invoice shared with client |
|
||||
| invoice | `partial` | Some payment received, balance remaining |
|
||||
| invoice | `paid` | Fully paid |
|
||||
| invoice | `overdue` | Past due_date and not fully paid (set by app logic) |
|
||||
| invoice | `void` | Cancelled invoice (kept for records) |
|
||||
| estimate | `draft` | Estimate created but not sent |
|
||||
| estimate | `sent` | Estimate shared with client |
|
||||
| estimate | `accepted` | Client approved; ready for invoice conversion |
|
||||
| estimate | `declined` | Client rejected the estimate |
|
||||
| estimate | `expired` | Past due_date (expiry) without acceptance |
|
||||
| estimate | `converted` | Converted to invoice (converted_from_id links back) |
|
||||
|
||||
### 2.6 Table: `line_items`
|
||||
|
||||
Shared line items table for both invoices and estimates. Each line item belongs to a document. The `sort_order` column preserves user-defined ordering.
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `id` | TEXT (UUID) | PRIMARY KEY | UUID v4 |
|
||||
| `document_id` | TEXT | FK → documents.id | Parent document |
|
||||
| `description` | TEXT | NOT NULL | Line item description |
|
||||
| `quantity` | REAL | NOT NULL, DEFAULT 1 | Supports fractional (e.g., 2.5 hours) |
|
||||
| `unit_price` | INTEGER | NOT NULL | Price per unit in cents |
|
||||
| `amount` | INTEGER | NOT NULL | quantity × unit_price (cents), app-calculated |
|
||||
| `sort_order` | INTEGER | DEFAULT 0 | Display ordering within document |
|
||||
| `created_at` | TEXT (ISO 8601) | NOT NULL | Row creation timestamp |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
### 2.7 Table: `payments`
|
||||
|
||||
Records individual payments against invoices. Supports partial payments and multiple payment methods. The document's `amount_paid` and `amount_due` fields are updated via application logic whenever a payment is added or removed.
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `id` | TEXT (UUID) | PRIMARY KEY | UUID v4 |
|
||||
| `document_id` | TEXT | FK → documents.id | Associated invoice |
|
||||
| `amount` | INTEGER | NOT NULL | Payment amount in cents |
|
||||
| `method` | TEXT | NOT NULL | 'cash', 'card', 'bank_transfer', 'check', 'other' |
|
||||
| `paid_at` | TEXT (ISO 8601) | NOT NULL | Date/time payment was received |
|
||||
| `notes` | TEXT | NULLABLE | Payment memo or reference number |
|
||||
| `created_at` | TEXT (ISO 8601) | NOT NULL | Row creation timestamp |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
### 2.8 Table: `app_settings`
|
||||
|
||||
Key-value store for application preferences (subscription status, theme, notification settings). Avoids hardcoding configuration and supports future settings without schema changes.
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|---|---|---|---|
|
||||
| `key` | TEXT | PRIMARY KEY | Setting identifier (e.g., 'subscription_tier') |
|
||||
| `value` | TEXT | NOT NULL | Serialized value (JSON string for complex values) |
|
||||
| `updated_at` | TEXT (ISO 8601) | NOT NULL | Last modification timestamp |
|
||||
|
||||
### 2.9 Indexes
|
||||
|
||||
| Index Name | Table | Columns | Purpose |
|
||||
|---|---|---|---|
|
||||
| `idx_clients_business` | clients | business_id, is_deleted | List active clients for a business |
|
||||
| `idx_documents_client` | documents | client_id, document_type, status | Filter docs by client and type |
|
||||
| `idx_documents_status` | documents | status, due_date | Dashboard: overdue detection, status filtering |
|
||||
| `idx_documents_type` | documents | document_type, is_deleted | Separate invoice vs estimate queries |
|
||||
| `idx_documents_number` | documents | document_number | Unique lookup by number (UNIQUE constraint) |
|
||||
| `idx_line_items_doc` | line_items | document_id, sort_order | Ordered retrieval of line items |
|
||||
| `idx_payments_doc` | payments | document_id | Sum payments for a document |
|
||||
|
||||
### 2.10 Future-Proofing Notes
|
||||
|
||||
The schema has been designed so the following v1.1 features can be added without breaking changes:
|
||||
|
||||
| Future Feature | Schema Extension Required | Why It Works Today |
|
||||
|---|---|---|
|
||||
| **Cloud Sync** | Add `sync_status` and `last_synced_at` columns; add `remote_id` column | UUID primary keys avoid ID collisions. Soft deletes support conflict resolution. |
|
||||
| **Recurring Invoices** | Add `recurrence_rule` table (document_id, frequency, next_run_date) | Documents table is self-contained; recurrence references an existing document as a template. |
|
||||
| **Multi-Currency** | UI work only; `currency_code` column already exists on documents | Currency is stored per-document, so mixed-currency invoices already work at the data layer. |
|
||||
| **Expense Tracking** | Add `expenses` table (id, business_id, category, amount, date, receipt_path) | Completely additive; no changes to existing tables. |
|
||||
| **Revenue Dashboard** | Query layer only (SUM/GROUP BY on documents and payments) | All necessary data (totals, dates, statuses, payments) already exists. |
|
||||
| **Multiple Businesses** | Remove single-row assumption in app logic | `business_id` FK already exists on clients and documents. |
|
||||
| **Custom PDF Templates** | Add `templates` table (id, name, html_template, is_default) | Additive table; documents reference template_id. |
|
||||
|
||||
---
|
||||
|
||||
## 3. MVP Feature Specifications
|
||||
|
||||
Each feature is specified with its screen behavior, data flow, business rules, and acceptance criteria. Features are grouped by the build phase in which they should be implemented.
|
||||
|
||||
### 3.1 Business Profile Setup (Onboarding)
|
||||
|
||||
#### 3.1.1 Screen Behavior
|
||||
|
||||
- First-launch flow: user sees a single-screen form to enter their business details.
|
||||
- Required field: Business Name only. All other fields are optional but encouraged.
|
||||
- Logo picker: tap a placeholder to select an image from the device gallery. Crop to square, save to app directory.
|
||||
- Currency selector: dropdown defaulting to device locale currency. Sets `businesses.default_currency`.
|
||||
- Tax rate field: numeric input with helper text showing format (e.g., 8.25%).
|
||||
- "Save & Start" button creates the businesses row and navigates to the Invoice Dashboard.
|
||||
- Accessible later from Settings to edit.
|
||||
|
||||
#### 3.1.2 Data Flow
|
||||
|
||||
- INSERT into `businesses` table.
|
||||
- Set `app_settings` key `'onboarding_complete'` = `'true'`.
|
||||
- Logo image saved to local file system; `logo_path` column stores the path.
|
||||
|
||||
#### 3.1.3 Acceptance Criteria
|
||||
|
||||
- App does not show main UI until onboarding is complete.
|
||||
- Business name appears on all generated PDFs immediately after setup.
|
||||
- Logo appears on PDFs if provided; placeholder text if not.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Invoice Dashboard (Home Screen)
|
||||
|
||||
#### 3.2.1 Screen Behavior
|
||||
|
||||
- Top summary bar: 4 stat cards showing total Outstanding, Overdue, Paid (this month), and Draft count.
|
||||
- Below: scrollable list of all invoices, newest first.
|
||||
- Each list item shows: invoice number, client name, total amount, due date, and a colored status pill (Draft = gray, Sent = blue, Paid = green, Overdue = red, Partial = orange).
|
||||
- Filter chips at top of list: All, Draft, Sent, Overdue, Paid. Tapping a chip filters the list.
|
||||
- Floating Action Button (FAB): "+ New Invoice" opens the Invoice Creator.
|
||||
- Tap any invoice to open it in read mode with Edit, Share, and Delete actions.
|
||||
- Bottom navigation bar: Invoices (active), Estimates, Clients, Settings.
|
||||
|
||||
#### 3.2.2 Data Flow
|
||||
|
||||
- Query: `SELECT * FROM documents WHERE document_type = 'invoice' AND is_deleted = 0 ORDER BY created_at DESC`.
|
||||
- Summary stats: aggregate queries on documents table filtered by status and current month.
|
||||
- Overdue detection: on app foreground, run `UPDATE documents SET status = 'overdue' WHERE due_date < date('now') AND status IN ('sent', 'partial') AND amount_due > 0`.
|
||||
|
||||
#### 3.2.3 Acceptance Criteria
|
||||
|
||||
- Dashboard loads in under 200ms with 100 invoices in the database.
|
||||
- Status pills update in real-time when an invoice is edited or a payment is recorded.
|
||||
- Overdue invoices are flagged within 1 second of the app returning to the foreground.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Invoice Creator
|
||||
|
||||
#### 3.3.1 Screen Behavior
|
||||
|
||||
- Single scrollable screen (no multi-step wizard).
|
||||
- Client selector: searchable dropdown of saved clients. "+ New Client" inline option opens a bottom sheet.
|
||||
- Invoice number: auto-generated from `businesses.invoice_prefix` + `next_invoice_number`. Editable.
|
||||
- Date fields: Issue Date (defaults to today) and Due Date (defaults to today + `default_payment_terms_days`).
|
||||
- Line items section: each row has Description, Quantity, Rate fields. Running line total on the right. "+ Add Item" button appends a new row. Swipe-to-delete on each row.
|
||||
- Totals section: live-updating Subtotal, Tax (with toggle to override business default rate), Discount (toggle between percentage and fixed amount), and Grand Total.
|
||||
- Notes field: multi-line text for payment terms or messages.
|
||||
- Action bar: "Save as Draft" and "Save & Share" buttons.
|
||||
|
||||
#### 3.3.2 Data Flow
|
||||
|
||||
- On save: INSERT into `documents` (document_type = 'invoice') and INSERT `line_items` rows.
|
||||
- Increment `businesses.next_invoice_number`.
|
||||
- "Save & Share" sets status = 'sent' and opens the share sheet (see PDF Export feature).
|
||||
- All monetary calculations happen in cents at the application layer.
|
||||
|
||||
#### 3.3.3 Business Rules
|
||||
|
||||
- Free tier: if active invoice count >= 3 in current calendar month, show paywall before allowing creation.
|
||||
- Invoice number must be unique; if user edits the auto-number, validate before save.
|
||||
- At least one line item is required to save.
|
||||
- Quantity must be > 0. Rate can be 0 (for "no charge" line items).
|
||||
|
||||
#### 3.3.4 Acceptance Criteria
|
||||
|
||||
- Creating an invoice with 5 line items takes under 60 seconds for a familiar user.
|
||||
- Grand total updates within 100ms of any field change.
|
||||
- Saving works entirely offline.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Client Manager
|
||||
|
||||
#### 3.4.1 Screen Behavior
|
||||
|
||||
- Alphabetically sorted list of all active clients.
|
||||
- Each list item shows: client name, email, and outstanding balance.
|
||||
- Tap a client to view their detail page: contact info, total invoiced, total paid, outstanding balance, and a list of all their documents.
|
||||
- FAB: "+ New Client" opens the client creation form.
|
||||
- Client form: Name (required), Email, Phone, Address fields, Notes.
|
||||
- Edit and delete (soft) available from detail page.
|
||||
|
||||
#### 3.4.2 Business Rules
|
||||
|
||||
- Free tier: if active (non-deleted) client count >= 2, show paywall on "+ New Client" tap.
|
||||
- Deleting a client soft-deletes (`is_deleted = 1`). Their invoices remain visible.
|
||||
- Outstanding balance is updated whenever an invoice for this client is saved, paid, or voided.
|
||||
|
||||
#### 3.4.3 Acceptance Criteria
|
||||
|
||||
- Client auto-fill in invoice creator works by typing 2+ characters of the name.
|
||||
- Outstanding balance is always accurate to the cent.
|
||||
|
||||
---
|
||||
|
||||
### 3.5 PDF Export & Sharing
|
||||
|
||||
#### 3.5.1 Screen Behavior
|
||||
|
||||
- "Share" button on any invoice or estimate opens a PDF preview, then the system share sheet.
|
||||
- Share sheet allows sending via WhatsApp, email, SMS, or any installed app.
|
||||
- PDF is generated locally using the Dart `pdf` package. No server required.
|
||||
|
||||
#### 3.5.2 PDF Layout
|
||||
|
||||
- **Header:** business logo (left), business name and contact info (right).
|
||||
- **Document title:** "INVOICE" or "ESTIMATE" with document number and status.
|
||||
- **Bill To:** client name and address block.
|
||||
- **Dates:** Issue Date and Due Date / Expiry Date.
|
||||
- **Line items table:** Description, Qty, Rate, Amount columns.
|
||||
- **Totals block:** Subtotal, Tax, Discount, Total Due.
|
||||
- **Footer:** notes/terms text.
|
||||
- **Watermark** (free tier only): small "Created with SwiftInvoice" text in footer.
|
||||
|
||||
#### 3.5.3 Business Rules
|
||||
|
||||
- Free tier: PDF includes watermark. Pro/Lifetime: no watermark.
|
||||
- Sharing an invoice automatically sets its status to 'sent' if currently 'draft'.
|
||||
- PDF file is cached locally for re-sharing without regeneration.
|
||||
|
||||
---
|
||||
|
||||
### 3.6 Estimate Maker (Pro Feature)
|
||||
|
||||
#### 3.6.1 Screen Behavior
|
||||
|
||||
- Identical form layout to Invoice Creator.
|
||||
- Uses `estimate_prefix` and `next_estimate_number` for numbering (EST-001).
|
||||
- Due Date field labeled as "Valid Until" (expiry date).
|
||||
- "Convert to Invoice" button available on accepted estimates: creates a new invoice document copying all fields and line items, sets estimate status to 'converted', and populates `converted_from_id`.
|
||||
|
||||
#### 3.6.2 One-Tap Conversion Logic
|
||||
|
||||
1. Deep-copy the estimate's document row with `document_type = 'invoice'`, `status = 'draft'`, new UUID, new invoice number.
|
||||
2. Deep-copy all `line_items` pointing to the new `document_id`.
|
||||
3. Set the original estimate's status to `'converted'`.
|
||||
4. Set new invoice's `converted_from_id` to the estimate's `id`.
|
||||
5. Navigate to the new invoice for final review before saving.
|
||||
|
||||
#### 3.6.3 Acceptance Criteria
|
||||
|
||||
- Converting a 10-line-item estimate to an invoice completes in under 500ms.
|
||||
- All line item data is preserved; user does not re-enter anything.
|
||||
- Converted estimate shows a visual indicator linking to its invoice.
|
||||
|
||||
---
|
||||
|
||||
### 3.7 Payment Tracker (Pro Feature)
|
||||
|
||||
#### 3.7.1 Screen Behavior
|
||||
|
||||
- On an invoice detail screen, a "Record Payment" button opens a bottom sheet.
|
||||
- Fields: Amount (defaults to `amount_due`), Payment Method (dropdown), Date (defaults to today), Notes (optional).
|
||||
- Payment history list shown below the invoice details with all recorded payments.
|
||||
- "Mark as Paid" shortcut: records a payment for the full remaining balance.
|
||||
|
||||
#### 3.7.2 Data Flow
|
||||
|
||||
- INSERT into `payments` table.
|
||||
- UPDATE `documents`: `amount_paid = SUM(payments.amount)`, `amount_due = total - amount_paid`.
|
||||
- If `amount_due = 0`, set status = 'paid'. If `amount_due > 0` and `amount_paid > 0`, set status = 'partial'.
|
||||
- UPDATE client's `outstanding_balance` cache.
|
||||
|
||||
#### 3.7.3 Acceptance Criteria
|
||||
|
||||
- Partial payments correctly track remaining balance.
|
||||
- Payment cannot exceed `amount_due` (validate before save).
|
||||
- Deleting a payment reverses the `amount_paid` and status updates.
|
||||
|
||||
---
|
||||
|
||||
### 3.8 Overdue Reminders (Pro Feature)
|
||||
|
||||
#### 3.8.1 Behavior
|
||||
|
||||
- Uses `flutter_local_notifications` to schedule a local notification on invoice `due_date`.
|
||||
- Notification text: "Invoice [INV-001] for [Client Name] is overdue. Tap to view."
|
||||
- Tapping the notification deep-links to the invoice detail screen.
|
||||
- If an invoice is marked paid before the due date, cancel the scheduled notification.
|
||||
- No backend or push notification service required.
|
||||
|
||||
#### 3.8.2 Acceptance Criteria
|
||||
|
||||
- Notification fires at 9:00 AM local time on the day after the due date.
|
||||
- Notification is cancelled when the invoice is paid or voided.
|
||||
- Works entirely offline.
|
||||
|
||||
---
|
||||
|
||||
### 3.9 In-App Purchase & Paywall
|
||||
|
||||
#### 3.9.1 Paywall Trigger Points
|
||||
|
||||
- **Trigger 1:** User attempts to create their 4th invoice in a calendar month.
|
||||
- **Trigger 2:** User attempts to add a 3rd client.
|
||||
- Never shown on app launch, on a timer, or as a pop-up ad.
|
||||
|
||||
#### 3.9.2 Paywall Screen
|
||||
|
||||
- Three plan cards displayed side by side (or stacked on small screens):
|
||||
- Monthly: $3.99/month
|
||||
- Yearly: $29.99/year ("Save 37%" badge)
|
||||
- Lifetime: $49.99 one-time ("Best Value" badge)
|
||||
- Feature comparison list below the cards.
|
||||
- "Restore Purchases" link at the bottom.
|
||||
- Dismiss button (X) allows the user to go back without purchasing.
|
||||
|
||||
#### 3.9.3 Technical Implementation
|
||||
|
||||
- RevenueCat SDK (`purchases_flutter`) handles purchase flow, receipt validation, and subscription status.
|
||||
- Subscription status cached in `app_settings` (`'subscription_tier'`: `'free'` | `'pro'` | `'lifetime'`).
|
||||
- On app launch: check RevenueCat for current entitlement status and sync to local cache.
|
||||
- All tier-gating logic reads from the local cache for instant, offline-capable checks.
|
||||
|
||||
#### 3.9.4 Acceptance Criteria
|
||||
|
||||
- Purchase completes and unlocks Pro within 3 seconds.
|
||||
- Restore Purchases works on a fresh install / new device.
|
||||
- Free tier limits are enforced even when offline (cached tier value).
|
||||
|
||||
---
|
||||
|
||||
### 3.10 Offline-First Architecture
|
||||
|
||||
#### 3.10.1 Design
|
||||
|
||||
- All data stored in a single SQLite database via Drift ORM.
|
||||
- Database file location: app documents directory (platform-managed, backed up by OS).
|
||||
- No API calls for any core feature (invoicing, clients, PDF generation, payments).
|
||||
- Only network-dependent features: in-app purchase validation, future cloud sync.
|
||||
|
||||
#### 3.10.2 Acceptance Criteria
|
||||
|
||||
- With airplane mode on, user can create a client, create an invoice, generate a PDF, and share via email draft — all without error.
|
||||
- Database survives app update without data loss.
|
||||
- Database size stays under 50MB with 1,000 invoices and 200 clients.
|
||||
|
||||
---
|
||||
|
||||
## 4. Build Timeline
|
||||
|
||||
The build is organized into 9 weekly sprints at 5–10 hours per week. Each sprint has defined deliverables and a clear definition of done.
|
||||
|
||||
| Week | Sprint Focus | Key Deliverables | Definition of Done |
|
||||
|---|---|---|---|
|
||||
| **1–2** | Foundation | Flutter project scaffold with folder structure; Drift ORM database with all MVP tables; Navigation shell (bottom nav, routing); Business profile onboarding screen | Onboarding flow creates a business row. Navigation between all 4 tabs works. Database migrations run clean. |
|
||||
| **3–5** | Core Invoicing | Invoice Creator (single-screen form); Client Manager (CRUD + search); Invoice Dashboard with status filters; Real-time total calculation | User can create a client, create an invoice with 3+ line items, and see it on the dashboard with correct status. |
|
||||
| **6** | PDF & Sharing | PDF generation with business branding; Watermark logic (free vs Pro); System share sheet integration; WhatsApp deep-link sharing | Generated PDF looks professional, includes logo, and can be shared via WhatsApp and email. |
|
||||
| **7** | Pro Features | Estimate flow with conversion logic; Payment tracker with partial payments; Overdue push notifications | Estimate converts to invoice in one tap. Payments update balances. Notifications fire on overdue date. |
|
||||
| **8** | Monetization | RevenueCat integration; Paywall screen with 3 plan options; Free tier limit enforcement; Restore purchases flow | Free tier blocks 4th invoice and 3rd client. Purchase unlocks Pro. Restore works on fresh install. |
|
||||
| **9** | Polish & Launch | UI polish and animations; App Store screenshots and descriptions; Beta testing (TestFlight + Play internal); Store submission | App passes both store review processes. No crashes in 48-hour beta soak test. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Tech Stack Reference
|
||||
|
||||
| Layer | Technology | Version | Purpose |
|
||||
|---|---|---|---|
|
||||
| Framework | Flutter | 3.x (latest stable) | Cross-platform UI |
|
||||
| Language | Dart | 3.x | Application logic |
|
||||
| Local Database | SQLite via Drift | 2.x | Offline-first data persistence |
|
||||
| PDF Generation | pdf (pub.dev) | Latest | Client-side PDF rendering |
|
||||
| State Management | Riverpod | 2.x | Reactive state, dependency injection |
|
||||
| Notifications | flutter_local_notifications | Latest | Overdue reminders |
|
||||
| In-App Purchases | purchases_flutter (RevenueCat) | Latest | Subscription management |
|
||||
| iOS CI/CD | Codemagic | N/A | Cloud Mac builds (~$20/build) |
|
||||
| Analytics | Firebase Analytics | Free tier | Usage tracking, crash reporting |
|
||||
|
||||
---
|
||||
|
||||
## 6. Recommended Project Structure
|
||||
|
||||
```
|
||||
lib/
|
||||
main.dart
|
||||
app.dart
|
||||
core/
|
||||
database/
|
||||
database.dart # Drift database class
|
||||
tables/ # Table definitions (1 file per table)
|
||||
daos/ # Data Access Objects (1 per entity)
|
||||
models/ # Shared data models / enums
|
||||
services/ # Business logic services
|
||||
utils/ # Formatters, validators, helpers
|
||||
theme/ # Material 3 theme, colors, typography
|
||||
features/
|
||||
onboarding/ # Business profile setup
|
||||
invoices/ # Dashboard, creator, detail
|
||||
estimates/ # Estimate list, creator, detail
|
||||
clients/ # Client list, form, detail
|
||||
payments/ # Payment recording widgets
|
||||
pdf/ # PDF template and generation logic
|
||||
paywall/ # Paywall screen, tier logic
|
||||
settings/ # App settings screen
|
||||
shared/ # Shared widgets (status pills, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Risks & Mitigations
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|---|---|---|---|
|
||||
| iOS App Store rejection | Medium | High | Strictly follow Apple HIG. Test on simulator before submission. Budget 1 week for review iteration. |
|
||||
| Lifetime tier cannibalizes subs | Medium | Medium | Price at 12.5× monthly ($49.99). Run lifetime discounts only during events. Monitor conversion ratios monthly. |
|
||||
| Drift ORM schema migration issues | Low | High | Write migration tests. Keep schema clean from day one. Use stepByStep migrations. |
|
||||
| RevenueCat integration complexity | Medium | Medium | Use RevenueCat's Flutter sample app as reference. Test sandbox purchases extensively. |
|
||||
| PDF rendering inconsistencies | Low | Medium | Test on 10+ device sizes. Use fixed-width PDF layout. Cache and preview before sharing. |
|
||||
| Low organic downloads at launch | High | High | Prepare Apple Search Ads + Google UAC campaigns targeting competitor keywords. Budget $200–500 for first month. |
|
||||
|
||||
---
|
||||
|
||||
## 8. Raw SQL Schema Reference
|
||||
|
||||
The following SQL can be used as a reference when defining the Drift ORM table classes. While Drift generates SQL from Dart code, this raw SQL serves as the canonical schema definition.
|
||||
|
||||
```sql
|
||||
-- ================================================
|
||||
-- SwiftInvoice MVP Database Schema
|
||||
-- SQLite / Drift ORM Compatible
|
||||
-- ================================================
|
||||
|
||||
CREATE TABLE businesses (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
address_line1 TEXT,
|
||||
address_line2 TEXT,
|
||||
city TEXT,
|
||||
state TEXT,
|
||||
postal_code TEXT,
|
||||
country_code TEXT DEFAULT 'US',
|
||||
tax_number TEXT,
|
||||
logo_path TEXT,
|
||||
default_currency TEXT DEFAULT 'USD',
|
||||
default_tax_rate INTEGER DEFAULT 0,
|
||||
default_payment_terms_days INTEGER DEFAULT 30,
|
||||
invoice_prefix TEXT DEFAULT 'INV',
|
||||
estimate_prefix TEXT DEFAULT 'EST',
|
||||
next_invoice_number INTEGER DEFAULT 1,
|
||||
next_estimate_number INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE clients (
|
||||
id TEXT PRIMARY KEY,
|
||||
business_id TEXT NOT NULL REFERENCES businesses(id),
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
address_line1 TEXT,
|
||||
address_line2 TEXT,
|
||||
city TEXT,
|
||||
state TEXT,
|
||||
postal_code TEXT,
|
||||
country_code TEXT,
|
||||
notes TEXT,
|
||||
outstanding_balance INTEGER DEFAULT 0,
|
||||
is_deleted INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE documents (
|
||||
id TEXT PRIMARY KEY,
|
||||
business_id TEXT NOT NULL REFERENCES businesses(id),
|
||||
client_id TEXT NOT NULL REFERENCES clients(id),
|
||||
document_type TEXT NOT NULL CHECK(document_type IN ('invoice','estimate')),
|
||||
document_number TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL,
|
||||
issue_date TEXT NOT NULL,
|
||||
due_date TEXT,
|
||||
currency_code TEXT DEFAULT 'USD',
|
||||
subtotal INTEGER DEFAULT 0,
|
||||
tax_rate INTEGER DEFAULT 0,
|
||||
tax_amount INTEGER DEFAULT 0,
|
||||
discount_type TEXT CHECK(discount_type IN ('percentage','fixed',NULL)),
|
||||
discount_value INTEGER DEFAULT 0,
|
||||
discount_amount INTEGER DEFAULT 0,
|
||||
total INTEGER DEFAULT 0,
|
||||
amount_paid INTEGER DEFAULT 0,
|
||||
amount_due INTEGER DEFAULT 0,
|
||||
notes TEXT,
|
||||
converted_from_id TEXT REFERENCES documents(id),
|
||||
is_deleted INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE line_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
document_id TEXT NOT NULL REFERENCES documents(id),
|
||||
description TEXT NOT NULL,
|
||||
quantity REAL NOT NULL DEFAULT 1,
|
||||
unit_price INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE payments (
|
||||
id TEXT PRIMARY KEY,
|
||||
document_id TEXT NOT NULL REFERENCES documents(id),
|
||||
amount INTEGER NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
paid_at TEXT NOT NULL,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- ================================================
|
||||
-- Indexes
|
||||
-- ================================================
|
||||
|
||||
CREATE INDEX idx_clients_business ON clients(business_id, is_deleted);
|
||||
CREATE INDEX idx_documents_client ON documents(client_id, document_type, status);
|
||||
CREATE INDEX idx_documents_status ON documents(status, due_date);
|
||||
CREATE INDEX idx_documents_type ON documents(document_type, is_deleted);
|
||||
CREATE INDEX idx_line_items_doc ON line_items(document_id, sort_order);
|
||||
CREATE INDEX idx_payments_doc ON payments(document_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*SwiftInvoice MVP Implementation Plan — Version 1.0 — March 2026*
|
||||
Reference in New Issue
Block a user