406 lines
12 KiB
Go
406 lines
12 KiB
Go
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,
|
|
})
|
|
}
|