package agenda import ( "context" "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } // Create godoc // @Summary Create a new agenda event // @Description Create a new agenda event // @Tags agenda // @Accept json // @Produce json // @Param request body CreateAgendaRequest true "Create Agenda Request" // @Success 201 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/agenda [post] func (h *Handler) Create(c *gin.Context) { var req CreateAgendaRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } userIDStr := c.GetString("userID") userID, err := uuid.Parse(userIDStr) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Usuário não autenticado"}) return } regiao := c.GetString("regiao") agenda, err := h.service.Create(c.Request.Context(), userID, req, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao criar agenda: " + err.Error()}) return } c.JSON(http.StatusCreated, agenda) } // List godoc // @Summary List all agenda events // @Description List all agenda events with details // @Tags agenda // @Accept json // @Produce json // @Success 200 {array} map[string]interface{} // @Failure 500 {object} map[string]string // @Router /api/agenda [get] func (h *Handler) List(c *gin.Context) { userIDStr := c.GetString("userID") role := c.GetString("role") userID, _ := uuid.Parse(userIDStr) regiao := c.GetString("regiao") agendas, err := h.service.List(c.Request.Context(), userID, role, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao listar agendas: " + err.Error()}) return } c.JSON(http.StatusOK, agendas) } // Get godoc // @Summary Get agenda event by ID // @Description Get agenda event details by ID // @Tags agenda // @Accept json // @Produce json // @Param id path string true "Agenda ID" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/agenda/{id} [get] func (h *Handler) Get(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido: " + err.Error()}) return } regiao := c.GetString("regiao") agenda, err := h.service.Get(c.Request.Context(), id, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar agenda: " + err.Error()}) return } c.JSON(http.StatusOK, agenda) } // Update godoc // @Summary Update agenda event // @Description Update agenda event by ID // @Tags agenda // @Accept json // @Produce json // @Param id path string true "Agenda ID" // @Param request body CreateAgendaRequest true "Update Agenda Request" // @Success 200 {object} map[string]interface{} // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/agenda/{id} [put] func (h *Handler) Update(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido: " + err.Error()}) return } var req CreateAgendaRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } fmt.Printf("Update Payload: %+v\n", req) regiao := c.GetString("regiao") agenda, err := h.service.Update(c.Request.Context(), id, req, regiao) if err != nil { fmt.Printf("Update Error for ID %s: %v\n", id, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar agenda: " + err.Error()}) return } c.JSON(http.StatusOK, agenda) } // Delete godoc // @Summary Delete agenda event // @Description Delete agenda event by ID // @Tags agenda // @Accept json // @Produce json // @Param id path string true "Agenda ID" // @Success 204 {object} nil // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/agenda/{id} [delete] func (h *Handler) Delete(c *gin.Context) { idParam := c.Param("id") id, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido: " + err.Error()}) return } regiao := c.GetString("regiao") if err := h.service.Delete(c.Request.Context(), id, regiao); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao deletar agenda: " + err.Error()}) return } c.Status(http.StatusNoContent) } // AssignProfessional godoc // @Summary Assign professional to agenda // @Tags agenda // @Router /api/agenda/{id}/professionals [post] func (h *Handler) AssignProfessional(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } var req struct { ProfessionalID string `json:"professional_id" binding:"required"` FuncaoID *string `json:"funcao_id"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } if idParam == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "ID inválido"}) return } // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } profID, err := uuid.Parse(req.ProfessionalID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) return } var funcaoID *uuid.UUID if req.FuncaoID != nil && *req.FuncaoID != "" { if parsed, err := uuid.Parse(*req.FuncaoID); err == nil { funcaoID = &parsed } } regiao := c.GetString("regiao") if err := h.service.AssignProfessional(c.Request.Context(), agendaID, profID, funcaoID, regiao); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atribuir profissional: " + err.Error()}) return } c.Status(http.StatusOK) } // RemoveProfessional godoc // @Summary Remove professional from agenda // @Tags agenda // @Router /api/agenda/{id}/professionals/{profId} [delete] func (h *Handler) RemoveProfessional(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } profIdParam := c.Param("profId") profID, err := uuid.Parse(profIdParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) return } // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } if err := h.service.RemoveProfessional(c.Request.Context(), agendaID, profID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao remover profissional: " + err.Error()}) return } c.Status(http.StatusNoContent) } // GetProfessionals godoc // @Summary Get professionals assigned to agenda // @Tags agenda // @Router /api/agenda/{id}/professionals [get] func (h *Handler) GetProfessionals(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } profs, err := h.service.GetAgendaProfessionals(c.Request.Context(), agendaID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar profissionais: " + err.Error()}) return } // Security: Mask data for RESEARCHER if c.GetString("role") == "RESEARCHER" { for i := range profs { // Set sensitive fields to generic or empty values // Using generated struct pointers or values? // generated.GetAgendaProfessionalsRow has fields like Email (pgtype.Text) // We can override them. // Note: 'profs' is a slice of structs, we iterate by index to modify. // Mask Email profs[i].Email = pgtype.Text{String: "bloqueado@acesso.restrito", Valid: true} // Mask Phone/Whatsapp if available in struct // Checking struct definition... list includes p.* // p (CadastroProfissionais) has Whatsapp, CpfCnpjTitular, Banco, Agencia, ContaPix profs[i].Whatsapp = pgtype.Text{String: "Bloqueado", Valid: true} profs[i].CpfCnpjTitular = pgtype.Text{String: "***", Valid: true} profs[i].Banco = pgtype.Text{String: "***", Valid: true} profs[i].Agencia = pgtype.Text{String: "***", Valid: true} profs[i].ContaPix = pgtype.Text{String: "***", Valid: true} } } c.JSON(http.StatusOK, profs) } // UpdateStatus godoc // @Summary Update agenda status // @Tags agenda // @Router /api/agenda/{id}/status [patch] func (h *Handler) UpdateStatus(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } var req struct { Status string `json:"status" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } regiao := c.GetString("regiao") agenda, err := h.service.UpdateStatus(c.Request.Context(), agendaID, req.Status, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar status: " + err.Error()}) return } c.JSON(http.StatusOK, agenda) } // UpdateAssignmentStatus godoc // @Summary Update professional assignment status // @Tags agenda // @Router /api/agenda/{id}/professionals/{profId}/status [patch] func (h *Handler) UpdateAssignmentStatus(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } profIdParam := c.Param("profId") profID, err := uuid.Parse(profIdParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) return } var req struct { Status string `json:"status" binding:"required"` Reason string `json:"reason"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } regiao := c.GetString("regiao") if err := h.service.UpdateAssignmentStatus(c.Request.Context(), agendaID, profID, req.Status, req.Reason, regiao); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar status: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "success"}) } // UpdateAssignmentPosition godoc // @Summary Update professional position in agenda // @Tags agenda // @Router /api/agenda/{id}/professionals/{profId}/position [patch] func (h *Handler) UpdateAssignmentPosition(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } profIdParam := c.Param("profId") profID, err := uuid.Parse(profIdParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) return } var req struct { Posicao string `json:"posicao" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } if err := h.service.UpdateAssignmentPosition(c.Request.Context(), agendaID, profID, req.Posicao); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar posição: " + err.Error()}) return } c.Status(http.StatusOK) } // ListAvailableProfessionals godoc // @Summary List available professionals for agenda date // @Tags agenda // @Router /api/agenda/{id}/available [get] func (h *Handler) ListAvailableProfessionals(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } // Fetch agenda to get date regiao := c.GetString("regiao") agenda, err := h.service.Get(c.Request.Context(), agendaID, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar agenda: " + err.Error()}) return } // Check if date is valid if !agenda.DataEvento.Valid { c.JSON(http.StatusBadRequest, gin.H{"error": "Agenda sem data definida"}) return } profs, err := h.service.ListAvailableProfessionals(c.Request.Context(), agenda.DataEvento.Time, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar profissionais disponíveis: " + err.Error()}) return } c.JSON(http.StatusOK, profs) } // GetProfessionalFinancialStatement godoc // @Summary Get professional financial statement // @Description Get financial statement for the logged-in professional // @Tags agenda // @Accept json // @Produce json // @Success 200 {object} FinancialStatementResponse // @Failure 401 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/professionals/me/financial-statement [get] func (h *Handler) GetProfessionalFinancialStatement(c *gin.Context) { userIDStr := c.GetString("userID") userID, err := uuid.Parse(userIDStr) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Usuário não autenticado"}) return } statement, err := h.service.GetProfessionalFinancialStatement(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar extrato: " + err.Error()}) return } c.JSON(http.StatusOK, statement) } // NotifyLogistics godoc // @Summary Send logistics notification to all professionals // @Tags agenda // @Router /api/agenda/{id}/notify-logistics [post] func (h *Handler) NotifyLogistics(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } // Async or Sync? User wants visual feedback "Liberar o gatilho". // We can do it sync to return "Success" only after initiation, or async. // Service.NotifyLogistics is currently synchronous (except the internal async parts? No, I copied it as sync loop). // Wait, I removed the `go func()` wrapper in the extraction, so the loop runs in the caller's goroutine. // But `UpdateStatus` calls it with `go s.NotifyLogistics(...)`. // For this endpoint, we might want to return quickly. // But returning success implies "Notification Sent". // Let's run it in background for speed. var req struct { PassengerOrders map[string]map[string]int `json:"passenger_orders"` } _ = c.ShouldBindJSON(&req) // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } regiao := c.GetString("regiao") go h.service.NotifyLogistics(context.Background(), agendaID, req.PassengerOrders, regiao) c.JSON(http.StatusOK, gin.H{"message": "Notificação de logística iniciada com sucesso."}) } // Import godoc // @Summary Import agenda events from Excel/JSON // @Tags agenda // @Router /api/import/agenda [post] func (h *Handler) Import(c *gin.Context) { var req []ImportAgendaRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } userIDStr := c.GetString("userID") userID, err := uuid.Parse(userIDStr) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Usuário não autenticado"}) return } regiao := c.GetString("regiao") if err := h.service.ImportAgenda(c.Request.Context(), userID, req, regiao); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao importar agenda: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Agenda importada com sucesso"}) } // SetCoordinator godoc // @Summary Set professional as coordinator // @Tags agenda // @Router /api/agenda/{id}/professionals/{profId}/coordinator [put] func (h *Handler) SetCoordinator(c *gin.Context) { idParam := c.Param("id") agendaID, err := uuid.Parse(idParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) return } profIdParam := c.Param("profId") profID, err := uuid.Parse(profIdParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) return } var req struct { IsCoordinator bool `json:"is_coordinator"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) return } // Security: Block RESEARCHER if c.GetString("role") == "RESEARCHER" { c.JSON(http.StatusForbidden, gin.H{"error": "Acesso negado: Somente leitura"}) return } // regiao := c.GetString("regiao") // Not needed for SetCoordinator if err := h.service.SetCoordinator(c.Request.Context(), agendaID, profID, req.IsCoordinator); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao definir coordenador: " + err.Error()}) return } c.Status(http.StatusOK) } // ToggleFinalizada godoc // @Summary Toggle FOT finalized status // @Tags agenda // @Router /api/fots/{id}/finalize [post]