From 95a4e441c1379b4b5cba24ded1aa05c005e13b7c Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Wed, 11 Feb 2026 10:20:46 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20corre=C3=A7=C3=A3o=20de=20duplicidade=20?= =?UTF-8?q?de=20pre=C3=A7os=20e=20melhorias=20na=20UX=20financeira?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: - Ajustada query `GetStandardPrice` para filtrar por região e ordenar por data. - Corrigido `SetPrice` para usar o contexto de região, evitando duplicatas. - Script de limpeza executado para remover entradas duplicadas no banco. - Frontend (Financeiro): - Reset completo do formulário ao abrir "Nova Transação" (limpa busca FOT e eventos). - Preenchimento automático da "Data Evento" ao selecionar um evento encontrado pela busca FOT. - Correção na lógica de busca de preço para usar nome da Função (`tabelaFree`). --- .../db/generated/tipos_eventos.sql.go | 6 +- backend/internal/db/queries/tipos_eventos.sql | 6 +- backend/internal/tipos_eventos/handler.go | 3 +- backend/internal/tipos_eventos/service.go | 3 +- frontend/pages/Finance.tsx | 61 +++++++++++++++++-- 5 files changed, 69 insertions(+), 10 deletions(-) diff --git a/backend/internal/db/generated/tipos_eventos.sql.go b/backend/internal/db/generated/tipos_eventos.sql.go index d6f3d1b..541f763 100644 --- a/backend/internal/db/generated/tipos_eventos.sql.go +++ b/backend/internal/db/generated/tipos_eventos.sql.go @@ -118,7 +118,11 @@ SELECT p.valor FROM precos_tipos_eventos p JOIN tipos_eventos te ON p.tipo_evento_id = te.id JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id -WHERE te.nome ILIKE $1 AND f.nome ILIKE $2 AND te.regiao = $3 +WHERE te.nome ILIKE $1 + AND f.nome ILIKE $2 + AND te.regiao = $3 + AND p.regiao = $3 +ORDER BY p.criado_em DESC LIMIT 1 ` diff --git a/backend/internal/db/queries/tipos_eventos.sql b/backend/internal/db/queries/tipos_eventos.sql index 2a800b3..7be3601 100644 --- a/backend/internal/db/queries/tipos_eventos.sql +++ b/backend/internal/db/queries/tipos_eventos.sql @@ -37,7 +37,11 @@ SELECT p.valor FROM precos_tipos_eventos p JOIN tipos_eventos te ON p.tipo_evento_id = te.id JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id -WHERE te.nome ILIKE $1 AND f.nome ILIKE $2 AND te.regiao = @regiao +WHERE te.nome ILIKE $1 + AND f.nome ILIKE $2 + AND te.regiao = @regiao + AND p.regiao = @regiao +ORDER BY p.criado_em DESC LIMIT 1; -- name: GetTipoEventoByNome :one diff --git a/backend/internal/tipos_eventos/handler.go b/backend/internal/tipos_eventos/handler.go index f0c6278..71b55e6 100644 --- a/backend/internal/tipos_eventos/handler.go +++ b/backend/internal/tipos_eventos/handler.go @@ -137,7 +137,8 @@ func (h *Handler) SetPrice(c *gin.Context) { return } - _, err := h.service.SetPrice(c.Request.Context(), req) + regiao := c.GetString("regiao") + _, err := h.service.SetPrice(c.Request.Context(), req, regiao) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/backend/internal/tipos_eventos/service.go b/backend/internal/tipos_eventos/service.go index a002fd4..f271652 100644 --- a/backend/internal/tipos_eventos/service.go +++ b/backend/internal/tipos_eventos/service.go @@ -86,7 +86,7 @@ type PriceInput struct { Valor float64 `json:"valor"` } -func (s *Service) SetPrice(ctx context.Context, input PriceInput) (*generated.PrecosTiposEvento, error) { +func (s *Service) SetPrice(ctx context.Context, input PriceInput, regiao string) (*generated.PrecosTiposEvento, error) { eventoUUID, err := uuid.Parse(input.TipoEventoID) if err != nil { return nil, errors.New("invalid tipo_evento_id") @@ -100,6 +100,7 @@ func (s *Service) SetPrice(ctx context.Context, input PriceInput) (*generated.Pr TipoEventoID: pgtype.UUID{Bytes: eventoUUID, Valid: true}, FuncaoProfissionalID: pgtype.UUID{Bytes: funcaoUUID, Valid: true}, Valor: toPgNumeric(input.Valor), + Regiao: pgtype.Text{String: regiao, Valid: true}, }) if err != nil { return nil, err diff --git a/frontend/pages/Finance.tsx b/frontend/pages/Finance.tsx index edee60e..0c685ac 100644 --- a/frontend/pages/Finance.tsx +++ b/frontend/pages/Finance.tsx @@ -526,7 +526,7 @@ const Finance: React.FC = () => { setFormData(prev => ({ ...prev, tipoEvento: ev.tipo_evento_nome, - // If event has date, we could pre-fill? User request suggests keeping it flexible or maybe they didn't ask explicitly. + data: ev.data_evento ? ev.data_evento.split("T")[0] : prev.data, })); setShowEventSelector(false); }; @@ -733,12 +733,59 @@ const Finance: React.FC = () => { }; // Calculations - // Calculations + // Calculations + useEffect(() => { + const vFree = Number(formData.valorFree) || 0; + const vExtra = Number(formData.valorExtra) || 0; + setFormData((prev) => ({ ...prev, totalPagar: vFree + vExtra })); + }, [formData.valorFree, formData.valorExtra]); + + // Fetch Price on Event/Service/Function Change useEffect(() => { - const vFree = Number(formData.valorFree) || 0; - const vExtra = Number(formData.valorExtra) || 0; - setFormData((prev) => ({ ...prev, totalPagar: vFree + vExtra })); - }, [formData.valorFree, formData.valorExtra]); + if (isEditInitializing.current) { + isEditInitializing.current = false; + return; + } + + // Use Tabela Free (Function) if available, otherwise Tipo Servico + const serviceParam = formData.tabelaFree || formData.tipoServico; + + if (!formData.tipoEvento || !serviceParam) return; + + const fetchPrice = async () => { + const token = localStorage.getItem("token"); + if (!token) return; + + try { + // Use the new endpoint + const res = await fetch(`${API_BASE_URL}/api/finance/price?event=${encodeURIComponent(formData.tipoEvento!)}&service=${encodeURIComponent(serviceParam)}`, { + headers: { + "Authorization": `Bearer ${token}`, + "x-regiao": localStorage.getItem("photum_selected_region") || "SP" + } + }); + + if (res.ok) { + const data = await res.json(); + // Update Valor Free if different + // Check if data.valor is a number + const price = Number(data.valor); + if (!isNaN(price) && price !== formData.valorFree) { + setFormData(prev => ({ ...prev, valorFree: price })); + } + } + } catch (err) { + console.error("Error fetching price:", err); + } + }; + + // Debounce slightly or just call + const timeoutId = setTimeout(() => { + fetchPrice(); + }, 300); + return () => clearTimeout(timeoutId); + + }, [formData.tipoEvento, formData.tipoServico, formData.tabelaFree]); const handleExportCSV = () => { if (sortedTransactions.length === 0) { @@ -866,6 +913,8 @@ const Finance: React.FC = () => { pgtoOk: false, }); setFotFound(false); + setFotQuery(""); // Clear FOT search query + setFotEvents([]); // Clear found events setProFunctions([]); // Clear professional functions setSelectedTransaction(null); // Ensure no transaction is selected for add setShowAddModal(true);