From ba0e817dd9c28afea438e8c19c7ab0e3ac1a88fe Mon Sep 17 00:00:00 2001 From: jeet Date: Sun, 22 Mar 2026 23:31:24 +0000 Subject: [PATCH] Add initial files --- CLAUDE(1).md | 53 +++ SwiftInvoice_Implementation_Plan.md | 685 ++++++++++++++++++++++++++++ 2 files changed, 738 insertions(+) create mode 100644 CLAUDE(1).md create mode 100644 SwiftInvoice_Implementation_Plan.md diff --git a/CLAUDE(1).md b/CLAUDE(1).md new file mode 100644 index 0000000..b527883 --- /dev/null +++ b/CLAUDE(1).md @@ -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. diff --git a/SwiftInvoice_Implementation_Plan.md b/SwiftInvoice_Implementation_Plan.md new file mode 100644 index 0000000..64d1050 --- /dev/null +++ b/SwiftInvoice_Implementation_Plan.md @@ -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*