Backend: - Migration 007: alterada coluna `fot` de INTEGER para VARCHAR(50). - Ajustados serviços (finance, agenda) e handlers para processar FOT como string. - Regenerados modelos e queries do banco de dados (sqlc). Frontend: - [FotForm](cci:1://file:///c:/Projetos/photum/frontend/components/FotForm.tsx:13:0-348:2): Permitido input de texto/alfanumérico (ex: "20000MG"). - [EventTable](cci:1://file:///c:/Projetos/photum/frontend/components/EventTable.tsx:29:0-684:2): Removido bloqueio do botão "Aprovar" para equipes incompletas. - [Dashboard](cci:1://file:///c:/Projetos/photum/frontend/pages/Dashboard.tsx:31:0-1749:2): Corrigida duplicação do campo "Qtd Formandos". - [Dashboard](cci:1://file:///c:/Projetos/photum/frontend/pages/Dashboard.tsx:31:0-1749:2): Filtros de "Gerenciar Equipe" agora usam funções dinâmicas (IDs) em vez de valores fixos. - `Navbar`: Logo agora redireciona corretamente para `/painel`.
293 lines
7.9 KiB
Go
293 lines
7.9 KiB
Go
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
|
|
}
|
|
|
|
list, err := h.service.ListAll(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
|
|
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)
|
|
}
|