Skip to content

Craft Easy — Financial Ecosystem Specification

Version: 1.0 Date: 2026-03-28 Status: Approved Related: advanced-features-specification.md


Contents

  1. Overview — the money flow
  2. Multi-currency
  3. Tenant agreements
  4. Revenue split (tenant, system owner, partner)
  5. Invoicing
  6. Subcontractors (underleverantörer)
  7. Data models
  8. Flows and examples
  9. Implementation phases

1. Overview — The Money Flow

The financial ecosystem involves multiple parties and multiple directions of money:

┌────────────────────────────────────────────────────────────────────────┐
│                        SYSTEM OWNER (you)                              │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                      Craft Easy Platform                         │  │
│  │                                                                  │  │
│  │  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐           │  │
│  │  │  Tenant A    │   │  Tenant B    │   │  Tenant C    │          │  │
│  │  │  (Company)   │   │  (Consortium)│   │  (Solo)      │          │  │
│  │  │              │   │              │   │              │          │  │
│  │  │  Agreement:  │   │  Agreement:  │   │  Agreement:  │          │  │
│  │  │  80/15/5     │   │  70/20/10    │   │  90/10/0     │          │  │
│  │  │  T/Sys/Part  │   │  T/Sys/Part  │   │  T/Sys       │          │  │
│  │  │              │   │              │   │              │          │  │
│  │  │  Customers   │   │  Companies   │   │  Customers   │          │  │
│  │  │  ├── Cust 1  │   │  ├── Org 1   │   │  ├── Cust X  │          │  │
│  │  │  ├── Cust 2  │   │  │  └─ Cust  │   │  └── Cust Y  │          │  │
│  │  │  └── Cust 3  │   │  └── Org 2   │   │              │          │  │
│  │  │              │   │     └─ Cust  │   │              │          │  │
│  │  │  Subcontr.   │   │              │   │              │          │  │
│  │  │  └── Sub X   │   │  Partner:    │   │              │          │  │
│  │  │              │   │  Partner AB  │   │              │          │  │
│  │  └─────────────┘   └─────────────┘   └─────────────┘           │  │
│  └──────────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────────────┘

Money flows:
─────────────
Customer → pays → Tenant (via Stripe/Klarna/Swish)
Tenant   → revenue split per agreement → System Owner + Partner
System Owner → invoices Tenant → feature/service fees
Tenant   → invoices Customers → for services rendered
Tenant   → pays → Subcontractor (for work done)

The parties

Party Role Examples
System Owner Operates the platform You (Easy Software System)
Tenant Customer of the platform A parking company, a healthcare provider
Partner Referred the tenant, gets revenue share Sales partner, franchise holder
Customer Tenant's end customer Person parking a car, patient booking appointment
Subcontractor Does work for the tenant Cleaning company, maintenance provider

2. Multi-Currency

2.1 Requirements

  • Each tenant can operate in one or more currencies
  • Transactions, invoices, and settlements are per-currency
  • New currencies can be added at any time without migration
  • Exchange rates are NOT handled by the system (each transaction is in a specific currency)

2.2 Currency model

class Currency(BaseDocument):
    """Supported currency. System-wide, not per tenant."""
    code: str  # ISO 4217: "SEK", "EUR", "USD", "NOK"
    name: str  # "Swedish Krona"
    symbol: str  # "kr", "€", "$"
    decimal_places: int = 2
    is_enabled: bool = True

    class TenantConfig:
        tenant_scoped = False  # Global

    class Settings:
        name = "currencies"

2.3 Multi-currency on tenant

class Tenant(BaseDocument):
    # ... existing fields
    default_currency: str = "SEK"  # ISO 4217
    enabled_currencies: list[str] = ["SEK"]  # Can add EUR, USD, etc.

2.4 All monetary fields use currency explicitly

class MoneyAmount(BaseModel):
    """Every monetary value carries its currency."""
    amount: Decimal
    currency: str  # "SEK"

No implicit currency anywhere. Every amount has a currency next to it.

2.5 Payment accounts (BG, PG, bank accounts)

Each tenant can either use their own payment accounts or use the system owner's. This determines where customer payments land and how they flow through the system.

class PaymentAccount(BaseDocument):
    """A bankgiro, plusgiro, or bank account that receives payments."""
    owner_type: str  # "system_owner" | "tenant"
    owner_id: Optional[PydanticObjectId] = None  # None for system owner

    type: str  # "bankgiro" | "plusgiro" | "bank_account" | "iban"
    account_number: str  # "123-4567" (BG) | "12 34 56-7" (PG) | IBAN
    bank_name: Optional[str] = None
    currency: str = "SEK"
    is_default: bool = False
    is_enabled: bool = True

    # Who uses this account
    used_by_tenants: list[PydanticObjectId] = []  # If system owner account shared with tenants

    class TenantConfig:
        tenant_scoped = False  # Scoped by owner_type/owner_id

    class Settings:
        name = "payment_accounts"

Two modes per tenant

Mode How it works Money flow
Own accounts Tenant has their own BG/PG. Customer pays tenant directly. Customer → Tenant's BG → Tenant settles with system owner
System owner's accounts Tenant uses system owner's BG/PG. Customer pays system owner. Customer → System owner's BG → System owner settles with tenant

Configured on the tenant agreement:

class TenantAgreement(BaseDocument):
    # ... existing fields

    # --- Payment accounts ---
    payment_account_mode: str  # "own" | "system_owner"
    payment_accounts: list[PydanticObjectId] = []  # Tenant's own accounts (if mode = "own")
    # If mode = "system_owner", the system owner's default accounts are used

Impact on settlement

Mode Who holds the money Settlement direction
own Tenant Tenant → pays system owner's share + partner's share
system_owner System owner System owner → pays tenant's share + partner's share
Mode "own" (tenant has BG):
    Customer pays 1000 → Tenant's BG
    Settlement: Tenant owes System Owner 150 + Partner 50
    Tenant pays out 200

Mode "system_owner" (tenant uses system owner's BG):
    Customer pays 1000 → System Owner's BG
    Settlement: System Owner owes Tenant 800 + Partner 50
    System Owner pays out 850

Payment file matching

When a payment file arrives, the system determines which tenant it belongs to:

# Payment file from System Owner's BG:
# → Match reference/OCR to claim → claim has tenant_id → route to correct tenant

# Payment file from Tenant's own BG:
# → File import config is tenant-scoped → already knows the tenant

3. Tenant Agreements

3.1 Concept

Every tenant has an agreement that governs: - Which features/modules are enabled - Revenue split percentages - Service fees (recurring charges for using the platform) - Billing cycle - Payment terms

3.2 Agreement model

class TenantAgreement(BaseDocument):
    """Contract between system owner and tenant."""
    tenant_id: PydanticObjectId
    name: str  # "Standard Agreement", "Enterprise Deal"
    status: str  # "draft" | "active" | "terminated"
    valid_from: datetime
    valid_until: Optional[datetime] = None

    # --- Feature access ---
    enabled_features: list[str] = []
    # e.g. ["payments", "invoicing", "bi-export", "gdpr", "client-funds"]

    # --- Revenue split ---
    revenue_splits: list[RevenueSplitRule] = []

    # --- Service fees (platform charges to tenant) ---
    service_fees: list[ServiceFee] = []

    # --- Settlement order (avräkningsordning) ---
    settlement_orders: list[SettlementOrder] = []

    # --- Billing ---
    billing_cycle: str = "monthly"  # "monthly" | "quarterly" | "annually"
    payment_terms_days: int = 30
    billing_currency: str = "SEK"
    self_billing: bool = False  # True = system owner issues invoice on behalf of tenant

    # --- Partner ---
    partner_id: Optional[PydanticObjectId] = None
    partner_name: Optional[str] = None

    class TenantConfig:
        tenant_scoped = True

    class Settings:
        name = "tenant_agreements"


class RevenueSplitRule(BaseModel):
    """How revenue is split for a specific category/product."""
    category: str  # "all" | "parking" | "subscriptions" | specific product
    tenant_percentage: Decimal  # 80.00
    system_owner_percentage: Decimal  # 15.00
    partner_percentage: Decimal  # 5.00
    # Must sum to 100.00

    def validate_sum(self) -> bool:
        return (
            self.tenant_percentage
            + self.system_owner_percentage
            + self.partner_percentage
        ) == Decimal("100.00")


class ServiceFee(BaseModel):
    """Recurring fee charged by system owner to tenant."""
    name: str  # "Platform fee", "API access", "Premium support"
    type: str  # "fixed" | "per_user" | "per_transaction" | "percentage"
    amount: Decimal  # Fixed: 999.00, Per user: 49.00, Percentage: 2.50
    currency: str = "SEK"
    billing_cycle: str = "monthly"  # Can differ from agreement cycle
    is_taxable: bool = True
    vat_rate: Decimal = Decimal("0.25")  # 25%

3.3 Settlement order (avräkningsordning)

When a payment comes in, the settlement order defines in which order cost types are paid off. This can vary by product category or by where in the collection process the claim is.

class SettlementOrder(BaseModel):
    """Defines the order in which cost types are settled when payment is received."""
    name: str  # "Standard", "Collection phase", "Enforcement phase"

    # When does this order apply?
    applies_to: SettlementOrderScope

    # The ordered list — payment is applied top to bottom
    order: list[SettlementOrderLine]


class SettlementOrderScope(BaseModel):
    """Defines when a settlement order applies."""
    product_categories: list[str] = ["all"]  # ["parking", "subscriptions"] or ["all"]
    collection_stages: list[str] = ["all"]  # ["normal", "reminder", "collection", "enforcement"] or ["all"]
    # Most specific match wins: product+stage > product > stage > all


class SettlementOrderLine(BaseModel):
    """One step in the settlement order."""
    cost_type: str  # References CostType.code: "capital", "interest", "collection_fee"
    priority: int  # 1 = first, 2 = second, etc.
    max_percentage: Optional[Decimal] = None  # Optional cap: "max 50% of payment to interest"

Example: Standard order (normal claims)

Priority Cost type Description
1 enforcement_fee Kronofogdeavgift — always first if applicable
2 collection_fee Inkassoavgift
3 reminder_fee Påminnelseavgift
4 interest Ränta
5 invoice_fee Fakturaavgift
6 capital Kapital — last

Example: Early collection order (reminder phase)

Priority Cost type
1 reminder_fee
2 interest
3 capital

Example: Subscription product order (different from standard)

Priority Cost type
1 capital
2 interest
3 invoice_fee

This way: - Each agreement can have multiple settlement orders - Different products can have different orders - Different collection stages can have different orders - Payment is applied line by line, top to bottom, until exhausted

class SettlementOrderService:
    @staticmethod
    async def apply_payment(
        tenant_id: PydanticObjectId,
        claim_id: PydanticObjectId,
        payment_amount: Decimal,
        currency: str,
    ) -> list[SettlementAllocation]:
        """
        Apply a payment to a claim using the settlement order from the agreement.

        Returns list of allocations showing how the payment was distributed.
        """
        claim = await Claim.get(claim_id)
        agreement = await _get_active_agreement(tenant_id)

        # Find matching settlement order
        order = _find_matching_order(
            agreement.settlement_orders,
            product_category=claim.product_category,
            collection_stage=claim.collection_stage,
        )

        remaining = payment_amount
        allocations = []

        for line in sorted(order.order, key=lambda l: l.priority):
            if remaining <= 0:
                break

            outstanding = claim.outstanding_by_cost_type.get(line.cost_type, Decimal("0"))
            if outstanding <= 0:
                continue

            # Apply cap if configured
            allocatable = outstanding
            if line.max_percentage:
                cap = payment_amount * line.max_percentage / 100
                allocatable = min(outstanding, cap)

            allocated = min(remaining, allocatable)
            remaining -= allocated

            allocations.append(SettlementAllocation(
                cost_type=line.cost_type,
                amount=allocated,
                currency=currency,
            ))

        return allocations


class SettlementAllocation(BaseModel):
    """How a portion of a payment was allocated."""
    cost_type: str
    amount: Decimal
    currency: str

3.4 Feature gating via agreement

The agreement controls which features a tenant can access:

# In access control middleware:
async def check_tenant_feature(tenant_id: str, feature: str) -> bool:
    agreement = await TenantAgreement.find_one(
        TenantAgreement.tenant_id == tenant_id,
        TenantAgreement.status == "active",
    )
    if not agreement:
        return False
    return feature in agreement.enabled_features

This is SEPARATE from user-level access control: - Agreement controls what the TENANT can do (module-level) - AccessGroup controls what a USER within the tenant can do (feature-level)

Agreement: Tenant has "payments" module enabled
    └── AccessGroup: User "Anna" has "payments.create" and "payments.refund"
    └── AccessGroup: User "Erik" has only "payments.list" (read-only)

4. Revenue Split (Tenant, System Owner, Partner)

4.1 How it works

When a customer pays, the revenue is split according to the tenant's agreement:

Customer pays 1000 SEK
    ▼ Agreement: 80/15/5 (Tenant/System/Partner)
    ├── Tenant:       800 SEK
    ├── System Owner: 150 SEK
    └── Partner:       50 SEK

4.2 Split calculation service

class RevenueSplitService:
    @staticmethod
    async def calculate_split(
        tenant_id: PydanticObjectId,
        amount: Decimal,
        currency: str,
        category: str = "all",
    ) -> list[SplitResult]:
        """Calculate revenue split based on tenant agreement."""
        agreement = await TenantAgreement.find_one(
            TenantAgreement.tenant_id == tenant_id,
            TenantAgreement.status == "active",
        )

        # Find matching rule (specific category first, then "all")
        rule = _find_matching_rule(agreement.revenue_splits, category)

        return [
            SplitResult(party="tenant", party_id=tenant_id,
                       amount=amount * rule.tenant_percentage / 100),
            SplitResult(party="system_owner", party_id=None,
                       amount=amount * rule.system_owner_percentage / 100),
            SplitResult(party="partner", party_id=agreement.partner_id,
                       amount=amount * rule.partner_percentage / 100),
        ]

4.3 Different splits per product category

A tenant agreement can have different splits for different products:

agreement.revenue_splits = [
    RevenueSplitRule(
        category="parking",
        tenant_percentage=Decimal("80.00"),
        system_owner_percentage=Decimal("15.00"),
        partner_percentage=Decimal("5.00"),
    ),
    RevenueSplitRule(
        category="subscriptions",
        tenant_percentage=Decimal("70.00"),
        system_owner_percentage=Decimal("25.00"),
        partner_percentage=Decimal("5.00"),
    ),
    RevenueSplitRule(
        category="all",  # Fallback
        tenant_percentage=Decimal("75.00"),
        system_owner_percentage=Decimal("20.00"),
        partner_percentage=Decimal("5.00"),
    ),
]

4.4 Claims (fordringar)

A claim tracks what is owed, broken down by cost type, with collection stage tracking:

class Claim(BaseDocument):
    """A claim/receivable — what a customer owes, broken down by cost type."""
    tenant_id: PydanticObjectId
    customer_id: PydanticObjectId
    customer_name: Optional[str] = Field(json_schema_extra={"readonly": True})

    # What generated the claim
    source_type: str  # "invoice" | "booking" | "subscription" | "manual"
    source_id: Optional[PydanticObjectId] = None
    product_category: str = "all"  # Matches settlement order scope

    # Collection process
    collection_stage: str = "normal"  # "normal" | "reminder" | "collection" | "enforcement"
    collection_history: list[CollectionEvent] = []

    # Amounts by cost type
    cost_lines: list[ClaimCostLine] = []
    total_amount: Decimal  # Sum of all cost lines
    paid_amount: Decimal = Decimal("0.00")
    outstanding_amount: Decimal  # total - paid

    currency: str
    due_date: datetime
    status: str  # "open" | "partially_paid" | "paid" | "written_off"

    class TenantConfig:
        tenant_scoped = True

    class Settings:
        name = "claims"

    @property
    def outstanding_by_cost_type(self) -> dict[str, Decimal]:
        return {
            line.cost_type: line.amount - line.paid_amount
            for line in self.cost_lines
            if line.amount > line.paid_amount
        }


class ClaimCostLine(BaseModel):
    """One cost component of a claim."""
    cost_type: str  # "capital", "interest", "reminder_fee", etc.
    description: str
    amount: Decimal
    paid_amount: Decimal = Decimal("0.00")
    added_at: datetime  # When this cost was added


class CollectionEvent(BaseModel):
    """Tracks progression through collection stages."""
    stage: str  # "reminder" | "collection" | "enforcement"
    date: datetime
    description: str
    fee_added: Optional[Decimal] = None  # Fee added at this stage
    cost_type: Optional[str] = None  # Which cost type the fee is

Flow: a claim progresses through collection

1. Invoice created → Claim (stage: normal, costs: [capital: 1000])
2. Due date passes → no payment
3. Reminder sent → stage: reminder, costs: [capital: 1000, reminder_fee: 60]
4. Still unpaid after 14 days
5. Sent to collection → stage: collection, costs: [..., collection_fee: 180]
6. Still unpaid
7. Sent to enforcement → stage: enforcement, costs: [..., enforcement_fee: 600]
8. Payment of 500 received:
   - Apply settlement order for "enforcement" stage:
     1. enforcement_fee: 500 → allocate 500, remaining 0
   - Result: enforcement_fee partially paid
9. Payment of 1340 received:
   - enforcement_fee: remaining 100 → allocate 100, remaining 1240
   - collection_fee: 180 → allocate 180, remaining 1060
   - reminder_fee: 60 → allocate 60, remaining 1000
   - interest: 0 (none added yet)
   - capital: 1000 → allocate 1000, remaining 0
   - Claim fully paid

5. Invoicing

5.1 Invoice directions

From To Type Example
System Owner → Tenant Service fees "Platform fee March 2026: 4 999 SEK"
Tenant → Customer Service/product charges "Parking booking #123: 299 SEK"
System Owner → Tenant (self-billing) Revenue share settlement "Your share of March revenue: 45 000 SEK"

5.2 Self-billing (självfakturering)

When agreement.self_billing = True, the system owner issues invoices on behalf of the tenant for the system owner's share of revenue. This is common in marketplace models.

Flow: 1. Settlement calculates system owner's share: 15 000 SEK 2. System generates a self-billing invoice: "Tenant A owes System Owner 15 000 SEK" 3. Amount is deducted from tenant's payout

5.3 Recurring service fee invoicing

class InvoiceScheduler:
    """Generates recurring invoices based on tenant agreements."""

    @staticmethod
    async def generate_service_fee_invoices(billing_period: str):
        """Run monthly/quarterly — generates invoices for all active agreements."""
        agreements = await TenantAgreement.find(
            TenantAgreement.status == "active",
        ).to_list()

        for agreement in agreements:
            for fee in agreement.service_fees:
                if fee.billing_cycle == billing_period:
                    await _create_service_invoice(agreement, fee)

5.4 Invoice model

class Invoice(BaseDocument):
    """An invoice — can be from any party to any party."""
    invoice_number: str  # Auto-generated, sequential per tenant
    type: str  # "service_fee" | "customer_charge" | "self_billing" | "credit_note"
    status: str  # "draft" | "sent" | "paid" | "overdue" | "cancelled" | "credited"

    # Parties
    issuer_tenant_id: Optional[PydanticObjectId] = None  # None = system owner
    issuer_name: str
    issuer_address: Optional[str] = None
    issuer_org_number: Optional[str] = None
    issuer_vat_number: Optional[str] = None

    recipient_tenant_id: Optional[PydanticObjectId] = None
    recipient_customer_id: Optional[PydanticObjectId] = None
    recipient_name: str
    recipient_address: Optional[str] = None
    recipient_org_number: Optional[str] = None

    # Amounts
    currency: str
    lines: list[InvoiceLine]
    subtotal: Decimal
    vat_amount: Decimal
    total: Decimal

    # Dates
    invoice_date: datetime
    due_date: datetime
    paid_date: Optional[datetime] = None

    # Payment
    payment_reference: Optional[str] = None  # OCR, reference number
    payment_method: Optional[str] = None

    # Related
    agreement_id: Optional[PydanticObjectId] = None
    settlement_id: Optional[PydanticObjectId] = None

    class TenantConfig:
        tenant_scoped = True

    class Settings:
        name = "invoices"


class InvoiceLine(BaseModel):
    """One line on an invoice."""
    description: str
    quantity: Decimal = Decimal("1")
    unit_price: Decimal
    vat_rate: Decimal = Decimal("0.25")
    amount: Decimal  # quantity * unit_price
    cost_type: Optional[str] = None  # Links to CostType

6. Subcontractors (Underleverantörer)

6.1 Concept

A tenant can have subcontractors that: - See a subset of the tenant's data (scoped access) - Perform work that generates costs/revenue - May need to be paid as part of settlement

6.2 Subcontractor model

class Subcontractor(BaseDocument):
    """A subcontractor that works for a tenant."""
    tenant_id: PydanticObjectId
    name: str
    org_number: Optional[str] = None
    contact_email: Optional[str] = None
    is_enabled: bool = True

    # Access: which resources and tags can the subcontractor see
    data_scope: SubcontractorScope

    class TenantConfig:
        tenant_scoped = True

    class Settings:
        name = "subcontractors"


class SubcontractorScope(BaseModel):
    """Defines what data a subcontractor can access."""
    resources: list[str] = []  # ["bookings", "customers"]
    tag_filters: list[TagFilter] = []  # Only see items with these tags
    methods: list[str] = ["GET"]  # Usually read-only


class TagFilter(BaseModel):
    """Tag-based filter for scoping."""
    category: str  # "location"
    include_tags: list[PydanticObjectId] = []
    exclude_tags: list[PydanticObjectId] = []

6.3 Subcontractor authentication

Subcontractors get their own user accounts with a special access group that limits them to their SubcontractorScope. The tag filters automatically apply to all queries.


7. Data Models — Complete Picture

7.1 Entity relationship overview

Tenant
├── TenantAgreement (1:1 active)
│   ├── RevenueSplitRule (N)
│   ├── ServiceFee (N)
│   └── Partner reference
├── Users (N)
│   └── AccessGroups (N)
│       └── Features (N)
├── Tags (N, hierarchical)
│   └── Used by any document for filtering
├── Subcontractors (N)
│   └── SubcontractorScope
├── Customers (N) — tenant's end customers
├── Invoices (N)
│   ├── System Owner → Tenant (service fees)
│   ├── Tenant → Customer (charges)
│   └── Self-billing (system owner's share)
├── Payments (N) — from customers
│   └── Revenue splits calculated per agreement
├── Settlements (N) — periodic reconciliation
│   └── Settlement lines (per party)
├── ClientFundsAccount (0-1)
│   └── ClientFundsTransactions (N)
├── Accounts (bookkeeping) (N)
│   └── JournalEntries (N)
└── CostTypes (N) — configurable cost categories

7.2 Currency flow

                    Customer pays 1000 SEK
                    ┌──────▼──────┐
                    │   Payment    │
                    │  1000 SEK   │
                    └──────┬──────┘
                    ┌──────▼──────┐
                    │ Revenue Split│
                    │ (Agreement)  │
                    └──┬───┬───┬──┘
                       │   │   │
              ┌────────┘   │   └────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Tenant    │ │ System   │ │ Partner  │
        │ 800 SEK   │ │ 150 SEK  │ │ 50 SEK   │
        └──────────┘ └──────────┘ └──────────┘
              │            │
              │            ▼
              │      ┌──────────┐
              │      │ Self-bill│
              │      │ invoice  │
              │      │ 150 SEK  │
              │      └──────────┘
        ┌──────────┐
        │Settlement │
        │ Payout:   │
        │ 800 SEK   │
        │ - fees    │
        │ = net     │
        └──────────┘

7.3 Multi-currency example

Tenant A operates in both SEK and EUR:

SEK transactions → settled in SEK → paid out in SEK
EUR transactions → settled in EUR → paid out in EUR

NO cross-currency conversion within the system.
Each currency has its own settlement cycle.

8. Flows and Examples

8.0 Flow: Payment received directly to tenant (file import)

When a tenant receives payments directly (not through Stripe/Klarna) — e.g. bank transfer, Bankgirot — these arrive as payment files and must be imported, matched, and reconciled:

1. Bank deposits payment to tenant's account
2. Bank generates payment file (Bankgirot, SEPA, etc.)
3. craft-easy-file-import polls SFTP → detects file
4. Parses file → list of payments with reference + amount
5. For each payment:
   a. Match reference to outstanding claim
   b. Apply settlement order (avräkningsordning from agreement)
   c. Allocate: enforcement_fee → collection_fee → reminder_fee → interest → capital
   d. Create journal entries in tenant's books
   e. Calculate revenue split per agreement
   f. Create journal entries in system owner's + partner's books
   g. Update claim status (partially_paid / paid)
6. Unmatched payments → flagged for manual review
7. Revenue from matched payments included in next settlement run

This is the connection between craft-easy-file-import and the financial ecosystem. Full spec: file-import-specification.md section 7.

8.1 Flow: Customer pays for parking

1. Customer books parking (299 SEK)
2. Payment via Stripe → Payment record created
3. Revenue split calculated:
   - Tenant: 239.20 SEK (80%)
   - System Owner: 44.85 SEK (15%)
   - Partner: 14.95 SEK (5%)
4. Split recorded in journal entries
5. End of month: settlement run
6. Settlement invoice generated (self-billing)
7. Payout to tenant: 239.20 SEK - service fees
8. System owner keeps: 44.85 SEK + service fees
9. Partner payout: 14.95 SEK

8.2 Flow: Monthly service fee billing

1. 1st of month: InvoiceScheduler runs
2. For each active agreement with monthly fees:
   - Platform fee: 4 999 SEK
   - Per-user fee: 49 SEK × 15 users = 735 SEK
   - Total: 5 734 SEK + VAT
3. Invoice created: System Owner → Tenant
4. Invoice sent (email/PDF)
5. Payment tracked

8.3 Flow: Tenant invoices their customer

1. Tenant creates invoice to Customer (via API or admin)
2. Invoice lines reference cost types
3. Payment tracking:
   - Customer pays → mark as paid
   - Customer doesn't pay → reminder fee added
   - Still unpaid → collection fee
   - Still unpaid → enforcement fee
4. All fees follow configurable cost types

8.4 Flow: Subcontractor access

1. Tenant adds subcontractor "Clean Co"
2. Scope: resource "bookings", tags "stockholm", methods ["GET"]
3. Clean Co user logs in → can only see:
   - Bookings tagged "stockholm"
   - Read-only
4. Clean Co completes work → Tenant logs it
5. Settlement includes subcontractor payment

9. Implementation Phases

This is an extension of the phases in advanced-features-specification.md:

Phase Module Weeks Depends on
6 Multi-currency support 1 Multi-tenant (Phase 1)
6 Bookkeeping (accounts, journal entries, cost types) 2 Multi-tenant
7 Client funds (Sweden, extensible) 1 Bookkeeping
8a Payment providers (abstract + Stripe) 2 Multi-tenant
8b Payment providers (Klarna + Swish) 1 Phase 8a
9 Tenant agreements (features, splits, fees) 2 Multi-tenant
10 Revenue split service 1 Agreements + Payments
11 Invoicing (all directions + self-billing) 2 Agreements + Bookkeeping
12 Settlement (calculation, approval, payout) 2 Revenue split + Invoicing
13 Subcontractor access + scoping 1 Tags + Access control
14 Recurring fee invoicing (scheduler) 1 Invoicing + Agreements

Total: ~16 additional weeks for the full financial ecosystem.

What to build first

The financial modules depend on the core modules from the advanced spec:

Phase 0: Remove fixed hierarchy          ← Already decided
Phase 1: Multi-tenant                    ← Foundation for everything
Phase 2: Tags                            ← Needed for subcontractor scoping
Phase 3: GDPR                            ← Needed for BI export
Phase 4: White label                     ← Independent
Phase 5: BI export                       ← Needs GDPR
Phase 6: Multi-currency + Bookkeeping    ← START of financial ecosystem
Phase 7: Client funds
Phase 8: Payments (Stripe, Klarna, Swish)
Phase 9: Agreements
Phase 10: Revenue splits
Phase 11: Invoicing
Phase 12: Settlement
Phase 13: Subcontractors
Phase 14: Recurring billing