32 KiB
SwiftInvoice — MVP Implementation Plan
Database Schema & Feature Specification
Version 1.0 • March 2026 Flutter • Dart • SQLite (Drift ORM) • Riverpod
Table of Contents
- Executive Summary
- Database Schema
- MVP Feature Specifications
- Build Timeline
- Tech Stack Reference
- Recommended Project Structure
- Key Risks & Mitigations
- 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_codecolumn included from day one (default to user locale, multi-currency UI deferred to v1.1). - Unified documents table: invoices and estimates share the same
documentstable via adocument_typediscriminator, eliminating duplication and making estimate-to-invoice conversion trivial. - Soft deletes via
is_deletedflag 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
businessestable. - Set
app_settingskey'onboarding_complete'='true'. - Logo image saved to local file system;
logo_pathcolumn 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 INSERTline_itemsrows. - 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
pdfpackage. 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_prefixandnext_estimate_numberfor 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
- Deep-copy the estimate's document row with
document_type = 'invoice',status = 'draft', new UUID, new invoice number. - Deep-copy all
line_itemspointing to the newdocument_id. - Set the original estimate's status to
'converted'. - Set new invoice's
converted_from_idto the estimate'sid. - 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
paymentstable. - UPDATE
documents:amount_paid = SUM(payments.amount),amount_due = total - amount_paid. - If
amount_due = 0, set status = 'paid'. Ifamount_due > 0andamount_paid > 0, set status = 'partial'. - UPDATE client's
outstanding_balancecache.
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_paidand status updates.
3.8 Overdue Reminders (Pro Feature)
3.8.1 Behavior
- Uses
flutter_local_notificationsto schedule a local notification on invoicedue_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.
-- ================================================
-- 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