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
248 lines
6.8 KiB
Go
248 lines
6.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// LandvexHandler hanterar Landvex bolagskontroll
|
|
type LandvexHandler struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
// NewLandvexHandler skapar en ny handler
|
|
func NewLandvexHandler(db *sql.DB) *LandvexHandler {
|
|
return &LandvexHandler{DB: db}
|
|
}
|
|
|
|
// Entity representerar en juridisk enhet
|
|
type Entity struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
OrgNumber string `json:"org_number"`
|
|
Country string `json:"country"`
|
|
City string `json:"city"`
|
|
Address string `json:"address"`
|
|
Status string `json:"status"`
|
|
FoundedAt time.Time `json:"founded_at"`
|
|
ParentID *string `json:"parent_id,omitempty"`
|
|
Ownership float64 `json:"ownership_percent"`
|
|
CEO string `json:"ceo"`
|
|
BoardMembers []Person `json:"board_members"`
|
|
Employees int `json:"employees"`
|
|
Revenue float64 `json:"revenue"`
|
|
Currency string `json:"currency"`
|
|
TaxStatus string `json:"tax_status"`
|
|
ComplianceStatus string `json:"compliance_status"`
|
|
}
|
|
|
|
// Person representerar en person
|
|
type Person struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Role string `json:"role"`
|
|
Email string `json:"email"`
|
|
Phone string `json:"phone"`
|
|
Nationality string `json:"nationality"`
|
|
Since string `json:"since"`
|
|
}
|
|
|
|
// Document representerar ett dokument
|
|
type Document struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
EntityID string `json:"entity_id"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
SignedBy []string `json:"signed_by"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// ComplianceItem representerar ett compliance-krav
|
|
type ComplianceItem struct {
|
|
ID string `json:"id"`
|
|
EntityID string `json:"entity_id"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
DueDate time.Time `json:"due_date"`
|
|
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
|
Responsible string `json:"responsible"`
|
|
Priority string `json:"priority"`
|
|
}
|
|
|
|
// GetEntities returnerar alla Landvex-enheter från databasen
|
|
func (h *LandvexHandler) GetEntities(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.DB.Query(`
|
|
SELECT entity_id, name, jurisdiction, entity_type, status
|
|
FROM boc_landvex_entities
|
|
WHERE status = 'active'
|
|
ORDER BY name
|
|
`)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entities []Entity
|
|
for rows.Next() {
|
|
var e Entity
|
|
if err := rows.Scan(&e.ID, &e.Name, &e.Country, &e.Type, &e.Status); err != nil {
|
|
continue
|
|
}
|
|
entities = append(entities, e)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"entities": entities,
|
|
})
|
|
}
|
|
|
|
// GetEntity returnerar en specifik enhet
|
|
func (h *LandvexHandler) GetEntity(w http.ResponseWriter, r *http.Request) {
|
|
entityID := chi.URLParam(r, "id")
|
|
|
|
var e Entity
|
|
err := h.DB.QueryRow(`
|
|
SELECT entity_id, name, jurisdiction, entity_type, status
|
|
FROM boc_landvex_entities
|
|
WHERE entity_id = $1
|
|
`, entityID).Scan(&e.ID, &e.Name, &e.Country, &e.Type, &e.Status)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, "entity not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"entity": e,
|
|
})
|
|
}
|
|
|
|
// GetDocuments returnerar alla dokument
|
|
func (h *LandvexHandler) GetDocuments(w http.ResponseWriter, r *http.Request) {
|
|
documents := []Document{
|
|
{
|
|
ID: "doc-001",
|
|
Title: "Styrelseprotokoll 2026-01-15",
|
|
Type: "board_minutes",
|
|
EntityID: "lvx-ab",
|
|
Status: "signed",
|
|
CreatedAt: time.Now().Add(-180 * 24 * time.Hour),
|
|
SignedBy: []string{"Erik Svensson", "Johan Berglund"},
|
|
URL: "/docs/board-2026-01-15.pdf",
|
|
},
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"documents": documents,
|
|
})
|
|
}
|
|
|
|
// GetCompliance returnerar compliance-krav från databasen
|
|
func (h *LandvexHandler) GetCompliance(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.DB.Query(`
|
|
SELECT id, entity_id, category, title, status, due_date, completed_at, notes
|
|
FROM boc_landvex_compliance
|
|
ORDER BY due_date ASC
|
|
`)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []ComplianceItem
|
|
for rows.Next() {
|
|
var c ComplianceItem
|
|
var notes sql.NullString
|
|
if err := rows.Scan(&c.ID, &c.EntityID, &c.Type, &c.Title, &c.Status, &c.DueDate, &c.CompletedAt, ¬es); err != nil {
|
|
continue
|
|
}
|
|
items = append(items, c)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"compliance_items": items,
|
|
"summary": map[string]interface{}{
|
|
"total": len(items),
|
|
"pending": countByStatus(items, "pending"),
|
|
"overdue": countByStatus(items, "overdue"),
|
|
"completed": countByStatus(items, "completed"),
|
|
"this_quarter": len(items),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetOwnership returnerar ägarstruktur från databasen
|
|
func (h *LandvexHandler) GetOwnership(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.DB.Query(`
|
|
SELECT e.entity_id, e.name, e.jurisdiction, e.entity_type,
|
|
o.owner_name, o.ownership_percent, o.parent_entity_id
|
|
FROM boc_landvex_entities e
|
|
LEFT JOIN boc_landvex_ownership o ON e.entity_id = o.entity_id
|
|
WHERE e.status = 'active'
|
|
ORDER BY e.name
|
|
`)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entities []map[string]interface{}
|
|
for rows.Next() {
|
|
var entityID, name, jurisdiction, entityType, ownerName string
|
|
var ownership float64
|
|
var parentID sql.NullString
|
|
if err := rows.Scan(&entityID, &name, &jurisdiction, &entityType, &ownerName, &ownership, &parentID); err != nil {
|
|
continue
|
|
}
|
|
entities = append(entities, map[string]interface{}{
|
|
"id": entityID,
|
|
"name": name,
|
|
"jurisdiction": jurisdiction,
|
|
"type": entityType,
|
|
"owner": ownerName,
|
|
"ownership": ownership,
|
|
})
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"ok": true,
|
|
"ownership": map[string]interface{}{
|
|
"structure": "linear",
|
|
"ultimate_beneficial_owner": map[string]interface{}{
|
|
"name": "Erik Svensson",
|
|
"nationality": "SE",
|
|
"ownership": 100,
|
|
},
|
|
"entities": entities,
|
|
},
|
|
})
|
|
}
|
|
|
|
func countByStatus(items []ComplianceItem, status string) int {
|
|
count := 0
|
|
for _, item := range items {
|
|
if item.Status == status {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func strPtr(s string) *string {
|
|
return &s
|
|
}
|