WEBSOCKETprogress

Pedido em Progresso

Evento WebSocket recebido quando um pedido entra em status "IN_PROGRESS" (em coleta). Emitido em dois contextos: 1. **Broadcast**: Quando um pedido é chamado via 'call', o evento é enviado para todos os clientes da conta. 2. **Cliente específico**: Quando o cliente solicita a lista inicial via 'get.orders.progress', recebe apenas os pedidos existentes. O frontend adiciona o pedido à lista "Em Progresso", evitando duplicatas. Pedidos mais recentes aparecem primeiro.

Access Token

Para realizar requisições GET, POST, PUT, DELETE e PATCH nos endpoints da API você precisa de uma chave de autorização. Chamamos essa chave de accessToken.

Para ter acesso ao accessToken, é necessário que o usuário master da licença efetue a liberação deste pela interface do ImageMais Clinic. O accessToken tem validade de 1 hora.

Headers

AuthorizationOBRIGATÓRIO
Tipo:string
Token de autenticação no formato Bearer {accessToken}. O token deve ser enviado durante o handshake de conexão WebSocket.

Parâmetros

Este método não possui parâmetros.

Fluxo passo a passo: progress

1

Contexto 1: Pedido chamado para coleta (broadcast)

Quando um usuário clica para chamar um pedido da lista de 'Standby' para coleta, o frontend emite o evento 'call' com o ID do pedido. O gateway recebe esta solicitação e executa uma série de validações antes de atualizar o pedido e propagar o evento 'progress' para todos os clientes conectados da mesma conta. Este é o primeiro contexto onde o evento 'progress' é emitido, usando broadcast para garantir que todos os usuários vejam o pedido sendo movido para 'Em Progresso' simultaneamente:

// accounts.gateways.ts
@SubscribeMessage('call')
async handleMessage(
    @ConnectedSocket() client: Socket,
    @MessageBody() data: UpdateOrderDto,
) {
    const payload = client.data.payload as PayloadTokenDto;
    
    // Valida se usuário já está coletando outro paciente
    const exists = await this.ordersService.existsOrder(
        OrdersStatusEnum.IN_PROGRESS,
        data.acceptepUserId ?? payload.sub,
        payload,
    );
    if (exists) {
        return {
            status: 'refused',
            message: 'O usuário já está coletando outro paciente',
        };
    }
    
    // Atualiza order para IN_PROGRESS
    const order = await this.ordersService.updateOrder(data, payload);

    // Emite evento progress via broadcast
    this.notifyOnEvent(
        'progress',
        await this.ordersService.findById(order.id, payload),
        client.data.payload,
    );

    return {
        status: 'receive',
        timestamp: new Date().toISOString(),
    };
}
Quem emite: backend (accounts.gateways.ts) após chamar pedido via 'call'
Modo de envio: broadcast (todos os clientes da conta)
Método usado: notifyOnEvent('progress', order, payload)
2

Validação e atualização do pedido

O servidor valida se o usuário já está coletando outro paciente. Se passar, atualiza o pedido: status muda de STANDBY para IN_PROGRESS e vincula o usuário coletor (acceptUser):

// orders.service.ts
async updateOrder(updateOrderDto: UpdateOrderDto, payload: PayloadTokenDto) {
    // Valida permissões se atribuir para outro usuário
    if (updateOrderDto.acceptepUserId) {
        const check = await this.permissionsService.hasPermissions(
            payload,
            [ASSIGN_LABORATORY],
        );
        if (!check) throw new UnauthorizedException();
    }
    
    // Atualiza pedido no banco
    const order = await this.prisma.orders.update({
        where: {
            id: updateOrderDto.id,
            accountId: payload.accountId,
            status: { name: OrdersStatusEnum.STANDBY },
        },
        data: {
            status: { connect: { name: OrdersStatusEnum.IN_PROGRESS } },
            acceptUser: {
                connect: { id: updateOrderDto.acceptepUserId ?? payload.sub },
            },
        },
    });
    return order;
}
Validação: verifica se usuário já está coletando outro paciente
Atualização: muda status de STANDBY para IN_PROGRESS
Vinculação: vincula usuário ao pedido (acceptUser)
3

Servidor propaga evento 'progress' via broadcast

O servidor envia o evento 'progress' via broadcast para todos os clientes na room 'orders.account.{accountId}'. Todos recebem o pedido completo simultaneamente:

// accounts.gateways.ts
async notifyOnEvent(
    event: string,
    data: any,
    payload: PayloadTokenDto,
): Promise<any> {
    if (this._events.includes(event)) {
        this.server
            .to(`${this.room}${payload.accountId}`)
            .emit(event, data);
    }
}

// this.room = 'orders.account.'
// Envia para: orders.account.{accountId}
// Evento: 'progress'
// Dados: objeto Order completo
Room: orders.account.{accountId}
Evento: 'progress' (está na lista de eventos permitidos)
Modo: broadcast (todos os clientes da conta)
4

Contexto 2: Carregamento inicial da lista

Quando o componente é montado, emite 'get.orders.progress' para carregar a lista inicial. Configura listeners para 'progress' e 'remove.progress':

// ListInProgress.tsx
useEffect(() => {
    if (!socket) return;
    
    // Solicita lista inicial
    socket.emit('get.orders.progress');
    
    // Escuta eventos
    socket.on('progress', (newData: Order) => {
        setOrders((prev) => {
            if (prev.find((d) => d.id === newData.id)) return prev;
            return [newData, ...prev];
        });
    });
    
    return () => {
        socket.off('progress');
        socket.off('remove.progress');
    };
}, [socket]);
Quem solicita: frontend (ListInProgress.tsx) ao montar componente
Evento emitido: 'get.orders.progress'
5

Gateway recebe solicitação e emite evento interno

O gateway recebe 'get.orders.progress', retorna confirmação e emite evento interno 'load.orders.progress' com client.id para enviar apenas ao cliente solicitante:

// accounts.gateways.ts
@SubscribeMessage('get.orders.progress')
async handleOrdersProgress(@ConnectedSocket() client: Socket) {
    this.eventEmitter.emit(
        'load.orders.progress',
        new LoadOrdersEvent(client.id, client.data.payload),
    );
    
    return {
        status: 'receive',
    };
}
Evento interno: 'load.orders.progress'
Dados: client.id (para enviar resposta ao cliente específico)
6

Listener busca pedidos e envia via 'progress'

O listener busca todos os pedidos IN_PROGRESS e envia cada um individualmente via 'progress' apenas para o cliente que solicitou:

// orders.listener.ts
@OnEvent('load.orders.progress')
async handleOnLoadOrdersProgress({ to, payload }: LoadOrdersEvent) {
    try {
        // Busca todos os pedidos IN_PROGRESS
        const orders = await this.loadOrdersByStatus(
            OrdersStatusEnum.IN_PROGRESS,
            payload,
        );
        
        // Envia cada pedido individualmente para o cliente específico
        orders.map((order) =>
            this.gateway.notifyOnEventClient(to, 'progress', order),
        );
    } catch (e) {
        this.logger.debug('Erro ao buscar pedidos em progresso', e);
    }
}
Método usado: notifyOnEventClient(to, 'progress', order)
Modo de envio: cliente específico (apenas quem solicitou)
Envio: cada pedido em uma mensagem separada
7

Método notifyOnEventClient envia para cliente específico

Diferente do broadcast, este método envia apenas para o socket específico (client.id). Garante que só quem solicitou receba os pedidos:

// accounts.gateways.ts
async notifyOnEventClient(to: string, event: string, data: any) {
    if (this._events.includes(event)) {
        this.server.to(to).emit(event, data);
    }
}

// to = client.id (ID do socket do cliente que solicitou)
// Evento: 'progress'
// Dados: objeto Order completo
Destino: client.id (cliente específico)
Evento: 'progress' (está na lista de eventos permitidos)
Modo: cliente específico (não broadcast)
8

Frontend recebe evento e atualiza a lista

O frontend verifica se o pedido já existe (evita duplicatas) e adiciona no início da lista. Pedidos mais recentes aparecem primeiro:

// ListInProgress.tsx
socket.on('progress', (newData: Order) => {
    setOrders((prev) => {
        // Verifica se pedido já existe (evita duplicatas)
        if (prev.find((d) => d.id === newData.id)) return prev;
        
        // Adiciona no início da lista (pedidos mais recentes primeiro)
        return [newData, ...prev];
    });
});
Validação: verifica se pedido já existe antes de adicionar
Posição: adiciona no início da lista (unshift)
Atualização: UI atualiza automaticamente
9

Resumo: Diferenças entre os contextos

Contexto 1 (via 'call'): broadcast para todos os clientes quando pedido é chamado. Contexto 2 (via 'get.orders.progress'): apenas para quem solicitou, para carregar lista inicial:

// Contexto 1: Via 'call' (broadcast)
// - Emitido quando pedido é chamado para coleta
// - Enviado para TODOS os clientes da conta
// - Usa: notifyOnEvent('progress', order, payload)
// - Todos os clientes recebem o novo pedido em tempo real

// Contexto 2: Via 'get.orders.progress' (cliente específico)
// - Emitido quando cliente solicita lista inicial
// - Enviado apenas para o CLIENTE que solicitou
// - Usa: notifyOnEventClient(to, 'progress', order)
// - Cliente recebe todos os pedidos IN_PROGRESS existentes
Broadcast: todos os clientes recebem novos pedidos em tempo real
Cliente específico: apenas quem solicitou recebe a lista inicial
{
  "id": 123,
  "ulid": "01J8X9K2M3N4P5Q6R7S8T9U0V",
  ...
{
  "id": 123,
  "ulid": "01J8X9K2M3N4P5Q6R7S8T9U0V",
  "accountId": 1,
  "status": {
    "id": 2,
    "name": "IN_PROGRESS",
    "color": "#3b82f6"
  },
  "acceptUser": {
    "id": 456,
    "name": "João Silva"
  },
  "patientExams": {
    "id": 789,
    "prefix": "EX",
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "patient": {
      "id": 101,
      "name": "Maria Santos",
      "dateBirth": "1990-01-15"
    }
  }
}

Respostas

{
  "id": 123,
  "ulid": "01J8X9K2M3N4P5Q6R7S8T9U0V",
  ...
{
  "id": 123,
  "ulid": "01J8X9K2M3N4P5Q6R7S8T9U0V",
  "status": {
    "name": "IN_PROGRESS"
  }
}