package service import ( "context" "fmt" "boc/middleware" "boc/repository" ) // CustomerService innehåller affärslogik för kunder type CustomerService struct { repo *repository.CustomerRepository } func NewCustomerService(repo *repository.CustomerRepository) *CustomerService { return &CustomerService{repo: repo} } // CreateCustomer skapar en ny kund med validering func (s *CustomerService) CreateCustomer(ctx context.Context, req *CreateCustomerRequest) (*repository.Customer, error) { // Validera input v := middleware.NewValidator() v.ValidateString("name", req.Name, 2, 255, true) v.ValidateEmail("email", req.Email, true) if req.Phone != "" { if !middleware.ValidatePhone(req.Phone) { v.AddError("phone", "invalid phone format") } } if req.OrgNumber != "" { if !middleware.ValidateOrgNumber(req.OrgNumber) { v.AddError("org_number", "invalid organization number format (XXXXXX-XXXX)") } } v.ValidateEnum("status", req.Status, []string{"active", "lead", "prospect", "inactive"}, false) if v.HasErrors() { return nil, fmt.Errorf("validation failed: %v", v.Errors()) } // Skapa kund customer := &repository.Customer{ TenantID: req.TenantID, Name: middleware.SanitizeString(req.Name), Email: req.Email, Phone: req.Phone, Company: middleware.SanitizeString(req.Company), OrgNumber: req.OrgNumber, Status: req.Status, Source: req.Source, Tags: req.Tags, AssignedTo: req.AssignedTo, } if customer.Status == "" { customer.Status = "lead" } if err := s.repo.CreateCustomer(ctx, customer); err != nil { return nil, fmt.Errorf("create customer: %w", err) } return customer, nil } // GetCustomer hämtar en kund med ID func (s *CustomerService) GetCustomer(ctx context.Context, id string) (*repository.Customer, error) { if !middleware.ValidateUUID(id) { return nil, fmt.Errorf("invalid customer ID") } customer, err := s.repo.GetCustomer(ctx, id) if err != nil { return nil, err } return customer, nil } // ListCustomers hämtar kunder med pagination func (s *CustomerService) ListCustomers(ctx context.Context, tenantID, status string, page, pageSize int) (*ListCustomersResponse, error) { if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } offset := (page - 1) * pageSize customers, total, err := s.repo.ListCustomers(ctx, tenantID, status, pageSize, offset) if err != nil { return nil, err } return &ListCustomersResponse{ Customers: customers, Total: total, Page: page, PageSize: pageSize, Pages: (total + pageSize - 1) / pageSize, }, nil } // UpdateCustomer uppdaterar en kund func (s *CustomerService) UpdateCustomer(ctx context.Context, id string, req *UpdateCustomerRequest) (*repository.Customer, error) { if !middleware.ValidateUUID(id) { return nil, fmt.Errorf("invalid customer ID") } // Hämta befintlig kund customer, err := s.repo.GetCustomer(ctx, id) if err != nil { return nil, err } // Uppdatera fält if req.Name != "" { customer.Name = middleware.SanitizeString(req.Name) } if req.Email != "" { customer.Email = req.Email } if req.Phone != "" { customer.Phone = req.Phone } if req.Company != "" { customer.Company = middleware.SanitizeString(req.Company) } if req.Status != "" { customer.Status = req.Status } if err := s.repo.UpdateCustomer(ctx, customer); err != nil { return nil, err } return customer, nil } // DeleteCustomer tar bort en kund (soft delete) func (s *CustomerService) DeleteCustomer(ctx context.Context, id string) error { if !middleware.ValidateUUID(id) { return fmt.Errorf("invalid customer ID") } return s.repo.DeleteCustomer(ctx, id) } // SearchCustomers söker efter kunder func (s *CustomerService) SearchCustomers(ctx context.Context, tenantID, query string, limit int) ([]repository.Customer, error) { if limit < 1 || limit > 100 { limit = 20 } return s.repo.SearchCustomers(ctx, tenantID, query, limit) } // ── Request/Response DTOs ───────────────────────────────────────────────── type CreateCustomerRequest struct { TenantID string `json:"tenant_id"` Name string `json:"name"` Email string `json:"email"` Phone string `json:"phone,omitempty"` Company string `json:"company,omitempty"` OrgNumber string `json:"org_number,omitempty"` Status string `json:"status,omitempty"` Source string `json:"source,omitempty"` Tags []string `json:"tags,omitempty"` AssignedTo *string `json:"assigned_to,omitempty"` } type UpdateCustomerRequest struct { Name string `json:"name,omitempty"` Email string `json:"email,omitempty"` Phone string `json:"phone,omitempty"` Company string `json:"company,omitempty"` Status string `json:"status,omitempty"` } type ListCustomersResponse struct { Customers []repository.Customer `json:"customers"` Total int `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` Pages int `json:"pages"` }