photum/backend/internal/finance/handler.go

389 lines
10 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"`
}
type BulkUpdateRequest struct {
IDs []string `json:"ids"`
ValorExtra float64 `json:"valor_extra"`
DescricaoExtra string `json:"descricao_extra"`
}
func (h *Handler) BulkUpdate(c *gin.Context) {
var req BulkUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
regiao := c.GetString("regiao")
err := h.service.BulkUpdate(c.Request.Context(), req.IDs, req.ValorExtra, req.DescricaoExtra, regiao)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "updated successfully"})
}
// 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)
}
regiao := c.GetString("regiao")
txn, err := h.service.Create(c.Request.Context(), params, regiao)
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)
}
regiao := c.GetString("regiao")
txn, err := h.service.Update(c.Request.Context(), params, regiao)
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
}
regiao := c.GetString("regiao")
if err := h.service.Delete(c.Request.Context(), idUUID, regiao); 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
}
regiao := c.GetString("regiao")
list, err := h.service.ListByFot(c.Request.Context(), fotUUID, regiao)
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")
curso := c.Query("curso")
instituicao := c.Query("instituicao")
ano := c.Query("ano")
empresa := c.Query("empresa")
startDate := c.Query("startDate")
endDate := c.Query("endDate")
includeWeekendsStr := c.Query("includeWeekends")
includeWeekends := true
if includeWeekendsStr == "false" {
includeWeekends = false
}
regiao := c.GetString("regiao")
list, count, err := h.service.ListPaginated(c.Request.Context(), int32(page), int32(limit), FilterParams{
Fot: fot,
Data: data,
Evento: evento,
Servico: servico,
Nome: nome,
Curso: curso,
Instituicao: instituicao,
Ano: ano,
Empresa: empresa,
StartDate: startDate,
EndDate: endDate,
IncludeWeekends: includeWeekends,
}, regiao)
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
}
regiao := c.GetString("regiao")
fotData, err := h.service.AutoFillSearch(c.Request.Context(), fotNumStr, regiao)
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
}
regiao := c.GetString("regiao")
events, err := h.service.ListFotEvents(c.Request.Context(), fotUUID, regiao)
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
}
regiao := c.GetString("regiao")
if functionName != "" {
pros, err := h.service.SearchProfessionalsByFunction(c.Request.Context(), query, functionName, regiao)
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, regiao)
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
}
regiao := c.GetString("regiao")
price, err := h.service.GetStandardPrice(c.Request.Context(), eventName, serviceName, regiao)
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
}
regiao := c.GetString("regiao")
results, err := h.service.SearchFot(c.Request.Context(), query, regiao)
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
}
regiao := c.GetString("regiao")
result, err := h.service.Import(c.Request.Context(), items, regiao)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}