78b57273e2
- Add password hashing with bcrypt - Add AuthService with proper login - Add password strength validation - Add RBAC middleware (AdminOnly, ManagerOrAdmin) - Add tenant isolation middleware - Update CRM handler with tenant filtering - Add JWT fallback for development mode - Add user context helpers - Build successful
242 lines
6.9 KiB
Go
242 lines
6.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// AccountingHandler hanterar dubbel bokföring — egen ledger + Visma
|
|
type AccountingHandler struct{}
|
|
|
|
func NewAccountingHandler() *AccountingHandler {
|
|
return &AccountingHandler{}
|
|
}
|
|
|
|
// LedgerEntry representerar en bokföringspost
|
|
type LedgerEntry struct {
|
|
ID string `json:"id"`
|
|
Date string `json:"date"`
|
|
VoucherNo string `json:"voucher_no"`
|
|
Description string `json:"description"`
|
|
Account string `json:"account"`
|
|
AccountName string `json:"account_name"`
|
|
Debit float64 `json:"debit"`
|
|
Credit float64 `json:"credit"`
|
|
Balance float64 `json:"balance"`
|
|
Source string `json:"source"`
|
|
Synced bool `json:"synced"`
|
|
VismaID *string `json:"visma_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// AccountBalance representerar kontosaldo
|
|
type AccountBalance struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Balance float64 `json:"balance"`
|
|
LastUpdated string `json:"last_updated"`
|
|
}
|
|
|
|
// VismaConnection representerar Visma-koppling
|
|
type VismaConnection struct {
|
|
Connected bool `json:"connected"`
|
|
Company string `json:"company"`
|
|
OrgNumber string `json:"org_number"`
|
|
LastSync time.Time `json:"last_sync"`
|
|
SyncStatus string `json:"sync_status"`
|
|
PendingSync int `json:"pending_sync"`
|
|
}
|
|
|
|
// GetLedger returnerar egen ledger
|
|
type GetLedger struct{}
|
|
|
|
func (h *AccountingHandler) GetLedger(w http.ResponseWriter, r *http.Request) {
|
|
entries := []LedgerEntry{
|
|
{
|
|
ID: "le-001",
|
|
Date: "2026-08-01",
|
|
VoucherNo: "V-2026-081",
|
|
Description: "Faktura #1001 — Kundtjänst AB",
|
|
Account: "1510",
|
|
AccountName: "Kundfordringar",
|
|
Debit: 25000,
|
|
Credit: 0,
|
|
Balance: 25000,
|
|
Source: "internal",
|
|
Synced: true,
|
|
VismaID: strPtr("visma-88123"),
|
|
CreatedAt: time.Now().Add(-96 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-002",
|
|
Date: "2026-08-01",
|
|
VoucherNo: "V-2026-081",
|
|
Description: "Faktura #1001 — Kundtjänst AB",
|
|
Account: "3010",
|
|
AccountName: "Försäljning tjänster",
|
|
Debit: 0,
|
|
Credit: 25000,
|
|
Balance: -25000,
|
|
Source: "internal",
|
|
Synced: true,
|
|
VismaID: strPtr("visma-88124"),
|
|
CreatedAt: time.Now().Add(-96 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-003",
|
|
Date: "2026-08-02",
|
|
VoucherNo: "V-2026-082",
|
|
Description: "Leverantörsfaktura #L-442 — AWS",
|
|
Account: "2440",
|
|
AccountName: "Leverantörsskulder",
|
|
Debit: 0,
|
|
Credit: 8500,
|
|
Balance: -8500,
|
|
Source: "visma",
|
|
Synced: true,
|
|
VismaID: strPtr("visma-88125"),
|
|
CreatedAt: time.Now().Add(-72 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-004",
|
|
Date: "2026-08-02",
|
|
VoucherNo: "V-2026-082",
|
|
Description: "Leverantörsfaktura #L-442 — AWS",
|
|
Account: "6540",
|
|
AccountName: "IT-kostnader",
|
|
Debit: 8500,
|
|
Credit: 0,
|
|
Balance: 8500,
|
|
Source: "visma",
|
|
Synced: true,
|
|
VismaID: strPtr("visma-88126"),
|
|
CreatedAt: time.Now().Add(-72 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-005",
|
|
Date: "2026-08-03",
|
|
VoucherNo: "V-2026-083",
|
|
Description: "Lön — Erik Svensson",
|
|
Account: "7210",
|
|
AccountName: "Löner",
|
|
Debit: 45000,
|
|
Credit: 0,
|
|
Balance: 45000,
|
|
Source: "internal",
|
|
Synced: false,
|
|
VismaID: nil,
|
|
CreatedAt: time.Now().Add(-48 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-006",
|
|
Date: "2026-08-03",
|
|
VoucherNo: "V-2026-083",
|
|
Description: "Lön — Erik Svensson",
|
|
Account: "1930",
|
|
AccountName: "Företagskonto/checkkonto/räkning",
|
|
Debit: 0,
|
|
Credit: 45000,
|
|
Balance: -45000,
|
|
Source: "internal",
|
|
Synced: false,
|
|
VismaID: nil,
|
|
CreatedAt: time.Now().Add(-48 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-007",
|
|
Date: "2026-08-04",
|
|
VoucherNo: "V-2026-084",
|
|
Description: "Zoomer-utbetalning — Anna Lindqvist",
|
|
Account: "7690",
|
|
AccountName: "Övriga personalkostnader",
|
|
Debit: 5000,
|
|
Credit: 0,
|
|
Balance: 5000,
|
|
Source: "quixzoom",
|
|
Synced: false,
|
|
VismaID: nil,
|
|
CreatedAt: time.Now().Add(-24 * time.Hour),
|
|
},
|
|
{
|
|
ID: "le-008",
|
|
Date: "2026-08-04",
|
|
VoucherNo: "V-2026-084",
|
|
Description: "Zoomer-utbetalning — Anna Lindqvist",
|
|
Account: "1930",
|
|
AccountName: "Företagskonto/checkkonto/räkning",
|
|
Debit: 0,
|
|
Credit: 5000,
|
|
Balance: -5000,
|
|
Source: "quixzoom",
|
|
Synced: false,
|
|
VismaID: nil,
|
|
CreatedAt: time.Now().Add(-24 * time.Hour),
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"entries": entries,
|
|
"summary": map[string]interface{}{
|
|
"total": len(entries),
|
|
"synced": 4,
|
|
"pending_sync": 4,
|
|
"sources": []string{"internal", "visma", "quixzoom"},
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetAccounts returnerar kontoplan
|
|
type GetAccounts struct{}
|
|
|
|
func (h *AccountingHandler) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
|
accounts := []AccountBalance{
|
|
{Code: "1510", Name: "Kundfordringar", Type: "asset", Balance: 25000, LastUpdated: "2026-08-05"},
|
|
{Code: "1930", Name: "Företagskonto", Type: "asset", Balance: -78500, LastUpdated: "2026-08-05"},
|
|
{Code: "2010", Name: "Eget kapital", Type: "equity", Balance: 50000, LastUpdated: "2026-08-01"},
|
|
{Code: "2440", Name: "Leverantörsskulder", Type: "liability", Balance: -8500, LastUpdated: "2026-08-02"},
|
|
{Code: "2610", Name: "Utgående moms", Type: "liability", Balance: 6250, LastUpdated: "2026-08-01"},
|
|
{Code: "3010", Name: "Försäljning tjänster", Type: "revenue", Balance: -25000, LastUpdated: "2026-08-01"},
|
|
{Code: "6540", Name: "IT-kostnader", Type: "expense", Balance: 8500, LastUpdated: "2026-08-02"},
|
|
{Code: "7210", Name: "Löner", Type: "expense", Balance: 45000, LastUpdated: "2026-08-03"},
|
|
{Code: "7690", Name: "Övriga personalkostnader", Type: "expense", Balance: 5000, LastUpdated: "2026-08-04"},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"accounts": accounts,
|
|
})
|
|
}
|
|
|
|
// GetVismaStatus returnerar Visma-kopplingsstatus
|
|
type GetVismaStatus struct{}
|
|
|
|
func (h *AccountingHandler) GetVismaStatus(w http.ResponseWriter, r *http.Request) {
|
|
status := VismaConnection{
|
|
Connected: true,
|
|
Company: "Landvex AB",
|
|
OrgNumber: "559141-7042",
|
|
LastSync: time.Now().Add(-2 * time.Hour),
|
|
SyncStatus: "partial",
|
|
PendingSync: 4,
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"visma": status,
|
|
})
|
|
}
|
|
|
|
// SyncVisma triggar synk till Visma
|
|
type SyncVisma struct{}
|
|
|
|
func (h *AccountingHandler) SyncVisma(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"message": "Sync initiated",
|
|
"status": "syncing",
|
|
"pending": 4,
|
|
})
|
|
}
|