369 lines
12 KiB
Go
369 lines
12 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"eslogad-be/internal/appcontext"
|
|
"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) {
|
|
// Parse query params
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
|
|
|
// Parse department_id
|
|
var departmentID *uuid.UUID
|
|
if departmentIDStr := c.Query("department_id"); departmentIDStr != "" {
|
|
if id, err := uuid.Parse(departmentIDStr); err == nil {
|
|
departmentID = &id
|
|
}
|
|
}
|
|
|
|
// Parse is_active
|
|
var isActive *bool
|
|
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
|
if active, err := strconv.ParseBool(isActiveStr); err == nil {
|
|
isActive = &active
|
|
}
|
|
}
|
|
|
|
// Parse search
|
|
var search *string
|
|
if searchStr := c.Query("search"); searchStr != "" {
|
|
search = &searchStr
|
|
}
|
|
|
|
// Build request - pass PAGE, bukan OFFSET
|
|
req := &contract.ListApprovalFlowsRequest{
|
|
Page: page, // ✅ Pass page number
|
|
Limit: limit,
|
|
DepartmentID: departmentID,
|
|
IsActive: isActive,
|
|
Search: search, // tambahkan ini juga
|
|
}
|
|
|
|
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) ListApprovalFlowsByDepartment(c *gin.Context) {
|
|
appCtx := appcontext.FromGinContext(c.Request.Context())
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
|
offset := (page - 1) * limit
|
|
|
|
req := &contract.ListApprovalFlowsRequest{
|
|
Limit: limit,
|
|
Page: offset,
|
|
DepartmentID: &appCtx.DepartmentID,
|
|
}
|
|
|
|
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))
|
|
}
|