diff --git a/internal/httpapi/handlers.go b/internal/httpapi/handlers.go index e85ed67..8609465 100644 --- a/internal/httpapi/handlers.go +++ b/internal/httpapi/handlers.go @@ -45,6 +45,7 @@ var navItems = []NavItem{ {Key: "approvals", Label: "승인 관리", Path: "/admin/approvals", Icon: "CheckSquare", AdminOnly: true, Section: "관리자"}, {Key: "attendance-admin", Label: "근무 관리", Path: "/admin/attendance", Icon: "ClipboardList", AdminOnly: true, Section: "관리자"}, {Key: "projects-admin", Label: "프로젝트 관리", Path: "/admin/projects", Icon: "FolderCog", AdminOnly: true, Section: "관리자"}, + {Key: "master", Label: "기준정보", Path: "/admin/master", Icon: "Database", AdminOnly: true, Section: "관리자"}, {Key: "incentive-admin", Label: "인센티브 관리", Path: "/admin/incentive", Icon: "Calculator", AdminOnly: true, Section: "관리자"}, {Key: "accounting", Label: "회계", Path: "/admin/accounting", Icon: "Wallet", AdminOnly: true, Section: "관리자"}, {Key: "members", Label: "구성원", Path: "/admin/members", Icon: "Users", AdminOnly: true, Section: "관리자"}, diff --git a/internal/httpapi/handlers_accounting.go b/internal/httpapi/handlers_accounting.go index 96e3927..3083204 100644 --- a/internal/httpapi/handlers_accounting.go +++ b/internal/httpapi/handlers_accounting.go @@ -141,6 +141,14 @@ func (s *Server) handlePatchTax(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, t) } +func (s *Server) handleDeleteTax(w http.ResponseWriter, r *http.Request) { + if !s.requireAdmin(w, r) { + return + } + s.db.Delete(&models.TaxRecord{}, "id = ?", chi.URLParam(r, "taxId")) + writeJSON(w, http.StatusOK, map[string]bool{"ok": true}) +} + // ---- summary: cashflow vs incentive gap ----------------------------------- type acctSummary struct { diff --git a/internal/httpapi/handlers_projects.go b/internal/httpapi/handlers_projects.go index ba72710..b2e9834 100644 --- a/internal/httpapi/handlers_projects.go +++ b/internal/httpapi/handlers_projects.go @@ -78,6 +78,60 @@ func (s *Server) handleCreateVersion(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, v) } +// patchByID applies a whitelisted-free JSON patch to a model row (admin only). +func (s *Server) patchModel(w http.ResponseWriter, r *http.Request, dest interface{}, id string) { + if !s.requireAdmin(w, r) { + return + } + if err := s.db.First(dest, "id = ?", id).Error; err != nil { + writeError(w, http.StatusNotFound, "찾을 수 없습니다") + return + } + var patch map[string]interface{} + if err := decodeJSON(r, &patch); err != nil { + writeError(w, http.StatusBadRequest, err.Error()) + return + } + delete(patch, "id") + if err := s.db.Model(dest).Updates(patch).Error; err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + s.db.First(dest, "id = ?", id) + writeJSON(w, http.StatusOK, dest) +} + +func (s *Server) handlePatchCompany(w http.ResponseWriter, r *http.Request) { + s.patchModel(w, r, &models.Company{}, chi.URLParam(r, "id")) +} +func (s *Server) handleDeleteCompany(w http.ResponseWriter, r *http.Request) { + if !s.requireAdmin(w, r) { + return + } + s.db.Delete(&models.Company{}, "id = ?", chi.URLParam(r, "id")) + writeJSON(w, http.StatusOK, map[string]bool{"ok": true}) +} +func (s *Server) handlePatchProduct(w http.ResponseWriter, r *http.Request) { + s.patchModel(w, r, &models.Product{}, chi.URLParam(r, "id")) +} +func (s *Server) handleDeleteProduct(w http.ResponseWriter, r *http.Request) { + if !s.requireAdmin(w, r) { + return + } + s.db.Delete(&models.Product{}, "id = ?", chi.URLParam(r, "id")) + writeJSON(w, http.StatusOK, map[string]bool{"ok": true}) +} +func (s *Server) handlePatchVersion(w http.ResponseWriter, r *http.Request) { + s.patchModel(w, r, &models.Version{}, chi.URLParam(r, "id")) +} +func (s *Server) handleDeleteVersion(w http.ResponseWriter, r *http.Request) { + if !s.requireAdmin(w, r) { + return + } + s.db.Delete(&models.Version{}, "id = ?", chi.URLParam(r, "id")) + writeJSON(w, http.StatusOK, map[string]bool{"ok": true}) +} + // ---- projects ------------------------------------------------------------- // myProjectIDs returns the project IDs the caller is a member of (or PM of). diff --git a/internal/httpapi/router.go b/internal/httpapi/router.go index 00a86ef..143750d 100644 --- a/internal/httpapi/router.go +++ b/internal/httpapi/router.go @@ -98,10 +98,16 @@ func NewRouter(db *gorm.DB, store *storage.Storage, cfg config.Config, pusher *p // ---- slice 3: projects ---- r.Get("/companies", s.handleListCompanies) r.Post("/companies", s.handleCreateCompany) + r.Patch("/companies/{id}", s.handlePatchCompany) + r.Delete("/companies/{id}", s.handleDeleteCompany) r.Get("/products", s.handleListProducts) r.Post("/products", s.handleCreateProduct) + r.Patch("/products/{id}", s.handlePatchProduct) + r.Delete("/products/{id}", s.handleDeleteProduct) r.Get("/versions", s.handleListVersions) r.Post("/versions", s.handleCreateVersion) + r.Patch("/versions/{id}", s.handlePatchVersion) + r.Delete("/versions/{id}", s.handleDeleteVersion) r.Get("/projects", s.handleListProjects) r.Post("/projects", s.handleCreateProject) r.Get("/projects/{id}", s.handleGetProject) @@ -153,6 +159,7 @@ func NewRouter(db *gorm.DB, store *storage.Storage, cfg config.Config, pusher *p r.Get("/taxes", s.handleListTaxes) r.Post("/taxes", s.handleCreateTax) r.Patch("/taxes/{taxId}", s.handlePatchTax) + r.Delete("/taxes/{taxId}", s.handleDeleteTax) r.Get("/accounting/summary", s.handleAccountingSummary) // cashflow vs incentive gap // ---- slice 6: dashboard ----