photum/backend/internal/codigos/service.go
NANDO9322 f8bb2e66dd feat: suporte completo multi-região (SP/MG) e melhorias na validação de importação
Detalhes das alterações:

[Banco de Dados]
- Ajuste nas constraints UNIQUE das tabelas de catálogo (cursos, empresas, tipos_eventos, etc.) para incluir a coluna `regiao`, permitindo dados duplicados entre regiões mas únicos por região.
- Correção crítica na constraint da tabela `precos_tipos_eventos` para evitar conflitos de UPSERT (ON CONFLICT) durante a inicialização.
- Implementação de lógica de Seed para a região 'MG':
  - Clonagem automática de catálogos base de 'SP' para 'MG' (Tipos de Evento, Serviços, etc.).
  - Inserção de tabela de preços específica para 'MG' via script de migração.

[Backend - Go]
- Atualização geral dos Handlers e Services para filtrar dados baseados no cabeçalho `x-regiao`.
- Ajuste no Middleware de autenticação para processar e repassar o contexto da região.
- Correção de queries SQL (geradas pelo sqlc) para suportar os novos filtros regionais.

[Frontend - React]
- Implementação do envio global do cabeçalho `x-regiao` nas requisições da API.
- Correção no componente [PriceTableEditor](cci:1://file:///c:/Projetos/photum/frontend/components/System/PriceTableEditor.tsx:26:0-217:2) para carregar e salvar preços respeitando a região selecionada (fix de "Preços zerados" em MG).
- Refatoração profunda na tela de Importação ([ImportData.tsx](cci:7://file:///c:/Projetos/photum/frontend/pages/ImportData.tsx:0:0-0:0)):
  - Adição de feedback visual detalhado para registros ignorados.
  - Categorização explícita de erros: "CPF Inválido", "Região Incompatível", "Linha Vazia/Separador".
  - Correção na lógica de contagem para considerar linhas vazias explicitamente no relatório final, garantindo que o total bata com o Excel.

[Geral]
- Correção de diversos erros de lint e tipagem TSX.
- Padronização de logs de erro no backend para facilitar debug.
2026-02-05 16:18:40 -03:00

127 lines
2.9 KiB
Go

package codigos
import (
"context"
"time"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
q *generated.Queries
}
func NewService(q *generated.Queries) *Service {
return &Service{
q: q,
}
}
type CreateCodigoInput struct {
Codigo string `json:"codigo"`
Descricao string `json:"descricao"`
ValidadeDias int32 `json:"validade_dias"`
EmpresaID string `json:"empresa_id"`
Regiao string `json:"regiao"` // From context/header
}
func (s *Service) Create(ctx context.Context, input CreateCodigoInput) (generated.CodigosAcesso, error) {
days := input.ValidadeDias
var expiraEm time.Time
if days == -1 {
// Infinite: 100 years
expiraEm = time.Now().AddDate(100, 0, 0)
} else {
if days <= 0 {
days = 30
}
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
}
}
// Default regiao to SP if empty
regiao := input.Regiao
if regiao == "" {
regiao = "SP"
}
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,
Regiao: pgtype.Text{String: regiao, Valid: true},
})
}
func (s *Service) List(ctx context.Context, regiao string) ([]generated.ListCodigosAcessoRow, error) {
if regiao == "" {
regiao = "SP"
}
return s.q.ListCodigosAcesso(ctx, pgtype.Text{String: regiao, Valid: true})
}
func (s *Service) Delete(ctx context.Context, id string) error {
uid, err := uuid.Parse(id)
if err != nil {
return err
}
// Convert uuid.UUID to pgtype.UUID
var pgUUID pgtype.UUID
pgUUID.Bytes = uid
pgUUID.Valid = true
return s.q.DeleteCodigoAcesso(ctx, pgUUID)
}
func (s *Service) GetByCode(ctx context.Context, code string) (generated.GetCodigoAcessoRow, error) {
return s.q.GetCodigoAcesso(ctx, code)
}
func (s *Service) IncrementUse(ctx context.Context, id uuid.UUID) error {
var pgUUID pgtype.UUID
pgUUID.Bytes = id
pgUUID.Valid = true
return s.q.IncrementCodigoAcessoUso(ctx, pgUUID)
}
// Custom error for validation
type AppError struct {
Message string
}
func (e *AppError) Error() string {
return e.Message
}
func (s *Service) Verify(ctx context.Context, code string) (*generated.GetCodigoAcessoRow, error) {
c, err := s.q.GetCodigoAcesso(ctx, code)
if err != nil {
return nil, err // Not found or DB error
}
if !c.Ativo {
return nil, &AppError{Message: "Código inativo"}
}
if time.Now().After(c.ExpiraEm.Time) {
return nil, &AppError{Message: "Código expirado"}
}
return &c, nil
}