diff --git a/export-biz-extension.md b/export-biz-extension.md new file mode 100644 index 00000000..3164d848 --- /dev/null +++ b/export-biz-extension.md @@ -0,0 +1,1153 @@ +# Export Business Extensions — Complete Idea Registry + +> **Sector slug**: `export` +> **Target user**: Small Swedish businesses (enskild firma & aktiebolag) that physically export goods from Sweden to EU and non-EU markets. +> **Constraint note**: Extensions primarily read core bookkeeping data. Some ideas below involve journal entry creation (revaluation, FX gain/loss) — these would require extending the extension permission model or routing through the core engine. + +--- + +## Research Summary + +### Key pain points for Swedish exporters +1. **VAT classification complexity** — Domestic (25/12/6%), EU B2B reverse charge (0%), EU B2C (OSS), non-EU export (0%) — each with different momsdeklaration boxes, documentation requirements, and penalty exposure. +2. **Currency management** — Three accounting moments per foreign invoice (invoice date, payment date, period-end revaluation). Most small exporters get this wrong. +3. **Multiple reporting obligations** — Momsdeklaration + Periodisk sammanställning (EC Sales List) + Intrastat (if >SEK 12M dispatches) + Tullverket customs declarations — all with different deadlines and formats. +4. **No market-level profitability visibility** — Same product shipped to Norway vs. USA has wildly different true margins after freight, customs, insurance, and FX costs. +5. **Missing export documentation = retroactive tax** — Non-EU zero-rating denied without proof of export (Tullverket EAD, CMR, bill of lading). 25% VAT + 20% penalty. + +### Relevant BAS accounts +| Account | Description | Momsdeklaration box | +|---------|-------------|-------------------| +| `3001` | Revenue goods 25% (domestic) | Box 05 | +| `3002` | Revenue goods 12% (domestic) | Box 05 | +| `3003` | Revenue goods 6% (domestic) | Box 05 | +| `3105` | Goods export outside EU | Box 36 | +| `3108` | Goods to EU B2B (reverse charge) | Box 35 | +| `3305` | Services outside EU | Box 40 | +| `3308` | Services to EU B2B (reverse charge) | Box 39 | +| `3109` | Triangular trade sales (trepartshandel) | Box 38 | +| `3521` | Invoiced freight, EU | Follows goods | +| `3522` | Invoiced freight, export | Box 36 | +| `3960` | FX gains (operating) | — | +| `7960` | FX losses (operating) | — | +| `3969` | Unrealized FX gains | — | +| `7969` | Unrealized FX losses | — | +| `2614` | Output VAT reverse charge 25% | — | +| `2641` | Deductible input VAT | Box 48 | +| `2645` | Calculated input VAT (EU acquisitions) | Box 48 | +| `5710` | Freight, transport, insurance | — | +| `5720` | Customs and forwarding costs | — | +| `6320` | Insurance costs | — | + +### Regulatory references +- **Mervärdesskattelagen (ML)** — Swedish VAT Act +- **Bokföringslagen (BFL)** — Swedish Bookkeeping Act (7-year retention) +- **VIES** — EU VAT Information Exchange System (free API for VAT number validation) +- **Riksbanken** — Daily exchange rates (REST API) +- **SCB Intrastat** — Monthly EU trade statistics (IDEP.web, transitioning to new platform 2026) +- **Skatteverket** — Momsdeklaration, Periodisk sammanställning (e-filing) +- **Tullverket** — Export declarations, EAD (Export Accompanying Document) + +--- + +## All Extension Ideas + +### IDEA 1: Export VAT Autopilot + +**Problem**: Every time an exporter creates an invoice to a customer in another EU country, they must manually determine the correct VAT treatment. Get it wrong and Skatteverket can deny the 0% rate or the customer can't deduct VAT. + +**What it does**: +- When creating an invoice, the user enters or selects the customer's country and VAT number (momsregistreringsnummer). +- The system automatically validates the VAT number via the EU VIES database (free API: https://ec.europa.eu/taxation_customs/vies/). +- Based on validated status + destination + goods vs. services, the system auto-applies the correct VAT treatment: + - **Intra-community supply (B2B, goods to EU)**: 0% VAT, auto-adds text "Omvänd skattskyldighet" and the legal reference. + - **Export outside EU**: 0% VAT, auto-adds "Export" and prompts for customs documentation. + - **B2C to EU (distance sale)**: Checks if OSS threshold (EUR 10,000) is exceeded, alerts if VAT registration in destination country may be needed. + - **Domestic**: Standard 25% / 12% / 6% as normal. +- Auto-books to the correct BAS account (e.g., 3108 for EU goods sales at 0%). +- Stores proof of transport (CMR, bill of lading reference) linked to the invoice — critical for defending the 0% rate in an audit. + +**Data pattern**: `both` (reads core invoice/customer data + stores VIES validation results and document references) + +**Why it wins**: Fortnox and Visma leave this entirely to the user. This extension eliminates the #1 compliance risk for exporters. + +**Note**: This idea touches core invoice creation flow. May require hooks into the invoice form rather than being a standalone workspace. Could also be implemented as a validation/enrichment layer that runs when invoices are created. + +--- + +### IDEA 2: Intrastat Generator + +**Problem**: Swedish companies dispatching goods worth >SEK 12M/year (threshold raised from 4.5M in 2025) to other EU countries must file monthly Intrastat reports to SCB. Currently done manually in Excel or IDEP.web. + +**What it does**: +- Each product in the system can be tagged with: CN commodity code (8-digit), net weight (kg), country of origin, and supplementary unit (pieces, liters, etc.). +- When an invoice is booked for an intra-community dispatch, the system automatically captures: commodity code, invoice value (in SEK), net weight, destination EU country, transaction nature code, delivery terms. +- At month end, generates a complete Intrastat dispatch declaration in the format accepted by SCB's IDEP.WEB (CSV/XML upload). +- Tracks cumulative dispatch value against the SEK 12M threshold and alerts when the company becomes obligated. +- Handles corrections: if a credit note is issued, generates a correction entry for the relevant month. + +**Data pattern**: `both` (reads core invoice data + stores product metadata: CN codes, weights, origin) + +**Required data fields per Intrastat line**: +| Field | Source | +|-------|--------| +| CN commodity code (8-digit) | Manual entry per product | +| Partner country (2-letter ISO) | From customer/invoice | +| Transaction nature code | From invoice type | +| Net mass (kg) | Manual entry per product | +| Supplementary unit | Manual entry per product (if required by CN code) | +| Invoiced value (SEK) | From invoice | +| Country of origin | Manual entry per product | +| Partner VAT ID | From customer | +| Delivery terms (Incoterms) | Manual entry per order | + +**Why it wins**: Pure pain for every exporting SME above the threshold. No Swedish bookkeeping system below ERP-level (SAP, Dynamics) does this well. Compelling reason to switch to erp-base. + +--- + +### IDEA 3: Multi-Currency Receivables Manager + +**Problem**: An exporter invoicing in EUR has open receivables whose SEK value fluctuates daily. At period end, these must be revalued. When payment arrives, there's an FX gain or loss to book. This is messy in current systems. + +**What it does**: +- Invoices can be created in any currency (EUR, USD, NOK, DKK, GBP, etc.) with the exchange rate auto-fetched from Riksbanken's daily rates. +- Open receivables dashboard showing: original amount, original SEK value, current SEK value, unrealized FX gain/loss — per customer and per currency. +- Period-end revaluation button: recalculates all open foreign-currency receivables at the closing rate and generates the required journal entries (BAS 3960 Valutakursvinster / 7960 Valutakursförluster / 3969 / 7969). +- Payment matching in foreign currency: When a EUR payment arrives, matches to EUR invoices and auto-calculates realized FX gain/loss, booking it to the correct BAS accounts. +- FX exposure summary: Shows total outstanding per currency — useful for deciding whether to hedge. + +**Data pattern**: `both` (reads core invoices/transactions + may create journal entries for revaluation) + +**Note**: The revaluation and payment-matching features involve journal entry creation, which currently isn't within extension permissions. Options: (a) route through core engine API, (b) generate draft entries for user approval, (c) expand extension capabilities. + +**Why it wins**: Fortnox handles basic multi-currency but the revaluation and FX gain/loss workflow is manual. This makes it automated and audit-ready. + +--- + +### IDEA 4: EU Sales List / Periodisk Sammanställning Auto-Reporter + +**Problem**: Every Swedish company making intra-community B2B supplies must file a quarterly (or monthly) EU sales list (periodisk sammanställning) to Skatteverket, listing each EU customer's VAT number and total value of supplies. + +**What it does**: +- Automatically compiles all 0%-rated intra-community invoices for the period. +- Groups by customer VAT number and destination country. +- Separates goods (momsdeklaration box 35) from services (box 39). +- Generates the report in Skatteverket's required format (XML for e-filing). +- Cross-references with VIES validation to catch invalid VAT numbers before filing. +- Handles credit notes (reduces the reported value for that customer). +- Alerts if any intra-community invoice is missing a validated VAT number. +- Cross-validates: total in this report should match box 35 + box 39 on the momsdeklaration — flags discrepancies. +- Filing deadline countdown with alerts. + +**Data pattern**: `core` (reads invoices + customer data, no manual data entry needed) + +**Filing frequency**: +| Type | Default | Reduced (if **Decision**: VAT Autopilot deferred (requires core invoice flow changes). 4 extensions is enough for a strong v1. +> **Language**: Swedish UI with English for international trade terms (Incoterms, VIES, CN codes, FOB/CIF). +> **Data**: All 4 extensions work with existing core schema — no database migrations needed for v1. +> **Product metadata**: Stored in `extension_data` table (isolated to Intrastat extension). + +### 1. EU Sales List / Periodisk Sammanställning (Idea 4) — Score: 9.00 +- Mandatory reporting, saves real hours, pure read-only, high standalone value +- Generates downloadable CSV/XML file for upload to Skatteverket +- Immediately useful to every exporter with EU B2B sales + +### 2. Export VAT Monitor / Exportmoms-monitor (Idea 7) — Score: 8.55 +- Post-hoc VAT analysis dashboard, 100% feasible within current architecture +- Maps revenue to momsdeklaration boxes, catches errors before filing + +### 3. Intrastat Generator (Idea 2) — Score: 8.30 +- No competing tool in the SME segment, generates SCB-compatible files +- Requires manual product metadata (stored in extension_data) but delivers massive time savings + +### 4. Multi-Currency Receivables Manager / Valutafordringar (Idea 3) — Score: 7.75 +- Dashboard portion (exposure by currency + unrealized gain/loss) is pure read-only +- Core schema already has full multi-currency support (currency, exchange_rate, total_sek on invoices) +- Journal entry generation for revaluation deferred to future phase + +### Deferred +- **Export VAT Autopilot** (Idea 1) — Deferred. Requires modifying core invoice creation flow, which violates the "extensions don't modify core data" constraint. Keep in this document for future consideration. + +### Future Phase +- **Export Document Center** (Idea 5) — Proforma invoices, packing lists, document archive +- **Freight Cost Allocator** (Idea 6) + **Market Profitability** (Idea 8) — Could merge into "Export Profitability" +- **Compliance Tracker** (Idea 9) — Partially covered by the 4 selected extensions combined + +--- + +## Core Schema Findings + +The existing schema already supports everything we need: + +**Invoice type** (`types/index.ts`): +- `currency: Currency` — EUR, USD, GBP, NOK, DKK, SEK +- `exchange_rate: number | null` — rate at invoice date +- `subtotal` / `subtotal_sek` — original and SEK amounts +- `total` / `total_sek` — original and SEK amounts +- `vat_treatment: VatTreatment` — includes `reverse_charge`, `export`, `exempt` +- `moms_ruta: string | null` — momsdeklaration box (05, 35, 36, 39, 40) + +**Customer type** (`types/index.ts`): +- `country: string` — ISO country code +- `vat_number: string | null` +- `vat_number_validated: boolean` +- `vat_number_validated_at: string | null` +- `customer_type: CustomerType` + +**JournalEntryLine type** (`types/index.ts`): +- `currency: string` +- `amount_in_currency: number | null` +- `exchange_rate: number | null` +- `account_number: string` — BAS account (3105, 3108, 3305, 3308, etc.) + +**Transaction type** (`types/index.ts`): +- `currency: Currency` +- `amount_sek: number | null` +- `exchange_rate: number | null` + +--- + +## Implementation Plan + +### Phase 0: Sector Registration & Shared Infrastructure + +**Goal**: Register the export sector, create shared components, set up the extension folder structure. + +#### 0.1 Create folder structure +``` +extensions/ + export/ + eu-sales-list/ + index.ts # Extension definition + lib/ + eu-sales-list-engine.ts # Core logic: aggregate, validate, generate file + vies-validator.ts # VIES API VAT number validation (shared utility) + skv-xml-generator.ts # Skatteverket XML format generation + vat-monitor/ + index.ts + lib/ + vat-monitor-engine.ts # GL account reading, box mapping, validation + intrastat/ + index.ts + lib/ + intrastat-engine.ts # Data aggregation, CN code management + scb-file-generator.ts # SCB IDEP.web compatible CSV/XML + currency-receivables/ + index.ts + lib/ + receivables-engine.ts # Exposure calc, unrealized gain/loss + riksbanken-rates.ts # Daily rate fetching (extend existing lib/currency/) +``` + +#### 0.2 Register sector in `lib/extensions/sectors.ts` +```typescript +{ + slug: 'export', + name: 'Export & Utrikeshandel', + icon: 'Ship', + description: 'Verktyg för svenska företag som exporterar varor till EU och övriga världen', + extensions: [ + { + slug: 'eu-sales-list', + name: 'Periodisk sammanställning', + sector: 'export', + category: 'accounting', + icon: 'FileText', + dataPattern: 'core', + readsCoreTables: ['invoices', 'customers'], + hasOwnData: false, + description: 'Generera periodisk sammanställning (EC Sales List) för Skatteverket', + longDescription: 'Sammanställer automatiskt alla momsfria EU-försäljningar grupperat per kund och momsregistreringsnummer. Genererar nedladdningsbar fil för uppladdning till Skatteverket. Validerar kundernas VAT-nummer via VIES och flaggar saknade uppgifter.' + }, + { + slug: 'vat-monitor', + name: 'Exportmoms-monitor', + sector: 'export', + category: 'reports', + icon: 'Shield', + dataPattern: 'core', + readsCoreTables: ['journal_entry_lines', 'journal_entries', 'invoices'], + hasOwnData: false, + description: 'Övervaka momsbehandling för export och EU-handel', + longDescription: 'Visar intäkter uppdelat på inhemsk försäljning, EU B2B (reverse charge) och export utanför EU. Mappar automatiskt till rätt rutor i momsdeklarationen (ruta 05, 35, 36, 39, 40). Flaggar potentiella fel som saknat momsregistreringsnummer på EU-kunder eller felaktig momsbehandling.' + }, + { + slug: 'intrastat', + name: 'Intrastat-generator', + sector: 'export', + category: 'accounting', + icon: 'BarChart3', + dataPattern: 'both', + readsCoreTables: ['invoices', 'customers'], + hasOwnData: true, + description: 'Generera Intrastat-deklarationer för rapportering till SCB', + longDescription: 'Tagga produkter med CN-koder (Combined Nomenclature), vikt och ursprungsland. Genererar kompletta Intrastat-deklarationer i CSV-format för uppladdning till SCB:s IDEP.web. Övervakar tröskelvärdet på 12 MSEK för utförsel och varnar när rapporteringsskyldighet uppstår.' + }, + { + slug: 'currency-receivables', + name: 'Valutafordringar', + sector: 'export', + category: 'reports', + icon: 'TrendingUp', + dataPattern: 'core', + readsCoreTables: ['invoices', 'journal_entry_lines', 'transactions'], + hasOwnData: false, + description: 'Övervaka valutaexponering och orealiserade kursvinster/-förluster', + longDescription: 'Visar öppna kundfordringar per valuta med aktuellt SEK-värde baserat på Riksbankens dagskurser. Beräknar orealiserade valutakursvinster och -förluster. Visar realiserade kursdifferenser per period (konto 3960/7960). Ger en samlad bild av företagets valutarisk.' + } + ] +} +``` + +#### 0.3 Shared components to build +All placed in `components/extensions/export/shared/`: + +| Component | Purpose | Used by | +|-----------|---------|---------| +| `PeriodSelector` | Month/quarter picker for reporting periods | All 4 | +| `CurrencyDisplay` | Shows amount in original currency + SEK | 3, 4 | +| `DeadlineCard` | Countdown to next filing deadline | 1, 2 | +| `ComplianceStatusBadge` | Filed / Pending / Overdue indicator | 1, 2 | +| `MomsrutaLabel` | Styled label for momsdeklaration box numbers | 1, 2 | +| `CountryFlag` | Small flag icon + country name for EU countries | 1, 3, 4 | +| `ExportKPICard` | Extension of existing KPICard with currency formatting | All 4 | +| `DownloadButton` | Trigger file download (CSV/XML) with loading state | 1, 3 | + +#### 0.4 Shared utilities +Placed in `extensions/export/shared/`: + +| Utility | Purpose | Used by | +|---------|---------|---------| +| `vies-client.ts` | VIES SOAP/REST API client for VAT number validation | 1 | +| `riksbanken-client.ts` | Extend existing `lib/currency/` to fetch daily rates | 4 | +| `eu-countries.ts` | EU member state list with ISO codes, currency, VAT prefixes | 1, 2, 3 | +| `moms-box-mapping.ts` | Maps BAS accounts + vat_treatment to momsdeklaration boxes | 1, 2 | +| `file-generators.ts` | CSV and XML file generation utilities | 1, 3 | + +--- + +### Phase 1: EU Sales List / Periodisk Sammanställning + +**Extension slug**: `eu-sales-list` +**Data pattern**: `core` (read-only from invoices + customers) +**Output**: Downloadable CSV/XML file + +#### 1.1 Engine (`eu-sales-list-engine.ts`) + +**Input**: User ID, period (year + month or quarter), filing type (goods/services/both) + +**Logic**: +1. Fetch all invoices for the period where: + - `vat_treatment = 'reverse_charge'` (EU B2B) + - `status` is `sent` or `paid` (not draft) + - Customer `country` is an EU member state (not Sweden) +2. Join with customers to get `vat_number`, `country`, `name` +3. Group by customer `vat_number` +4. For each customer, separate goods invoices (revenue accounts 3108) from services (3308) +5. Sum `total_sek` for goods and services separately +6. Handle credit notes: subtract from the customer's total (can result in negative amounts) +7. Validate: + - Flag customers with missing or unvalidated VAT numbers + - Flag invoices without `moms_ruta` set to '35' or '39' + - Cross-check: sum of goods should equal journal entries on account 3108 for the period + - Cross-check: sum of services should equal journal entries on account 3308 for the period + +**Output structure**: +```typescript +interface ECSalesListReport { + period: { year: number; month?: number; quarter?: number } + filingType: 'monthly' | 'quarterly' + reporterVatNumber: string + reporterName: string + lines: ECSalesListLine[] + totals: { goods: number; services: number; triangulation: number } + warnings: ECSalesListWarning[] + crossCheck: { boxMatch: boolean; box35Total: number; box39Total: number } +} + +interface ECSalesListLine { + customerVatNumber: string + customerName: string + customerCountry: string // ISO 2-letter + goodsAmount: number // SEK, rounded to whole number + servicesAmount: number // SEK, rounded to whole number + triangulationAmount: number // SEK, for trepartshandel +} + +interface ECSalesListWarning { + type: 'missing_vat_number' | 'unvalidated_vat_number' | 'missing_moms_ruta' | 'cross_check_mismatch' + invoiceId?: string + customerId?: string + message: string +} +``` + +#### 1.2 File generators + +**Skatteverket XML format** (`skv-xml-generator.ts`): +- Generate XML matching SKV 5740 schema +- Include header: reporter VAT number, period, contact info +- Include lines: customer VAT number, goods amount, services amount +- Encoding: UTF-8 + +**CSV fallback** (`csv-generator.ts`): +- Simple CSV with columns: Customer VAT Number, Country, Goods (SEK), Services (SEK) +- BOM for Excel compatibility + +#### 1.3 VIES integration (`vies-client.ts`) +- Call EU VIES API to validate customer VAT numbers +- Cache validation results (valid for 24 hours) +- Show validation status indicator (valid / invalid / pending / error) +- Used in the warnings system to flag invalid numbers before filing + +#### 1.4 Workspace UI (`EuSalesListWorkspace.tsx`) + +**Layout**: +``` +┌─────────────────────────────────────────────────┐ +│ Periodisk sammanställning │ +│ │ +│ [Period selector: 2026 / Kvartal 1 ▼] │ +│ Filing type: ○ Varor (monthly) ○ Tjänster (quarterly) ○ Båda │ +│ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ Total varor│ │Total tjänst│ │ Kunder │ │ +│ │ 1 250 000 │ │ 340 000 │ │ 12 │ │ +│ │ SEK │ │ SEK │ │ │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ │ +│ ⚠ 2 varningar [Ladda ner ▼] │ +│ │ +│ ┌───────────────────────────────────────────┐ │ +│ │ VAT-nummer │ Land │ Varor │ Tjänster│ │ +│ │ DE123456789 │ 🇩🇪 │ 450 000│ 0 │ │ +│ │ FR87654321 │ 🇫🇷 │ 320 000│ 120 000 │ │ +│ │ FI11223344 │ 🇫🇮 │ 280 000│ 80 000 │ │ +│ │ ⚠ NL(saknas) │ 🇳🇱 │ 200 000│ 0 │ │ +│ │ ... │ │ │ │ │ +│ └───────────────────────────────────────────┘ │ +│ │ +│ Korsvalidering mot momsdeklaration: │ +│ Ruta 35 (varor): 1 250 000 ✓ │ +│ Ruta 39 (tjänster): 340 000 ✓ │ +│ Nästa deadline: 25 april 2026 (31 dagar kvar) │ +└─────────────────────────────────────────────────┘ +``` + +**Features**: +- Period selector (month or quarter) +- KPI cards: total goods, total services, customer count +- Warning banner with expandable details +- Sortable table of customers with VAT numbers, country flags, amounts +- Download button: CSV or XML format +- Cross-validation section: compares with momsdeklaration box totals +- Deadline countdown + +#### 1.5 Tests (`__tests__/eu-sales-list-engine.test.ts`) + +Test cases: +- Aggregation by customer VAT number (multiple invoices to same customer) +- Goods vs services separation (based on revenue account) +- Credit note handling (reduces customer total, can go negative) +- Missing VAT number warning +- Unvalidated VAT number warning +- Cross-check with GL account totals +- Empty period (no EU sales) +- Mixed period (some EU, some non-EU, some domestic) +- Currency conversion (all amounts in SEK regardless of invoice currency) + +--- + +### Phase 2: Export VAT Monitor / Exportmoms-monitor + +**Extension slug**: `vat-monitor` +**Data pattern**: `core` (read-only from journal entries + invoices) +**Output**: Dashboard with momsdeklaration box mapping + +#### 2.1 Engine (`vat-monitor-engine.ts`) + +**Input**: User ID, period (year + month or quarter) + +**Logic**: +1. Fetch all journal entry lines for the period on revenue accounts: + - Domestic: `3001` (25%), `3002` (12%), `3003` (6%) + - EU goods: `3108` (reverse charge, 0%) + - EU services: `3308` (reverse charge, 0%) + - Non-EU goods: `3105` (export, 0%) + - Non-EU services: `3305` (export, 0%) + - Triangular: `3109` (trepartshandel) + - Invoiced freight: `3521` (EU), `3522` (export) +2. Map each account to the correct momsdeklaration box: + - `3001/3002/3003` → Box 05 (standard taxable sales) + - `3108` → Box 35 (EU goods) + - `3305` → Box 40 (other services abroad) + - `3308` → Box 39 (EU services, main rule) + - `3105` → Box 36 (goods export outside EU) + - `3109` → Box 38 (triangular trade sales) + - `3521` → follows goods treatment (Box 35) + - `3522` → Box 36 +3. Sum credit amounts per box (revenue is credit-side) +4. Also fetch VAT account totals: + - `2611` (output VAT 25%), `2621` (12%), `2631` (6%) → Boxes 10, 11, 12 + - `2641` (input VAT) → Box 48 +5. Calculate net VAT (output - input) → Box 49 +6. Validate: + - Box 35 + Box 39 should match EU Sales List totals (if extension 1 is enabled) + - Invoices with `vat_treatment = 'reverse_charge'` should be on accounts 3108/3308 + - Invoices with `vat_treatment = 'export'` should be on accounts 3105/3305 + - Flag any invoice where the `moms_ruta` doesn't match the expected box for its account + +**Output structure**: +```typescript +interface VatMonitorReport { + period: { year: number; month?: number; quarter?: number } + boxes: Record // '05', '10', '11', '12', '35', '36', '39', '40', '48', '49' + revenueBreakdown: { + domestic: { amount: number; percentage: number } + euGoods: { amount: number; percentage: number } + euServices: { amount: number; percentage: number } + exportGoods: { amount: number; percentage: number } + exportServices: { amount: number; percentage: number } + triangular: { amount: number; percentage: number } + } + warnings: VatMonitorWarning[] + previousPeriod?: VatMonitorReport // For comparison +} + +interface VatBoxData { + boxNumber: string + label: string // Swedish label + amount: number // SEK + accounts: string[] // Contributing BAS accounts +} + +interface VatMonitorWarning { + type: 'wrong_account' | 'missing_moms_ruta' | 'vat_treatment_mismatch' | 'missing_vat_number' | 'cross_check_mismatch' + severity: 'error' | 'warning' + invoiceId?: string + message: string +} +``` + +#### 2.2 Box mapping reference (`moms-box-mapping.ts`) + +```typescript +// Shared between EU Sales List and VAT Monitor +const ACCOUNT_TO_BOX: Record = { + '3001': '05', '3002': '05', '3003': '05', // Domestic revenue + '3108': '35', // EU goods (reverse charge) + '3308': '39', // EU services (reverse charge) + '3105': '36', // Export goods (non-EU) + '3305': '40', // Export services (non-EU) + '3109': '38', // Triangular trade + '3521': '35', // Invoiced freight EU + '3522': '36', // Invoiced freight export + '2611': '10', '2621': '11', '2631': '12', // Output VAT + '2641': '48', // Input VAT +} + +const BOX_LABELS: Record = { + '05': 'Momspliktig försäljning', + '10': 'Utgående moms 25%', + '11': 'Utgående moms 12%', + '12': 'Utgående moms 6%', + '35': 'Varuförsäljning till annat EU-land', + '36': 'Varuförsäljning utanför EU (export)', + '37': 'Mellanmans inköp vid trepartshandel', + '38': 'Mellanmans försäljning vid trepartshandel', + '39': 'Tjänsteförsäljning till EU (huvudregeln)', + '40': 'Övrig försäljning av tjänster utomlands', + '41': 'Försäljning med omvänd skattskyldighet (Sverige)', + '42': 'Övrig försäljning m.m.', + '48': 'Ingående moms att dra av', + '49': 'Moms att betala eller få tillbaka', +} +``` + +#### 2.3 Workspace UI (`VatMonitorWorkspace.tsx`) + +**Layout**: +``` +┌─────────────────────────────────────────────────────────┐ +│ Exportmoms-monitor │ +│ │ +│ [Period: 2026-03 ▼] [Jämför med: 2026-02 ▼] │ +│ │ +│ ── Intäktsfördelning ────────────────────────────── │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Inrikes │ │ EU varor │ │EU tjänst │ │ Export │ │ +│ │2 100 000 │ │1 250 000 │ │ 340 000 │ │ 890 000 │ │ +│ │ 46% │ │ 27% │ │ 7% │ │ 20% │ │ +│ │ ↑ +5% │ │ ↓ -3% │ │ ↑ +12% │ │ ↑ +8% │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ ── Momsdeklaration (förhandsvisning) ────────────── │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Ruta │ Beskrivning │ Belopp │ │ +│ │ 05 │ Momspliktig försäljning │2 100 000│ │ +│ │ 10 │ Utgående moms 25% │ 525 000│ │ +│ │ 35 │ Varuförsäljning EU │1 250 000│ │ +│ │ 36 │ Export utanför EU │ 890 000│ │ +│ │ 39 │ Tjänsteförsäljning EU │ 340 000│ │ +│ │ 48 │ Ingående moms │ 380 000│ │ +│ │ 49 │ Moms att betala │ 145 000│ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ ⚠ 1 varning: Faktura #2026-042 till DE-kund saknar │ +│ validerat VAT-nummer men är bokförd på konto 3108. │ +│ │ +│ ── Trend (senaste 6 månader) ────────────────────── │ +│ [Bar chart: domestic vs EU vs export per month] │ +└─────────────────────────────────────────────────────────┘ +``` + +**Features**: +- Period selector with comparison period +- KPI cards: revenue by destination type with % share and delta +- Momsdeklaration preview table with all relevant boxes pre-filled +- Warning panel with actionable messages +- 6-month trend chart showing revenue mix over time +- Drill-down: click a box number to see contributing invoices + +#### 2.4 Tests (`__tests__/vat-monitor-engine.test.ts`) + +Test cases: +- Correct box mapping for each revenue account +- Mixed domestic + EU + export revenue +- Period comparison (delta calculation) +- Warning: invoice on 3108 without validated VAT number +- Warning: invoice `vat_treatment` doesn't match account +- Warning: `moms_ruta` doesn't match expected box for account +- Empty period +- Only domestic sales (no export boxes populated) +- Freight accounts follow goods treatment +- VAT calculation: output minus input = box 49 + +--- + +### Phase 3: Intrastat Generator + +**Extension slug**: `intrastat` +**Data pattern**: `both` (reads invoices + stores product metadata in extension_data) +**Output**: Downloadable CSV file for SCB IDEP.web + +#### 3.1 Product metadata storage + +Uses `extension_data` table with these key patterns: +- `product:{productId}` → `{ cn_code, description, net_weight_kg, country_of_origin, supplementary_unit, supplementary_unit_type }` +- `settings` → `{ default_transaction_nature: '11', default_delivery_terms: 'FCA', threshold_alert_enabled: true }` + +The `productId` is a user-defined identifier (e.g., SKU or product name) since there's no core products table. + +#### 3.2 Engine (`intrastat-engine.ts`) + +**Input**: User ID, period (year + month) + +**Logic**: +1. Fetch all invoices for the period where: + - Customer `country` is an EU member state (not Sweden) + - `vat_treatment = 'reverse_charge'` (B2B goods) + - Revenue account is `3108` (goods to EU) + - `status` is `sent` or `paid` +2. For each invoice line, look up product metadata from extension_data +3. Aggregate by: CN code + partner country + country of origin + transaction nature + delivery terms +4. For each aggregated line, calculate: + - Total invoiced value in SEK (using `total_sek` from invoice) + - Total net mass (kg) from product metadata × quantity + - Supplementary units (if required by CN code) +5. Handle credit notes: generate correction lines for the original period +6. Calculate cumulative dispatch value (rolling 12 months) for threshold monitoring + +**Output structure**: +```typescript +interface IntrastatReport { + period: { year: number; month: number } + reporterVatNumber: string + reporterName: string + flowType: 'dispatch' // We focus on exports + lines: IntrastatLine[] + totals: { invoicedValue: number; netMass: number; lineCount: number } + thresholdStatus: { + cumulativeValue: number // Rolling 12 months + threshold: 12_000_000 // SEK + isObligated: boolean + percentageUsed: number + } + warnings: IntrastatWarning[] +} + +interface IntrastatLine { + cnCode: string // 8-digit CN commodity code + partnerCountry: string // 2-letter ISO (destination) + countryOfOrigin: string // 2-letter ISO + transactionNature: string // 2-digit code (e.g., '11' for outright sale) + deliveryTerms: string // Incoterms code + invoicedValue: number // SEK, rounded to whole + netMass: number // kg, up to 3 decimals + supplementaryUnit?: number + supplementaryUnitType?: string + partnerVatId: string // Customer VAT number +} + +interface IntrastatWarning { + type: 'missing_cn_code' | 'missing_weight' | 'missing_origin' | 'unmatched_invoice_line' | 'threshold_approaching' + invoiceId?: string + productId?: string + message: string +} +``` + +#### 3.3 SCB file generator (`scb-file-generator.ts`) + +Generates CSV compatible with IDEP.web upload: +- Header row with field names +- One row per aggregated line +- Encoding: UTF-8 with BOM +- Semicolon-separated (IDEP.web standard) +- Fields: CN code, partner country, country of origin, transaction nature, delivery terms, invoiced value, net mass, supplementary unit, partner VAT ID + +#### 3.4 Workspace UI (`IntrastatWorkspace.tsx`) + +**Layout**: +``` +┌──────────────────────────────────────────────────────────┐ +│ Intrastat-generator │ +│ │ +│ [Period: 2026-03 ▼] │ +│ │ +│ ── Tröskelvärde ──────────────────────────────────── │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Ackumulerad utförsel (12 mån): 8 450 000 SEK │ │ +│ │ ████████████████░░░░░░░░ 70% av 12 000 000 │ │ +│ │ Status: Under tröskelvärdet (frivillig) │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ ── Produktregister ─────────────────── [+ Lägg till] │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Produkt │ CN-kod │ Vikt(kg) │ Ursprung │ │ +│ │ Stålbalk M8 │ 72163100 │ 45.5 │ SE │ │ +│ │ Ventil DN50 │ 84818019 │ 2.3 │ DE │ │ +│ │ ⚠ Pump XL │ (saknas) │ 12.0 │ SE │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ── Deklaration mars 2026 ──────────── [Ladda ner CSV] │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ CN-kod │ Land │ Urspr │ Värde SEK│ Vikt kg │ │ +│ │ 72163100 │ DE │ SE │ 450 000 │ 4 550 │ │ +│ │ 72163100 │ FI │ SE │ 120 000 │ 1 200 │ │ +│ │ 84818019 │ DE │ DE │ 230 000 │ 46 │ │ +│ │ Total │ │ │ 800 000 │ 5 796 │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ⚠ 1 varning: Produkt "Pump XL" saknar CN-kod. │ +│ Deadline: 14 april 2026 (10:e arbetsdagen) │ +└──────────────────────────────────────────────────────────┘ +``` + +**Features**: +- Threshold progress bar (cumulative 12-month dispatches vs SEK 12M) +- Product registry: CRUD for product metadata (CN codes, weights, origin) +- Auto-generated declaration table from period's EU goods invoices +- Warning panel for missing metadata +- Download button for SCB-compatible CSV +- Deadline display (10th business day of following month) + +#### 3.5 Tests (`__tests__/intrastat-engine.test.ts`) + +Test cases: +- Aggregation by CN code + country + origin +- Multiple invoices to same country with same CN code (should aggregate) +- Credit note correction (negative line for original period) +- Missing CN code warning +- Missing weight warning +- Threshold calculation (rolling 12 months) +- Threshold crossing alert +- Empty period (no EU goods dispatches) +- Non-EU invoices excluded +- Services excluded (only goods on account 3108) + +--- + +### Phase 4: Multi-Currency Receivables Manager / Valutafordringar + +**Extension slug**: `currency-receivables` +**Data pattern**: `core` (read-only from invoices + journal entries + transactions) +**Output**: Dashboard showing FX exposure and unrealized gains/losses + +#### 4.1 Engine (`receivables-engine.ts`) + +**Input**: User ID, reference date (default: today) + +**Logic**: +1. Fetch all unpaid invoices (`status = 'sent'` or `'overdue'`) where `currency != 'SEK'` +2. For each invoice, calculate: + - Original amount in foreign currency (`total`) + - Booked SEK value (`total_sek` or `total × exchange_rate`) + - Current SEK value using today's Riksbanken rate + - Unrealized gain/loss = current SEK value - booked SEK value +3. Group by currency for exposure summary +4. Fetch realized gains/losses from journal entry lines: + - Account `3960` (gains) credit amounts for the period + - Account `7960` (losses) debit amounts for the period +5. Fetch historical realized FX per month for trend analysis +6. Calculate totals: + - Total foreign receivables (SEK equivalent at current rate) + - Total unrealized gain/loss + - Total realized gain/loss for current period + +**Output structure**: +```typescript +interface CurrencyReceivablesReport { + referenceDate: string + exchangeRates: Record // From Riksbanken + + // Exposure by currency + exposureByCurrency: CurrencyExposure[] + + // Individual receivables + receivables: ForeignReceivable[] + + // Realized FX for period + realizedGainLoss: { + period: { year: number; month: number } + gains: number // Account 3960 credit total + losses: number // Account 7960 debit total + net: number + } + + // Monthly trend (last 12 months) + monthlyTrend: MonthlyFXTrend[] +} + +interface CurrencyExposure { + currency: string + totalForeignAmount: number // In original currency + bookedSekValue: number // At invoice-date rates + currentSekValue: number // At today's Riksbanken rate + unrealizedGainLoss: number // currentSek - bookedSek + invoiceCount: number + averageBookedRate: number + currentRate: number +} + +interface ForeignReceivable { + invoiceId: string + invoiceNumber: string + customerName: string + customerCountry: string + currency: string + foreignAmount: number + bookedSekAmount: number + bookedRate: number + currentSekAmount: number + currentRate: number + unrealizedGainLoss: number + invoiceDate: string + dueDate: string + daysOutstanding: number +} + +interface MonthlyFXTrend { + month: string // 'YYYY-MM' + realizedGains: number + realizedLosses: number + netRealized: number + unrealizedAtMonthEnd: number +} +``` + +#### 4.2 Riksbanken rate integration + +Extend `lib/currency/` or create `riksbanken-client.ts`: +- Fetch daily mid-rates from Riksbanken's REST API +- Cache rates for the current day +- Support historical rate lookup (for trend calculations) +- Fallback: use the most recent available rate if today's isn't published yet (rates published at 16:15 on business days) + +**Riksbanken API**: `https://api.riksbank.se/swea/v1/CrossRates` + +#### 4.3 Workspace UI (`CurrencyReceivablesWorkspace.tsx`) + +**Layout**: +``` +┌──────────────────────────────────────────────────────────┐ +│ Valutafordringar │ +│ │ +│ Växelkurser per 2026-03-15 (Riksbanken) │ +│ EUR: 11.42 USD: 10.85 GBP: 13.72 NOK: 1.02 │ +│ │ +│ ── Valutaexponering ──────────────────────────────── │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ EUR │ │ USD │ │ GBP │ │ Totalt │ │ +│ │€ 125 000 │ │$ 45 000 │ │£ 12 000 │ │ │ │ +│ │1 427 500 │ │ 488 250 │ │ 164 640 │ │2 080 390 │ │ +│ │ SEK │ │ SEK │ │ SEK │ │ SEK │ │ +│ │ +32 500 │ │ -8 200 │ │ +1 440 │ │ +25 740 │ │ +│ │ orealis. │ │ orealis. │ │ orealis. │ │ orealis. │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ ── Öppna fordringar ───────────────────────────────── │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Faktura │ Kund │ Valuta│ Belopp │Orealis.│ │ +│ │ 2026-031 │ Müller GmbH│ EUR │ 50 000 │+12 500 │ │ +│ │ 2026-035 │ Smith Inc │ USD │ 25 000 │ -5 200 │ │ +│ │ 2026-038 │ Dupont SA │ EUR │ 75 000 │+20 000 │ │ +│ │ 2026-041 │ Jones Ltd │ GBP │ 12 000 │ +1 440 │ │ +│ │ 2026-044 │ Brown Corp │ USD │ 20 000 │ -3 000 │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ── Realiserade kursdifferenser 2026 ───────────────── │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Månad │ Vinst(3960)│ Förlust(7960)│ Netto │ │ +│ │ Jan │ +8 500 │ -3 200 │ +5 300 │ │ +│ │ Feb │ +12 300 │ -7 800 │ +4 500 │ │ +│ │ Mar │ +4 200 │ -1 100 │ +3 100 │ │ +│ │ Totalt │ +25 000 │ -12 100 │ +12 900 │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ── Period-end revaluation preview ─────────────────── │ +│ Om bokslut görs idag: netto orealiserad vinst +25 740 │ +│ (Konto 3969: +33 940 / Konto 7969: -8 200) │ +│ ℹ Bokföringsposterna skapas inte av detta tillägg. │ +│ Använd värdena ovan som underlag vid periodbokslut. │ +└──────────────────────────────────────────────────────────┘ +``` + +**Features**: +- Live Riksbanken exchange rates display +- Exposure KPI cards per currency showing foreign amount, SEK value, unrealized gain/loss +- Sortable receivables table with per-invoice unrealized gain/loss +- Color coding: green for gains, red for losses +- Realized FX trend table (monthly, from accounts 3960/7960) +- Period-end revaluation preview (informational — tells user what entries to make, doesn't create them) +- Note clarifying that this extension doesn't create journal entries + +#### 4.4 Tests (`__tests__/receivables-engine.test.ts`) + +Test cases: +- Unrealized gain calculation (rate increased since invoice date) +- Unrealized loss calculation (rate decreased) +- Multiple currencies aggregation +- Paid invoices excluded from exposure +- Realized gains from account 3960 +- Realized losses from account 7960 +- Monthly trend calculation +- Empty state (no foreign receivables) +- SEK-only invoices excluded +- Exchange rate not available (use most recent) + +--- + +### Phase 5: Integration & Polish + +#### 5.1 Register all extensions in loader +Add to `FIRST_PARTY_EXTENSIONS` in `lib/extensions/loader.ts`: +```typescript +import { euSalesListExtension } from '@/extensions/export/eu-sales-list' +import { vatMonitorExtension } from '@/extensions/export/vat-monitor' +import { intrastatExtension } from '@/extensions/export/intrastat' +import { currencyReceivablesExtension } from '@/extensions/export/currency-receivables' +``` + +#### 5.2 Register workspace components +Add to `lib/extensions/workspace-registry.tsx`: +```typescript +'export/eu-sales-list': dynamic(() => import('@/components/extensions/export/EuSalesListWorkspace')), +'export/vat-monitor': dynamic(() => import('@/components/extensions/export/VatMonitorWorkspace')), +'export/intrastat': dynamic(() => import('@/components/extensions/export/IntrastatWorkspace')), +'export/currency-receivables': dynamic(() => import('@/components/extensions/export/CurrencyReceivablesWorkspace')), +``` + +#### 5.3 Add icon imports +Update `lib/extensions/icon-resolver.tsx` with new icons: +- `Ship` — sector icon +- `FileText` — EU Sales List +- `Shield` — VAT Monitor +- `BarChart3` — Intrastat (already exists) +- `TrendingUp` — Currency Receivables (already exists) + +#### 5.4 Cross-extension validation +When multiple export extensions are enabled: +- VAT Monitor can cross-reference with EU Sales List totals +- Intrastat threshold data validates against VAT Monitor's box 35 total +- Currency Receivables exposure aligns with invoices visible in EU Sales List + +#### 5.5 API routes +Each extension needs data-fetching API routes: + +| Route | Method | Purpose | +|-------|--------|---------| +| `/api/extensions/export/eu-sales-list/report` | GET | Generate report for period | +| `/api/extensions/export/eu-sales-list/download` | GET | Download CSV/XML file | +| `/api/extensions/export/eu-sales-list/validate-vat` | POST | VIES VAT number validation | +| `/api/extensions/export/vat-monitor/report` | GET | Generate VAT box report | +| `/api/extensions/export/intrastat/report` | GET | Generate Intrastat declaration | +| `/api/extensions/export/intrastat/download` | GET | Download SCB CSV file | +| `/api/extensions/export/intrastat/products` | GET/POST/DELETE | Product metadata CRUD | +| `/api/extensions/export/currency-receivables/report` | GET | Exposure + unrealized report | +| `/api/extensions/export/currency-receivables/rates` | GET | Current Riksbanken rates | + +--- + +## Build Order Summary + +| Phase | Extension | Key deliverables | Depends on | +|-------|-----------|-----------------|------------| +| 0 | Infrastructure | Sector registration, shared components, shared utilities | — | +| 1 | EU Sales List | Engine, VIES client, XML generator, workspace, tests | Phase 0 | +| 2 | VAT Monitor | Engine, box mapping, workspace, tests | Phase 0 | +| 3 | Intrastat | Engine, product CRUD, SCB CSV generator, workspace, tests | Phase 0 | +| 4 | Currency Receivables | Engine, Riksbanken client, workspace, tests | Phase 0 | +| 5 | Integration | Loader registration, workspace registry, icon imports, cross-validation | Phases 1-4 | diff --git a/lib/bookkeeping/bas-reference.ts b/lib/bookkeeping/bas-reference.ts index 7bbdba94..698a6c39 100644 --- a/lib/bookkeeping/bas-reference.ts +++ b/lib/bookkeeping/bas-reference.ts @@ -1015,6 +1015,38 @@ export const BAS_REFERENCE: BASReferenceAccount[] = [ sru_code: '7311', }, + // 31 - Forsaljning varor utanfor Sverige + { + account_number: '3105', + account_name: 'Forsaljning varor export utanfor EU', + account_class: 3, + account_group: '31', + account_type: 'revenue', + normal_balance: 'credit', + description: 'Intakter fran forsaljning av varor till kunder utanfor EU. Momsfritt (momsdeklaration ruta 36).', + sru_code: '7310', + }, + { + account_number: '3108', + account_name: 'Forsaljning varor till annat EU-land', + account_class: 3, + account_group: '31', + account_type: 'revenue', + normal_balance: 'credit', + description: 'Intakter fran forsaljning av varor till momsregistrerade foretag i andra EU-lander. Omvand skattskyldighet (momsdeklaration ruta 35).', + sru_code: '7310', + }, + { + account_number: '3109', + account_name: 'Forsaljning vid trepartshandel', + account_class: 3, + account_group: '31', + account_type: 'revenue', + normal_balance: 'credit', + description: 'Mellanmans forsaljning av varor vid trepartshandel inom EU (momsdeklaration ruta 38).', + sru_code: '7310', + }, + // 33 - Forsaljning tjanster utanfor Sverige { account_number: '3305', @@ -1037,6 +1069,28 @@ export const BAS_REFERENCE: BASReferenceAccount[] = [ sru_code: '7310', }, + // 35 - Fakturerade kostnader och frakter + { + account_number: '3521', + account_name: 'Fakturerade frakter, EU-land', + account_class: 3, + account_group: '35', + account_type: 'revenue', + normal_balance: 'credit', + description: 'Fraktkostnader som vidarefaktureras till kunder i andra EU-lander. Foljer varans momsbehandling.', + sru_code: '7310', + }, + { + account_number: '3522', + account_name: 'Fakturerade frakter, export', + account_class: 3, + account_group: '35', + account_type: 'revenue', + normal_balance: 'credit', + description: 'Fraktkostnader som vidarefaktureras till kunder utanfor EU. Momsfritt (momsdeklaration ruta 36).', + sru_code: '7310', + }, + // 35 - Fakturerade kostnader { account_number: '3510', diff --git a/lib/extensions/__tests__/sectors.test.ts b/lib/extensions/__tests__/sectors.test.ts index e4caa0b0..88fa4a18 100644 --- a/lib/extensions/__tests__/sectors.test.ts +++ b/lib/extensions/__tests__/sectors.test.ts @@ -7,12 +7,12 @@ import { } from '../sectors' describe('sectors registry', () => { - it('should have 6 sectors', () => { - expect(SECTORS.length).toBe(6) + it('should have 7 sectors', () => { + expect(SECTORS.length).toBe(7) }) - it('should have 18 total extensions', () => { - expect(getAllExtensions().length).toBe(18) + it('should have 22 total extensions', () => { + expect(getAllExtensions().length).toBe(22) }) it('should have unique slugs within each sector', () => { diff --git a/lib/extensions/icon-resolver.tsx b/lib/extensions/icon-resolver.tsx index 00f25858..6ddde149 100644 --- a/lib/extensions/icon-resolver.tsx +++ b/lib/extensions/icon-resolver.tsx @@ -23,6 +23,9 @@ import { BarChart3, Layers, Puzzle, + Ship, + FileText, + Shield, type LucideIcon, } from 'lucide-react' @@ -51,6 +54,9 @@ const ICON_MAP: Record = { BarChart3, Layers, Puzzle, + Ship, + FileText, + Shield, } export function resolveIcon(name: string): LucideIcon { diff --git a/lib/extensions/loader.ts b/lib/extensions/loader.ts index b1dd0054..026d62cc 100644 --- a/lib/extensions/loader.ts +++ b/lib/extensions/loader.ts @@ -6,6 +6,10 @@ import { sruExportExtension } from '@/extensions/sru-export' import { neBilagaExtension } from '@/extensions/ne-bilaga' import { aiChatExtension } from '@/extensions/general/ai-chat' import { invoiceInboxExtension } from '@/extensions/general/invoice-inbox' +import { euSalesListExtension } from '@/extensions/export/eu-sales-list' +import { vatMonitorExtension } from '@/extensions/export/vat-monitor' +import { intrastatExtension } from '@/extensions/export/intrastat' +import { currencyReceivablesExtension } from '@/extensions/export/currency-receivables' import type { Extension } from './types' // ── Enable Banking (PSD2) — opt-in extension ─────────────────────────── @@ -29,6 +33,12 @@ const FIRST_PARTY_EXTENSIONS: Extension[] = [ aiChatExtension, invoiceInboxExtension, // enableBankingExtension, // Uncomment to activate PSD2 bank sync + + // ── Export sector ────────────────────────────────────────── + euSalesListExtension, + vatMonitorExtension, + intrastatExtension, + currencyReceivablesExtension, ] let loaded = false diff --git a/lib/extensions/sectors.ts b/lib/extensions/sectors.ts index 0c7c22ec..f649f926 100644 --- a/lib/extensions/sectors.ts +++ b/lib/extensions/sectors.ts @@ -292,6 +292,68 @@ export const SECTORS: Sector[] = [ }, ], }, + + // ── Export ────────────────────────────────────────────── + { + slug: 'export', + name: 'Export & Utrikeshandel', + icon: 'Ship', + description: 'Verktyg för svenska företag som exporterar varor till EU och övriga världen', + extensions: [ + { + slug: 'eu-sales-list', + name: 'Periodisk sammanställning', + sector: 'export', + category: 'accounting', + icon: 'FileText', + dataPattern: 'core', + readsCoreTables: ['invoices', 'customers'], + hasOwnData: false, + description: 'Generera periodisk sammanställning (EC Sales List) för Skatteverket', + longDescription: + 'Sammanställer automatiskt alla momsfria EU-försäljningar grupperat per kund och momsregistreringsnummer. Genererar nedladdningsbar fil (CSV/XML) för uppladdning till Skatteverket. Validerar kundernas VAT-nummer via VIES och flaggar saknade uppgifter. Korsvaliderar mot momsdeklarationens ruta 35 och 39.', + }, + { + slug: 'vat-monitor', + name: 'Exportmoms-monitor', + sector: 'export', + category: 'reports', + icon: 'Shield', + dataPattern: 'core', + readsCoreTables: ['journal_entry_lines', 'journal_entries', 'invoices'], + hasOwnData: false, + description: 'Övervaka momsbehandling för export och EU-handel', + longDescription: + 'Visar intäkter uppdelat på inhemsk försäljning, EU B2B (reverse charge) och export utanför EU. Mappar automatiskt till rätt rutor i momsdeklarationen (ruta 05, 35, 36, 39, 40). Flaggar potentiella fel som saknat momsregistreringsnummer på EU-kunder eller felaktig momsbehandling.', + }, + { + slug: 'intrastat', + name: 'Intrastat-generator', + sector: 'export', + category: 'accounting', + icon: 'BarChart3', + dataPattern: 'both', + readsCoreTables: ['invoices', 'customers'], + hasOwnData: true, + description: 'Generera Intrastat-deklarationer för rapportering till SCB', + longDescription: + 'Tagga produkter med CN-koder (Combined Nomenclature), vikt och ursprungsland. Genererar kompletta Intrastat-deklarationer i CSV-format för uppladdning till SCB:s IDEP.web. Övervakar tröskelvärdet på 12 MSEK för utförsel och varnar när rapporteringsskyldighet uppstår.', + }, + { + slug: 'currency-receivables', + name: 'Valutafordringar', + sector: 'export', + category: 'reports', + icon: 'TrendingUp', + dataPattern: 'core', + readsCoreTables: ['invoices', 'journal_entry_lines', 'transactions'], + hasOwnData: false, + description: 'Övervaka valutaexponering och orealiserade kursvinster/-förluster', + longDescription: + 'Visar öppna kundfordringar per valuta med aktuellt SEK-värde baserat på Riksbankens dagskurser. Beräknar orealiserade valutakursvinster och -förluster. Visar realiserade kursdifferenser per period (konto 3960/7960). Ger en samlad bild av företagets valutarisk.', + }, + ], + }, ] // ============================================================ diff --git a/lib/extensions/types.ts b/lib/extensions/types.ts index b74cacf8..9f7d0881 100644 --- a/lib/extensions/types.ts +++ b/lib/extensions/types.ts @@ -10,7 +10,7 @@ import type { EntityType, RawTransaction, IngestResult } from '@/types' export type ExtensionCategory = 'accounting' | 'reports' | 'import' | 'operations' /** Sector slugs for extension organization */ -export type SectorSlug = 'general' | 'restaurant' | 'construction' | 'hotel' | 'tech' | 'ecommerce' +export type SectorSlug = 'general' | 'restaurant' | 'construction' | 'hotel' | 'tech' | 'ecommerce' | 'export' /** How an extension gets its data */ export type ExtensionDataPattern = 'core' | 'manual' | 'both' diff --git a/lib/extensions/workspace-registry.tsx b/lib/extensions/workspace-registry.tsx index a7b5863a..f6443f37 100644 --- a/lib/extensions/workspace-registry.tsx +++ b/lib/extensions/workspace-registry.tsx @@ -32,6 +32,11 @@ const WORKSPACES: Record> = // E-commerce 'ecommerce/shopify-import': dynamic(() => import('@/components/extensions/ecommerce/ShopifyImportWorkspace')), 'ecommerce/multichannel-revenue': dynamic(() => import('@/components/extensions/ecommerce/MultichannelRevenueWorkspace')), + // Export + 'export/eu-sales-list': dynamic(() => import('@/components/extensions/export/EuSalesListWorkspace')), + 'export/vat-monitor': dynamic(() => import('@/components/extensions/export/VatMonitorWorkspace')), + 'export/intrastat': dynamic(() => import('@/components/extensions/export/IntrastatWorkspace')), + 'export/currency-receivables': dynamic(() => import('@/components/extensions/export/CurrencyReceivablesWorkspace')), } export function getWorkspaceComponent(