package handler import ( "context" "net/http" "strconv" "eslogad-be/internal/contract" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type ApprovalFlowService interface { CreateApprovalFlow(ctx context.Context, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) GetApprovalFlow(ctx context.Context, id uuid.UUID) (*contract.ApprovalFlowResponse, error) GetApprovalFlowByDepartment(ctx context.Context, departmentID uuid.UUID) (*contract.ApprovalFlowResponse, error) UpdateApprovalFlow(ctx context.Context, id uuid.UUID, req *contract.ApprovalFlowRequest) (*contract.ApprovalFlowResponse, error) DeleteApprovalFlow(ctx context.Context, id uuid.UUID) error ListApprovalFlows(ctx context.Context, req *contract.ListApprovalFlowsRequest) (*contract.ListApprovalFlowsResponse, error) } type AdminApprovalFlowHandler struct { svc ApprovalFlowService } func NewAdminApprovalFlowHandler(svc ApprovalFlowService) *AdminApprovalFlowHandler { return &AdminApprovalFlowHandler{svc: svc} } func (h *AdminApprovalFlowHandler) CreateApprovalFlow(c *gin.Context) { var req contract.ApprovalFlowRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if len(req.Steps) == 0 { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) return } for i, step := range req.Steps { if step.ApproverRoleID == nil && step.ApproverUserID == nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "step " + strconv.Itoa(i+1) + " must have either approver_role_id or approver_user_id", Code: http.StatusBadRequest, }) return } if step.ApproverRoleID != nil && step.ApproverUserID != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "step " + strconv.Itoa(i+1) + " cannot have both approver_role_id and approver_user_id", Code: http.StatusBadRequest, }) return } } resp, err := h.svc.CreateApprovalFlow(c.Request.Context(), &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) GetApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetApprovalFlow(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) GetApprovalFlowByDepartment(c *gin.Context) { departmentID, err := uuid.Parse(c.Param("department_id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid department_id", Code: http.StatusBadRequest}) return } resp, err := h.svc.GetApprovalFlowByDepartment(c.Request.Context(), departmentID) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) UpdateApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var req contract.ApprovalFlowRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } if len(req.Steps) == 0 { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest}) return } for i, step := range req.Steps { if step.ApproverRoleID == nil && step.ApproverUserID == nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "step " + strconv.Itoa(i+1) + " must have either approver_role_id or approver_user_id", Code: http.StatusBadRequest, }) return } if step.ApproverRoleID != nil && step.ApproverUserID != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{ Error: "step " + strconv.Itoa(i+1) + " cannot have both approver_role_id and approver_user_id", Code: http.StatusBadRequest, }) return } } resp, err := h.svc.UpdateApprovalFlow(c.Request.Context(), id, &req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) DeleteApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } if err := h.svc.DeleteApprovalFlow(c.Request.Context(), id); err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "approval flow deleted"}) } func (h *AdminApprovalFlowHandler) ListApprovalFlows(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset := (page - 1) * limit departmentIDStr := c.Query("department_id") isActiveStr := c.Query("is_active") var departmentID *uuid.UUID var isActive *bool if departmentIDStr != "" { if id, err := uuid.Parse(departmentIDStr); err == nil { departmentID = &id } } if isActiveStr != "" { if isActiveStr == "true" { active := true isActive = &active } else if isActiveStr == "false" { active := false isActive = &active } } req := &contract.ListApprovalFlowsRequest{ Limit: limit, Offset: offset, DepartmentID: departmentID, IsActive: isActive, } resp, err := h.svc.ListApprovalFlows(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) ActivateApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } // Get the current flow flow, err := h.svc.GetApprovalFlow(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } // Update only the IsActive field req := &contract.ApprovalFlowRequest{ DepartmentID: flow.DepartmentID, Name: flow.Name, Description: flow.Description, IsActive: true, Steps: make([]contract.ApprovalFlowStepRequest, len(flow.Steps)), } // Copy existing steps for i, step := range flow.Steps { req.Steps[i] = contract.ApprovalFlowStepRequest{ StepOrder: step.StepOrder, ParallelGroup: step.ParallelGroup, ApproverRoleID: step.ApproverRoleID, ApproverUserID: step.ApproverUserID, Required: step.Required, } } resp, err := h.svc.UpdateApprovalFlow(c.Request.Context(), id, req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) DeactivateApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } // Get the current flow flow, err := h.svc.GetApprovalFlow(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } // Update only the IsActive field req := &contract.ApprovalFlowRequest{ DepartmentID: flow.DepartmentID, Name: flow.Name, Description: flow.Description, IsActive: false, Steps: make([]contract.ApprovalFlowStepRequest, len(flow.Steps)), } // Copy existing steps for i, step := range flow.Steps { req.Steps[i] = contract.ApprovalFlowStepRequest{ StepOrder: step.StepOrder, ParallelGroup: step.ParallelGroup, ApproverRoleID: step.ApproverRoleID, ApproverUserID: step.ApproverUserID, Required: step.Required, } } resp, err := h.svc.UpdateApprovalFlow(c.Request.Context(), id, req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp)) } func (h *AdminApprovalFlowHandler) CloneApprovalFlow(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest}) return } var cloneReq struct { DepartmentID uuid.UUID `json:"department_id" validate:"required"` Name string `json:"name" validate:"required"` } if err := c.ShouldBindJSON(&cloneReq); err != nil { c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid body", Code: http.StatusBadRequest}) return } // Get the source flow sourceFlow, err := h.svc.GetApprovalFlow(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } // Create new flow request with cloned data req := &contract.ApprovalFlowRequest{ DepartmentID: cloneReq.DepartmentID, Name: cloneReq.Name, Description: sourceFlow.Description, IsActive: false, // New cloned flow starts as inactive Steps: make([]contract.ApprovalFlowStepRequest, len(sourceFlow.Steps)), } // Copy steps from source flow for i, step := range sourceFlow.Steps { req.Steps[i] = contract.ApprovalFlowStepRequest{ StepOrder: step.StepOrder, ParallelGroup: step.ParallelGroup, ApproverRoleID: step.ApproverRoleID, ApproverUserID: step.ApproverUserID, Required: step.Required, } } resp, err := h.svc.CreateApprovalFlow(c.Request.Context(), req) if err != nil { c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError}) return } c.JSON(http.StatusCreated, contract.BuildSuccessResponse(resp)) }