All 11 agents (S2-H01 through S2-H09 + S2-H03A + S2-H07A) have completed their technical specifications: - S2-H01: NaviDocs codebase architecture analysis - S2-H02: Inventory tracking system (€15K-€50K value recovery) - S2-H03: Maintenance log & reminder system - S2-H04: Camera & Home Assistant integration - S2-H05: Contact management system - S2-H06: Accounting module & receipt OCR integration - S2-H07: Impeccable search UX (Meilisearch facets) - S2-H08: WhatsApp Business API + AI agent integration - S2-H09: Document versioning with IF.TTT compliance - S2-H03A: VAT/tax jurisdiction tracking & compliance - S2-H07A: Multi-calendar system (4 calendar types) Total: ~15,600 lines of technical specifications Status: Ready for S2-H10 synthesis (awaiting Session 1 completion) IF.bus: All inter-agent communications documented
65 KiB
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
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
// 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)
// 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:
# 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
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
// 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
// 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
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
-- 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
{
"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 expensesservice_type: "engine", "electronics", "hull", etc. → helps categorize expensesdate: Service date → can auto-fill expense datecost_estimate: Maintenance module tracks estimated vs. actual
Expense module queries maintenance:
// 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
# .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:
-
✅ Multi-user expense tracking design
- Owner, Captain, Crew roles with separate workflows
- Expense categories (hierarchical, boat-specific)
- Database schema with reimbursement tracking
-
✅ 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)
-
✅ Accounting module architecture
- Recommendation: Spliit fork (MIT license, receipt-focused)
- Integration with NaviDocs backend (Express.js + SQLite)
- Multi-currency support (€, $, £)
-
✅ 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
-
✅ 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
-
✅ Export functionality
- Excel export (detailed, summary, VAT, audit sheets)
- CSV export for accountant software (QuickBooks, Xero)
- IF.TTT audit sheet included
-
✅ 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)