From 67a82f21895caf15c084d45a05e4a0c1b8144e0f Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Tue, 3 Feb 2026 11:29:45 -0300 Subject: [PATCH] fix: (codigo-acesso) ajustado filtros --- backend/internal/codigos/handler.go | 23 ++++- backend/internal/codigos/service.go | 25 ++++-- .../db/generated/codigos_acesso.sql.go | 61 ++++++++++--- backend/internal/db/generated/models.go | 1 + .../internal/db/queries/codigos_acesso.sql | 16 ++-- backend/internal/db/schema.sql | 3 +- frontend/pages/ImportData.tsx | 87 +++++++++++++++---- 7 files changed, 169 insertions(+), 47 deletions(-) diff --git a/backend/internal/codigos/handler.go b/backend/internal/codigos/handler.go index 09618f5..b089510 100644 --- a/backend/internal/codigos/handler.go +++ b/backend/internal/codigos/handler.go @@ -54,6 +54,14 @@ func (h *Handler) List(c *gin.Context) { resp := make([]map[string]interface{}, len(codes)) for i, v := range codes { + var empID, empNome string + if v.EmpresaID.Valid { + empID = uuid.UUID(v.EmpresaID.Bytes).String() + } + if v.EmpresaNome.Valid { + empNome = v.EmpresaNome.String + } + resp[i] = map[string]interface{}{ "id": uuid.UUID(v.ID.Bytes).String(), "codigo": v.Codigo, @@ -63,6 +71,8 @@ func (h *Handler) List(c *gin.Context) { "expira_em": v.ExpiraEm.Time, "ativo": v.Ativo, "usos": v.Usos, + "empresa_id": empID, + "empresa_nome": empNome, } } c.JSON(http.StatusOK, resp) @@ -103,7 +113,7 @@ func (h *Handler) Verify(c *gin.Context) { return } - err := h.service.Verify(c.Request.Context(), code) + codeData, err := h.service.Verify(c.Request.Context(), code) if err != nil { // Distinguish validation error from DB error strictly? // For security, just say invalid. @@ -117,5 +127,14 @@ func (h *Handler) Verify(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"valid": true}) + // Prepare response + resp := gin.H{"valid": true} + if codeData.EmpresaID.Valid { + resp["empresa_id"] = uuid.UUID(codeData.EmpresaID.Bytes).String() + } + if codeData.EmpresaNome.Valid { + resp["empresa_nome"] = codeData.EmpresaNome.String + } + + c.JSON(http.StatusOK, resp) } diff --git a/backend/internal/codigos/service.go b/backend/internal/codigos/service.go index 82f3829..e70dc5f 100644 --- a/backend/internal/codigos/service.go +++ b/backend/internal/codigos/service.go @@ -24,6 +24,7 @@ type CreateCodigoInput struct { Codigo string `json:"codigo"` Descricao string `json:"descricao"` ValidadeDias int32 `json:"validade_dias"` + EmpresaID string `json:"empresa_id"` } func (s *Service) Create(ctx context.Context, input CreateCodigoInput) (generated.CodigosAcesso, error) { @@ -40,16 +41,26 @@ func (s *Service) Create(ctx context.Context, input CreateCodigoInput) (generate expiraEm = time.Now().Add(time.Duration(days) * 24 * time.Hour) } + var empresaUUID pgtype.UUID + if input.EmpresaID != "" { + parsed, err := uuid.Parse(input.EmpresaID) + if err == nil { + empresaUUID.Bytes = parsed + empresaUUID.Valid = true + } + } + return s.q.CreateCodigoAcesso(ctx, generated.CreateCodigoAcessoParams{ Codigo: input.Codigo, Descricao: pgtype.Text{String: input.Descricao, Valid: input.Descricao != ""}, ValidadeDias: days, ExpiraEm: pgtype.Timestamptz{Time: expiraEm, Valid: true}, Ativo: true, + EmpresaID: empresaUUID, }) } -func (s *Service) List(ctx context.Context) ([]generated.CodigosAcesso, error) { +func (s *Service) List(ctx context.Context) ([]generated.ListCodigosAcessoRow, error) { return s.q.ListCodigosAcesso(ctx) } @@ -67,7 +78,7 @@ func (s *Service) Delete(ctx context.Context, id string) error { return s.q.DeleteCodigoAcesso(ctx, pgUUID) } -func (s *Service) GetByCode(ctx context.Context, code string) (generated.CodigosAcesso, error) { +func (s *Service) GetByCode(ctx context.Context, code string) (generated.GetCodigoAcessoRow, error) { return s.q.GetCodigoAcesso(ctx, code) } @@ -87,19 +98,19 @@ func (e *AppError) Error() string { return e.Message } -func (s *Service) Verify(ctx context.Context, code string) error { +func (s *Service) Verify(ctx context.Context, code string) (*generated.GetCodigoAcessoRow, error) { c, err := s.q.GetCodigoAcesso(ctx, code) if err != nil { - return err // Not found or DB error + return nil, err // Not found or DB error } if !c.Ativo { - return &AppError{Message: "Código inativo"} + return nil, &AppError{Message: "Código inativo"} } if time.Now().After(c.ExpiraEm.Time) { - return &AppError{Message: "Código expirado"} + return nil, &AppError{Message: "Código expirado"} } - return nil + return &c, nil } diff --git a/backend/internal/db/generated/codigos_acesso.sql.go b/backend/internal/db/generated/codigos_acesso.sql.go index e893de4..c2b5ea2 100644 --- a/backend/internal/db/generated/codigos_acesso.sql.go +++ b/backend/internal/db/generated/codigos_acesso.sql.go @@ -13,11 +13,11 @@ import ( const createCodigoAcesso = `-- name: CreateCodigoAcesso :one INSERT INTO codigos_acesso ( - codigo, descricao, validade_dias, expira_em, ativo + codigo, descricao, validade_dias, expira_em, ativo, empresa_id ) VALUES ( - $1, $2, $3, $4, $5 + $1, $2, $3, $4, $5, $6 ) -RETURNING id, codigo, descricao, validade_dias, criado_em, expira_em, ativo, usos +RETURNING id, codigo, descricao, validade_dias, criado_em, expira_em, ativo, usos, empresa_id ` type CreateCodigoAcessoParams struct { @@ -26,6 +26,7 @@ type CreateCodigoAcessoParams struct { ValidadeDias int32 `json:"validade_dias"` ExpiraEm pgtype.Timestamptz `json:"expira_em"` Ativo bool `json:"ativo"` + EmpresaID pgtype.UUID `json:"empresa_id"` } func (q *Queries) CreateCodigoAcesso(ctx context.Context, arg CreateCodigoAcessoParams) (CodigosAcesso, error) { @@ -35,6 +36,7 @@ func (q *Queries) CreateCodigoAcesso(ctx context.Context, arg CreateCodigoAcesso arg.ValidadeDias, arg.ExpiraEm, arg.Ativo, + arg.EmpresaID, ) var i CodigosAcesso err := row.Scan( @@ -46,6 +48,7 @@ func (q *Queries) CreateCodigoAcesso(ctx context.Context, arg CreateCodigoAcesso &i.ExpiraEm, &i.Ativo, &i.Usos, + &i.EmpresaID, ) return i, err } @@ -61,13 +64,28 @@ func (q *Queries) DeleteCodigoAcesso(ctx context.Context, id pgtype.UUID) error } const getCodigoAcesso = `-- name: GetCodigoAcesso :one -SELECT id, codigo, descricao, validade_dias, criado_em, expira_em, ativo, usos FROM codigos_acesso -WHERE codigo = $1 LIMIT 1 +SELECT c.id, c.codigo, c.descricao, c.validade_dias, c.criado_em, c.expira_em, c.ativo, c.usos, c.empresa_id, e.nome as empresa_nome +FROM codigos_acesso c +LEFT JOIN empresas e ON c.empresa_id = e.id +WHERE c.codigo = $1 LIMIT 1 ` -func (q *Queries) GetCodigoAcesso(ctx context.Context, codigo string) (CodigosAcesso, error) { +type GetCodigoAcessoRow struct { + ID pgtype.UUID `json:"id"` + Codigo string `json:"codigo"` + Descricao pgtype.Text `json:"descricao"` + ValidadeDias int32 `json:"validade_dias"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` + ExpiraEm pgtype.Timestamptz `json:"expira_em"` + Ativo bool `json:"ativo"` + Usos int32 `json:"usos"` + EmpresaID pgtype.UUID `json:"empresa_id"` + EmpresaNome pgtype.Text `json:"empresa_nome"` +} + +func (q *Queries) GetCodigoAcesso(ctx context.Context, codigo string) (GetCodigoAcessoRow, error) { row := q.db.QueryRow(ctx, getCodigoAcesso, codigo) - var i CodigosAcesso + var i GetCodigoAcessoRow err := row.Scan( &i.ID, &i.Codigo, @@ -77,6 +95,8 @@ func (q *Queries) GetCodigoAcesso(ctx context.Context, codigo string) (CodigosAc &i.ExpiraEm, &i.Ativo, &i.Usos, + &i.EmpresaID, + &i.EmpresaNome, ) return i, err } @@ -93,19 +113,34 @@ func (q *Queries) IncrementCodigoAcessoUso(ctx context.Context, id pgtype.UUID) } const listCodigosAcesso = `-- name: ListCodigosAcesso :many -SELECT id, codigo, descricao, validade_dias, criado_em, expira_em, ativo, usos FROM codigos_acesso -ORDER BY criado_em DESC +SELECT c.id, c.codigo, c.descricao, c.validade_dias, c.criado_em, c.expira_em, c.ativo, c.usos, c.empresa_id, e.nome as empresa_nome +FROM codigos_acesso c +LEFT JOIN empresas e ON c.empresa_id = e.id +ORDER BY c.criado_em DESC ` -func (q *Queries) ListCodigosAcesso(ctx context.Context) ([]CodigosAcesso, error) { +type ListCodigosAcessoRow struct { + ID pgtype.UUID `json:"id"` + Codigo string `json:"codigo"` + Descricao pgtype.Text `json:"descricao"` + ValidadeDias int32 `json:"validade_dias"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` + ExpiraEm pgtype.Timestamptz `json:"expira_em"` + Ativo bool `json:"ativo"` + Usos int32 `json:"usos"` + EmpresaID pgtype.UUID `json:"empresa_id"` + EmpresaNome pgtype.Text `json:"empresa_nome"` +} + +func (q *Queries) ListCodigosAcesso(ctx context.Context) ([]ListCodigosAcessoRow, error) { rows, err := q.db.Query(ctx, listCodigosAcesso) if err != nil { return nil, err } defer rows.Close() - var items []CodigosAcesso + var items []ListCodigosAcessoRow for rows.Next() { - var i CodigosAcesso + var i ListCodigosAcessoRow if err := rows.Scan( &i.ID, &i.Codigo, @@ -115,6 +150,8 @@ func (q *Queries) ListCodigosAcesso(ctx context.Context) ([]CodigosAcesso, error &i.ExpiraEm, &i.Ativo, &i.Usos, + &i.EmpresaID, + &i.EmpresaNome, ); err != nil { return nil, err } diff --git a/backend/internal/db/generated/models.go b/backend/internal/db/generated/models.go index 53e8dfc..eb16408 100644 --- a/backend/internal/db/generated/models.go +++ b/backend/internal/db/generated/models.go @@ -135,6 +135,7 @@ type CodigosAcesso struct { ExpiraEm pgtype.Timestamptz `json:"expira_em"` Ativo bool `json:"ativo"` Usos int32 `json:"usos"` + EmpresaID pgtype.UUID `json:"empresa_id"` } type Curso struct { diff --git a/backend/internal/db/queries/codigos_acesso.sql b/backend/internal/db/queries/codigos_acesso.sql index b84808e..a37b876 100644 --- a/backend/internal/db/queries/codigos_acesso.sql +++ b/backend/internal/db/queries/codigos_acesso.sql @@ -1,22 +1,26 @@ -- name: CreateCodigoAcesso :one INSERT INTO codigos_acesso ( - codigo, descricao, validade_dias, expira_em, ativo + codigo, descricao, validade_dias, expira_em, ativo, empresa_id ) VALUES ( - $1, $2, $3, $4, $5 + $1, $2, $3, $4, $5, $6 ) RETURNING *; -- name: ListCodigosAcesso :many -SELECT * FROM codigos_acesso -ORDER BY criado_em DESC; +SELECT c.*, e.nome as empresa_nome +FROM codigos_acesso c +LEFT JOIN empresas e ON c.empresa_id = e.id +ORDER BY c.criado_em DESC; -- name: DeleteCodigoAcesso :exec DELETE FROM codigos_acesso WHERE id = $1; -- name: GetCodigoAcesso :one -SELECT * FROM codigos_acesso -WHERE codigo = $1 LIMIT 1; +SELECT c.*, e.nome as empresa_nome +FROM codigos_acesso c +LEFT JOIN empresas e ON c.empresa_id = e.id +WHERE c.codigo = $1 LIMIT 1; -- name: IncrementCodigoAcessoUso :exec UPDATE codigos_acesso diff --git a/backend/internal/db/schema.sql b/backend/internal/db/schema.sql index b0056cd..9786ac5 100644 --- a/backend/internal/db/schema.sql +++ b/backend/internal/db/schema.sql @@ -440,7 +440,8 @@ CREATE TABLE IF NOT EXISTS codigos_acesso ( criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), expira_em TIMESTAMPTZ NOT NULL, ativo BOOLEAN NOT NULL DEFAULT TRUE, - usos INT NOT NULL DEFAULT 0 + usos INT NOT NULL DEFAULT 0, + empresa_id UUID REFERENCES empresas(id) ON DELETE SET NULL ); -- Financeiro Extrato diff --git a/frontend/pages/ImportData.tsx b/frontend/pages/ImportData.tsx index e883200..0be090c 100644 --- a/frontend/pages/ImportData.tsx +++ b/frontend/pages/ImportData.tsx @@ -376,26 +376,75 @@ export const ImportData: React.FC = () => { if (activeTab === 'fot') { - const fot = getStr(0); - if (!fot) continue; - // Parse Gastos - let gastosStr = getStr(8); - gastosStr = gastosStr.replace(/[R$\s.]/g, '').replace(',', '.'); - const gastos = parseFloat(gastosStr) || 0; + // Dynamic Mapping for FOT + let headerIdx = -1; + const colMap: {[key: string]: number} = {}; + + // 1. Find header row + for (let i = 0; i < 20 && i < rows.length; i++) { + const rowStrings = (rows[i] as any[]).map(c => String(c).toLowerCase().trim()); + if (rowStrings.some(s => s === 'fot' || s === 'empresa' || s.includes('contrato'))) { + headerIdx = i; + rowStrings.forEach((h, idx) => { + if (h === 'fot' || h.includes('contrato')) colMap['fot'] = idx; + else if (h === 'empresa' || h.includes('cliente')) colMap['empresa'] = idx; + else if (h.includes('curso')) colMap['curso'] = idx; + else if (h.includes('obs') || h.includes('observa')) colMap['obs'] = idx; + else if (h.includes('institui')) colMap['instituicao'] = idx; + else if (h.includes('ano') || h.includes('formatur')) colMap['ano'] = idx; + else if (h === 'cidade') colMap['cidade'] = idx; + else if (h === 'estado' || h === 'uf') colMap['estado'] = idx; + else if (h.includes('gasto') || h.includes('capta')) colMap['gastos'] = idx; + else if (h.includes('pré') || h.includes('pre') || h.includes('venda')) colMap['prevenda'] = idx; + }); + break; + } + } - const item: ImportFotInput = { - fot: fot, - empresa_nome: getStr(1), - curso_nome: getStr(2), - observacoes: getStr(3), - instituicao: getStr(4), - ano_formatura_label: getStr(5), - cidade: getStr(6), - estado: getStr(7), - gastos_captacao: gastos, - pre_venda: getStr(9).toLowerCase().includes('sim'), - }; - mappedData.push(item); + if (headerIdx === -1) { + // Fallback to indices if not found (Legacy/Default) + headerIdx = 0; + // Default: FOT, Empresa, Curso, Obs, Inst, Ano, Cid, UF, Gastos, Prevenda + colMap['fot'] = 0; colMap['empresa'] = 1; colMap['curso'] = 2; colMap['obs'] = 3; + colMap['instituicao'] = 4; colMap['ano'] = 5; colMap['cidade'] = 6; colMap['estado'] = 7; + colMap['gastos'] = 8; colMap['prevenda'] = 9; + } + + // Iterate + for (let i = headerIdx + 1; i < rows.length; i++) { + const row = rows[i] as any[]; + if (!row || row.length === 0) continue; + + const getCol = (key: string) => { + const idx = colMap[key]; + if (idx === undefined || idx < 0) return ""; + return row[idx] !== undefined ? String(row[idx]).trim() : ""; + }; + + const fot = getCol('fot'); + + // Strict validation: Must have at least 3 chars to be valid FOT (usually 5 digits) + if (!fot || fot.length < 3) continue; + + // Parse Gastos + let gastosStr = getCol('gastos'); + gastosStr = gastosStr.replace(/[R$\s.]/g, '').replace(',', '.'); + const gastos = parseFloat(gastosStr) || 0; + + const item: ImportFotInput = { + fot: fot, + empresa_nome: getCol('empresa'), + curso_nome: getCol('curso'), + observacoes: getCol('obs'), + instituicao: getCol('instituicao'), + ano_formatura_label: getCol('ano'), + cidade: getCol('cidade'), + estado: getCol('estado'), + gastos_captacao: gastos, + pre_venda: getCol('prevenda').toLowerCase().includes('sim'), + }; + mappedData.push(item); + } } else if (activeTab === 'agenda') { const fot = getStr(0);