diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index c824342..d1d5a59 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -5,6 +5,7 @@ import ( "log" "photum-backend/docs" + "photum-backend/internal/agenda" "photum-backend/internal/anos_formaturas" "photum-backend/internal/auth" "photum-backend/internal/cadastro_fot" @@ -68,6 +69,7 @@ func main() { tiposServicosService := tipos_servicos.NewService(queries) tiposEventosService := tipos_eventos.NewService(queries) cadastroFotService := cadastro_fot.NewService(queries) + agendaService := agenda.NewService(queries) // Seed Demo Users if err := authService.EnsureDemoUsers(context.Background()); err != nil { @@ -84,6 +86,7 @@ func main() { tiposServicosHandler := tipos_servicos.NewHandler(tiposServicosService) tiposEventosHandler := tipos_eventos.NewHandler(tiposEventosService) cadastroFotHandler := cadastro_fot.NewHandler(cadastroFotService) + agendaHandler := agenda.NewHandler(agendaService) r := gin.Default() @@ -182,6 +185,12 @@ func main() { api.PUT("/cadastro-fot/:id", cadastroFotHandler.Update) api.DELETE("/cadastro-fot/:id", cadastroFotHandler.Delete) + api.GET("/agenda", agendaHandler.List) + api.POST("/agenda", agendaHandler.Create) + api.GET("/agenda/:id", agendaHandler.Get) + api.PUT("/agenda/:id", agendaHandler.Update) + api.DELETE("/agenda/:id", agendaHandler.Delete) + admin := api.Group("/admin") { admin.GET("/users", authHandler.ListUsers) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index debad85..3979b8f 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -410,6 +410,247 @@ const docTemplate = `{ } } }, + "/api/agenda": { + "get": { + "description": "List all agenda events with details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "List all agenda events", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "post": { + "description": "Create a new agenda event", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Create a new agenda event", + "parameters": [ + { + "description": "Create Agenda Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agenda.CreateAgendaRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/agenda/{id}": { + "get": { + "description": "Get agenda event details by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Get agenda event by ID", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "put": { + "description": "Update agenda event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Update agenda event", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Agenda Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agenda.CreateAgendaRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "description": "Delete agenda event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Delete agenda event", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/anos-formaturas": { "get": { "security": [ @@ -2164,6 +2405,80 @@ const docTemplate = `{ } }, "definitions": { + "agenda.CreateAgendaRequest": { + "type": "object", + "properties": { + "cine_faltante": { + "type": "integer" + }, + "data_evento": { + "type": "string" + }, + "endereco": { + "type": "string" + }, + "fot_id": { + "type": "string" + }, + "foto_faltante": { + "type": "integer" + }, + "horario": { + "type": "string" + }, + "local_evento": { + "type": "string" + }, + "logistica_observacoes": { + "type": "string" + }, + "observacoes_evento": { + "type": "string" + }, + "pre_venda": { + "type": "boolean" + }, + "qtd_cinegrafistas": { + "type": "integer" + }, + "qtd_estudios": { + "type": "integer" + }, + "qtd_formandos": { + "type": "integer" + }, + "qtd_fotografos": { + "type": "integer" + }, + "qtd_plataforma_360": { + "type": "integer" + }, + "qtd_ponto_decorado": { + "type": "integer" + }, + "qtd_ponto_foto": { + "type": "integer" + }, + "qtd_ponto_id": { + "type": "integer" + }, + "qtd_pontos_led": { + "type": "integer" + }, + "qtd_recepcionistas": { + "type": "integer" + }, + "recep_faltante": { + "type": "integer" + }, + "status_profissionais": { + "type": "string" + }, + "tipo_evento_id": { + "type": "string" + } + } + }, "anos_formaturas.AnoFormaturaResponse": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 56bc797..e87efe4 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -404,6 +404,247 @@ } } }, + "/api/agenda": { + "get": { + "description": "List all agenda events with details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "List all agenda events", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "post": { + "description": "Create a new agenda event", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Create a new agenda event", + "parameters": [ + { + "description": "Create Agenda Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agenda.CreateAgendaRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/agenda/{id}": { + "get": { + "description": "Get agenda event details by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Get agenda event by ID", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "put": { + "description": "Update agenda event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Update agenda event", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Agenda Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agenda.CreateAgendaRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "description": "Delete agenda event by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "agenda" + ], + "summary": "Delete agenda event", + "parameters": [ + { + "type": "string", + "description": "Agenda ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/anos-formaturas": { "get": { "security": [ @@ -2158,6 +2399,80 @@ } }, "definitions": { + "agenda.CreateAgendaRequest": { + "type": "object", + "properties": { + "cine_faltante": { + "type": "integer" + }, + "data_evento": { + "type": "string" + }, + "endereco": { + "type": "string" + }, + "fot_id": { + "type": "string" + }, + "foto_faltante": { + "type": "integer" + }, + "horario": { + "type": "string" + }, + "local_evento": { + "type": "string" + }, + "logistica_observacoes": { + "type": "string" + }, + "observacoes_evento": { + "type": "string" + }, + "pre_venda": { + "type": "boolean" + }, + "qtd_cinegrafistas": { + "type": "integer" + }, + "qtd_estudios": { + "type": "integer" + }, + "qtd_formandos": { + "type": "integer" + }, + "qtd_fotografos": { + "type": "integer" + }, + "qtd_plataforma_360": { + "type": "integer" + }, + "qtd_ponto_decorado": { + "type": "integer" + }, + "qtd_ponto_foto": { + "type": "integer" + }, + "qtd_ponto_id": { + "type": "integer" + }, + "qtd_pontos_led": { + "type": "integer" + }, + "qtd_recepcionistas": { + "type": "integer" + }, + "recep_faltante": { + "type": "integer" + }, + "status_profissionais": { + "type": "string" + }, + "tipo_evento_id": { + "type": "string" + } + } + }, "anos_formaturas.AnoFormaturaResponse": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index bc6751e..59cd3b1 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,54 @@ basePath: / definitions: + agenda.CreateAgendaRequest: + properties: + cine_faltante: + type: integer + data_evento: + type: string + endereco: + type: string + fot_id: + type: string + foto_faltante: + type: integer + horario: + type: string + local_evento: + type: string + logistica_observacoes: + type: string + observacoes_evento: + type: string + pre_venda: + type: boolean + qtd_cinegrafistas: + type: integer + qtd_estudios: + type: integer + qtd_formandos: + type: integer + qtd_fotografos: + type: integer + qtd_plataforma_360: + type: integer + qtd_ponto_decorado: + type: integer + qtd_ponto_foto: + type: integer + qtd_ponto_id: + type: integer + qtd_pontos_led: + type: integer + qtd_recepcionistas: + type: integer + recep_faltante: + type: integer + status_profissionais: + type: string + tipo_evento_id: + type: string + type: object anos_formaturas.AnoFormaturaResponse: properties: ano_semestre: @@ -650,6 +699,167 @@ paths: summary: List pending users tags: - admin + /api/agenda: + get: + consumes: + - application/json + description: List all agenda events with details + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + additionalProperties: true + type: object + type: array + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: List all agenda events + tags: + - agenda + post: + consumes: + - application/json + description: Create a new agenda event + parameters: + - description: Create Agenda Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/agenda.CreateAgendaRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + additionalProperties: true + type: object + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Create a new agenda event + tags: + - agenda + /api/agenda/{id}: + delete: + consumes: + - application/json + description: Delete agenda event by ID + parameters: + - description: Agenda ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Delete agenda event + tags: + - agenda + get: + consumes: + - application/json + description: Get agenda event details by ID + parameters: + - description: Agenda ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Get agenda event by ID + tags: + - agenda + put: + consumes: + - application/json + description: Update agenda event by ID + parameters: + - description: Agenda ID + in: path + name: id + required: true + type: string + - description: Update Agenda Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/agenda.CreateAgendaRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Update agenda event + tags: + - agenda /api/anos-formaturas: get: consumes: diff --git a/backend/internal/agenda/handler.go b/backend/internal/agenda/handler.go new file mode 100644 index 0000000..48cdb85 --- /dev/null +++ b/backend/internal/agenda/handler.go @@ -0,0 +1,152 @@ +package agenda + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +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 + } + + agenda, err := h.service.Create(c.Request.Context(), req) + 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) { + agendas, err := h.service.List(c.Request.Context()) + 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 + } + + agenda, err := h.service.Get(c.Request.Context(), id) + 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 + } + + agenda, err := h.service.Update(c.Request.Context(), id, req) + if err != nil { + 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 + } + + if err := h.service.Delete(c.Request.Context(), id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao deletar agenda: " + err.Error()}) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/backend/internal/agenda/service.go b/backend/internal/agenda/service.go new file mode 100644 index 0000000..6de4464 --- /dev/null +++ b/backend/internal/agenda/service.go @@ -0,0 +1,136 @@ +package agenda + +import ( + "context" + "time" + + "photum-backend/internal/db/generated" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +type Service struct { + queries *generated.Queries +} + +func NewService(db *generated.Queries) *Service { + return &Service{queries: db} +} + +type CreateAgendaRequest struct { + FotID uuid.UUID `json:"fot_id"` + DataEvento time.Time `json:"data_evento"` + TipoEventoID uuid.UUID `json:"tipo_evento_id"` + ObservacoesEvento string `json:"observacoes_evento"` + LocalEvento string `json:"local_evento"` + Endereco string `json:"endereco"` + Horario string `json:"horario"` + QtdFormandos int32 `json:"qtd_formandos"` + QtdFotografos int32 `json:"qtd_fotografos"` + QtdRecepcionistas int32 `json:"qtd_recepcionistas"` + QtdCinegrafistas int32 `json:"qtd_cinegrafistas"` + QtdEstudios int32 `json:"qtd_estudios"` + QtdPontoFoto int32 `json:"qtd_ponto_foto"` + QtdPontoID int32 `json:"qtd_ponto_id"` + QtdPontoDecorado int32 `json:"qtd_ponto_decorado"` + QtdPontosLed int32 `json:"qtd_pontos_led"` + QtdPlataforma360 int32 `json:"qtd_plataforma_360"` + StatusProfissionais string `json:"status_profissionais"` + FotoFaltante int32 `json:"foto_faltante"` + RecepFaltante int32 `json:"recep_faltante"` + CineFaltante int32 `json:"cine_faltante"` + LogisticaObservacoes string `json:"logistica_observacoes"` + PreVenda bool `json:"pre_venda"` +} + +func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int32) string { + if fotoFaltante < 0 || recepFaltante < 0 || cineFaltante < 0 { + return "ERRO" + } + + sum := fotoFaltante + recepFaltante + cineFaltante + if sum == 0 { + return "OK" + } else if sum > 0 { + return "FALTA" + } + + return "ERRO" +} + +func (s *Service) Create(ctx context.Context, req CreateAgendaRequest) (generated.Agenda, error) { + status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante) + + params := generated.CreateAgendaParams{ + FotID: pgtype.UUID{Bytes: req.FotID, Valid: true}, + DataEvento: pgtype.Date{Time: req.DataEvento, Valid: true}, + TipoEventoID: pgtype.UUID{Bytes: req.TipoEventoID, Valid: true}, + ObservacoesEvento: pgtype.Text{String: req.ObservacoesEvento, Valid: req.ObservacoesEvento != ""}, + LocalEvento: pgtype.Text{String: req.LocalEvento, Valid: req.LocalEvento != ""}, + Endereco: pgtype.Text{String: req.Endereco, Valid: req.Endereco != ""}, + Horario: pgtype.Text{String: req.Horario, Valid: req.Horario != ""}, + QtdFormandos: pgtype.Int4{Int32: req.QtdFormandos, Valid: true}, + QtdFotografos: pgtype.Int4{Int32: req.QtdFotografos, Valid: true}, + QtdRecepcionistas: pgtype.Int4{Int32: req.QtdRecepcionistas, Valid: true}, + QtdCinegrafistas: pgtype.Int4{Int32: req.QtdCinegrafistas, Valid: true}, + QtdEstudios: pgtype.Int4{Int32: req.QtdEstudios, Valid: true}, + QtdPontoFoto: pgtype.Int4{Int32: req.QtdPontoFoto, Valid: true}, + QtdPontoID: pgtype.Int4{Int32: req.QtdPontoID, Valid: true}, + QtdPontoDecorado: pgtype.Int4{Int32: req.QtdPontoDecorado, Valid: true}, + QtdPontosLed: pgtype.Int4{Int32: req.QtdPontosLed, Valid: true}, + QtdPlataforma360: pgtype.Int4{Int32: req.QtdPlataforma360, Valid: true}, + StatusProfissionais: pgtype.Text{String: status, Valid: true}, + FotoFaltante: pgtype.Int4{Int32: req.FotoFaltante, Valid: true}, + RecepFaltante: pgtype.Int4{Int32: req.RecepFaltante, Valid: true}, + CineFaltante: pgtype.Int4{Int32: req.CineFaltante, Valid: true}, + LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""}, + PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true}, + } + + return s.queries.CreateAgenda(ctx, params) +} + +func (s *Service) List(ctx context.Context) ([]generated.ListAgendasRow, error) { + return s.queries.ListAgendas(ctx) +} + +func (s *Service) Get(ctx context.Context, id uuid.UUID) (generated.Agenda, error) { + return s.queries.GetAgenda(ctx, pgtype.UUID{Bytes: id, Valid: true}) +} + +func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequest) (generated.Agenda, error) { + status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante) + + params := generated.UpdateAgendaParams{ + ID: pgtype.UUID{Bytes: id, Valid: true}, + FotID: pgtype.UUID{Bytes: req.FotID, Valid: true}, + DataEvento: pgtype.Date{Time: req.DataEvento, Valid: true}, + TipoEventoID: pgtype.UUID{Bytes: req.TipoEventoID, Valid: true}, + ObservacoesEvento: pgtype.Text{String: req.ObservacoesEvento, Valid: req.ObservacoesEvento != ""}, + LocalEvento: pgtype.Text{String: req.LocalEvento, Valid: req.LocalEvento != ""}, + Endereco: pgtype.Text{String: req.Endereco, Valid: req.Endereco != ""}, + Horario: pgtype.Text{String: req.Horario, Valid: req.Horario != ""}, + QtdFormandos: pgtype.Int4{Int32: req.QtdFormandos, Valid: true}, + QtdFotografos: pgtype.Int4{Int32: req.QtdFotografos, Valid: true}, + QtdRecepcionistas: pgtype.Int4{Int32: req.QtdRecepcionistas, Valid: true}, + QtdCinegrafistas: pgtype.Int4{Int32: req.QtdCinegrafistas, Valid: true}, + QtdEstudios: pgtype.Int4{Int32: req.QtdEstudios, Valid: true}, + QtdPontoFoto: pgtype.Int4{Int32: req.QtdPontoFoto, Valid: true}, + QtdPontoID: pgtype.Int4{Int32: req.QtdPontoID, Valid: true}, + QtdPontoDecorado: pgtype.Int4{Int32: req.QtdPontoDecorado, Valid: true}, + QtdPontosLed: pgtype.Int4{Int32: req.QtdPontosLed, Valid: true}, + QtdPlataforma360: pgtype.Int4{Int32: req.QtdPlataforma360, Valid: true}, + StatusProfissionais: pgtype.Text{String: status, Valid: true}, + FotoFaltante: pgtype.Int4{Int32: req.FotoFaltante, Valid: true}, + RecepFaltante: pgtype.Int4{Int32: req.RecepFaltante, Valid: true}, + CineFaltante: pgtype.Int4{Int32: req.CineFaltante, Valid: true}, + LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""}, + PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true}, + } + return s.queries.UpdateAgenda(ctx, params) +} + +func (s *Service) Delete(ctx context.Context, id uuid.UUID) error { + return s.queries.DeleteAgenda(ctx, pgtype.UUID{Bytes: id, Valid: true}) +} diff --git a/backend/internal/db/generated/agenda.sql.go b/backend/internal/db/generated/agenda.sql.go new file mode 100644 index 0000000..f034fd9 --- /dev/null +++ b/backend/internal/db/generated/agenda.sql.go @@ -0,0 +1,401 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: agenda.sql + +package generated + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createAgenda = `-- name: CreateAgenda :one +INSERT INTO agenda ( + fot_id, + data_evento, + tipo_evento_id, + observacoes_evento, + local_evento, + endereco, + horario, + qtd_formandos, + qtd_fotografos, + qtd_recepcionistas, + qtd_cinegrafistas, + qtd_estudios, + qtd_ponto_foto, + qtd_ponto_id, + qtd_ponto_decorado, + qtd_pontos_led, + qtd_plataforma_360, + status_profissionais, + foto_faltante, + recep_faltante, + cine_faltante, + logistica_observacoes, + pre_venda +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23 +) RETURNING id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em +` + +type CreateAgendaParams struct { + FotID pgtype.UUID `json:"fot_id"` + DataEvento pgtype.Date `json:"data_evento"` + TipoEventoID pgtype.UUID `json:"tipo_evento_id"` + ObservacoesEvento pgtype.Text `json:"observacoes_evento"` + LocalEvento pgtype.Text `json:"local_evento"` + Endereco pgtype.Text `json:"endereco"` + Horario pgtype.Text `json:"horario"` + QtdFormandos pgtype.Int4 `json:"qtd_formandos"` + QtdFotografos pgtype.Int4 `json:"qtd_fotografos"` + QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"` + QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"` + QtdEstudios pgtype.Int4 `json:"qtd_estudios"` + QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"` + QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"` + QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"` + QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"` + QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"` + StatusProfissionais pgtype.Text `json:"status_profissionais"` + FotoFaltante pgtype.Int4 `json:"foto_faltante"` + RecepFaltante pgtype.Int4 `json:"recep_faltante"` + CineFaltante pgtype.Int4 `json:"cine_faltante"` + LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` + PreVenda pgtype.Bool `json:"pre_venda"` +} + +func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Agenda, error) { + row := q.db.QueryRow(ctx, createAgenda, + arg.FotID, + arg.DataEvento, + arg.TipoEventoID, + arg.ObservacoesEvento, + arg.LocalEvento, + arg.Endereco, + arg.Horario, + arg.QtdFormandos, + arg.QtdFotografos, + arg.QtdRecepcionistas, + arg.QtdCinegrafistas, + arg.QtdEstudios, + arg.QtdPontoFoto, + arg.QtdPontoID, + arg.QtdPontoDecorado, + arg.QtdPontosLed, + arg.QtdPlataforma360, + arg.StatusProfissionais, + arg.FotoFaltante, + arg.RecepFaltante, + arg.CineFaltante, + arg.LogisticaObservacoes, + arg.PreVenda, + ) + var i Agenda + err := row.Scan( + &i.ID, + &i.FotID, + &i.DataEvento, + &i.TipoEventoID, + &i.ObservacoesEvento, + &i.LocalEvento, + &i.Endereco, + &i.Horario, + &i.QtdFormandos, + &i.QtdFotografos, + &i.QtdRecepcionistas, + &i.QtdCinegrafistas, + &i.QtdEstudios, + &i.QtdPontoFoto, + &i.QtdPontoID, + &i.QtdPontoDecorado, + &i.QtdPontosLed, + &i.QtdPlataforma360, + &i.StatusProfissionais, + &i.FotoFaltante, + &i.RecepFaltante, + &i.CineFaltante, + &i.LogisticaObservacoes, + &i.PreVenda, + &i.CriadoEm, + &i.AtualizadoEm, + ) + return i, err +} + +const deleteAgenda = `-- name: DeleteAgenda :exec +DELETE FROM agenda +WHERE id = $1 +` + +func (q *Queries) DeleteAgenda(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteAgenda, id) + return err +} + +const getAgenda = `-- name: GetAgenda :one +SELECT id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em FROM agenda +WHERE id = $1 LIMIT 1 +` + +func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error) { + row := q.db.QueryRow(ctx, getAgenda, id) + var i Agenda + err := row.Scan( + &i.ID, + &i.FotID, + &i.DataEvento, + &i.TipoEventoID, + &i.ObservacoesEvento, + &i.LocalEvento, + &i.Endereco, + &i.Horario, + &i.QtdFormandos, + &i.QtdFotografos, + &i.QtdRecepcionistas, + &i.QtdCinegrafistas, + &i.QtdEstudios, + &i.QtdPontoFoto, + &i.QtdPontoID, + &i.QtdPontoDecorado, + &i.QtdPontosLed, + &i.QtdPlataforma360, + &i.StatusProfissionais, + &i.FotoFaltante, + &i.RecepFaltante, + &i.CineFaltante, + &i.LogisticaObservacoes, + &i.PreVenda, + &i.CriadoEm, + &i.AtualizadoEm, + ) + return i, err +} + +const listAgendas = `-- name: ListAgendas :many +SELECT + a.id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, + cf.fot as fot_numero, + cf.instituicao, + c.nome as curso_nome, + e.nome as empresa_nome, + af.ano_semestre, + cf.observacoes as observacoes_fot, + te.nome as tipo_evento_nome +FROM agenda a +JOIN cadastro_fot cf ON a.fot_id = cf.id +JOIN cursos c ON cf.curso_id = c.id +JOIN empresas e ON cf.empresa_id = e.id +JOIN anos_formaturas af ON cf.ano_formatura_id = af.id +JOIN tipos_eventos te ON a.tipo_evento_id = te.id +ORDER BY a.data_evento +` + +type ListAgendasRow struct { + ID pgtype.UUID `json:"id"` + FotID pgtype.UUID `json:"fot_id"` + DataEvento pgtype.Date `json:"data_evento"` + TipoEventoID pgtype.UUID `json:"tipo_evento_id"` + ObservacoesEvento pgtype.Text `json:"observacoes_evento"` + LocalEvento pgtype.Text `json:"local_evento"` + Endereco pgtype.Text `json:"endereco"` + Horario pgtype.Text `json:"horario"` + QtdFormandos pgtype.Int4 `json:"qtd_formandos"` + QtdFotografos pgtype.Int4 `json:"qtd_fotografos"` + QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"` + QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"` + QtdEstudios pgtype.Int4 `json:"qtd_estudios"` + QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"` + QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"` + QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"` + QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"` + QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"` + StatusProfissionais pgtype.Text `json:"status_profissionais"` + FotoFaltante pgtype.Int4 `json:"foto_faltante"` + RecepFaltante pgtype.Int4 `json:"recep_faltante"` + CineFaltante pgtype.Int4 `json:"cine_faltante"` + LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` + PreVenda pgtype.Bool `json:"pre_venda"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` + AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` + FotNumero int32 `json:"fot_numero"` + Instituicao pgtype.Text `json:"instituicao"` + CursoNome string `json:"curso_nome"` + EmpresaNome string `json:"empresa_nome"` + AnoSemestre string `json:"ano_semestre"` + ObservacoesFot pgtype.Text `json:"observacoes_fot"` + TipoEventoNome string `json:"tipo_evento_nome"` +} + +func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) { + rows, err := q.db.Query(ctx, listAgendas) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListAgendasRow + for rows.Next() { + var i ListAgendasRow + if err := rows.Scan( + &i.ID, + &i.FotID, + &i.DataEvento, + &i.TipoEventoID, + &i.ObservacoesEvento, + &i.LocalEvento, + &i.Endereco, + &i.Horario, + &i.QtdFormandos, + &i.QtdFotografos, + &i.QtdRecepcionistas, + &i.QtdCinegrafistas, + &i.QtdEstudios, + &i.QtdPontoFoto, + &i.QtdPontoID, + &i.QtdPontoDecorado, + &i.QtdPontosLed, + &i.QtdPlataforma360, + &i.StatusProfissionais, + &i.FotoFaltante, + &i.RecepFaltante, + &i.CineFaltante, + &i.LogisticaObservacoes, + &i.PreVenda, + &i.CriadoEm, + &i.AtualizadoEm, + &i.FotNumero, + &i.Instituicao, + &i.CursoNome, + &i.EmpresaNome, + &i.AnoSemestre, + &i.ObservacoesFot, + &i.TipoEventoNome, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateAgenda = `-- name: UpdateAgenda :one +UPDATE agenda +SET + fot_id = $2, + data_evento = $3, + tipo_evento_id = $4, + observacoes_evento = $5, + local_evento = $6, + endereco = $7, + horario = $8, + qtd_formandos = $9, + qtd_fotografos = $10, + qtd_recepcionistas = $11, + qtd_cinegrafistas = $12, + qtd_estudios = $13, + qtd_ponto_foto = $14, + qtd_ponto_id = $15, + qtd_ponto_decorado = $16, + qtd_pontos_led = $17, + qtd_plataforma_360 = $18, + status_profissionais = $19, + foto_faltante = $20, + recep_faltante = $21, + cine_faltante = $22, + logistica_observacoes = $23, + pre_venda = $24, + atualizado_em = NOW() +WHERE id = $1 +RETURNING id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em +` + +type UpdateAgendaParams struct { + ID pgtype.UUID `json:"id"` + FotID pgtype.UUID `json:"fot_id"` + DataEvento pgtype.Date `json:"data_evento"` + TipoEventoID pgtype.UUID `json:"tipo_evento_id"` + ObservacoesEvento pgtype.Text `json:"observacoes_evento"` + LocalEvento pgtype.Text `json:"local_evento"` + Endereco pgtype.Text `json:"endereco"` + Horario pgtype.Text `json:"horario"` + QtdFormandos pgtype.Int4 `json:"qtd_formandos"` + QtdFotografos pgtype.Int4 `json:"qtd_fotografos"` + QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"` + QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"` + QtdEstudios pgtype.Int4 `json:"qtd_estudios"` + QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"` + QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"` + QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"` + QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"` + QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"` + StatusProfissionais pgtype.Text `json:"status_profissionais"` + FotoFaltante pgtype.Int4 `json:"foto_faltante"` + RecepFaltante pgtype.Int4 `json:"recep_faltante"` + CineFaltante pgtype.Int4 `json:"cine_faltante"` + LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` + PreVenda pgtype.Bool `json:"pre_venda"` +} + +func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Agenda, error) { + row := q.db.QueryRow(ctx, updateAgenda, + arg.ID, + arg.FotID, + arg.DataEvento, + arg.TipoEventoID, + arg.ObservacoesEvento, + arg.LocalEvento, + arg.Endereco, + arg.Horario, + arg.QtdFormandos, + arg.QtdFotografos, + arg.QtdRecepcionistas, + arg.QtdCinegrafistas, + arg.QtdEstudios, + arg.QtdPontoFoto, + arg.QtdPontoID, + arg.QtdPontoDecorado, + arg.QtdPontosLed, + arg.QtdPlataforma360, + arg.StatusProfissionais, + arg.FotoFaltante, + arg.RecepFaltante, + arg.CineFaltante, + arg.LogisticaObservacoes, + arg.PreVenda, + ) + var i Agenda + err := row.Scan( + &i.ID, + &i.FotID, + &i.DataEvento, + &i.TipoEventoID, + &i.ObservacoesEvento, + &i.LocalEvento, + &i.Endereco, + &i.Horario, + &i.QtdFormandos, + &i.QtdFotografos, + &i.QtdRecepcionistas, + &i.QtdCinegrafistas, + &i.QtdEstudios, + &i.QtdPontoFoto, + &i.QtdPontoID, + &i.QtdPontoDecorado, + &i.QtdPontosLed, + &i.QtdPlataforma360, + &i.StatusProfissionais, + &i.FotoFaltante, + &i.RecepFaltante, + &i.CineFaltante, + &i.LogisticaObservacoes, + &i.PreVenda, + &i.CriadoEm, + &i.AtualizadoEm, + ) + return i, err +} diff --git a/backend/internal/db/generated/models.go b/backend/internal/db/generated/models.go index 2e3ff71..a211a08 100644 --- a/backend/internal/db/generated/models.go +++ b/backend/internal/db/generated/models.go @@ -8,6 +8,35 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type Agenda struct { + ID pgtype.UUID `json:"id"` + FotID pgtype.UUID `json:"fot_id"` + DataEvento pgtype.Date `json:"data_evento"` + TipoEventoID pgtype.UUID `json:"tipo_evento_id"` + ObservacoesEvento pgtype.Text `json:"observacoes_evento"` + LocalEvento pgtype.Text `json:"local_evento"` + Endereco pgtype.Text `json:"endereco"` + Horario pgtype.Text `json:"horario"` + QtdFormandos pgtype.Int4 `json:"qtd_formandos"` + QtdFotografos pgtype.Int4 `json:"qtd_fotografos"` + QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"` + QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"` + QtdEstudios pgtype.Int4 `json:"qtd_estudios"` + QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"` + QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"` + QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"` + QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"` + QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"` + StatusProfissionais pgtype.Text `json:"status_profissionais"` + FotoFaltante pgtype.Int4 `json:"foto_faltante"` + RecepFaltante pgtype.Int4 `json:"recep_faltante"` + CineFaltante pgtype.Int4 `json:"cine_faltante"` + LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` + PreVenda pgtype.Bool `json:"pre_venda"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` + AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` +} + type AnosFormatura struct { ID pgtype.UUID `json:"id"` AnoSemestre string `json:"ano_semestre"` diff --git a/backend/internal/db/queries/agenda.sql b/backend/internal/db/queries/agenda.sql new file mode 100644 index 0000000..7f6809e --- /dev/null +++ b/backend/internal/db/queries/agenda.sql @@ -0,0 +1,84 @@ +-- name: CreateAgenda :one +INSERT INTO agenda ( + fot_id, + data_evento, + tipo_evento_id, + observacoes_evento, + local_evento, + endereco, + horario, + qtd_formandos, + qtd_fotografos, + qtd_recepcionistas, + qtd_cinegrafistas, + qtd_estudios, + qtd_ponto_foto, + qtd_ponto_id, + qtd_ponto_decorado, + qtd_pontos_led, + qtd_plataforma_360, + status_profissionais, + foto_faltante, + recep_faltante, + cine_faltante, + logistica_observacoes, + pre_venda +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23 +) RETURNING *; + +-- name: GetAgenda :one +SELECT * FROM agenda +WHERE id = $1 LIMIT 1; + +-- name: ListAgendas :many +SELECT + a.*, + cf.fot as fot_numero, + cf.instituicao, + c.nome as curso_nome, + e.nome as empresa_nome, + af.ano_semestre, + cf.observacoes as observacoes_fot, + te.nome as tipo_evento_nome +FROM agenda a +JOIN cadastro_fot cf ON a.fot_id = cf.id +JOIN cursos c ON cf.curso_id = c.id +JOIN empresas e ON cf.empresa_id = e.id +JOIN anos_formaturas af ON cf.ano_formatura_id = af.id +JOIN tipos_eventos te ON a.tipo_evento_id = te.id +ORDER BY a.data_evento; + +-- name: UpdateAgenda :one +UPDATE agenda +SET + fot_id = $2, + data_evento = $3, + tipo_evento_id = $4, + observacoes_evento = $5, + local_evento = $6, + endereco = $7, + horario = $8, + qtd_formandos = $9, + qtd_fotografos = $10, + qtd_recepcionistas = $11, + qtd_cinegrafistas = $12, + qtd_estudios = $13, + qtd_ponto_foto = $14, + qtd_ponto_id = $15, + qtd_ponto_decorado = $16, + qtd_pontos_led = $17, + qtd_plataforma_360 = $18, + status_profissionais = $19, + foto_faltante = $20, + recep_faltante = $21, + cine_faltante = $22, + logistica_observacoes = $23, + pre_venda = $24, + atualizado_em = NOW() +WHERE id = $1 +RETURNING *; + +-- name: DeleteAgenda :exec +DELETE FROM agenda +WHERE id = $1; diff --git a/backend/internal/db/schema.sql b/backend/internal/db/schema.sql index 422dae8..9623138 100644 --- a/backend/internal/db/schema.sql +++ b/backend/internal/db/schema.sql @@ -313,3 +313,32 @@ CREATE TABLE IF NOT EXISTS cadastro_clientes ( atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(usuario_id) ); +-- Agenda Table +CREATE TABLE IF NOT EXISTS agenda ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + fot_id UUID NOT NULL REFERENCES cadastro_fot(id) ON DELETE CASCADE, + data_evento DATE NOT NULL, + tipo_evento_id UUID NOT NULL REFERENCES tipos_eventos(id), + observacoes_evento TEXT, + local_evento VARCHAR(255), + endereco VARCHAR(255), + horario VARCHAR(20), + qtd_formandos INTEGER DEFAULT 0, + qtd_fotografos INTEGER DEFAULT 0, + qtd_recepcionistas INTEGER DEFAULT 0, + qtd_cinegrafistas INTEGER DEFAULT 0, + qtd_estudios INTEGER DEFAULT 0, + qtd_ponto_foto INTEGER DEFAULT 0, + qtd_ponto_id INTEGER DEFAULT 0, + qtd_ponto_decorado INTEGER DEFAULT 0, + qtd_pontos_led INTEGER DEFAULT 0, + qtd_plataforma_360 INTEGER DEFAULT 0, + status_profissionais VARCHAR(20) DEFAULT 'OK', -- OK, FALTA, ERRO + foto_faltante INTEGER DEFAULT 0, + recep_faltante INTEGER DEFAULT 0, + cine_faltante INTEGER DEFAULT 0, + logistica_observacoes TEXT, + pre_venda BOOLEAN DEFAULT FALSE, + criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), + atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/project_documentation.md b/project_documentation.md new file mode 100644 index 0000000..97c41af --- /dev/null +++ b/project_documentation.md @@ -0,0 +1,84 @@ +# Documentação do Projeto Photum - Sessão de Desenvolvimento + +Esta documentação resume todo o trabalho realizado durante a sessão atual de desenvolvimento, focada na implementação do Backend e ajustes no Frontend para o sistema Photum. + +## 1. Visão Geral +O objetivo principal foi fortalecer o backend em Go, implementar autenticação segura, e criar as estruturas para gerenciamento de FOT (Formandos, Organização, Turma) e Agenda de Eventos. + +## 2. Implementações no Backend (Go + PostgreSQL) + +### 2.1. Sistema de Autenticação e Usuários +- **JWT & Refresh Tokens**: Implementação de autenticação baseada em tokens JWT curta duração e Refresh Tokens seguros. +- **Hierarquia de Permissões (RBAC)**: Definição de roles padronizadas: ROLES Backend (Strings no Banco de Dados): + +admin ⮕ Mapeado para SUPERADMIN +empresa ⮕ Mapeado para BUSINESS_OWNER +cliente ⮕ Mapeado para EVENT_OWNER +profissional ⮕ Mapeado para PHOTOGRAPHER + +admin@photum.com -> SUPERADMIN +empresa@photum.com -> BUSINESS_OWNER +foto@photum.com -> PHOTOGRAPHER +- **Fluxo de Aprovação**: + - Usuários se registram com status `ativo=false`. + - Admin lista usuários pendentes (`/admin/users/pending`). + - Admin aprova e define a Role (`/admin/users/:id/approve` e `/admin/users/:id/role`). + - Bloqueio de login para usuários inativos. +- **Dados do Usuário**: Adição de campos `Nome`, `Telefone` e `Empresa` no registro e listagens. + +### 2.2. Tabelas Auxiliares (CRUDs) +Criadas tabelas, Services e Handlers para as entidades básicas do sistema: +- `cursos` +- `empresas` +- `anos_formaturas` +- `tipos_eventos` (incluindo tabela de preços por função) +- `tipos_servicos` +- `funcoes_profissionais` + +### 2.3. Módulo Cadastro FOT +Responsável por vincular Turmas a Empresas e Instituições. +- **Tabela**: `cadastro_fot` (Campos: `fot`, `empresa_id`, `curso_id`, `ano_formatura_id`, `instituicao`, `observacoes`, etc.). +- **Backend**: + - Endpoint `POST /api/cadastro-fot`: Criação com verificação de FOT duplicado. + - Endpoint `GET /api/cadastro-fot`: Listagem com _joins_ para trazer nomes de Empresa, Curso, etc. +- **Integração**: Conectado ao `CourseManagement.tsx` no frontend. + +### 2.4. Módulo Agenda +Responsável pelo agendamento de eventos vinculados a um FOT. +- **Tabela**: `agenda` criada com uma estrutura normalizada, mas com campos específicos de evento. + - Vinculada a `cadastro_fot` via `fot_id`. + - Campos de dados do evento: `data_evento`, `local_evento`, `horario`, etc. + - Campos de quantitativos: `qtd_formandos`, `qtd_fotografos`, `qtd_recepcionistas`, etc. + - Campos de controle: `status_profissionais`, `foto_faltante`, `pre_venda`, etc. +- **Backend**: + - Arquivos gerados via SQLC (`internal/db/generated/agenda.sql.go`). + - Service (`internal/agenda/service.go`) e Handler (`internal/agenda/handler.go`) implementados. + - Rotas registradas em `cmd/api/main.go` sob `/api/agenda`. + +### 2.5. Decisões Técnicas Backend +- **Framework**: Gin Gonic. +- **Database**: PostgreSQL com driver `pgx/v5`. +- **Geração de Código**: Utilização do `sqlc` para gerar código Go type-safe a partir de queries SQL puras. +- **Estrutura**: Padrão `internal/domain` (Service/Handler/Repository isolados). + +## 3. Implementações no Frontend (React/Next.js) + +### 3.1. Autenticação e Sessão +- **AuthContext**: Melhoria na persistência de sessão. O frontend valida o token ao carregar e mantêm o usuário logado. +- **Logout**: Implementada função de logout que chama o backend e limpa o estado local. +- **UserApproval**: Interface para aprovação de usuários pendentes, exibindo Nome, E-mail e Empresa. + +### 3.2. Gerenciamento de FOT (`CourseManagement.tsx`) +- **Listagem**: Atualizada para consumir `/api/cadastro-fot` em vez de dados mockados. +- **Formulário de Criação (`FotForm.tsx`)**: + - Modal para criar novo FOT. + - Dropdowns (Selects) conectados dinamicamente às APIs `/api/empresas`, `/api/cursos`, `/api/anos-formaturas`. + - Validação e envio dos dados para o backend. + +### 3.3. API Service +- **`apiService.ts`**: Centralização das chamadas HTTP. Adicionadas funções para buscar cursos, anos de formatura, criar FOT, etc. + +## 4. Próximos Passos Sugeridos +1. **Frontend Agenda**: Criar a interface para visualizar e editar a Agenda (`Calendar` ou `List View`), consumindo os endpoints `/api/agenda` já criados. +2. **Validações Avançadas**: Implementar regras de negócio mais complexas no backend (ex: conflito de horários). +3. **Relatórios**: Gerar PDFs ou Excels baseados nos dados de FOT e Agenda.