feat: improve logistics notification persistence and finance grouping
This commit is contained in:
parent
c1af6eb8b4
commit
6b9299dd7a
18 changed files with 636 additions and 361 deletions
|
|
@ -210,7 +210,7 @@ func main() {
|
|||
api.GET("/agenda/:id", agendaHandler.Get)
|
||||
api.GET("/agenda/:id/professionals", agendaHandler.GetProfessionals)
|
||||
api.GET("/agenda/:id/available", agendaHandler.ListAvailableProfessionals)
|
||||
|
||||
|
||||
// Agenda routes - write access (blocked for AGENDA_VIEWER)
|
||||
api.POST("/agenda", auth.RequireWriteAccess(), agendaHandler.Create)
|
||||
api.PUT("/agenda/:id", auth.RequireWriteAccess(), agendaHandler.Update)
|
||||
|
|
@ -220,6 +220,7 @@ func main() {
|
|||
api.PATCH("/agenda/:id/professionals/:profId/status", auth.RequireWriteAccess(), agendaHandler.UpdateAssignmentStatus)
|
||||
api.PATCH("/agenda/:id/professionals/:profId/position", auth.RequireWriteAccess(), agendaHandler.UpdateAssignmentPosition)
|
||||
api.PATCH("/agenda/:id/status", auth.RequireWriteAccess(), agendaHandler.UpdateStatus)
|
||||
api.POST("/agenda/:id/notify-logistics", auth.RequireWriteAccess(), agendaHandler.NotifyLogistics)
|
||||
|
||||
api.POST("/availability", availabilityHandler.SetAvailability)
|
||||
api.GET("/availability", availabilityHandler.ListAvailability)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package agenda
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
|
|
@ -416,3 +417,34 @@ func (h *Handler) GetProfessionalFinancialStatement(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, statement)
|
||||
}
|
||||
|
||||
// NotifyLogistics godoc
|
||||
// @Summary Send logistics notification to all professionals
|
||||
// @Tags agenda
|
||||
// @Router /api/agenda/{id}/notify-logistics [post]
|
||||
func (h *Handler) NotifyLogistics(c *gin.Context) {
|
||||
idParam := c.Param("id")
|
||||
agendaID, err := uuid.Parse(idParam)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"})
|
||||
return
|
||||
}
|
||||
|
||||
// Async or Sync? User wants visual feedback "Liberar o gatilho".
|
||||
// We can do it sync to return "Success" only after initiation, or async.
|
||||
// Service.NotifyLogistics is currently synchronous (except the internal async parts? No, I copied it as sync loop).
|
||||
// Wait, I removed the `go func()` wrapper in the extraction, so the loop runs in the caller's goroutine.
|
||||
// But `UpdateStatus` calls it with `go s.NotifyLogistics(...)`.
|
||||
// For this endpoint, we might want to return quickly.
|
||||
// But returning success implies "Notification Sent".
|
||||
// Let's run it in background for speed.
|
||||
|
||||
var req struct {
|
||||
PassengerOrders map[string]map[string]int `json:"passenger_orders"`
|
||||
}
|
||||
_ = c.ShouldBindJSON(&req)
|
||||
|
||||
go h.service.NotifyLogistics(context.Background(), agendaID, req.PassengerOrders)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Notificação de logística iniciada com sucesso."})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"photum-backend/internal/config"
|
||||
|
|
@ -60,6 +62,7 @@ type Assignment struct {
|
|||
ProfessionalID string `json:"professional_id"`
|
||||
Status string `json:"status"`
|
||||
MotivoRejeicao *string `json:"motivo_rejeicao"`
|
||||
FuncaoID *string `json:"funcao_id"`
|
||||
}
|
||||
|
||||
type AgendaResponse struct {
|
||||
|
|
@ -368,153 +371,225 @@ func (s *Service) UpdateStatus(ctx context.Context, agendaID uuid.UUID, status s
|
|||
|
||||
// Se o evento for confirmado, enviar notificações com logística
|
||||
if status == "Confirmado" {
|
||||
go func() {
|
||||
bgCtx := context.Background()
|
||||
|
||||
// 1. Buscar Detalhes do Evento
|
||||
tipoEventoNome := "Evento"
|
||||
if agenda.TipoEventoID.Valid {
|
||||
te, err := s.queries.GetTipoEventoByID(bgCtx, pgtype.UUID{Bytes: agenda.TipoEventoID.Bytes, Valid: true})
|
||||
if err == nil {
|
||||
tipoEventoNome = te.Nome
|
||||
}
|
||||
}
|
||||
|
||||
dataFmt := "Data a definir"
|
||||
if agenda.DataEvento.Valid {
|
||||
dataFmt = agenda.DataEvento.Time.Format("02/01/2006")
|
||||
}
|
||||
horaFmt := "Horário a definir"
|
||||
if agenda.Horario.Valid {
|
||||
horaFmt = agenda.Horario.String
|
||||
}
|
||||
localFmt := ""
|
||||
if agenda.LocalEvento.Valid && agenda.LocalEvento.String != "" {
|
||||
localFmt = agenda.LocalEvento.String
|
||||
}
|
||||
if agenda.Endereco.Valid && agenda.Endereco.String != "" {
|
||||
if localFmt != "" {
|
||||
localFmt += " - " + agenda.Endereco.String
|
||||
} else {
|
||||
localFmt = agenda.Endereco.String
|
||||
}
|
||||
}
|
||||
if localFmt == "" {
|
||||
localFmt = "Local a definir"
|
||||
}
|
||||
|
||||
// 2. Buscar Profissionais Escalados
|
||||
profs, err := s.queries.GetAgendaProfessionals(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true})
|
||||
if err != nil {
|
||||
log.Printf("[Notification] Erro ao buscar profissionais: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Buscar Logística (Carros)
|
||||
carros, err := s.queries.ListCarrosByAgendaID(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true})
|
||||
if err != nil {
|
||||
log.Printf("[Notification] Erro ao buscar carros: %v", err)
|
||||
// Segue sem logística detalhada
|
||||
}
|
||||
|
||||
// Mapear Passageiros por Carro e Carro por Profissional
|
||||
passengersByCar := make(map[uuid.UUID][]string)
|
||||
carByProfessional := make(map[uuid.UUID]generated.ListCarrosByAgendaIDRow) // ProfID -> Carro
|
||||
|
||||
for _, carro := range carros {
|
||||
// Converter pgtype.UUID para uuid.UUID para usar como chave de mapa
|
||||
carUuid := uuid.UUID(carro.ID.Bytes)
|
||||
|
||||
passengers, err := s.queries.ListPassageirosByCarroID(bgCtx, carro.ID)
|
||||
if err == nil {
|
||||
var names []string
|
||||
for _, p := range passengers {
|
||||
names = append(names, p.Nome)
|
||||
if p.ProfissionalID.Valid {
|
||||
profUuid := uuid.UUID(p.ProfissionalID.Bytes)
|
||||
carByProfessional[profUuid] = carro
|
||||
}
|
||||
}
|
||||
passengersByCar[carUuid] = names
|
||||
}
|
||||
|
||||
// O motorista também está no carro
|
||||
if carro.MotoristaID.Valid {
|
||||
driverUuid := uuid.UUID(carro.MotoristaID.Bytes)
|
||||
carByProfessional[driverUuid] = carro
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Enviar Mensagens
|
||||
for _, p := range profs {
|
||||
// O retorno de GetAgendaProfessionals traz p.*, então o ID é p.ID (cadastro_profissionais.id)
|
||||
targetID := uuid.UUID(p.ID.Bytes)
|
||||
|
||||
// Buscar dados completos para ter o whatsapp atualizado (se necessario, mas p.* ja tem whatsapp)
|
||||
// Vamos usar p.Whatsapp direto se tiver.
|
||||
phone := p.Whatsapp.String
|
||||
if phone == "" {
|
||||
// Tenta buscar novamente caso GetAgendaProfessionals não trouxer (mas traz p.*)
|
||||
continue
|
||||
}
|
||||
|
||||
// Montar mensagem de logística
|
||||
logisticaMsg := ""
|
||||
if carro, ok := carByProfessional[targetID]; ok {
|
||||
motorista := carro.NomeMotorista.String
|
||||
if carro.MotoristaNomeSistema.Valid {
|
||||
motorista = carro.MotoristaNomeSistema.String
|
||||
}
|
||||
|
||||
chegada := carro.HorarioChegada.String
|
||||
|
||||
carroUuid := uuid.UUID(carro.ID.Bytes)
|
||||
passageiros := passengersByCar[carroUuid]
|
||||
|
||||
// Filtrar o próprio nome
|
||||
var outrosPassageiros []string
|
||||
for _, nome := range passageiros {
|
||||
if nome != p.Nome {
|
||||
outrosPassageiros = append(outrosPassageiros, nome)
|
||||
}
|
||||
}
|
||||
|
||||
listaPassageiros := ""
|
||||
if len(outrosPassageiros) > 0 {
|
||||
listaPassageiros = "\nCom: "
|
||||
for i, n := range outrosPassageiros {
|
||||
if i > 0 {
|
||||
listaPassageiros += ", "
|
||||
}
|
||||
listaPassageiros += n
|
||||
}
|
||||
}
|
||||
|
||||
logisticaMsg = fmt.Sprintf("\n\n🚗 *Transporte Definido*\nCarro de: *%s*\nChegada: *%s*%s", motorista, chegada, listaPassageiros)
|
||||
} else {
|
||||
logisticaMsg = "\n\n🚗 *Transporte:* Verifique no painel ou entre em contato."
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("✅ *Evento Confirmado!* 🚀\n\nOlá *%s*! O evento a seguir foi confirmado e sua escala está valendo.\n\n📅 *%s*\n⏰ *%s*\n📍 *%s*\n📌 *%s*%s\n\nBom trabalho!",
|
||||
p.Nome,
|
||||
dataFmt,
|
||||
horaFmt,
|
||||
localFmt,
|
||||
tipoEventoNome,
|
||||
logisticaMsg,
|
||||
)
|
||||
|
||||
if err := s.notification.SendWhatsApp(phone, msg); err != nil {
|
||||
// Não logar erro para todos se for falha de validação de numero, mas logar warning
|
||||
log.Printf("[Notification] Erro ao enviar para %s: %v", p.Nome, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go s.NotifyLogistics(ctx, agendaID, nil)
|
||||
}
|
||||
|
||||
return agenda, nil
|
||||
}
|
||||
|
||||
func (s *Service) NotifyLogistics(ctx context.Context, agendaID uuid.UUID, passengerOrders map[string]map[string]int) error {
|
||||
// 1. Buscar Detalhes do Evento
|
||||
bgCtx := context.Background()
|
||||
agenda, err := s.queries.GetAgenda(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true})
|
||||
if err != nil {
|
||||
log.Printf("[Notification] Erro ao buscar agenda: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tipoEventoNome := "Evento"
|
||||
if agenda.TipoEventoID.Valid {
|
||||
te, err := s.queries.GetTipoEventoByID(bgCtx, pgtype.UUID{Bytes: agenda.TipoEventoID.Bytes, Valid: true})
|
||||
if err == nil {
|
||||
tipoEventoNome = te.Nome
|
||||
}
|
||||
}
|
||||
|
||||
dataFmt := "Data a definir"
|
||||
if agenda.DataEvento.Valid {
|
||||
dataFmt = agenda.DataEvento.Time.Format("02/01/2006")
|
||||
}
|
||||
horaFmt := "Horário a definir"
|
||||
if agenda.Horario.Valid {
|
||||
horaFmt = agenda.Horario.String
|
||||
}
|
||||
localFmt := ""
|
||||
if agenda.LocalEvento.Valid && agenda.LocalEvento.String != "" {
|
||||
localFmt = agenda.LocalEvento.String
|
||||
}
|
||||
if agenda.Endereco.Valid && agenda.Endereco.String != "" {
|
||||
if localFmt != "" {
|
||||
localFmt += " - " + agenda.Endereco.String
|
||||
} else {
|
||||
localFmt = agenda.Endereco.String
|
||||
}
|
||||
}
|
||||
if localFmt == "" {
|
||||
localFmt = "Local a definir"
|
||||
}
|
||||
|
||||
// 2. Buscar Profissionais Escalados
|
||||
profs, err := s.queries.GetAgendaProfessionals(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true})
|
||||
if err != nil {
|
||||
log.Printf("[Notification] Erro ao buscar profissionais: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Buscar Logística (Carros)
|
||||
carros, err := s.queries.ListCarrosByAgendaID(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true})
|
||||
if err != nil {
|
||||
log.Printf("[Notification] Erro ao buscar carros: %v", err)
|
||||
// Segue sem logística detalhada
|
||||
}
|
||||
|
||||
// Struct para passageiro + ID para ordenação
|
||||
type PassengerInfo struct {
|
||||
ID string
|
||||
Nome string
|
||||
}
|
||||
|
||||
// Mapear Passageiros por Carro e Carro por Profissional
|
||||
passengersByCar := make(map[uuid.UUID][]PassengerInfo)
|
||||
carByProfessional := make(map[uuid.UUID]generated.ListCarrosByAgendaIDRow) // ProfID -> Carro
|
||||
|
||||
for _, carro := range carros {
|
||||
// Converter pgtype.UUID para uuid.UUID para usar como chave de mapa
|
||||
carUuid := uuid.UUID(carro.ID.Bytes)
|
||||
carIDStr := carUuid.String()
|
||||
|
||||
passengers, err := s.queries.ListPassageirosByCarroID(bgCtx, carro.ID)
|
||||
if err == nil {
|
||||
var pList []PassengerInfo
|
||||
for _, p := range passengers {
|
||||
pUUID := uuid.UUID(p.ProfissionalID.Bytes)
|
||||
pList = append(pList, PassengerInfo{ID: pUUID.String(), Nome: p.Nome})
|
||||
|
||||
if p.ProfissionalID.Valid {
|
||||
carByProfessional[pUUID] = carro
|
||||
}
|
||||
}
|
||||
|
||||
// Sort passengers if order provided
|
||||
if passengerOrders != nil {
|
||||
if orders, ok := passengerOrders[carIDStr]; ok {
|
||||
sort.Slice(pList, func(i, j int) bool {
|
||||
ordA := orders[pList[i].ID]
|
||||
ordB := orders[pList[j].ID]
|
||||
// If order missing (0), push to end? Or treat as 999 equivalent?
|
||||
if ordA == 0 {
|
||||
ordA = 999
|
||||
}
|
||||
if ordB == 0 {
|
||||
ordB = 999
|
||||
}
|
||||
if ordA == ordB {
|
||||
return pList[i].Nome < pList[j].Nome
|
||||
}
|
||||
return ordA < ordB
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
passengersByCar[carUuid] = pList
|
||||
}
|
||||
|
||||
// O motorista também está no carro
|
||||
if carro.MotoristaID.Valid {
|
||||
driverUuid := uuid.UUID(carro.MotoristaID.Bytes)
|
||||
carByProfessional[driverUuid] = carro
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Enviar Mensagens
|
||||
for _, p := range profs {
|
||||
// O retorno de GetAgendaProfessionals traz p.*, então o ID é p.ID (cadastro_profissionais.id)
|
||||
targetID := uuid.UUID(p.ID.Bytes)
|
||||
|
||||
// Buscar dados completos para ter o whatsapp atualizado (se necessario, mas p.* ja tem whatsapp)
|
||||
// Vamos usar p.Whatsapp direto se tiver.
|
||||
phone := p.Whatsapp.String
|
||||
if phone == "" {
|
||||
// Tenta buscar novamente caso GetAgendaProfessionals não trouxer (mas traz p.*)
|
||||
continue
|
||||
}
|
||||
|
||||
// Montar mensagem de logística
|
||||
logisticaMsg := ""
|
||||
if carro, ok := carByProfessional[targetID]; ok {
|
||||
motorista := carro.NomeMotorista.String
|
||||
if carro.MotoristaNomeSistema.Valid {
|
||||
motorista = carro.MotoristaNomeSistema.String
|
||||
}
|
||||
|
||||
chegada := carro.HorarioChegada.String
|
||||
|
||||
carroUuid := uuid.UUID(carro.ID.Bytes)
|
||||
passageirosInfos := passengersByCar[carroUuid]
|
||||
|
||||
// Filtrar o próprio nome para "Outros", mas manter a lista completa para "Rota" se ordenada
|
||||
// Se o usuário é passageiro, mostramos a rota completa ou "Você é o Xº"?
|
||||
// Melhor mostrar a lista ordenada de todos os passageiros.
|
||||
|
||||
// Se passengerOrders != nil, mostramos Rota Ordenada.
|
||||
// Se nil, mostramos "Com: A, B" (Style antigo).
|
||||
|
||||
hasOrder := passengerOrders != nil && len(passengerOrders[carroUuid.String()]) > 0
|
||||
|
||||
listaPassageiros := ""
|
||||
|
||||
if hasOrder {
|
||||
listaPassageiros = "\n\n📋 *Ordem de Busca:*"
|
||||
foundSelf := false
|
||||
for i, passInfo := range passageirosInfos {
|
||||
marker := ""
|
||||
if passInfo.ID == targetID.String() {
|
||||
marker = " (Você)"
|
||||
foundSelf = true
|
||||
}
|
||||
listaPassageiros += fmt.Sprintf("\n%d. %s%s", i+1, passInfo.Nome, marker)
|
||||
}
|
||||
if !foundSelf {
|
||||
// Caso seja o motorista, não aparece na lista de passageiros? ou aparece?
|
||||
// ListaPassageiros só tem passageiros. Motorista é separado.
|
||||
// Se o alvo é motorista, ele vê a ordem de busca.
|
||||
if targetID == uuid.UUID(carro.MotoristaID.Bytes) {
|
||||
// Motorista vê a lista
|
||||
} else {
|
||||
// Passageiro não está na lista? Erro de lógica?
|
||||
// pList vem de ListPassageirosByCarroID.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy list style (excludes self if passenger)
|
||||
var outros []string
|
||||
for _, passInfo := range passageirosInfos {
|
||||
if passInfo.ID != targetID.String() {
|
||||
outros = append(outros, passInfo.Nome)
|
||||
}
|
||||
}
|
||||
|
||||
if len(outros) > 0 {
|
||||
listaPassageiros = "\nCom: " + strings.Join(outros, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
logisticaMsg = fmt.Sprintf("\n\n🚗 *Transporte Definido*\nCarro de: *%s*\nChegada: *%s*%s", motorista, chegada, listaPassageiros)
|
||||
} else {
|
||||
logisticaMsg = "\n\n🚗 *Transporte:* Verifique no painel ou entre em contato."
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("✅ *Evento Confirmado!* 🚀\n\nOlá *%s*! O evento a seguir foi confirmado e sua escala está valendo.\n\n📅 *%s*\n⏰ *%s*\n📍 *%s*\n📌 *%s*%s\n\nBom trabalho!",
|
||||
p.Nome,
|
||||
dataFmt,
|
||||
horaFmt,
|
||||
localFmt,
|
||||
tipoEventoNome,
|
||||
logisticaMsg,
|
||||
)
|
||||
|
||||
if err := s.notification.SendWhatsApp(phone, msg); err != nil {
|
||||
// Não logar erro para todos se for falha de validação de numero, mas logar warning
|
||||
log.Printf("[Notification] Erro ao enviar para %s: %v", p.Nome, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Atualizar timestamp da notificação
|
||||
if err := s.queries.UpdateLogisticsNotificationTimestamp(bgCtx, pgtype.UUID{Bytes: agendaID, Valid: true}); err != nil {
|
||||
log.Printf("[Notification] Erro ao atualizar timestamp: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateAssignmentStatus(ctx context.Context, agendaID, professionalID uuid.UUID, status string, reason string) error {
|
||||
// Conflict Validation on Accept
|
||||
if status == "ACEITO" {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ INSERT INTO agenda (
|
|||
user_id
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24
|
||||
) RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status
|
||||
) RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em
|
||||
`
|
||||
|
||||
type CreateAgendaParams struct {
|
||||
|
|
@ -186,6 +186,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -201,7 +202,7 @@ func (q *Queries) DeleteAgenda(ctx context.Context, id pgtype.UUID) error {
|
|||
}
|
||||
|
||||
const getAgenda = `-- name: GetAgenda :one
|
||||
SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status FROM agenda
|
||||
SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em FROM agenda
|
||||
WHERE id = $1 LIMIT 1
|
||||
`
|
||||
|
||||
|
|
@ -237,6 +238,7 @@ func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error)
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -338,7 +340,7 @@ func (q *Queries) GetAgendaProfessionals(ctx context.Context, agendaID pgtype.UU
|
|||
|
||||
const listAgendas = `-- name: ListAgendas :many
|
||||
SELECT
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status,
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em,
|
||||
cf.fot as fot_numero,
|
||||
cf.instituicao,
|
||||
c.nome as curso_nome,
|
||||
|
|
@ -368,43 +370,44 @@ ORDER BY a.data_evento
|
|||
`
|
||||
|
||||
type ListAgendasRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
FotNumero string `json:"fot_numero"`
|
||||
Instituicao pgtype.Text `json:"instituicao"`
|
||||
CursoNome string `json:"curso_nome"`
|
||||
EmpresaNome string `json:"empresa_nome"`
|
||||
AnoSemestre string `json:"ano_semestre"`
|
||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
|
||||
FotNumero string `json:"fot_numero"`
|
||||
Instituicao pgtype.Text `json:"instituicao"`
|
||||
CursoNome string `json:"curso_nome"`
|
||||
EmpresaNome string `json:"empresa_nome"`
|
||||
AnoSemestre string `json:"ano_semestre"`
|
||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
|
||||
|
|
@ -445,6 +448,7 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
&i.FotNumero,
|
||||
&i.Instituicao,
|
||||
&i.CursoNome,
|
||||
|
|
@ -467,7 +471,7 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
|
|||
|
||||
const listAgendasByFot = `-- name: ListAgendasByFot :many
|
||||
SELECT
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status,
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em,
|
||||
te.nome as tipo_evento_nome
|
||||
FROM agenda a
|
||||
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
|
||||
|
|
@ -476,35 +480,36 @@ ORDER BY a.data_evento
|
|||
`
|
||||
|
||||
type ListAgendasByFotRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListAgendasByFot(ctx context.Context, fotID pgtype.UUID) ([]ListAgendasByFotRow, error) {
|
||||
|
|
@ -545,6 +550,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, fotID pgtype.UUID) ([]Li
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
&i.TipoEventoNome,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -559,7 +565,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, fotID pgtype.UUID) ([]Li
|
|||
|
||||
const listAgendasByUser = `-- name: ListAgendasByUser :many
|
||||
SELECT
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status,
|
||||
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em,
|
||||
cf.fot as fot_numero,
|
||||
cf.instituicao,
|
||||
c.nome as curso_nome,
|
||||
|
|
@ -590,43 +596,44 @@ ORDER BY a.data_evento
|
|||
`
|
||||
|
||||
type ListAgendasByUserRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
FotNumero string `json:"fot_numero"`
|
||||
Instituicao pgtype.Text `json:"instituicao"`
|
||||
CursoNome string `json:"curso_nome"`
|
||||
EmpresaNome string `json:"empresa_nome"`
|
||||
AnoSemestre string `json:"ano_semestre"`
|
||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
|
||||
FotNumero string `json:"fot_numero"`
|
||||
Instituicao pgtype.Text `json:"instituicao"`
|
||||
CursoNome string `json:"curso_nome"`
|
||||
EmpresaNome string `json:"empresa_nome"`
|
||||
AnoSemestre string `json:"ano_semestre"`
|
||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([]ListAgendasByUserRow, error) {
|
||||
|
|
@ -667,6 +674,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([]
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
&i.FotNumero,
|
||||
&i.Instituicao,
|
||||
&i.CursoNome,
|
||||
|
|
@ -840,7 +848,7 @@ SET
|
|||
pre_venda = $24,
|
||||
atualizado_em = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status
|
||||
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em
|
||||
`
|
||||
|
||||
type UpdateAgendaParams struct {
|
||||
|
|
@ -927,6 +935,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -935,7 +944,7 @@ const updateAgendaStatus = `-- name: UpdateAgendaStatus :one
|
|||
UPDATE agenda
|
||||
SET status = $2, atualizado_em = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status
|
||||
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em
|
||||
`
|
||||
|
||||
type UpdateAgendaStatusParams struct {
|
||||
|
|
@ -975,6 +984,7 @@ func (q *Queries) UpdateAgendaStatus(ctx context.Context, arg UpdateAgendaStatus
|
|||
&i.CriadoEm,
|
||||
&i.AtualizadoEm,
|
||||
&i.Status,
|
||||
&i.LogisticaNotificacaoEnviadaEm,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
34
backend/internal/db/generated/agenda_supp.sql.go
Normal file
34
backend/internal/db/generated/agenda_supp.sql.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: agenda_supp.sql
|
||||
|
||||
package generated
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const resetLogisticsNotificationTimestamp = `-- name: ResetLogisticsNotificationTimestamp :exec
|
||||
UPDATE agenda
|
||||
SET logistica_notificacao_enviada_em = NULL
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ResetLogisticsNotificationTimestamp(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, resetLogisticsNotificationTimestamp, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateLogisticsNotificationTimestamp = `-- name: UpdateLogisticsNotificationTimestamp :exec
|
||||
UPDATE agenda
|
||||
SET logistica_notificacao_enviada_em = NOW()
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateLogisticsNotificationTimestamp(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, updateLogisticsNotificationTimestamp, id)
|
||||
return err
|
||||
}
|
||||
|
|
@ -73,13 +73,15 @@ func (q *Queries) CreateCarro(ctx context.Context, arg CreateCarroParams) (Logis
|
|||
return i, err
|
||||
}
|
||||
|
||||
const deleteCarro = `-- name: DeleteCarro :exec
|
||||
DELETE FROM logistica_carros WHERE id = $1
|
||||
const deleteCarro = `-- name: DeleteCarro :one
|
||||
DELETE FROM logistica_carros WHERE id = $1 RETURNING agenda_id
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteCarro(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteCarro, id)
|
||||
return err
|
||||
func (q *Queries) DeleteCarro(ctx context.Context, id pgtype.UUID) (pgtype.UUID, error) {
|
||||
row := q.db.QueryRow(ctx, deleteCarro, id)
|
||||
var agenda_id pgtype.UUID
|
||||
err := row.Scan(&agenda_id)
|
||||
return agenda_id, err
|
||||
}
|
||||
|
||||
const getCarroByID = `-- name: GetCarroByID :one
|
||||
|
|
|
|||
|
|
@ -9,34 +9,35 @@ import (
|
|||
)
|
||||
|
||||
type Agenda struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
FotID pgtype.UUID `json:"fot_id"`
|
||||
DataEvento pgtype.Date `json:"data_evento"`
|
||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||
LocalEvento pgtype.Text `json:"local_evento"`
|
||||
Endereco pgtype.Text `json:"endereco"`
|
||||
Horario pgtype.Text `json:"horario"`
|
||||
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
|
||||
}
|
||||
|
||||
type AgendaEscala struct {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
-- Migration skipped because column 'logistica_notificacao_enviada_em' already exists in DB.
|
||||
-- This ensures the migration version is recorded as applied.
|
||||
10
backend/internal/db/queries/agenda_supp.sql
Normal file
10
backend/internal/db/queries/agenda_supp.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
-- name: UpdateLogisticsNotificationTimestamp :exec
|
||||
UPDATE agenda
|
||||
SET logistica_notificacao_enviada_em = NOW()
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ResetLogisticsNotificationTimestamp :exec
|
||||
UPDATE agenda
|
||||
SET logistica_notificacao_enviada_em = NULL
|
||||
WHERE id = $1;
|
||||
|
|
@ -29,8 +29,8 @@ SET motorista_id = COALESCE($2, motorista_id),
|
|||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteCarro :exec
|
||||
DELETE FROM logistica_carros WHERE id = $1;
|
||||
-- name: DeleteCarro :one
|
||||
DELETE FROM logistica_carros WHERE id = $1 RETURNING agenda_id;
|
||||
|
||||
-- name: AddPassageiro :one
|
||||
INSERT INTO logistica_passageiros (carro_id, profissional_id)
|
||||
|
|
|
|||
|
|
@ -351,7 +351,8 @@ CREATE TABLE IF NOT EXISTS agenda (
|
|||
pre_venda BOOLEAN DEFAULT FALSE,
|
||||
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
status VARCHAR(50) DEFAULT 'Pendente' -- Pendente, Aprovado, Arquivado
|
||||
status VARCHAR(50) DEFAULT 'Pendente', -- Pendente, Aprovado, Arquivado
|
||||
logistica_notificacao_enviada_em TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agenda_profissionais (
|
||||
|
|
@ -471,3 +472,10 @@ BEGIN
|
|||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='agenda' AND column_name='logistica_notificacao_enviada_em') THEN
|
||||
ALTER TABLE agenda ADD COLUMN logistica_notificacao_enviada_em TIMESTAMP;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package logistica
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"photum-backend/internal/config"
|
||||
"photum-backend/internal/db/generated"
|
||||
|
|
@ -64,6 +62,12 @@ func (s *Service) CreateCarro(ctx context.Context, input CreateCarroInput) (*gen
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reset notification timestamp
|
||||
if err := s.queries.ResetLogisticsNotificationTimestamp(ctx, params.AgendaID); err != nil {
|
||||
// log error
|
||||
}
|
||||
|
||||
return &carro, nil
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +84,19 @@ func (s *Service) DeleteCarro(ctx context.Context, id string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.queries.DeleteCarro(ctx, pgtype.UUID{Bytes: parsedUUID, Valid: true})
|
||||
// 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
|
||||
|
|
@ -145,65 +161,67 @@ func (s *Service) AddPassageiro(ctx context.Context, carroID, profissionalID str
|
|||
return err
|
||||
}
|
||||
|
||||
// Notification Logic
|
||||
go func() {
|
||||
bgCtx := context.Background()
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
horarioSaida := "A combinar"
|
||||
if carro.HorarioChegada.Valid {
|
||||
horarioSaida = carro.HorarioChegada.String
|
||||
}
|
||||
|
||||
destino := "Local do Evento"
|
||||
if agendaVal.LocalEvento.Valid {
|
||||
destino = agendaVal.LocalEvento.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,
|
||||
)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
if err := s.notification.SendWhatsApp(prof.Whatsapp.String, msg); err != nil {
|
||||
log.Printf("[Logistica Notification] Falha ao enviar: %v", err)
|
||||
}
|
||||
}()
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Plus, Trash, User, Truck, Car } from "lucide-react";
|
||||
import { Plus, Trash, User, Truck, Car, Send } from "lucide-react";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { listCarros, createCarro, deleteCarro, addPassenger, removePassenger, listPassengers, listCarros as fetchCarrosApi } from "../services/apiService";
|
||||
import { listCarros, createCarro, deleteCarro, addPassenger, removePassenger, listPassengers, listCarros as fetchCarrosApi, notifyLogistics } from "../services/apiService";
|
||||
import { useData } from "../contexts/DataContext";
|
||||
import { UserRole } from "../types";
|
||||
|
||||
interface EventLogisticsProps {
|
||||
agendaId: string;
|
||||
token?: string;
|
||||
isEditable?: boolean;
|
||||
assignedProfessionals?: string[];
|
||||
}
|
||||
|
||||
|
|
@ -27,9 +29,10 @@ interface PassengerWithOrder {
|
|||
order: number;
|
||||
}
|
||||
|
||||
const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, assignedProfessionals }) => {
|
||||
const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, isEditable: propsIsEditable, assignedProfessionals }) => {
|
||||
const { token, user } = useAuth();
|
||||
const { professionals } = useData();
|
||||
const { professionals, events } = useData();
|
||||
const eventData = events.find(e => e.id === agendaId);
|
||||
const [carros, setCarros] = useState<Carro[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [passengerOrders, setPassengerOrders] = useState<Record<string, Record<string, number>>>({});
|
||||
|
|
@ -39,7 +42,7 @@ const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, assignedProfe
|
|||
const [arrivalTime, setArrivalTime] = useState("07:00");
|
||||
const [notes, setNotes] = useState("");
|
||||
|
||||
const isEditable = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER;
|
||||
const isEditable = propsIsEditable !== undefined ? propsIsEditable : (user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER);
|
||||
|
||||
// Carregar ordens do localStorage ao montar o componente
|
||||
useEffect(() => {
|
||||
|
|
@ -175,12 +178,31 @@ const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, assignedProfe
|
|||
});
|
||||
};
|
||||
|
||||
const handleNotifyLogistics = async () => {
|
||||
if (!confirm("Confirmar logística e enviar notificações para TODA a equipe?")) return;
|
||||
|
||||
const res = await notifyLogistics(token!, agendaId, passengerOrders);
|
||||
if (res.error) {
|
||||
alert("Erro ao enviar notificações: " + res.error);
|
||||
} else {
|
||||
alert("Notificações de logística enviadas com sucesso!");
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 rounded-lg shadow space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-800 flex items-center">
|
||||
<Truck className="w-5 h-5 mr-2 text-orange-500" />
|
||||
Logística de Transporte
|
||||
</h3>
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold text-gray-800 flex items-center">
|
||||
<Truck className="w-5 h-5 mr-2 text-orange-500" />
|
||||
Logística de Transporte
|
||||
</h3>
|
||||
{isEditable && eventData?.logisticaNotificacaoEnviadaEm && (
|
||||
<div className="text-sm text-green-700 bg-green-50 px-2 py-1 rounded border border-green-200">
|
||||
Notificação enviada em: {new Date(eventData.logisticaNotificacaoEnviadaEm).toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Add Car Form - Only for Admins */}
|
||||
{isEditable && (
|
||||
|
|
@ -306,6 +328,30 @@ const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, assignedProfe
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Notification Button moved to bottom */}
|
||||
{isEditable && (
|
||||
<div className="flex flex-col items-center pt-4 border-t border-gray-100">
|
||||
<button
|
||||
onClick={handleNotifyLogistics}
|
||||
disabled={!!eventData?.logisticaNotificacaoEnviadaEm}
|
||||
className={`px-6 py-2.5 rounded-md flex items-center text-sm font-medium shadow-sm transition-colors w-full justify-center ${
|
||||
eventData?.logisticaNotificacaoEnviadaEm
|
||||
? "bg-gray-400 cursor-not-allowed text-white"
|
||||
: "bg-green-600 hover:bg-green-700 text-white"
|
||||
}`}
|
||||
title={eventData?.logisticaNotificacaoEnviadaEm ? "Notificação já enviada" : "Confirmar logística e notificar equipe"}
|
||||
>
|
||||
<Send className="w-5 h-5 mr-2" />
|
||||
{eventData?.logisticaNotificacaoEnviadaEm ? "Notificação Enviada" : "Finalizar Logística e Notificar Equipe"}
|
||||
</button>
|
||||
{eventData?.logisticaNotificacaoEnviadaEm && (
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
Última notificação: {new Date(eventData.logisticaNotificacaoEnviadaEm).toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ const timeSlots = [
|
|||
|
||||
const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, allowedProfessionals, onUpdateStats, defaultTime }) => {
|
||||
const { token, user } = useAuth();
|
||||
const { professionals, events } = useData();
|
||||
const { professionals, events, functions } = useData();
|
||||
const [escalas, setEscalas] = useState<any[]>([]);
|
||||
const [roles, setRoles] = useState<{ id: string; nome: string }[]>([]);
|
||||
const roles = functions;
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// New entry state
|
||||
|
|
@ -80,9 +80,7 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
|||
useEffect(() => {
|
||||
if (agendaId && token) {
|
||||
fetchEscalas();
|
||||
getFunctions().then(res => {
|
||||
if (res.data) setRoles(res.data);
|
||||
});
|
||||
// Functions handled via context
|
||||
}
|
||||
}, [agendaId, token]);
|
||||
|
||||
|
|
@ -168,8 +166,10 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
|||
ids.push(pid);
|
||||
allowedMap.set(pid, status);
|
||||
|
||||
if (p.funcaoId) {
|
||||
const r = roles.find(role => role.id === p.funcaoId);
|
||||
// Use assigned role ID (handle both casing)
|
||||
const fId = p.funcaoId || p.funcao_id;
|
||||
if (fId) {
|
||||
const r = roles.find(role => role.id === fId);
|
||||
if (r) assignedRoleMap.set(pid, r.nome);
|
||||
}
|
||||
}
|
||||
|
|
@ -217,7 +217,9 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
|||
const isDisabled = isBusy || isPending;
|
||||
|
||||
const assignedRole = assignedRoleMap.get(p.id);
|
||||
const displayRole = assignedRole || p.role || "Profissional";
|
||||
// Resolve role name from ID if not assigned specifically
|
||||
const defaultRole = roles.find(r => r.id === p.funcao_profissional_id)?.nome;
|
||||
const displayRole = assignedRole || defaultRole || (p as any).funcao_nome || p.role || "Profissional";
|
||||
|
||||
let label = "";
|
||||
if (isPending) label = "(Pendente de Aceite)";
|
||||
|
|
@ -281,7 +283,9 @@ const EventScheduler: React.FC<EventSchedulerProps> = ({ agendaId, dataEvento, a
|
|||
// Ideally backend should return it, but for now we look up in global list if available
|
||||
const profData = professionals.find(p => p.id === item.profissional_id);
|
||||
const assignedRole = assignedRoleMap.get(item.profissional_id);
|
||||
const displayRole = assignedRole || profData?.role;
|
||||
// Resolve role name
|
||||
const defaultProfRole = profData ? roles.find(r => r.id === profData.funcao_profissional_id)?.nome : undefined;
|
||||
const displayRole = assignedRole || item.profissional_role || defaultProfRole || (profData as any)?.funcao_nome || profData?.role;
|
||||
|
||||
return (
|
||||
<div key={item.id} className="flex flex-col p-2 hover:bg-gray-50 rounded border-b">
|
||||
|
|
|
|||
|
|
@ -725,6 +725,7 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
|||
funcaoId: a.funcao_id
|
||||
}))
|
||||
: [],
|
||||
logisticaNotificacaoEnviadaEm: e.logistica_notificacao_enviada_em,
|
||||
}));
|
||||
setEvents(mappedEvents);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -271,7 +271,12 @@ const Finance: React.FC = () => {
|
|||
// Default sort is grouped by FOT
|
||||
if (!sortConfig) {
|
||||
return result.sort((a, b) => {
|
||||
if (a.fot !== b.fot) return b.fot - a.fot; // Group by FOT
|
||||
// Group by FOT (String comparison to handle "20000MG")
|
||||
const fotA = String(a.fot || "");
|
||||
const fotB = String(b.fot || "");
|
||||
if (fotA !== fotB) return fotB.localeCompare(fotA, undefined, { numeric: true });
|
||||
|
||||
// Secondary: Date
|
||||
return new Date(b.dataRaw || b.data).getTime() - new Date(a.dataRaw || a.data).getTime();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -819,6 +819,31 @@ export async function updateEventStatus(token: string, eventId: string, status:
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia notificação de logística para todos os profissionais do evento
|
||||
*/
|
||||
export async function notifyLogistics(token: string, eventId: string, passengerOrders?: any): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/notify-logistics`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ passenger_orders: passengerOrders })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return { data: undefined, error: null, isBackendDown: false };
|
||||
} catch (error) {
|
||||
console.error("Error notifying logistics:", error);
|
||||
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um usuário pela interface administrativa
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ export interface EventData {
|
|||
photographerIds: string[]; // IDs dos fotógrafos designados
|
||||
institutionId?: string; // ID da instituição vinculada (obrigatório)
|
||||
attendees?: number; // Número de pessoas participantes
|
||||
logisticaNotificacaoEnviadaEm?: string;
|
||||
courseId?: string; // ID do curso/turma relacionado ao evento
|
||||
fotId?: string; // ID da Turma (FOT)
|
||||
typeId?: string; // ID do Tipo de Evento (UUID)
|
||||
|
|
|
|||
Loading…
Reference in a new issue