dukcapil/internal/handler/admin_approval_flow_handler.go
2025-08-19 00:31:04 +07:00

344 lines
11 KiB
Go

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
}
// Validate that at least one step is provided
if len(req.Steps) == 0 {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest})
return
}
// Validate each step has either a role or user as approver
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
}
// Validate that at least one step is provided
if len(req.Steps) == 0 {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "at least one approval step is required", Code: http.StatusBadRequest})
return
}
// Validate each step has either a role or user as approver
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))
}