package handler import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "Engine/internal/database" "Engine/internal/model" ) type ReferenceHandler struct { repo *database.ReferenceRepo } func NewReferenceHandler(repo *database.ReferenceRepo) *ReferenceHandler { return &ReferenceHandler{repo: repo} } // ── Departments ─────────────────────────────────────────────────────────────── // POST /api/v1/department/create // PUT /api/v1/departments/{id} (same body, id from path) func (h *ReferenceHandler) CreateDepartment(w http.ResponseWriter, r *http.Request) { var req model.CreateDepartmentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } req.Code = strings.TrimSpace(req.Code) req.Name = strings.TrimSpace(req.Name) if req.Code == "" || req.Name == "" { writeError(w, http.StatusBadRequest, "code and name are required") return } // Default active to true when not explicitly set to false if !req.Active { req.Active = true } dept, err := h.repo.CreateDepartment(r.Context(), req) if err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("create department: %v", err)) return } writeJSON(w, http.StatusCreated, dept) } // GET /api/v1/department/list func (h *ReferenceHandler) ListDepartments(w http.ResponseWriter, r *http.Request) { depts, err := h.repo.ListDepartments(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("list departments: %v", err)) return } if depts == nil { depts = []model.Department{} } writeJSON(w, http.StatusOK, depts) } // GET /api/v1/department/ func (h *ReferenceHandler) GetDepartment(w http.ResponseWriter, r *http.Request) { var req model.GetDepartmentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } // exactly one field must be set set := 0 if req.Code != nil { set++ } if req.Name != nil { set++ } if req.CostCenter != nil { set++ } if set == 0 { writeError(w, http.StatusBadRequest, "one of code, name, cost_center is required") return } if set > 1 { writeError(w, http.StatusBadRequest, "only one of code, name, cost_center may be set") return } var ( dept *model.Department err error ) switch { case req.Code != nil: dept, err = h.repo.GetDepartmentByCode(r.Context(), *req.Code) case req.Name != nil: dept, err = h.repo.GetDepartmentByName(r.Context(), *req.Name) case req.CostCenter != nil: dept, err = h.repo.GetDepartmentByCostCenter(r.Context(), *req.CostCenter) } if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if dept == nil { writeError(w, http.StatusNotFound, "department not found") return } writeJSON(w, http.StatusOK, dept) } func (h *ReferenceHandler) SetActivityDepartment(w http.ResponseWriter, r *http.Request) { var req model.SetDepartmentActivity if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } set := 0 if req.Code != nil { set++ } if req.Name != nil { set++ } if req.CostCenter != nil { set++ } if set == 0 { writeError(w, http.StatusBadRequest, "one of code, name, cost_center is required") return } if set > 1 { writeError(w, http.StatusBadRequest, "only one of code, name, cost_center may be set") return } var err error switch { case req.Code != nil: err = h.repo.SetDepartmentActivityByCode(r.Context(), *req.Code, req.Active) case req.Name != nil: err = h.repo.SetDepartmentActivityByName(r.Context(), *req.Name, req.Active) case req.CostCenter != nil: err = h.repo.SetDepartmentActivityByCostCenter(r.Context(), *req.CostCenter, req.Active) } if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) } // DELETE /api/v1/department/delete func (h *ReferenceHandler) DeleteDepartment(w http.ResponseWriter, r *http.Request) { var req model.DeleteDepartmentRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } if req.ID == 0 { writeError(w, http.StatusBadRequest, "id is required") return } if err := h.repo.DeleteDepartment(r.Context(), req); err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("delete department: %v", err)) return } w.WriteHeader(http.StatusNoContent) } // ── GL Accounts ─────────────────────────────────────────────────────────────── var validGLTypes = map[string]bool{ "revenue": true, "cogs": true, "opex": true, "capex": true, "headcount": true, } // POST /api/v1/gl-accounts func (h *ReferenceHandler) CreateGLAccount(w http.ResponseWriter, r *http.Request) { var req model.GLAccount if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } req.Code = strings.TrimSpace(req.Code) req.Description = strings.TrimSpace(req.Description) if req.Code == "" || req.Description == "" { writeError(w, http.StatusBadRequest, "code and description are required") return } if !validGLTypes[req.Type] { writeError(w, http.StatusBadRequest, "type must be one of: revenue, cogs, opex, capex, headcount") return } if !req.Active { req.Active = true } acct, err := h.repo.CreateGLAccount(r.Context(), req) if err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("create gl_account: %v", err)) return } writeJSON(w, http.StatusCreated, acct) } // GET /api/v1/gl-accounts func (h *ReferenceHandler) ListGLAccounts(w http.ResponseWriter, r *http.Request) { accts, err := h.repo.ListGLAccounts(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("list gl_accounts: %v", err)) return } if accts == nil { accts = []model.GLAccount{} } writeJSON(w, http.StatusOK, accts) } // DELETE /api/v1/gl-accounts/delete func (h *ReferenceHandler) DeleteGLAccount(w http.ResponseWriter, r *http.Request) { var req model.DeleteGLAccountRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err)) return } if req.ID == 0 { writeError(w, http.StatusBadRequest, "id is required") return } if err := h.repo.DeleteGLAccount(r.Context(), req.ID); err != nil { writeError(w, http.StatusInternalServerError, fmt.Sprintf("delete gl_account: %v", err)) return } w.WriteHeader(http.StatusNoContent) } // ── Shared helpers ──────────────────────────────────────────────────────────── func pathID(r *http.Request, key string) (int, error) { return strconv.Atoi(r.PathValue(key)) }