package handlers import ( "database/sql" "encoding/json" "net/http" "time" "github.com/go-chi/chi/v5" ) type CRMHandler struct { DB *sql.DB } func NewCRMHandler(db *sql.DB) *CRMHandler { return &CRMHandler{DB: db} } type Customer struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` Phone string `json:"phone"` Company string `json:"company"` OrgNumber string `json:"org_number"` Status string `json:"status"` Source string `json:"source"` Tags []string `json:"tags"` AssignedTo *string `json:"assigned_to"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type CustomerInteraction struct { ID string `json:"id"` CustomerID string `json:"customer_id"` Type string `json:"type"` Direction string `json:"direction"` Subject string `json:"subject"` Content string `json:"content"` Metadata map[string]interface{} `json:"metadata"` CreatedBy *string `json:"created_by"` CreatedAt time.Time `json:"created_at"` } type PipelineStage struct { Stage string `json:"stage"` Count int `json:"count"` Value float64 `json:"value"` } func (h *CRMHandler) ListCustomers(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") if status == "" { status = "active" } rows, err := h.DB.Query(` SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at FROM boc_customers WHERE status = $1 ORDER BY created_at DESC LIMIT 100 `, status) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() customers := []Customer{} for rows.Next() { var c Customer if err := rows.Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber, &c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt); err != nil { continue } customers = append(customers, c) } writeJSON(w, http.StatusOK, map[string]interface{}{ "customers": customers, "total": len(customers), }) } func (h *CRMHandler) CreateCustomer(w http.ResponseWriter, r *http.Request) { var req Customer if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } var id string err := h.DB.QueryRow(` INSERT INTO boc_customers (name, email, phone, company, org_number, status, source, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id `, req.Name, req.Email, req.Phone, req.Company, req.OrgNumber, req.Status, req.Source, req.Tags).Scan(&id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to create customer") return } writeJSON(w, http.StatusCreated, map[string]interface{}{ "id": id, "message": "Customer created", }) } func (h *CRMHandler) GetCustomer(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var c Customer err := h.DB.QueryRow(` SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at FROM boc_customers WHERE id = $1 `, id).Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber, &c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt) if err == sql.ErrNoRows { writeError(w, http.StatusNotFound, "customer not found") return } if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } writeJSON(w, http.StatusOK, c) } func (h *CRMHandler) UpdateCustomer(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var req Customer if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } _, err := h.DB.Exec(` UPDATE boc_customers SET name = $1, email = $2, phone = $3, company = $4, org_number = $5, status = $6, source = $7, tags = $8, assigned_to = $9 WHERE id = $10 `, req.Name, req.Email, req.Phone, req.Company, req.OrgNumber, req.Status, req.Source, req.Tags, req.AssignedTo, id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to update customer") return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "Customer updated", }) } func (h *CRMHandler) DeleteCustomer(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") _, err := h.DB.Exec(`DELETE FROM boc_customers WHERE id = $1`, id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to delete customer") return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "Customer deleted", }) } func (h *CRMHandler) ListLeads(w http.ResponseWriter, r *http.Request) { rows, err := h.DB.Query(` SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at FROM boc_customers WHERE status = 'lead' ORDER BY created_at DESC `) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() leads := []Customer{} for rows.Next() { var c Customer if err := rows.Scan(&c.ID, &c.Name, &c.Email, &c.Phone, &c.Company, &c.OrgNumber, &c.Status, &c.Source, &c.Tags, &c.AssignedTo, &c.CreatedAt, &c.UpdatedAt); err != nil { continue } leads = append(leads, c) } writeJSON(w, http.StatusOK, map[string]interface{}{ "leads": leads, "total": len(leads), }) } func (h *CRMHandler) GetPipeline(w http.ResponseWriter, r *http.Request) { rows, err := h.DB.Query(` SELECT stage, COUNT(*), COALESCE(SUM(value), 0) FROM boc_deals WHERE status = 'open' GROUP BY stage ORDER BY CASE stage WHEN 'prospect' THEN 1 WHEN 'qualified' THEN 2 WHEN 'proposal' THEN 3 WHEN 'negotiation' THEN 4 WHEN 'closed_won' THEN 5 ELSE 6 END `) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() stages := []PipelineStage{} for rows.Next() { var s PipelineStage if err := rows.Scan(&s.Stage, &s.Count, &s.Value); err != nil { continue } stages = append(stages, s) } writeJSON(w, http.StatusOK, map[string]interface{}{ "pipeline": stages, }) } func (h *CRMHandler) CreateInteraction(w http.ResponseWriter, r *http.Request) { var req CustomerInteraction if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request") return } metadata, _ := json.Marshal(req.Metadata) var id string err := h.DB.QueryRow(` INSERT INTO boc_customer_interactions (customer_id, type, direction, subject, content, metadata) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id `, req.CustomerID, req.Type, req.Direction, req.Subject, req.Content, metadata).Scan(&id) if err != nil { writeError(w, http.StatusInternalServerError, "failed to create interaction") return } writeJSON(w, http.StatusCreated, map[string]interface{}{ "id": id, "message": "Interaction created", }) } func (h *CRMHandler) GetCustomerInteractions(w http.ResponseWriter, r *http.Request) { customerID := chi.URLParam(r, "id") rows, err := h.DB.Query(` SELECT id, customer_id, type, direction, subject, content, metadata, created_by, created_at FROM boc_customer_interactions WHERE customer_id = $1 ORDER BY created_at DESC `, customerID) if err != nil { writeError(w, http.StatusInternalServerError, "database error") return } defer rows.Close() interactions := []CustomerInteraction{} for rows.Next() { var i CustomerInteraction var metadata []byte if err := rows.Scan(&i.ID, &i.CustomerID, &i.Type, &i.Direction, &i.Subject, &i.Content, &metadata, &i.CreatedBy, &i.CreatedAt); err != nil { continue } json.Unmarshal(metadata, &i.Metadata) interactions = append(interactions, i) } writeJSON(w, http.StatusOK, map[string]interface{}{ "interactions": interactions, "total": len(interactions), }) }