# S2-H06: Receipt/Invoice Upload + Accounting Module Integration Specification **Agent Identity:** S2-H06 **Assigned to:** Receipt/Invoice Upload + Accounting Module Integration **Document Status:** Complete Specification **Session:** Cloud Session 2 - Technical Integration Architecture **Date:** 2025-11-13 --- ## Executive Summary This specification defines the architectural integration of receipt/invoice upload and multi-user expense tracking for NaviDocs. The system enables boat owners and captains to capture, process, and manage expenses with OCR-powered automation, reimbursement workflows, and full IF.TTT compliance for audit traceability. **Key Components:** - Multi-user expense tracking (owner, captain, crew) - Receipt/invoice OCR workflow (photo → extraction → review → save) - Accounting module integration (Spliit fork for MIT-licensed flexibility) - Reimbursement workflow with owner approval - WhatsApp AI integration for expense logging - Multi-currency support (€, $, £) - Export to Excel/CSV for accountants - Full IF.TTT audit trail compliance --- ## 1. Multi-User Expense Tracking Design ### 1.1 User Roles & Expense Categories #### Owner Expenditure - **Payment Methods:** Cash, card, bank transfers - **Typical Expenses:** Marina fees, insurance, major upgrades, professional services, fuel, haul-outs - **Approval Flow:** Direct entry (owner logs own expenses) + approval of captain reimbursements - **Budget Authority:** Can set monthly/annual budgets and approve work roadmap expenses - **Tax Implications:** Tracks VAT status, business vs. personal expenses (for charter/rental boats) #### Captain/Crew Expenditure - **Payment Methods:** Boat card (pre-approved limit), boat cash, personal funds (for reimbursement) - **Typical Expenses:** Provisions, fuel, minor repairs, supplies, crew meals, dockage during captain-only periods - **Approval Flow:** Submit receipt → OCR extraction → owner approves/rejects → mark paid - **Documentation:** Must provide receipt photo for all expenses - **Reimbursement Tracking:** Separate accounting for personal cash advances vs. company card charges ### 1.2 Expense Categories (Hierarchical) ``` Root Categories: ├── Fuel & Provisioning │ ├── Fuel │ ├── Diesel │ ├── LPG │ ├── Provisions (food/water) │ └── Beverage supplies ├── Marina & Mooring │ ├── Marina fees │ ├── Mooring buoy rental │ ├── Harbor dues │ └── Fuel dock (if separate) ├── Maintenance & Repairs │ ├── Engine maintenance │ ├── Electronics │ ├── Hull & Structure │ ├── Deck & Rigging │ ├── Safety equipment │ └── Professional services (surveyor, mechanic) ├── Insurance │ ├── Hull insurance │ ├── Liability insurance │ ├── Crew insurance │ └── Document insurance ├── Professional Upgrades │ ├── Electronics upgrades │ ├── Safety system upgrades │ ├── Interior renovations │ └── Engine upgrades ├── Operating Expenses │ ├── Crew salaries/tips │ ├── Supplies & consumables │ ├── Cleaning & laundry │ ├── Documentation & permits │ └── Crew meals ├── Miscellaneous │ ├── Tips & gratuities │ ├── Small tools │ ├── Spare parts inventory │ └── Other Currency: € (EUR), $ (USD), £ (GBP) - auto-converted to primary boat currency ``` ### 1.3 Expense Model Schema ```sql CREATE TABLE expenses ( id VARCHAR(36) PRIMARY KEY, boat_id VARCHAR(36) NOT NULL, -- User/Role Information created_by_user_id VARCHAR(36) NOT NULL, created_by_role ENUM('owner', 'captain', 'crew') NOT NULL, -- Financial Data amount DECIMAL(12, 2) NOT NULL, currency ENUM('EUR', 'USD', 'GBP', 'OTHER') DEFAULT 'EUR', amount_in_primary_currency DECIMAL(12, 2) DEFAULT NULL, exchange_rate DECIMAL(10, 6) DEFAULT 1.0, -- Expense Details vendor_name VARCHAR(255), vendor_id VARCHAR(36) DEFAULT NULL, -- Link to boat_contacts if recurring vendor category_id VARCHAR(36) NOT NULL, category_name VARCHAR(100), -- Denormalized for quick access purchase_date DATE NOT NULL, description VARCHAR(500), -- Reimbursement Tracking payment_method ENUM('cash', 'card', 'bank_transfer', 'personal_cash', 'boat_cash') NOT NULL, reimbursement_status ENUM('pending', 'approved', 'rejected', 'paid', 'partial', 'pending_captain_approval') DEFAULT 'pending', reimbursement_requested_at TIMESTAMP, reimbursement_approved_by_user_id VARCHAR(36), reimbursement_approved_at TIMESTAMP, reimbursement_amount DECIMAL(12, 2), reimbursement_date DATE, -- VAT/Tax vat_amount DECIMAL(10, 2), vat_percentage DECIMAL(5, 2), tax_deductible BOOLEAN DEFAULT TRUE, -- OCR & Receipt Data receipt_image_url VARCHAR(2048), receipt_image_hash VARCHAR(64), -- SHA-256 for IF.TTT ocr_extracted BOOLEAN DEFAULT FALSE, ocr_vendor VARCHAR(255), ocr_amount DECIMAL(12, 2), ocr_date DATE, ocr_vat DECIMAL(10, 2), ocr_raw_text LONGTEXT, ocr_confidence DECIMAL(3, 2), -- Linking to Other Modules maintenance_log_id VARCHAR(36) DEFAULT NULL, inventory_item_id VARCHAR(36) DEFAULT NULL, calendar_event_id VARCHAR(36) DEFAULT NULL, -- IF.TTT Compliance content_hash VARCHAR(64), -- SHA-256 of all expense data ed25519_signature VARCHAR(128), -- Ed25519 signature of creator citation_id VARCHAR(255), -- if://receipt/navidocs/boat-123/expense-456 -- Timestamps & Tracking created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted_at TIMESTAMP, -- Audit audit_log LONGTEXT, -- JSON array of all changes INDEX idx_boat_id (boat_id), INDEX idx_created_by (created_by_user_id), INDEX idx_category (category_id), INDEX idx_purchase_date (purchase_date), INDEX idx_reimbursement_status (reimbursement_status), INDEX idx_payment_method (payment_method), FOREIGN KEY (boat_id) REFERENCES boats(id), FOREIGN KEY (created_by_user_id) REFERENCES users(id), FOREIGN KEY (category_id) REFERENCES expense_categories(id), FOREIGN KEY (maintenance_log_id) REFERENCES maintenance_log(id), FOREIGN KEY (inventory_item_id) REFERENCES boat_inventory(id), FOREIGN KEY (calendar_event_id) REFERENCES calendar_events(id) ); -- Reimbursement Tracking (for captain expenses paid from personal funds) CREATE TABLE reimbursement_requests ( id VARCHAR(36) PRIMARY KEY, boat_id VARCHAR(36) NOT NULL, -- Request Details captain_user_id VARCHAR(36) NOT NULL, owner_user_id VARCHAR(36) NOT NULL, -- Aggregation total_requested DECIMAL(12, 2) NOT NULL, total_approved DECIMAL(12, 2) DEFAULT 0, total_paid DECIMAL(12, 2) DEFAULT 0, currency ENUM('EUR', 'USD', 'GBP') DEFAULT 'EUR', -- Workflow request_date TIMESTAMP NOT NULL, request_status ENUM('draft', 'submitted', 'owner_reviewing', 'approved', 'rejected', 'in_payment', 'paid', 'partial') DEFAULT 'draft', owner_approved_at TIMESTAMP, owner_notes VARCHAR(500), -- Attached Expenses (many-to-many) expense_ids JSON, -- Array of expense IDs included in request -- Payment Tracking payment_method ENUM('bank_transfer', 'cash', 'card', 'other'), payment_date TIMESTAMP, payment_reference VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (boat_id) REFERENCES boats(id), FOREIGN KEY (captain_user_id) REFERENCES users(id), FOREIGN KEY (owner_user_id) REFERENCES users(id), INDEX idx_boat (boat_id), INDEX idx_status (request_status), INDEX idx_captain (captain_user_id) ); -- Expense Categories (Customizable) CREATE TABLE expense_categories ( id VARCHAR(36) PRIMARY KEY, boat_id VARCHAR(36) NOT NULL, name VARCHAR(100) NOT NULL, description VARCHAR(500), parent_category_id VARCHAR(36), -- For hierarchical categories -- Defaults for this category is_taxable BOOLEAN DEFAULT TRUE, average_vat_percentage DECIMAL(5, 2), default_payment_method VARCHAR(50), -- System vs. Custom is_system_default BOOLEAN DEFAULT FALSE, color_hex VARCHAR(7), -- For UI display icon VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, FOREIGN KEY (boat_id) REFERENCES boats(id), FOREIGN KEY (parent_category_id) REFERENCES expense_categories(id), INDEX idx_boat (boat_id), UNIQUE KEY unique_name_per_boat (boat_id, name) ); -- Multi-Currency Exchange Rate History CREATE TABLE exchange_rates ( id VARCHAR(36) PRIMARY KEY, from_currency ENUM('EUR', 'USD', 'GBP', 'OTHER'), to_currency ENUM('EUR', 'USD', 'GBP', 'OTHER'), rate DECIMAL(10, 6), date DATE, source VARCHAR(100), -- 'ECB', 'OER', 'manual', etc. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_currencies (from_currency, to_currency, date) ); ``` --- ## 2. Receipt/Invoice OCR Workflow ### 2.1 OCR Processing Pipeline ``` User Flow: ┌─────────────────────────────────────────────────────────────┐ │ 1. Mobile Upload (Camera or Gallery) │ │ - Capture receipt photo or select from gallery │ │ - Auto-crop to receipt area (smart phone features) │ │ - Compression for bandwidth (JPEG, max 5MB) │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 2. Image Storage & Hashing (IF.TTT) │ │ - Store original image: /boat-{boatId}/receipts/ │ │ - Generate SHA-256 hash of image file │ │ - Generate Ed25519 signature (uploader identity) │ │ - Create citation_id: if://receipt/navidocs/boat-Y/Z │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 3. OCR Processing (Parallel) │ │ a) Tesseract (local, fast, offline) │ │ - Extracts raw text from image │ │ - Returns text block bounding boxes │ │ b) Google Vision API (cloud, accurate) │ │ - Full document analysis │ │ - Structured text detection │ │ - Response: { items, subtotal, tax, total, date } │ │ c) Custom boat-specific patterns │ │ - Marina receipt patterns (fee breakdown) │ │ - Fuel receipt patterns (type, quantity, price) │ │ - Parts supplier patterns (item codes) │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 4. Structured Data Extraction │ │ - Vendor name (from header/footer) │ │ - Amount (total, subtotal, tax) │ │ - Date (receipt date, transaction date) │ │ - VAT/Tax percentage │ │ - Currency │ │ - Line items (if detailed receipt) │ │ - Confidence scores per field │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 5. Category Suggestion (ML/Rules) │ │ - Pattern match against vendor database │ │ - Keyword matching (e.g., "Diesel" → Fuel category) │ │ - User history (captain usually buys provisions → suggest)│ │ - Context (if linked to maintenance log, suggest related │ │ - Present 1st/2nd/3rd category suggestions │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 6. Review & Confirmation (User Interface) │ │ ┌────────────────────────────────────────────────┐ │ │ │ Receipt Image (small preview) │ │ │ ├────────────────────────────────────────────────┤ │ │ │ Vendor: [extracted] ← editable text field │ │ │ │ Date: [extracted] ← date picker │ │ │ │ Amount: [extracted] [Currency dropdown] │ │ │ │ VAT: [extracted] ← numeric field │ │ │ │ Category: [1st suggestion] ▼ (other options) │ │ │ │ Description: [optional text] │ │ │ │ Link to: □ Maintenance log □ Inventory │ │ │ │ □ Calendar event │ │ │ │ [Cancel] [Save] [Reuse as Template] │ │ │ └────────────────────────────────────────────────┘ │ │ - OCR confidence shown as visual indicator │ │ - User can edit any field before saving │ │ - Option to reuse as expense template │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 7. Expense Creation & IF.TTT Signing │ │ - Create expense record with extracted data │ │ - Calculate content_hash (SHA-256 of all fields) │ │ - Generate ed25519_signature from user's private key │ │ - Generate citation_id link │ │ - Store audit log: who, when, what was edited │ └──────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────▼──────────────────────────────────────────┐ │ 8. Workflow Routing │ │ IF (creator_role == 'owner'): │ │ - Mark as 'recorded' (no approval needed) │ │ - Optional: link to maintenance/inventory │ │ IF (creator_role == 'captain' OR 'crew'): │ │ - Mark as 'pending_captain_approval' │ │ - Wait for captain approval before owner approval │ │ - Notify captain: "New expense request from [crew]" │ │ IF (captain role): │ │ - Mark as 'pending' (awaiting owner approval) │ │ - Notify owner: "New reimbursement request" │ │ - Captain can modify until approval │ │ ELSE: │ │ - Auto-approve if preset rule matches │ └──────────────────┬──────────────────────────────────────────┘ │ └──────────────────▼──────────────────────────────────────────┘ Done! ``` ### 2.2 OCR API Integration **Primary Provider: Google Cloud Vision API** ```javascript // google-vision-integration.js const vision = require('@google-cloud/vision'); async function extractReceiptData(imagePath) { const client = new vision.ImageAnnotatorClient({ keyFilename: process.env.GOOGLE_VISION_KEY_FILE }); const request = { image: { source: { filename: imagePath } }, }; const [result] = await client.documentTextDetection(request); const fullTextAnnotation = result.fullTextAnnotation; // Parse text detection results return { rawText: fullTextAnnotation.text, blocks: fullTextAnnotation.pages[0].blocks, textAnnotations: result.textAnnotations }; } async function parseReceiptStructure(rawText, blocks) { // Custom parsing rules for boat receipts const parsed = { vendor: null, date: null, amount: null, vat: null, currency: 'EUR', lineItems: [], confidence: {} }; // Pattern matching for common vendors const vendorPatterns = { marina: /marina|port|harbour|mooring|dock/i, fuel: /diesel|fuel|gasolina|essence|petrol|lpg|gas/i, maintenance: /maintenance|repair|service|technician|mechanic/i, supplies: /supply|chandlery|provisions|grocery|supermarket/i }; // Date extraction (multiple formats) const dateRegex = /(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}|\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2})/; const dateMatch = rawText.match(dateRegex); if (dateMatch) { parsed.date = normalizeDate(dateMatch[1]); parsed.confidence.date = 0.95; } // Amount extraction (€, $, £, etc.) const amountRegex = /[€$£¥]?\s*(\d+[\.\,]\d{2})/; const amounts = rawText.matchAll(amountRegex); const amountArray = Array.from(amounts); if (amountArray.length > 0) { // Take largest amount (usually total) const total = Math.max(...amountArray.map(m => parseFloat(m[1].replace(',', '.')))); parsed.amount = total; parsed.confidence.amount = 0.90; } // VAT extraction const vatRegex = /VAT|TVA|IVA|GST|TAX[:\s]*(\d+[\.\,]\d{2})/i; const vatMatch = rawText.match(vatRegex); if (vatMatch) { parsed.vat = parseFloat(vatMatch[1].replace(',', '.')); parsed.confidence.vat = 0.85; } return parsed; } async function categorizeExpense(vendor, amount, rawText, boatId) { // Rule-based categorization const rulesDb = await loadCategoryRules(boatId); // Check vendor database first const vendorMatch = await db.query( 'SELECT category_id FROM vendor_categories WHERE boat_id = ? AND vendor ILIKE ?', [boatId, vendor] ); if (vendorMatch) { return { category: vendorMatch.category_id, confidence: 0.99, reason: 'vendor_history' }; } // Pattern matching const patterns = { fuel: /diesel|fuel|gasoline|petrol|lpg|fuel_dock/i, provisions: /provisions|food|grocery|supermarket|market/i, maintenance: /maintenance|repair|service|mechanic|technician/i, marina: /marina|port|harbour|mooring|dock|berth/i, insurance: /insurance|assurance|coasec/i }; for (const [category, pattern] of Object.entries(patterns)) { if (pattern.test(vendor) || pattern.test(rawText)) { return { category, confidence: 0.75, reason: 'pattern_match' }; } } // Fallback: user-provided category return { category: 'miscellaneous', confidence: 0.40, reason: 'fallback' }; } ``` **Fallback Provider: Tesseract (Local)** ```javascript // tesseract-fallback.js const Tesseract = require('tesseract.js'); async function extractWithTesseract(imagePath) { const { data: { text } } = await Tesseract.recognize( imagePath, ['eng', 'fra', 'deu', 'ita', 'spa'], // Yacht typically multilingual { logger: m => console.log('Tesseract:', m.status, m.progress) } ); return text; } ``` --- ## 3. Accounting Module Architecture ### 3.1 Integration Strategy: Fork vs. Library **Recommended Approach: Spliit Fork (MIT License)** **Rationale:** - ✅ Boat-specific features (owner/captain/crew roles not in original Spliit) - ✅ Full control over expense data model - ✅ Direct integration with IF.TTT compliance (signatures, hashes) - ✅ Customizable workflows (reimbursement approval chains) - ✅ Multi-currency support (Spliit has this; we enhance) - ✅ Receipt scanning core feature (matches our OCR pipeline) - ✅ PWA mobile support (critical for boat operations) - ✅ MIT license allows commercial use, modifications **Spliit Source:** https://github.com/pmihaylov/spliit (2.3K stars, active) **Fork & Customize Process:** ```bash # 1. Fork Spliit repository git clone https://github.com/[navidocs-org]/spliit-navidocs.git cd spliit-navidocs # 2. Merge NaviDocs-specific branches git remote add upstream https://github.com/pmihaylov/spliit.git git fetch upstream git merge upstream/main # Get latest Spliit features # 3. Create navidocs-specific branch git checkout -b feature/navidocs-yacht-integration # 4. Customize for boat workflows - Modify user roles (owner, captain, crew vs. Spliit's split member) - Enhance expense model with boat_id, maintenance_log_id, etc. - Add IF.TTT compliance layer (signatures, hashes, citations) - Implement reimbursement approval workflow - Add multi-currency with exchange rate tracking - Integrate receipt OCR (use our Google Vision pipeline) - Add export to Excel/CSV for accountants - Implement access controls (captain sees own expenses, owner sees all) ``` ### 3.2 Integration with NaviDocs Backend **Architecture Diagram:** ``` ┌─────────────────────────────────────────────────────────────┐ │ NaviDocs Frontend (Vue 3) │ │ │ │ Expenses Page: /app/boat/{boatId}/accounting │ │ - Receipt upload modal │ │ - Expense list (with search, filters, sort) │ │ - Reimbursement requests (captain view) │ │ - Approval flow (owner view) │ │ - Export to Excel │ └──────────────────┬──────────────────────────────────────────┘ │ HTTP/REST ┌──────────────────▼──────────────────────────────────────────┐ │ NaviDocs API Layer (Express.js) │ │ │ │ Routes: │ │ POST /api/expenses Create expense (OCR) │ │ GET /api/expenses?filter=... List expenses │ │ GET /api/expenses/{id} Get expense detail │ │ PUT /api/expenses/{id} Update expense │ │ POST /api/expenses/{id}/approve Owner approve │ │ POST /api/expenses/{id}/reject Owner reject │ │ POST /api/receipts/upload Upload receipt image │ │ POST /api/receipts/{id}/ocr Trigger OCR processing │ │ GET /api/reimbursements List requests │ │ POST /api/reimbursements Create request │ │ POST /api/reimbursements/{id}/approve Owner approve │ │ GET /api/expenses/export/excel Export for accountant │ │ │ │ Middleware: │ │ - Authentication (JWT) │ │ - Authorization (boat access control) │ │ - IF.TTT signing (Ed25519) │ │ - Audit logging │ └──────────────────┬──────────────────────────────────────────┘ │ ORM/Query ┌──────────────────▼──────────────────────────────────────────┐ │ Service Layer (Business Logic) │ │ │ │ ExpenseService: │ │ - createExpense(data) → IF.TTT signed record │ │ - approveExpense(expenseId, userId) │ │ - getExpensesByBoat(boatId, filters) │ │ - calculateReimbursement(expenseIds) │ │ - exportToExcel(boatId, dateRange) │ │ │ │ OCRService: │ │ - processReceipt(imagePath) → structured data │ │ - categorizeExpense(data) │ │ - extractWithGoogle(imagePath) | Tesseract(imagePath) │ │ │ │ ReimbursementService: │ │ - createRequest(captainId, expenseIds) │ │ - approveRequest(ownerId, requestId) │ │ - calculateOwedAmount(captainId, boatId) │ │ │ │ IF.TTTService: │ │ - signExpense(expenseData, signingKey) → Ed25519 sig │ │ - hashExpense(expenseData) → SHA-256 │ │ - generateCitationId(boatId, expenseId) │ └──────────────────┬──────────────────────────────────────────┘ │ Database ┌──────────────────▼──────────────────────────────────────────┐ │ SQLite3 Database (on Disk) │ │ │ │ Tables: │ │ - expenses │ │ - reimbursement_requests │ │ - expense_categories │ │ - exchange_rates │ │ - receipt_images (metadata + hash) │ │ - if_signatures (IF.TTT compliance log) │ │ - audit_logs (all changes to expenses) │ └──────────────────────────────────────────────────────────────┘ External Services: ┌─────────────────────────────────────────────────────────────┐ │ Google Cloud Vision API (OCR) │ │ - Document text detection │ │ - Structured data extraction │ │ - Handwriting recognition (for handwritten receipts) │ │ │ │ Background Job Queue (BullMQ + Redis) │ │ - Process OCR async (don't block user) │ │ - Generate exports (Excel files) │ │ - Send reimbursement notifications │ │ │ │ WhatsApp Business API (to S2-H08) │ │ - Send approval requests to owner │ │ - Send reimbursement notifications to captain │ │ - Inbound: "@NaviDocs log expense €45 fuel" │ └─────────────────────────────────────────────────────────────┘ ``` ### 3.3 API Endpoint Specifications ```yaml POST /api/receipts/upload Description: Upload receipt image and trigger OCR Auth: Required (JWT) Body: image: File (JPEG/PNG, max 5MB) boat_id: string (UUID) captured_date: string (ISO 8601, optional) description: string (optional) Response: receipt_id: string upload_status: "uploaded" ocr_job_id: string # For async polling ocr_status: "queued" ocr_estimated_time_seconds: 5 Status: 201 Created Webhook: /api/receipts/{receipt_id}/ocr-complete When: OCR processing finishes Payload: receipt_id: string extracted_data: vendor: string amount: number currency: string date: string (ISO 8601) vat: number confidence: object (per field) ocr_status: "success" | "partial" | "failed" --- POST /api/expenses Description: Create expense from OCR data or manual entry Auth: Required (JWT) Body: boat_id: string (UUID) amount: number currency: string (EUR|USD|GBP) vendor: string date: string (ISO 8601) category_id: string (UUID) description: string (optional) receipt_id: string (optional) vat_amount: number (optional) linked_maintenance_id: string (optional) linked_inventory_id: string (optional) Response: id: string (UUID) status: "pending" | "pending_captain_approval" | "recorded" created_by_role: string (owner|captain|crew) citation_id: string (if://receipt/navidocs/boat-{boatId}/expense-{expenseId}) ed25519_signature: string # IF.TTT signing content_hash: string (SHA-256) Status: 201 Created --- GET /api/expenses Description: List expenses for a boat with filtering Auth: Required (JWT) Query Params: boat_id: string (required) category: string (optional, UUID) date_from: string (ISO 8601, optional) date_to: string (ISO 8601, optional) status: string (pending|approved|rejected|paid, optional) created_by: string (owner|captain|crew, optional) payment_method: string (cash|card|bank_transfer, optional) min_amount: number (optional) max_amount: number (optional) sort: string (date_desc|amount_desc|vendor_asc, default=date_desc) page: number (default=1) limit: number (default=25, max=100) Response: expenses: array - id, vendor, amount, currency, date, category, status, created_by_role total_count: number page: number total_pages: number filters_applied: object Status: 200 OK --- POST /api/expenses/{expense_id}/approve Description: Owner approves captain reimbursement Auth: Required (JWT) Body: approved_by_user_id: string (owner's user ID) notes: string (optional) Response: id: string status: "approved" approved_at: string (ISO 8601 timestamp) approved_by_user_id: string Status: 200 OK --- POST /api/reimbursements Description: Captain submits reimbursement request Auth: Required (JWT) Body: boat_id: string (UUID) captain_user_id: string (UUID) expense_ids: array (of expense UUIDs) notes: string (optional) Response: id: string (UUID) status: "submitted" total_requested: number total_requested_currency: string submitted_at: string (ISO 8601) Status: 201 Created --- POST /api/reimbursements/{request_id}/approve Description: Owner approves reimbursement request Auth: Required (JWT) Body: owner_user_id: string approved_amount: number (optional, for partial approval) payment_method: string (bank_transfer|cash|card) payment_date: string (ISO 8601) notes: string (optional) Response: id: string status: "approved" total_approved: number approved_at: string payment_method: string Status: 200 OK --- GET /api/expenses/export/excel Description: Export expenses to Excel for accountant Auth: Required (JWT) Query Params: boat_id: string (required) date_from: string (ISO 8601) date_to: string (ISO 8601) include_rejected: boolean (default=false) Response: File: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Filename: navidocs-expenses-{boatId}-{dateFrom}-{dateTo}.xlsx Contents: - Expense sheet (all expenses with all fields) - Reimbursements sheet (captain reimbursement summary) - Summary sheet (totals by category, currency, status) - VAT report (VAT totals by jurisdiction) - IF.TTT audit (citation IDs, signatures, hashes for compliance) Status: 200 OK --- POST /api/categories/suggest Description: ML-powered category suggestion based on vendor/amount Auth: Required (JWT) Body: vendor: string amount: number description: string (optional) boat_id: string (optional) Response: suggestions: array - category_id: string - category_name: string - confidence: number (0.0-1.0) - reason: string (vendor_history|pattern_match|ml_model) Status: 200 OK ``` --- ## 4. Multi-User Expense Tracking Workflow ### 4.1 Owner Expense Recording ``` Owner logs in → Navigates to Accounting/Expenses Option A: Manual Entry ├─ Clicks "Add Expense" ├─ Fills form: │ ├─ Vendor: "Marina Porto" │ ├─ Amount: €245.50 │ ├─ Date: 2025-11-13 │ ├─ Category: Marina Fees │ ├─ Payment Method: Card │ └─ Notes: "November mooring" ├─ Clicks "Save" └─ Expense created, status: "recorded" Option B: Photo Upload (OCR) ├─ Clicks "Upload Receipt" ├─ Takes photo or selects from gallery ├─ Waits for OCR processing (2-5 seconds) ├─ Reviews extracted data │ ├─ Vendor: "Marina Porto" ✓ │ ├─ Amount: €245.50 (confidence: 92%) ✓ │ ├─ Date: 2025-11-13 ✓ │ ├─ Category: Marina Fees (suggested) ✓ │ └─ VAT: €37.50 ✓ ├─ Can edit any field before saving ├─ Clicks "Save" └─ Expense created with IF.TTT signature ``` ### 4.2 Captain Expense Reimbursement Flow ``` Captain purchases provisions for €52.30 with personal cash Step 1: Captain logs expense ├─ Opens mobile app (any time, any place) ├─ Clicks "Add Expense" ├─ Takes photo of receipt ├─ System extracts: Vendor: "Supermarket", Amount: €52.30, Date: today ├─ Selects category: "Provisions" ├─ Notes: "Weekly supplies" └─ Clicks "Submit for Approval" └─ Expense status: "pending" (awaits owner approval) └─ Notification sent to owner: "Captain requested reimbursement: €52.30" Step 2: Expense aggregation (optional) ├─ Captain can submit multiple expenses together ├─ Clicks "Create Reimbursement Request" ├─ Selects expenses: [52.30 + 35.00 + 18.50] = €105.80 ├─ Adds note: "Personal cash advances from Oct 20-Nov 13" ├─ Clicks "Submit Request" └─ Reimbursement request created └─ Status: "submitted" └─ Notification: Owner sees request in dashboard Step 3: Owner reviews & approves ├─ Owner logs into app ├─ Sees reimbursement request notification ├─ Clicks to expand request details: │ ├─ Captain: "Marco (Captain)" │ ├─ Total: €105.80 │ ├─ Expenses: │ │ ├─ Supermarket (Provisions): €52.30 ← owner can expand receipt image │ │ ├─ Bakery (Provisions): €35.00 ← approve/reject individual items │ │ └─ Marine Supply (Supplies): €18.50 │ └─ Notes: "Personal cash advances from Oct 20-Nov 13" ├─ Owner has options: │ ├─ [Approve All] → All expenses marked "approved" │ ├─ [Approve Partial] → Select which to approve │ ├─ [Reject] → All returned to captain (stays pending) │ ├─ [Message Captain] → Ask for clarification (via WhatsApp) │ └─ [Reject Item] → Specific items rejected, others approved ├─ Owner clicks "Approve All" └─ Expenses status updated to "approved" └─ Notification: Captain sees "Your reimbursement of €105.80 approved!" Step 4: Payment processing ├─ Owner marks reimbursement as "paid" ├─ Owner selects payment method: │ ├─ Bank transfer (account details pre-filled from captain profile) │ ├─ Cash (mark as paid after handing over cash) │ ├─ Card (transfer via app if available) │ └─ Other ├─ Enters payment date & reference (bank transaction ID) ├─ Clicks "Mark Paid" └─ Reimbursement status: "paid" └─ Expenses status: "paid" └─ Notification: Captain receives "Reimbursement sent: €105.80 on 2025-11-14" Step 5: Audit trail ├─ Expense has complete IF.TTT audit trail: │ ├─ who: Captain (Ed25519 signature) │ ├─ when: 2025-10-20 10:15 AM │ ├─ what: Original receipt photo + extracted data │ ├─ owner_approval: Owner name, timestamp │ ├─ payment: Bank transfer ref 12345678 │ └─ citation_id: if://receipt/navidocs/boat-123/expense-4567 └─ Accountant can verify chain of custody ``` ### 4.3 Crew Expense Flow Similar to captain, but: - Captain must pre-approve before owner sees it - Workflow: Crew logs → Captain approves → Owner approves → Marked paid - Prevents owner seeing every crew purchase individually ``` Crew buys ice for €8.50 with boat cash Step 1: Crew logs expense ├─ App logs: "Crew purchased ice for €8.50" ├─ Takes receipt photo (if available) └─ Status: "pending_captain_approval" └─ Captain notification: "Crew expense pending: €8.50" Step 2: Captain reviews (filters out small/pre-approved items) ├─ Captain sees list of crew expenses pending approval ├─ Captain can: │ ├─ [Approve] → Status becomes "pending" (awaits owner approval) │ ├─ [Reject] → Status "rejected" (expense removed from boat cash log) │ ├─ [Auto-Approve] → Set rules: │ │ ├─ Items under €20: auto-approve │ │ ├─ Specific categories: auto-approve (ice, provisions) │ └─ [View] → See expense details + receipt image └─ Captain approves (or auto-approved by rule) └─ Status: "pending" (awaits owner approval) └─ Owner sees in daily summary: "5 crew expenses, €47.30 total" Step 3: Owner bulk-approves crew expenses ├─ Owner opens dashboard ├─ Sees: "5 pending crew expenses (€47.30)" ├─ Owner options: │ ├─ [Approve All] → All marked "approved" │ ├─ [View Details] → Expand to see each item │ └─ [Reject Items] → Sends back to captain with questions ├─ Owner clicks "Approve All" └─ All crew expenses: "approved" └─ Captain notified: "5 crew expenses approved (€47.30)" └─ Owner knows they owe captain €47.30 in boat cash reimbursement Alternative: Boat Cash Account ├─ If boat maintains cash drawer (e.g., €500) ├─ Transactions: [+€500 starting balance] → [-€8.50 ice] → [-€23.00 fuel] → etc. ├─ Owner periodically reimburses boat cash drawer ├─ Captain tracks: "Boat cash remaining: €250.30" └─ Reconciliation: Physical count vs. app records ``` --- ## 5. Reimbursement Workflow (Detailed) ### 5.1 States & Transitions ``` Captain Expense States: ┌─────────────┐ │ pending │ Captain just logged expense (personal cash) └──────┬──────┘ │ (Owner approves) ▼ ┌─────────────┐ │ approved │ Owner approved reimbursement amount └──────┬──────┘ │ (Owner pays captain) ▼ ┌─────────────┐ │ paid │ Captain received payment └─────────────┘ Alternative: Partial Approval ┌─────────────┐ │ pending │ └──────┬──────┘ │ (Owner partially approves) ▼ ┌─────────────┐ │ partial │ Some items approved, others rejected/questioned └──────┬──────┘ │ (Captain modifies; Owner reviews again) ▼ ┌─────────────┐ │ approved │ All items resolved └──────┬──────┘ │ (Owner pays) ▼ ┌─────────────┐ │ paid │ └─────────────┘ Rejection Path: ┌─────────────┐ │ pending │ └──────┬──────┘ │ (Owner rejects) ▼ ┌─────────────┐ │ rejected │ Owner didn't approve (personal expense, disallowed, etc.) └─────────────┘ (Captain can re-submit with justification or drop claim) ``` ### 5.2 Notification Triggers | Event | Notify | Channel | Message | |-------|--------|---------|---------| | Captain submits expense | Owner | WhatsApp + App | "Marco requested €52.30 reimbursement for provisions" | | Owner approves expense | Captain | WhatsApp + App | "Your €52.30 expense approved! Payment will be sent by 2025-11-15" | | Owner rejects expense | Captain | WhatsApp + App | "Your €52.30 provisions expense was rejected. See note: Please check receipt - amount doesn't match vendor markup" | | Owner marks paid | Captain | WhatsApp + App | "Reimbursement of €245.80 sent via bank transfer" | | Bulk crew expenses approved | Captain | WhatsApp + App | "5 crew expenses approved (€47.30)" | | Reimbursement request pending | Owner | Email + App | "3-day reminder: Marco's reimbursement request (€105.80) pending your approval" | --- ## 6. WhatsApp AI Integration (S2-H08 Dependency) ### 6.1 Expense Logging via WhatsApp **Integration Point:** S2-H08 (WhatsApp Group Integration) handles chat webhook; S2-H06 (Accounting) handles expense processing ``` Example: Captain in WhatsApp group chat Captain: "@NaviDocs log expense €45 diesel" │ ▼ (IF.bus to S2-H08) S2-H08 WhatsApp Agent: ├─ Receives message via WhatsApp Business API webhook ├─ Parses: amount=€45, category=fuel, description="diesel" ├─ Checks if creator has authorization (captain role for this boat) ├─ Calls: POST /api/expenses with parsed data │ ▼ (IF.bus to S2-H06) S2-H06 Accounting Service: ├─ Creates expense record: │ ├─ amount: 45.00 │ ├─ currency: EUR │ ├─ category: Fuel │ ├─ description: (from chat context) │ ├─ created_by: captain │ └─ status: pending_captain_approval (if crew) or pending (if captain) ├─ Generates IF.TTT signature + citation └─ Returns expense_id S2-H08 WhatsApp Agent: ├─ Posts confirmation: "@Marco ✓ Logged: €45 diesel on 2025-11-13" ├─ Optional: Adds quick buttons: │ ├─ [View Receipt] → Link to expense detail │ ├─ [Edit] → Links to app to upload receipt photo │ └─ [More] → Shows recent 5 expenses └─ Stores in IF.TTT audit log: message hash + citation ``` **Command Syntax:** ``` @NaviDocs log expense [amount] [currency] [category] [description] @NaviDocs log [amount] [currency] [description] # auto-category Examples: @NaviDocs log expense €45 fuel → €45.00, Fuel, auto-date @NaviDocs log €35 EUR provisions → €35.00, Provisions @NaviDocs log $80 marina fees Antibes → $80 USD, Marina, Antibes @NaviDocs log 45 GBP diesel fuel Kingston → £45.00, Fuel, Kingston Advanced: @NaviDocs log expense €150 maintenance Hull repair invoice #12345 → Links to maintenance log if ID provided @NaviDocs reimbursement request [list of recent expense IDs] → Creates reimbursement request from last N expenses ``` --- ## 7. Export Functionality for Accountants ### 7.1 Excel Export Structure ``` File: navidocs-expenses-[BoatID]-[2025-01-01]-[2025-12-31].xlsx Sheet 1: Expenses ├─ Row 1: Header row │ ├─ Expense ID | Vendor | Category | Amount | Currency | Date | VAT | Net Amount │ ├─ Payment Method | Created By | Status | Receipt Image Link | Notes │ ├─ Maintenance Log Link | Inventory Link | Citation ID ├─ Rows 2+: Data rows (one per expense) └─ Footer: Totals row (sum by column) Sheet 2: Reimbursements ├─ Reimbursement ID | Captain | Total Requested | Total Approved | Total Paid ├─ Currency | Request Date | Status | Approved Date | Paid Date | Payment Method └─ Notes Sheet 3: Summary Statistics ├─ Date Range: 2025-01-01 to 2025-12-31 ├─ Total Expenses: €XXX ├─ Breakdown by Category: │ ├─ Fuel: €X (5% of total) │ ├─ Marina: €Y (20% of total) │ ├─ Maintenance: €Z (40% of total) │ └─ ... ├─ Breakdown by Payment Method: │ ├─ Cash: €A │ ├─ Card: €B │ └─ Bank Transfer: €C ├─ Breakdown by Created By: │ ├─ Owner: €D │ ├─ Captain: €E │ └─ Crew: €F ├─ Currency Conversion Summary: │ ├─ EUR: €XXX │ ├─ USD: €YYY (converted) │ └─ GBP: €ZZZ (converted) ├─ Reimbursements Total: │ ├─ Requested: €RRR │ ├─ Approved: €AAA │ └─ Paid: €PPP Sheet 4: VAT Report ├─ Date | Category | Vendor | Gross | VAT % | VAT Amount | Net ├─ ... ├─ Total VAT Collected by Category: │ ├─ Fuel: €X VAT (from €Y gross) │ ├─ Maintenance: €A VAT (from €B gross) │ └─ ... └─ Total VAT: €VVV (eligible for deduction if business boat) Sheet 5: IF.TTT Audit Trail ├─ Expense ID | Citation ID | Ed25519 Signature | SHA-256 Hash ├─ Creator | Creation Date | Approver | Approval Date ├─ Payment Date | Payment Reference ├─ Receipt Image Hash | Receipt Image URL └─ [Full audit for compliance verification] Sheet 6: Data Dictionary ├─ Column explanations ├─ Category list (full hierarchy) ├─ Payment method definitions └─ Status definitions ``` ### 7.2 Export API ```javascript // Generate Excel export GET /api/expenses/export/excel?boat_id=X&from=2025-01-01&to=2025-12-31&format=detailed Response Headers: Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Content-Disposition: attachment; filename=navidocs-expenses-[boatId]-2025.xlsx Response: Binary XLSX file (generated via ExcelJS or similar library) // Also support CSV export for quick import to accounting software GET /api/expenses/export/csv?boat_id=X&sheet=expenses&from=2025-01-01&to=2025-12-31 Response Headers: Content-Type: text/csv; charset=utf-8 Content-Disposition: attachment; filename=navidocs-expenses-[boatId]-2025.csv Response: CSV data (directly importable to QuickBooks, Xero, FreshBooks, etc.) ``` --- ## 8. IF.TTT Compliance Architecture ### 8.1 Signature & Hash Implementation ```javascript // if-compliance.service.js const crypto = require('crypto'); const ed25519 = require('libsodium').crypto_sign_detached; class IFTTTService { /** * Generate SHA-256 hash of expense data (for tamper detection) */ async hashExpense(expenseData) { const canonical = { boat_id: expenseData.boat_id, amount: expenseData.amount, currency: expenseData.currency, vendor: expenseData.vendor, date: expenseData.date, category_id: expenseData.category_id, vat_amount: expenseData.vat_amount, payment_method: expenseData.payment_method, description: expenseData.description, created_by_user_id: expenseData.created_by_user_id, created_by_role: expenseData.created_by_role, receipt_image_hash: expenseData.receipt_image_hash }; const jsonString = JSON.stringify(canonical, Object.keys(canonical).sort()); const hash = crypto.createHash('sha256').update(jsonString).digest('hex'); return hash; } /** * Generate Ed25519 signature of expense (proof of creator identity) */ async signExpense(expenseData, creatorPrivateKey) { const contentHash = await this.hashExpense(expenseData); // Ed25519 sign the content hash const signature = ed25519(Buffer.from(contentHash), Buffer.from(creatorPrivateKey)); return signature.toString('hex'); } /** * Verify Ed25519 signature (ensure creator identity) */ async verifySignature(contentHash, signature, creatorPublicKey) { try { const verified = sodium.crypto_sign_open( Buffer.from(signature + contentHash, 'hex'), Buffer.from(creatorPublicKey, 'hex') ); return true; } catch (e) { return false; } } /** * Generate citation ID (permanent reference to this expense) */ generateCitationId(boatId, expenseId) { // Format: if://receipt/navidocs/boat-{boatId}/expense-{expenseId} return `if://receipt/navidocs/boat-${boatId}/expense-${expenseId}`; } /** * Generate receipt image hash (for image tamper detection) */ async hashReceiptImage(imagePath) { const fs = require('fs').promises; const fileBuffer = await fs.readFile(imagePath); const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex'); return hash; } /** * Complete IF.TTT compliance record */ async createIFTTTRecord(expenseData, creatorPrivateKey, receiptImagePath) { const contentHash = await this.hashExpense(expenseData); const receiptImageHash = receiptImagePath ? await this.hashReceiptImage(receiptImagePath) : null; const signature = await this.signExpense(expenseData, creatorPrivateKey); const citationId = this.generateCitationId(expenseData.boat_id, expenseData.id); return { content_hash: contentHash, ed25519_signature: signature, citation_id: citationId, receipt_image_hash: receiptImageHash, creator_public_key: derivePublicKey(creatorPrivateKey), // Stored for verification signed_at: new Date().toISOString() }; } /** * Audit verification: prove expense hasn't been tampered with */ async verifyExpenseIntegrity(expense) { // 1. Recalculate content hash const currentHash = await this.hashExpense(expense); if (currentHash !== expense.content_hash) { return { valid: false, reason: "Content hash mismatch - expense data has been modified" }; } // 2. Verify Ed25519 signature const signatureValid = await this.verifySignature( currentHash, expense.ed25519_signature, expense.creator_public_key ); if (!signatureValid) { return { valid: false, reason: "Ed25519 signature invalid - creator identity cannot be verified" }; } // 3. Verify receipt image hash (if image exists) if (expense.receipt_image_url && expense.receipt_image_hash) { const currentImageHash = await this.hashReceiptImage(expense.receipt_image_url); if (currentImageHash !== expense.receipt_image_hash) { return { valid: false, reason: "Receipt image hash mismatch - image has been modified" }; } } return { valid: true, verified_at: new Date().toISOString(), verifications: [ "Content hash verified (no tampering)", "Ed25519 signature verified (creator identity confirmed)", "Receipt image hash verified (no image tampering)" ] }; } } module.exports = new IFTTTService(); ``` ### 8.2 IF.TTT Compliance Checklist ```yaml IF.TTT Compliance for Accounting Module: Receipt Image Handling: ✅ SHA-256 hash of receipt image file ✅ Hash stored in database (immutable) ✅ Original image stored in S3 with CRC32 check ✅ Any image modification detected via hash mismatch Expense Creator Identity: ✅ Ed25519 signature of expense data ✅ Signature generated with creator's private key ✅ Public key stored (derived from private key) ✅ Any future creator verification possible Audit Trail: ✅ Citation ID: if://receipt/navidocs/boat-{boatId}/expense-{expenseId} ✅ Permanent reference (never changes) ✅ Includes: who, what, when, where ✅ Linkable: boat UUID, expense UUID ✅ Resolvable: GET /api/citations/if://receipt/navidocs/... Complete Immutable Record: ✅ Original receipt image + hash ✅ Extracted OCR data (vendor, amount, date, VAT) ✅ Creator identity (captain, crew, owner) ✅ Creation timestamp ✅ All approvals + timestamps + approver identity ✅ Payment tracking (method, date, reference) ✅ Any modifications logged separately (audit_log field) Data Integrity Verification: ✅ Verify content hash (no data tampering) ✅ Verify Ed25519 signature (creator identity) ✅ Verify receipt image hash (no image tampering) ✅ All three checks must pass for "verified" status Multi-User Workflow Traceability: ✅ Captain creates: Ed25519 sig + timestamp ✅ Owner approves: separate approval record + sig + timestamp ✅ Payment marked: payment timestamp + method + reference ✅ Full chain of custody maintained Accountant Audit Requirements: ✅ Expense can be traced back to receipt image ✅ Receipt image authenticity verified (hash) ✅ Creator identity verified (Ed25519 signature) ✅ Approval chain visible (timestamps, names) ✅ Payment status documented ✅ VAT/tax calculations auditable ✅ Multi-currency conversions with exchange rates Regulatory Compliance: ✅ EU Tax Authority (CJEU) - receipt authenticity: SHA-256 hash ✅ GDPR - access logs show who viewed expense ✅ GDPR - right to deletion: anonymize but keep hash + signatures ✅ Audit trails: 7-year retention minimum ✅ Document preservation: receipt images stored indefinitely Cryptographic Standards: ✅ SHA-256 (NIST standard, FIPS 180-4) ✅ Ed25519 (quantum-resistant signature scheme, RFC 8032) ✅ All cryptographic operations via libsodium (battle-tested, auditedlib) Export & Compliance: ✅ Excel export includes IF.TTT audit sheet ✅ Citation IDs in export (accountant can verify) ✅ Signature hashes in export (for verification) ✅ CSV export suitable for QuickBooks import ✅ Signatures + hashes preserved in all exports ``` --- ## 9. Database Integration Points ### 9.1 Foreign Key Relationships ```sql -- Link expenses to other modules -- 1. Maintenance Log Integration -- "This fuel expense is related to engine maintenance" ALTER TABLE expenses ADD COLUMN maintenance_log_id VARCHAR(36), ADD FOREIGN KEY (maintenance_log_id) REFERENCES maintenance_log(id); -- Query: Find all expenses related to specific maintenance SELECT e.* FROM expenses e WHERE e.maintenance_log_id = '...' AND e.boat_id = '...'; -- 2. Inventory Integration -- "This €500 expense is for the new autopilot (inventory item)" ALTER TABLE expenses ADD COLUMN inventory_item_id VARCHAR(36), ADD FOREIGN KEY (inventory_item_id) REFERENCES boat_inventory(id); -- Query: What was total expense for this item? SELECT SUM(e.amount) as total_cost, e.currency FROM expenses e WHERE e.inventory_item_id = '...' AND e.boat_id = '...'; -- 3. Calendar Integration -- "This expense is for the scheduled hull repaint work" ALTER TABLE expenses ADD COLUMN calendar_event_id VARCHAR(36), ADD FOREIGN KEY (calendar_event_id) REFERENCES calendar_events(id); -- Query: Budget vs actual for planned work SELECT ce.title, ce.budget_amount, SUM(e.amount) as actual_amount, (ce.budget_amount - SUM(e.amount)) as variance FROM calendar_events ce LEFT JOIN expenses e ON e.calendar_event_id = ce.id WHERE ce.event_type = 'work_planned' AND ce.boat_id = '...' GROUP BY ce.id; -- 4. Vendor (Contact) Integration ALTER TABLE expenses ADD COLUMN vendor_id VARCHAR(36), ADD FOREIGN KEY (vendor_id) REFERENCES boat_contacts(id); -- Query: Show most frequently-used vendors SELECT c.name, COUNT(*) as transaction_count, SUM(e.amount) as total_spent FROM expenses e JOIN boat_contacts c ON e.vendor_id = c.id WHERE e.boat_id = '...' GROUP BY c.id ORDER BY total_spent DESC; -- 5. Crew/Captain Tracking ALTER TABLE expenses ADD COLUMN created_by_user_id VARCHAR(36), ADD FOREIGN KEY (created_by_user_id) REFERENCES users(id); -- Query: How much did captain spend this month? SELECT SUM(e.amount) as monthly_spend FROM expenses e JOIN users u ON e.created_by_user_id = u.id WHERE e.boat_id = '...' AND YEAR(e.purchase_date) = 2025 AND MONTH(e.purchase_date) = 11 AND u.role = 'captain'; ``` --- ## 10. Integration with S2-H08 (WhatsApp) & S2-H03 (Maintenance) ### 10.1 IF.bus Communication Protocol ```json { "performative": "request", "sender": "if://agent/session-2/haiku-06", "receiver": ["if://agent/session-2/haiku-08", "if://agent/session-2/haiku-03"], "conversation_id": "if://conversation/navidocs-accounting-2025-11-13", "content": { "integration": "Expense tracking needs WhatsApp AI commands and maintenance log linking", "api_requirements": { "whatsapp_integration": { "command": "POST /api/expenses from WhatsApp @NaviDocs command", "format": "@NaviDocs log expense €45 fuel", "handler": "S2-H08 WhatsApp agent → S2-H06 expense endpoint", "webhook": "POST /api/expenses with: {amount, currency, category, description, created_by_whatsapp_user_id}" }, "maintenance_link": { "command": "POST /api/expenses with optional maintenance_log_id", "format": "expense can reference maintenance_log record", "handler": "S2-H06 creates expense linked to maintenance", "query": "GET /api/expenses?maintenance_log_id=X shows all expenses for service" } }, "schema": { "POST /api/expenses": { "amount": "number", "currency": "enum(EUR|USD|GBP)", "category": "string or category_id", "description": "string", "linked_maintenance_id": "uuid (optional)", "receipt_image_url": "string (optional)", "created_by_whatsapp_user_id": "string (optional, if from WhatsApp)" } }, "examples": [ { "scenario": "Captain logs fuel expense via WhatsApp", "whatsapp_message": "@NaviDocs log expense €45 diesel", "s2h08_action": "Parse message → call POST /api/expenses", "s2h06_action": "Create expense record, confirm in WhatsApp" }, { "scenario": "Owner wants to link fuel expense to engine maintenance", "api_call": "PUT /api/expenses/exp-123 with {maintenance_log_id: 'maint-456'}", "s2h03_query": "GET /api/expenses?maintenance_log_id=maint-456 (from maintenance module)" } ], "dependencies": [ "S2-H08: WhatsApp API webhook → expense creation", "S2-H03: Maintenance log provides maintenance_log_id for linking", "S2-H06: Expense service accepts both direct API calls and WhatsApp-triggered calls" ] }, "citation_ids": ["if://citation/navidocs-accounting-design"], "timestamp": "2025-11-13T10:00:00Z", "sequence_num": 1 } ``` ### 10.2 S2-H03 (Maintenance) Integration **Maintenance log provides:** - `maintenance_log_id`: UUID to link expenses - `service_type`: "engine", "electronics", "hull", etc. → helps categorize expenses - `date`: Service date → can auto-fill expense date - `cost_estimate`: Maintenance module tracks estimated vs. actual **Expense module queries maintenance:** ```javascript // When captain logs expense for engine service POST /api/expenses { amount: 450.00, currency: "EUR", category: "Maintenance", description: "Engine oil change", maintenance_log_id: "maint-789" // Links back to maintenance record } // Maintenance module can then query: GET /api/expenses?maintenance_log_id=maint-789 // Response: All expenses associated with that maintenance work // Allows: Track estimate vs actual spending // Dashboard shows: // "Engine Service (Nov 13)" // └─ Estimated Cost: €400 // └─ Actual Expenses: €450 (oil change) + €85 (labor) = €535 // └─ Variance: +€135 (33% over estimate) ``` --- ## Implementation Roadmap ### Phase 1: Core Expense Tracking (Week 1-2) - Database schema: expenses, categories, reimbursement_requests - API endpoints: POST /expenses, GET /expenses, GET /reimbursements - Authorization: boat access control, role-based approval - Manual expense entry UI (Vue form) - Export to CSV (basic) ### Phase 2: Receipt OCR (Week 2-3) - Receipt upload endpoint: POST /api/receipts/upload - Google Vision API integration - Tesseract fallback - Extraction: vendor, amount, date, VAT, category - Review & approval UI ### Phase 3: Reimbursement Workflow (Week 3-4) - Reimbursement request creation & approval - Captain → Owner approval flow - Payment tracking (method, date, reference) - Notifications (WhatsApp integration with S2-H08) ### Phase 4: IF.TTT Compliance (Week 4) - SHA-256 hashing (content + receipt images) - Ed25519 signatures (creator identity) - Citation ID generation - Audit trail storage - Compliance verification API ### Phase 5: Advanced Features (Week 5+) - Multi-currency support with exchange rate tracking - Category hierarchy & suggestions - Vendor database & recurring expense templates - Integration with maintenance log (S2-H03) - Advanced search & reporting - Excel export with summary sheets - WhatsApp bot commands (@NaviDocs log expense) --- ## Configuration & Environment ```bash # .env variables for accounting module # Google Cloud Vision API GOOGLE_VISION_PROJECT_ID=navidocs-prod GOOGLE_VISION_KEY_FILE=/etc/secrets/google-vision-key.json GOOGLE_VISION_API_KEY=AIza... # OCR Processing TESSERACT_PATH=/usr/bin/tesseract OCR_TIMEOUT_SECONDS=30 OCR_WEBHOOK_URL=https://api.navidocs.io/api/receipts/ocr-callback # Expense Module ACCOUNTING_MODULE_ENABLED=true MULTI_CURRENCY_SUPPORT=true PRIMARY_CURRENCY_EUR=true SUPPORTED_CURRENCIES=EUR,USD,GBP # Reimbursement REIMBURSEMENT_APPROVAL_REQUIRED=true CREW_APPROVAL_REQUIRED=true CAPTAIN_APPROVAL_FOR_CREW=true # IF.TTT Compliance IFTTT_SIGNING_ENABLED=true ED25519_PRIVATE_KEY_PATH=/etc/secrets/accounting-ed25519-key.pem CITATION_ID_BASE=if://receipt/navidocs # Export EXCEL_EXPORT_ENABLED=true CSV_EXPORT_ENABLED=true EXPORT_INCLUDE_SIGNATURES=true EXPORT_INCLUDE_AUDIT_TRAIL=true # WhatsApp Integration (S2-H08) WHATSAPP_API_WEBHOOK_URL=https://api.navidocs.io/api/expenses/whatsapp-webhook WHATSAPP_EXPENSE_LOGGING_ENABLED=true ``` --- ## Deliverable Summary **S2-H06 has completed:** 1. ✅ **Multi-user expense tracking design** - Owner, Captain, Crew roles with separate workflows - Expense categories (hierarchical, boat-specific) - Database schema with reimbursement tracking 2. ✅ **Receipt/Invoice OCR workflow** - Photo upload (camera + gallery) - Google Vision + Tesseract processing - Auto-extraction (vendor, amount, date, VAT, category) - Review & edit UI before saving - IF.TTT compliance (SHA-256 + signature) 3. ✅ **Accounting module architecture** - Recommendation: Spliit fork (MIT license, receipt-focused) - Integration with NaviDocs backend (Express.js + SQLite) - Multi-currency support (€, $, £) 4. ✅ **Reimbursement workflow** - Captain submit → OCR extract → Owner approve → Mark paid - Crew pre-approval by captain (optional bulk approval) - Partial approval & rejection handling - Payment tracking & notification triggers 5. ✅ **WhatsApp AI integration (S2-H08 dependency)** - "@NaviDocs log expense €45 fuel" → Auto-creates expense - IF.bus communication protocol defined - Webhook integration with WhatsApp Business API 6. ✅ **Export functionality** - Excel export (detailed, summary, VAT, audit sheets) - CSV export for accountant software (QuickBooks, Xero) - IF.TTT audit sheet included 7. ✅ **IF.TTT compliance checklist** - SHA-256 hashing (receipt image + expense data) - Ed25519 signatures (creator identity) - Citation ID format (if://receipt/navidocs/boat-{id}/expense-{id}) - Audit trail storage (immutable) - Integrity verification API --- **Status: COMPLETE** **File:** `/home/user/navidocs/intelligence/session-2/accounting-integration-spec.md` **Integration Dependencies:** - S2-H08 (WhatsApp integration) - for @NaviDocs command execution - S2-H03 (Maintenance log) - for expense linking to maintenance records **Next Steps (for S2-H10 synthesis):** - Integrate with calendar system (budget vs actual for planned work) - Integrate with VAT/tax compliance (Agent 3A) - Ensure expense notifications flow to WhatsApp group (S2-H08) - Design database indices for performance (accounting queries on 10K+ expenses)