686 lines
32 KiB
Markdown
686 lines
32 KiB
Markdown
# 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*
|