package finance import ( "fmt" "net/http" "photum-backend/internal/db/generated" "time" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgtype" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } // Request DTO type TransactionRequest struct { FotID *string `json:"fot_id"` DataCobranca string `json:"data_cobranca"` // YYYY-MM-DD TipoEvento string `json:"tipo_evento"` TipoServico string `json:"tipo_servico"` ProfessionalName string `json:"professional_name"` Whatsapp string `json:"whatsapp"` Cpf string `json:"cpf"` TabelaFree string `json:"tabela_free"` ValorFree float64 `json:"valor_free"` ValorExtra float64 `json:"valor_extra"` DescricaoExtra string `json:"descricao_extra"` DataPagamento *string `json:"data_pagamento"` PgtoOk bool `json:"pgto_ok"` } // Helper: Parse Date String to pgtype.Date func parseDate(dateStr string) pgtype.Date { if dateStr == "" { return pgtype.Date{Valid: false} } t, err := time.Parse("2006-01-02", dateStr) if err != nil { return pgtype.Date{Valid: false} } return pgtype.Date{Time: t, Valid: true} } // Helper: Float to Numeric func floatToNumeric(f float64) pgtype.Numeric { s := fmt.Sprintf("%.2f", f) var n pgtype.Numeric n.Scan(s) return n } // Helper: String UUID to pgtype.UUID func parseUUID(uuidStr *string) pgtype.UUID { if uuidStr == nil || *uuidStr == "" { return pgtype.UUID{Valid: false} } var u pgtype.UUID err := u.Scan(*uuidStr) if err != nil { return pgtype.UUID{Valid: false} } return u } // Helper: String to pgtype.Text func toText(s string) pgtype.Text { return pgtype.Text{String: s, Valid: s != ""} } func (h *Handler) Create(c *gin.Context) { var req TransactionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } params := generated.CreateTransactionParams{ FotID: parseUUID(req.FotID), DataCobranca: parseDate(req.DataCobranca), TipoEvento: toText(req.TipoEvento), TipoServico: toText(req.TipoServico), ProfessionalName: toText(req.ProfessionalName), Whatsapp: toText(req.Whatsapp), Cpf: toText(req.Cpf), TabelaFree: toText(req.TabelaFree), ValorFree: floatToNumeric(req.ValorFree), ValorExtra: floatToNumeric(req.ValorExtra), DescricaoExtra: toText(req.DescricaoExtra), TotalPagar: floatToNumeric(req.ValorFree + req.ValorExtra), PgtoOk: pgtype.Bool{Bool: req.PgtoOk, Valid: true}, } if req.DataPagamento != nil { params.DataPagamento = parseDate(*req.DataPagamento) } txn, err := h.service.Create(c.Request.Context(), params) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, txn) } func (h *Handler) Update(c *gin.Context) { idStr := c.Param("id") var idUUID pgtype.UUID if err := idUUID.Scan(idStr); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) return } var req TransactionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } params := generated.UpdateTransactionParams{ ID: idUUID, FotID: parseUUID(req.FotID), DataCobranca: parseDate(req.DataCobranca), TipoEvento: toText(req.TipoEvento), TipoServico: toText(req.TipoServico), ProfessionalName: toText(req.ProfessionalName), Whatsapp: toText(req.Whatsapp), Cpf: toText(req.Cpf), TabelaFree: toText(req.TabelaFree), ValorFree: floatToNumeric(req.ValorFree), ValorExtra: floatToNumeric(req.ValorExtra), DescricaoExtra: toText(req.DescricaoExtra), TotalPagar: floatToNumeric(req.ValorFree + req.ValorExtra), PgtoOk: pgtype.Bool{Bool: req.PgtoOk, Valid: true}, } if req.DataPagamento != nil { params.DataPagamento = parseDate(*req.DataPagamento) } txn, err := h.service.Update(c.Request.Context(), params) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, txn) } func (h *Handler) Delete(c *gin.Context) { idStr := c.Param("id") var idUUID pgtype.UUID if err := idUUID.Scan(idStr); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) return } if err := h.service.Delete(c.Request.Context(), idUUID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "deleted"}) } func (h *Handler) List(c *gin.Context) { fotIDStr := c.Query("fot_id") if fotIDStr != "" { var fotUUID pgtype.UUID if err := fotUUID.Scan(fotIDStr); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Fot ID"}) return } list, err := h.service.ListByFot(c.Request.Context(), fotUUID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, list) return } // Pagination page := 1 limit := 50 if p := c.Query("page"); p != "" { fmt.Sscanf(p, "%d", &page) } if l := c.Query("limit"); l != "" { fmt.Sscanf(l, "%d", &limit) } // Filters fot := c.Query("fot") data := c.Query("data") evento := c.Query("evento") servico := c.Query("servico") nome := c.Query("nome") list, count, err := h.service.ListPaginated(c.Request.Context(), int32(page), int32(limit), FilterParams{ Fot: fot, Data: data, Evento: evento, Servico: servico, Nome: nome, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "data": list, "total": count, "page": page, "limit": limit, }) } func (h *Handler) AutoFill(c *gin.Context) { fotNumStr := c.Query("fot") if fotNumStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "FOT Number required"}) return } fotData, err := h.service.AutoFillSearch(c.Request.Context(), fotNumStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "FOT not found"}) return } c.JSON(http.StatusOK, fotData) } func (h *Handler) GetFotEvents(c *gin.Context) { fotNumStr := c.Query("fot_id") // Accepts UUID string currently, or we can look up by number if needed. // User has UUID from AutoFill var fotUUID pgtype.UUID if err := fotUUID.Scan(fotNumStr); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid FOT ID"}) return } events, err := h.service.ListFotEvents(c.Request.Context(), fotUUID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, events) } func (h *Handler) SearchProfessionals(c *gin.Context) { query := c.Query("q") // can be empty if function is provided potentially? No, search usually needs text. functionName := c.Query("function") // If function is provided, we might want to list all if query is empty? // For now let's enforce query length if function is missing, or allow empty query if function is present. if query == "" && functionName == "" { c.JSON(http.StatusOK, []interface{}{}) return } if functionName != "" { pros, err := h.service.SearchProfessionalsByFunction(c.Request.Context(), query, functionName) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, pros) return } pros, err := h.service.SearchProfessionals(c.Request.Context(), query) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, pros) } func (h *Handler) GetPrice(c *gin.Context) { eventName := c.Query("event") serviceName := c.Query("service") if eventName == "" || serviceName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Missing event or service parameters"}) return } price, err := h.service.GetStandardPrice(c.Request.Context(), eventName, serviceName) if err != nil { // likely not found, return 0 or 404 c.JSON(http.StatusOK, gin.H{"valor": 0}) return } val, _ := price.Float64Value() c.JSON(http.StatusOK, gin.H{"valor": val.Float64}) } func (h *Handler) SearchFot(c *gin.Context) { query := c.Query("q") if query == "" { c.JSON(http.StatusOK, []interface{}{}) return } results, err := h.service.SearchFot(c.Request.Context(), query) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, results) } func (h *Handler) Import(c *gin.Context) { var items []ImportFinanceItem if err := c.ShouldBindJSON(&items); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON: " + err.Error()}) return } result, err := h.service.Import(c.Request.Context(), items) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, result) }