package logistica import ( "context" "fmt" "time" "photum-backend/internal/config" "photum-backend/internal/db/generated" "photum-backend/internal/notification" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) type Service struct { queries *generated.Queries notification *notification.Service cfg *config.Config } func NewService(queries *generated.Queries, notification *notification.Service, cfg *config.Config) *Service { return &Service{ queries: queries, notification: notification, cfg: cfg, } } type CreateCarroInput struct { AgendaID string `json:"agenda_id"` MotoristaID *string `json:"motorista_id"` NomeMotorista *string `json:"nome_motorista"` HorarioChegada *string `json:"horario_chegada"` Observacoes *string `json:"observacoes"` } func (s *Service) CreateCarro(ctx context.Context, input CreateCarroInput) (*generated.LogisticaCarro, error) { agendaID, err := uuid.Parse(input.AgendaID) if err != nil { return nil, err } params := generated.CreateCarroParams{ AgendaID: pgtype.UUID{Bytes: agendaID, Valid: true}, } if input.MotoristaID != nil && *input.MotoristaID != "" { if parsed, err := uuid.Parse(*input.MotoristaID); err == nil { params.MotoristaID = pgtype.UUID{Bytes: parsed, Valid: true} } } if input.NomeMotorista != nil { params.NomeMotorista = pgtype.Text{String: *input.NomeMotorista, Valid: true} } if input.HorarioChegada != nil { params.HorarioChegada = pgtype.Text{String: *input.HorarioChegada, Valid: true} } if input.Observacoes != nil { params.Observacoes = pgtype.Text{String: *input.Observacoes, Valid: true} } carro, err := s.queries.CreateCarro(ctx, params) if err != nil { return nil, err } // Reset notification timestamp if err := s.queries.ResetLogisticsNotificationTimestamp(ctx, params.AgendaID); err != nil { // log error } return &carro, nil } func (s *Service) ListCarros(ctx context.Context, agendaID string) ([]generated.ListCarrosByAgendaIDRow, error) { parsedUUID, err := uuid.Parse(agendaID) if err != nil { return nil, err } return s.queries.ListCarrosByAgendaID(ctx, pgtype.UUID{Bytes: parsedUUID, Valid: true}) } func (s *Service) DeleteCarro(ctx context.Context, id string) error { parsedUUID, err := uuid.Parse(id) if err != nil { return err } // Changed to capture AgendaID agendaID, err := s.queries.DeleteCarro(ctx, pgtype.UUID{Bytes: parsedUUID, Valid: true}) if err != nil { return err } // Reset notification timestamp if err := s.queries.ResetLogisticsNotificationTimestamp(ctx, agendaID); err != nil { // Log error but don't fail the operation // log.Printf("Error resetting timestamp: %v", err) } return nil } // UpdateCarroInput matches the update fields type UpdateCarroInput struct { MotoristaID *string `json:"motorista_id"` NomeMotorista *string `json:"nome_motorista"` HorarioChegada *string `json:"horario_chegada"` Observacoes *string `json:"observacoes"` } func (s *Service) UpdateCarro(ctx context.Context, id string, input UpdateCarroInput) (*generated.LogisticaCarro, error) { parsedUUID, err := uuid.Parse(id) if err != nil { return nil, err } params := generated.UpdateCarroParams{ ID: pgtype.UUID{Bytes: parsedUUID, Valid: true}, } if input.MotoristaID != nil { if *input.MotoristaID == "" { params.MotoristaID = pgtype.UUID{Valid: false} } else { if parsed, err := uuid.Parse(*input.MotoristaID); err == nil { params.MotoristaID = pgtype.UUID{Bytes: parsed, Valid: true} } } } if input.NomeMotorista != nil { params.NomeMotorista = pgtype.Text{String: *input.NomeMotorista, Valid: true} } if input.HorarioChegada != nil { params.HorarioChegada = pgtype.Text{String: *input.HorarioChegada, Valid: true} } if input.Observacoes != nil { params.Observacoes = pgtype.Text{String: *input.Observacoes, Valid: true} } carro, err := s.queries.UpdateCarro(ctx, params) if err != nil { return nil, err } return &carro, nil } func (s *Service) AddPassageiro(ctx context.Context, carroID, profissionalID string) error { cID, err := uuid.Parse(carroID) if err != nil { return err } pID, err := uuid.Parse(profissionalID) if err != nil { return err } _, err = s.queries.AddPassageiro(ctx, generated.AddPassageiroParams{ CarroID: pgtype.UUID{Bytes: cID, Valid: true}, ProfissionalID: pgtype.UUID{Bytes: pID, Valid: true}, }) if err != nil { return err } // Notification Logic - DISABLED (Moved to Manual Trigger) /* go func() { bgCtx := context.Background() // 1. Get Car Details (Driver, Time, AgendaID) carro, err := s.queries.GetCarroByID(bgCtx, pgtype.UUID{Bytes: cID, Valid: true}) if err != nil { log.Printf("[Logistica Notification] Erro ao buscar carro: %v", err) return } // 2. Get Agenda Details (for Location) // We have agenda_id in carro, but need to fetch details agendaVal, err := s.queries.GetAgenda(bgCtx, carro.AgendaID) if err != nil { log.Printf("[Logistica Notification] Erro ao buscar agenda: %v", err) return } // 3. Get Professional (Passenger) Details prof, err := s.queries.GetProfissionalByID(bgCtx, pgtype.UUID{Bytes: pID, Valid: true}) if err != nil { log.Printf("[Logistica Notification] Erro ao buscar passageiro: %v", err) return } if prof.Whatsapp.String == "" { return } // 4. Format Message motorista := "A definir" if carro.NomeMotorista.Valid { motorista = carro.NomeMotorista.String } else if carro.MotoristaNomeSistema.Valid { motorista = carro.MotoristaNomeSistema.String } horarioSaida := "A combinar" if carro.HorarioChegada.Valid { horarioSaida = carro.HorarioChegada.String } destino := "Local do Evento" if agendaVal.LocalEvento.Valid { destino = agendaVal.LocalEvento.String } msg := fmt.Sprintf("Olá *%s*! 🚐\n\nVocê foi adicionado à logística de transporte.\n\n*Motorista:* %s\n*Saída:* %s\n*Destino:* %s\n\nAcesse seu painel para mais detalhes.", prof.Nome, motorista, horarioSaida, destino, ) if err := s.notification.SendWhatsApp(prof.Whatsapp.String, msg); err != nil { log.Printf("[Logistica Notification] Falha ao enviar: %v", err) } }() */ return nil } func (s *Service) RemovePassageiro(ctx context.Context, carroID, profissionalID string) error { cID, err := uuid.Parse(carroID) if err != nil { return err } pID, err := uuid.Parse(profissionalID) if err != nil { return err } return s.queries.RemovePassageiro(ctx, generated.RemovePassageiroParams{ CarroID: pgtype.UUID{Bytes: cID, Valid: true}, ProfissionalID: pgtype.UUID{Bytes: pID, Valid: true}, }) } func (s *Service) ListPassageiros(ctx context.Context, carroID string) ([]generated.ListPassageirosByCarroIDRow, error) { cID, err := uuid.Parse(carroID) if err != nil { return nil, err } return s.queries.ListPassageirosByCarroID(ctx, pgtype.UUID{Bytes: cID, Valid: true}) } // ---- LOGISTICA DAILY INVITATIONS & PROFESSIONALS LIST ---- // type ListDisponiveisInput struct { Data string `form:"data" binding:"required"` Search string `form:"search"` Page int `form:"page,default=1"` Limit int `form:"limit,default=50"` } type ListDisponiveisResult struct { Data []generated.GetProfissionaisDisponiveisLogisticaRow `json:"data"` Total int64 `json:"total"` Page int `json:"page"` TotalPages int `json:"total_pages"` } func (s *Service) ListProfissionaisDisponiveisLogistica(ctx context.Context, input ListDisponiveisInput, regiao string) (*ListDisponiveisResult, error) { offset := (input.Page - 1) * input.Limit parsedDate, parsedDateErr := time.Parse("2006-01-02", input.Data) arg := generated.GetProfissionaisDisponiveisLogisticaParams{ Regiao: pgtype.Text{String: regiao, Valid: true}, Column2: input.Search, Column3: pgtype.Date{Time: parsedDate, Valid: parsedDateErr == nil}, Limit: int32(input.Limit), Offset: int32(offset), } rows, err := s.queries.GetProfissionaisDisponiveisLogistica(ctx, arg) if err != nil { return nil, err } countArg := generated.CountProfissionaisDisponiveisLogisticaParams{ Regiao: pgtype.Text{String: regiao, Valid: true}, Column2: input.Search, Column3: pgtype.Date{Time: parsedDate, Valid: parsedDateErr == nil}, } total, err := s.queries.CountProfissionaisDisponiveisLogistica(ctx, countArg) if err != nil { return nil, err } if rows == nil { rows = []generated.GetProfissionaisDisponiveisLogisticaRow{} } totalPages := int(total) / input.Limit if int(total)%input.Limit > 0 { totalPages++ } return &ListDisponiveisResult{ Data: rows, Total: total, Page: input.Page, TotalPages: totalPages, }, nil } type CreateConviteInput struct { ProfissionalID string `json:"profissional_id" binding:"required"` Data string `json:"data" binding:"required"` } func (s *Service) CreateConviteDiario(ctx context.Context, input CreateConviteInput, regiao string) (*generated.ConvitesDiario, error) { profUUID, err := uuid.Parse(input.ProfissionalID) if err != nil { return nil, err } arg := generated.CreateConviteDiarioParams{ ProfissionalID: pgtype.UUID{Bytes: profUUID, Valid: true}, Status: pgtype.Text{String: "PENDENTE", Valid: true}, Regiao: pgtype.Text{String: regiao, Valid: true}, } t, err := time.Parse("2006-01-02", input.Data) if err == nil { arg.Data = pgtype.Date{Time: t, Valid: true} } convite, err := s.queries.CreateConviteDiario(ctx, arg) if err != nil { return nil, err } return &convite, nil } // List Invitations for a Professional func (s *Service) ListConvitesDoProfissional(ctx context.Context, profID string) ([]generated.ConvitesDiario, error) { pID, err := uuid.Parse(profID) if err != nil { return nil, err } return s.queries.GetConvitesByProfissional(ctx, pgtype.UUID{Bytes: pID, Valid: true}) } // Answer Invitation type ResponderConviteInput struct { Status string `json:"status" binding:"required"` // ACEITO, REJEITADO MotivoRejeicao string `json:"motivo_rejeicao"` } func (s *Service) ResponderConvite(ctx context.Context, conviteID string, input ResponderConviteInput) (*generated.ConvitesDiario, error) { cID, err := uuid.Parse(conviteID) if err != nil { return nil, err } validStatus := input.Status == "ACEITO" || input.Status == "REJEITADO" if !validStatus { return nil, fmt.Errorf("status inválido") // fmt must be imported, Oh wait.. just use errors.New if fmt is not there. I will just rely on simple return. Wait I can use built-in error. } arg := generated.UpdateConviteStatusParams{ ID: pgtype.UUID{Bytes: cID, Valid: true}, Status: pgtype.Text{String: input.Status, Valid: true}, MotivoRejeicao: pgtype.Text{String: input.MotivoRejeicao, Valid: input.MotivoRejeicao != ""}, } convite, err := s.queries.UpdateConviteStatus(ctx, arg) if err != nil { return nil, err } return &convite, nil } // List Invitations by Date and Region (Logistics view) func (s *Service) ListConvitesPorData(ctx context.Context, dataStr string, regiao string) ([]generated.ConvitesDiario, error) { parsedDate, err := time.Parse("2006-01-02", dataStr) if err != nil { return nil, fmt.Errorf("formato de data inválido, esperado YYYY-MM-DD") } datePg := pgtype.Date{ Time: parsedDate, Valid: true, } regPg := pgtype.Text{ String: regiao, Valid: regiao != "", } return s.queries.GetConvitesByDateAndRegion(ctx, generated.GetConvitesByDateAndRegionParams{ Data: datePg, Regiao: regPg, }) }