Initial copy from influencer-biz

This commit is contained in:
Jakob Wennberg
2026-02-13 14:00:46 +01:00
commit a25d10a528
391 changed files with 96224 additions and 0 deletions
+519
View File
@@ -0,0 +1,519 @@
# Future Features & Regulatory Integrations
## Phase 2: SIE File Import (Migration)
### Overview
SIE (Standard Import Export) is the Swedish standard for accounting data exchange. Users migrating from Fortnox, Visma, or other systems can export their data as SIE files.
### SIE File Types
| Type | Content | Use Case |
|------|---------|----------|
| SIE1 | Årssaldon | Year-end balances only |
| SIE2 | Periodsaldon | Monthly/periodic balances |
| SIE3 | Objektsaldon | Balances with cost centers |
| SIE4 | Transaktioner | Full transaction history |
**Recommended:** Support SIE4 for complete migration, SIE1/2 for simpler imports.
### Implementation Notes
```typescript
// lib/import/sie-parser.ts
interface SIETransaction {
verifikationsnummer: string
datum: Date
kontonummer: string
belopp: number
text: string
}
interface SIEImportResult {
company: {
name: string
orgNumber: string
fiscalYearStart: Date
}
accounts: Array<{ number: string; name: string }>
transactions: SIETransaction[]
balances: Array<{ account: string; balance: number }>
}
// SIE files use CP437 encoding and specific format
// Example line: #VER A 1 20240115 "Faktura 1001"
```
### Mapping Decisions Needed
Before implementing, determine mapping for:
- BAS account numbers → system categories
- Opening balances → how to represent
- Customer/supplier data (if present in SIE)
- Historical invoices → create as "imported" status?
---
## Phase 2: Benefits & Gifts Module (Förmånshantering)
### Problem
Influencers receive products ("PR-utskick", "gifted collabs") that may be taxable income. Skatteverket actively audits this. Getting it wrong = skattetillägg.
### Decision Tree Logic
```typescript
// lib/benefits/gift-classifier.ts
interface GiftClassification {
taxable: boolean
marketValue: number
deductibleAsExpense: boolean
bookingType: 'income' | 'income_and_expense' | 'tax_free'
reasoning: string
}
interface GiftInput {
estimatedValue: number
hasMotprestation: boolean // Required post/video/mention?
usedInBusiness: boolean // Used as props/equipment?
usedPrivately: boolean // Personal use?
isSimplePromoItem: boolean // Pen, mug, basic merch?
}
const TAX_FREE_PROMO_THRESHOLD = 450 // SEK, Skatteverket guideline
export function classifyGift(input: GiftInput): GiftClassification {
// Rule 1: Simple promotional items under threshold = tax free
if (input.isSimplePromoItem && input.estimatedValue <= TAX_FREE_PROMO_THRESHOLD && !input.hasMotprestation) {
return {
taxable: false,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'tax_free',
reasoning: 'Enklare reklamgåva under 450 kr utan krav på motprestation'
}
}
// Rule 2: Motprestation exists = always taxable
if (input.hasMotprestation) {
if (input.usedInBusiness && !input.usedPrivately) {
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: true,
bookingType: 'income_and_expense',
reasoning: 'Skattepliktig förmån (motprestation krävdes). Avdragsgill som rekvisita då den endast används i verksamheten.'
}
}
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'income',
reasoning: 'Skattepliktig förmån (motprestation krävdes). Ej avdragsgill då produkten används privat.'
}
}
// Rule 3: High value without motprestation but used privately
if (input.estimatedValue > TAX_FREE_PROMO_THRESHOLD && input.usedPrivately) {
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'income',
reasoning: 'Värdet överstiger gränsen för skattefria reklamgåvor och produkten används privat.'
}
}
// Default: taxable if significant value
return {
taxable: input.estimatedValue > TAX_FREE_PROMO_THRESHOLD,
marketValue: input.estimatedValue,
deductibleAsExpense: input.usedInBusiness,
bookingType: input.estimatedValue > TAX_FREE_PROMO_THRESHOLD ? 'income' : 'tax_free',
reasoning: 'Klassificering baserad på värde och användning'
}
}
```
### UI Flow
1. User taps "Logga gåva/produkt"
2. Photo capture or manual entry
3. System queries price APIs (Prisjakt, Google Shopping) for value estimate
4. Decision tree questions:
- "Fanns krav på att du skulle posta om denna produkt?" [Ja/Nej]
- "Använder du produkten i din content-produktion?" [Ja/Nej]
- "Använder du produkten privat?" [Ja/Nej]
5. System shows classification + tax impact
6. Auto-booking to correct accounts
---
## Phase 2: NE-bilaga Generation
### SRU File Format
Skatteverket accepts SRU (Standardiserat RäkenskapsUtdrag) files for tax form import.
```typescript
// lib/tax/sru-generator.ts
interface NEBilaga {
// R1-series: Income
R1_nettoomsattning: number // Net revenue
R2_ovriga_intakter: number // Other income
// R3-series: Costs
R3_varor: number // Cost of goods
R4_ovriga_kostnader: number // Other expenses
R5_personal: number // Personnel costs (usually 0 for sole proprietor)
R6_avskrivningar: number // Depreciation
// R7-series: Financial
R7_ranteintakter: number
R8_rantekostnader: number
// Result
R9_resultat: number // Profit/loss before adjustments
// Adjustments
R10_periodiseringsfond_avsatt: number // Max 30% of profit
R11_periodiseringsfond_aterforing: number
R12_expansionsfond: number
// Final
R14_overskott_underskott: number
}
function generateSRUFile(data: NEBilaga, personnummer: string, year: number): string {
const lines: string[] = []
// Header
lines.push('#DATABESKRIVNING_START')
lines.push('#PRODUKT SIE')
lines.push('#FORMAT PC8')
lines.push('#FILTYP NARINGSBILAGA')
lines.push(`#UPPGIFTSLAMNARE ${personnummer}`)
lines.push(`#INKOMSTAR ${year}`)
lines.push('#DATABESKRIVNING_SLUT')
// Data rows (simplified)
lines.push(`#UPPGIFT 7001 ${data.R1_nettoomsattning}`) // R1
lines.push(`#UPPGIFT 7002 ${data.R2_ovriga_intakter}`) // R2
// ... continue for all fields
lines.push('#UPPGIFT_SLUT')
return lines.join('\n')
}
```
### BAS Account to NE Mapping
| BAS Account | Description | NE Ruta |
|-------------|-------------|---------|
| 3000-3999 | Intäkter | R1 |
| 3900-3999 | Övriga rörelseintäkter | R2 |
| 4000-4999 | Varuinköp | R3 |
| 5000-6999 | Övriga externa kostnader | R4 |
| 7000-7699 | Personalkostnader | R5 |
| 7800-7899 | Avskrivningar | R6 |
| 8300-8399 | Ränteintäkter | R7 |
| 8400-8499 | Räntekostnader | R8 |
### Tax Optimization Suggestions
Before generating NE-bilaga, system should suggest:
1. **Periodiseringsfond** (max 30% of profit)
- Defer tax to future years
- Must be reversed within 6 years
- Good for income smoothing
2. **Expansionsfond**
- Tax profit at 22% corporate rate instead of marginal personal rate
- Good if marginal rate > 22%
- More complex rules
```typescript
function suggestTaxOptimization(profit: number, marginalTaxRate: number) {
const suggestions = []
if (profit > 0) {
const maxPeriodisering = profit * 0.30
suggestions.push({
type: 'periodiseringsfond',
amount: maxPeriodisering,
taxSaved: maxPeriodisering * marginalTaxRate,
description: `Sätt av ${formatSEK(maxPeriodisering)} till periodiseringsfond för att skjuta upp ${formatSEK(maxPeriodisering * marginalTaxRate)} i skatt.`
})
}
if (profit > 100000 && marginalTaxRate > 0.30) {
suggestions.push({
type: 'expansionsfond',
description: 'Din marginalskatt är hög. Överväg expansionsfond för att beskattas med 22% istället.'
})
}
return suggestions
}
```
---
## Phase 2b: AB Salary Optimization (3:12-reglerna)
### Overview
For aktiebolag owners, the split between lön (salary) and utdelning (dividend) significantly impacts total tax. The 3:12 rules (Inkomstskattelagen kapitel 57) determine how much dividend can be taxed at the favorable 20% rate vs marginal income tax rates.
### Key Concepts
| Term | Description |
|------|-------------|
| Gränsbelopp | Annual limit for dividend taxed at 20% |
| Löneunderlag | Salary base that increases gränsbelopp |
| Kvalificerade andelar | Shares where owner is "active" in company |
| Sparat utdelningsutrymme | Unused gränsbelopp carried forward |
### Gränsbelopp Calculation (Simplified)
```typescript
// lib/tax/ab-optimization.ts
interface GransbeloppCalculation {
schablonbelopp: number // 2.75 × inkomstbasbelopp
lonebaserat: number // 50% of löneunderlag (if meets salary requirement)
sparat: number // Carried forward from previous years
totalGransbelopp: number
}
const INKOMSTBASBELOPP_2024 = 74300
function calculateGransbelopp(
ownerSalary: number,
totalCompanySalaries: number,
sparatUtdelningsutrymme: number
): GransbeloppCalculation {
// Schablonbelopp: 2.75 × IBB
const schablonbelopp = 2.75 * INKOMSTBASBELOPP_2024 // ~204k
// Lönebaserat: requires owner salary ≥ 6 IBB (or 9.6% of total + 6 IBB)
const minOwnerSalary = Math.min(
6 * INKOMSTBASBELOPP_2024,
0.096 * totalCompanySalaries + 6 * INKOMSTBASBELOPP_2024
)
const lonebaserat = ownerSalary >= minOwnerSalary
? totalCompanySalaries * 0.50
: 0
return {
schablonbelopp,
lonebaserat,
sparat: sparatUtdelningsutrymme,
totalGransbelopp: schablonbelopp + lonebaserat + sparatUtdelningsutrymme
}
}
```
### Optimization Engine
```typescript
interface OptimizationRecommendation {
recommendedSalary: number
recommendedDividend: number
taxOnSalary: number
taxOnDividend: number
totalTax: number
savingsVsAllSalary: number
explanation: string
}
function optimizeSalaryDividendSplit(
availableProfit: number,
ownerMarginalTaxRate: number,
sparatUtdelningsutrymme: number
): OptimizationRecommendation {
// Consider:
// - Minimum salary for lönebaserat gränsbelopp (6 IBB = ~446k)
// - Arbetsgivaravgifter (31.42%) on salary
// - 20% tax on dividend within gränsbelopp
// - Marginal tax on dividend above gränsbelopp
// - Bolagsskatt (20.6%) already paid on profit
// Return optimal split with explanation
}
```
### UI Concept
```
┌─────────────────────────────────────────────────────────────┐
│ 💡 OPTIMERINGSFÖRSLAG │
│ │
│ Baserat på ditt resultat och skattesituation: │
│ │
│ Rekommenderad lön: 445 800 kr/år │
│ Rekommenderad utdelning: 204 325 kr │
│ │
│ Beräknad total skatt: ~142 000 kr │
│ vs allt som lön: ~185 000 kr │
│ ───────────────────────────────────────────────────────── │
│ Potentiell besparing: ~43 000 kr │
│ │
│ ⚠️ Detta är en uppskattning. Rådgör med revisor. │
└─────────────────────────────────────────────────────────────┘
```
### Implementation Requirements
- Track sparat utdelningsutrymme (historical data)
- Input for owner's other income (affects marginal rate)
- Annual update of inkomstbasbelopp
- Strong disclaimer: Not tax advice
---
## Phase 3: Skatteverket API Integrations
### Available APIs
Skatteverket offers several APIs that can enhance the product:
#### 1. Skattekonto API
Fetch user's tax account balance and transactions.
**Prerequisite:** User must grant "Läsombud" access in Skatteverket's system.
```typescript
// Conceptual - actual implementation requires OAuth2 + organizational agreement
interface SkattekontoBala {
saldo: number
senastInbetalning: Date
kommandeForfall: Array<{
datum: Date
belopp: number
typ: string // 'F-skatt', 'Moms', etc.
}>
}
// Use case: "Du har 15 000 kr på skattekontot, men beräknad skatt är 20 000 kr. Sätt in 5 000 kr nu."
```
#### 2. Momsdeklaration API
Submit VAT declaration directly from app.
**Prerequisites:**
- Registered as e-tjänstleverantör with Skatteverket
- User authentication via BankID
- Signed agreement with Skatteverket
```typescript
interface MomsdeklarationSubmission {
period: string // '2024-01' for January
ruta05: number // Momspliktig försäljning
ruta39: number // Tjänsteförsäljning EU
ruta40: number // Export
ruta21: number // Inköp tjänster EU
ruta48: number // Utgående moms inköp
// ... all required rutor
}
```
#### 3. Arbetsgivardeklaration API
Not typically needed for sole proprietors without employees, but relevant if they hire.
### Integration Roadmap
| Phase | Integration | User Value |
|-------|-------------|------------|
| MVP | None (manual) | - |
| v1.5 | VIES VAT validation | Automated reverse charge |
| v2.0 | Skattekonto (read) | Real-time tax position |
| v2.5 | Momsdeklaration (submit) | One-click VAT filing |
| v3.0 | NE-bilaga (submit) | Full declaration automation |
---
## Phase 2: Reklammärkning Compliance
### Marknadsföringslagen Requirement
Swedish law requires clear ad disclosure. The "Kissie" case established that marking must be:
- At the very beginning of content
- Immediately visible
- Unambiguous ("Reklam" or "Annons", not just "Samarbete")
### Implementation
In Deal-to-Invoice flow, add mandatory checkbox:
```typescript
interface DealComplianceCheck {
reklamMarkerad: boolean
markeringTyp: 'inlagg' | 'video' | 'story' | 'podcast'
bekraftelse: string // Timestamp of user confirmation
}
// Before invoice can be marked as complete:
const complianceText = `Jag intygar att allt publicerat material för detta samarbete har reklammarkerats tydligt i början av varje inlägg/video i enlighet med Marknadsföringslagen (2008:486).`
```
---
## Data Retention & GDPR
### Conflict Resolution
**Bokföringslagen**: 7 years mandatory retention for räkenskapsinformation
**GDPR**: Right to erasure
Resolution:
- Accounting records (verifikationer, fakturor, kvitton): 7-year mandatory retention
- Non-accounting data (support chats, preferences, analytics): Can be deleted on request
- Technical implementation: Separate data stores with different retention policies
```typescript
// Database design consideration
interface UserDataPolicy {
accountingData: {
retention: '7_years_from_fiscal_year_end',
deletable: false,
legal_basis: 'Bokföringslagen 7 kap. 2 §'
},
operationalData: {
retention: 'until_deletion_request',
deletable: true,
legal_basis: 'Consent / Legitimate interest'
}
}
```
### Digital Archive Requirements (July 2024 Law)
Receipt photos must be:
- Stored in immutable format (PDF/A-3, locked JPEG)
- Backed up regularly
- Hosted within EU/EES or adequate country
- Tamper-evident (hash verification)
```typescript
interface ReceiptArchive {
originalFilename: string
storedAs: string // UUID.pdf
format: 'PDF/A-3'
sha256Hash: string
capturedAt: Date
linkedTransactionId: string
storageLocation: 'eu-north-1' // Must be EU
}
```