WEBSOCKETcreate.label

Criar Etiquetas do Pedido

Evento WebSocket para criar etiquetas de um pedido em coleta via integração SOAP com a API externa DiagnosticsBrazil. O cliente emite este evento com o ID do pedido, campos adicionais validados pelo usuário (priority, height, weight, procedures) e o token JWT. Dois Pipes interceptam a requisição em sequência: o primeiro busca as integrações e o segundo valida e filtra apenas as integrações válidas. Se todas as integrações forem validadas (array vazio), o handler emite evento interno para gerar as etiquetas via SOAP. O serviço SOAP completa o pedido automaticamente (muda para COMPLETED) se as etiquetas forem geradas com sucesso. O frontend escuta o evento 'completed' e busca as etiquetas automaticamente.

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

orderIdOBRIGATÓRIO
Tipo:number
ID do pedido para criar etiquetas

ID do pedido para operações relacionadas às etiquetas. É um identificador numérico único que identifica um pedido específico no sistema.

Para encontrar o ID do pedido, para acessar a página de listar todos os pedidos do laboratório.

fieldsOBRIGATÓRIO
Tipo:object
Campos adicionais validados pelo usuário
tokenOBRIGATÓRIO
Tipo:string
Token JWT do usuário para validação e autenticação

Fluxo passo a passo: create.label

1

Cliente emite a requisição via WebSocket com callback

O frontend emite o evento 'create.label' com dados validados pelo usuário e define flag para busca automática:

// Label.tsx
const validate = useCallback(() => {
  if (!socket) return;

  setIsLoading(true);
  // Define flag para busca automática de etiquetas quando pedido for completado
  isWindowPrint.current = true;
  
  socket.emit(
    'create.label',
    {
      orderId: order.id,
      fields: {
        priority,
        height,
        weight,
        procedures,
      },
      token,
    },
    (data: ValiditionsResponseWs) => {
      console.log('Validation response:', data);
      
      // Se há integrações pendentes, remove loading (usuário precisa ajustar dados)
      if (data.integrations.length > 0) setIsLoading(false);

      // Trata erros
      if (data.status === 'refused') {
        setIsLoading(false);
        addNotification({
          title: 'Atenção',
          message: data.message,
          bg: 'danger',
          className: 'text-white',
          autohide: true,
        });
      }
      // Se não há integrações pendentes, loading continua (aguardando geração de etiquetas)
    }
  );
}, [socket, order.id, priority, height, weight, procedures, token, addNotification]);
Quem emite: frontend (Label.tsx)
Quando: após usuário validar/ajustar dados no modal Validation
Payload: { orderId: number, fields: CamposAdicionais, token: string }
Flag: isWindowPrint.current = true (indica que está aguardando impressão)
Callback: processa resposta e gerencia loading baseado em integrações pendentes
Gerenciamento de loading: remove loading se há integrações pendentes, mantém se não há (aguardando etiquetas)
2

Pipe 1 (ProceduresRequiredPipe) intercepta e busca integrações

O primeiro Pipe intercepta a requisição, valida o token, busca o pedido e as integrações:

// procedures-required.pipe.ts
async transform({
  orderId,
  fields,
  token,
}: {
  orderId: number;
  fields: CamposAdicionais;
  token: string;
}): Promise<any> {
  try {
    // Valida e decodifica o token JWT
    const payload = await this.jwtService.verifyAsync(token, {
      secret: this.jwtConfiguration.access.secret,
      audience: this.jwtConfiguration.access.audience,
      issuer: this.jwtConfiguration.access.issuer,
    });
    
    // Valida orderId
    if (orderId === undefined || orderId === null) {
      throw new WsException('orderId is required');
    }
    
    const parsedOrderId = Number(orderId);
    if (isNaN(parsedOrderId)) {
      throw new WsException('orderId must be a valid number');
    }
    
    // Busca o pedido completo
    const order = await this.ordersService.findById(
      parsedOrderId,
      payload,
    );
    
    // Extrai IDs dos procedimentos do pedido
    const procedureIds = order.patientExams.procedureSupplier.map(
      (ps) => ps.procedureId,
    );
    
    // Busca integrações relacionadas aos procedimentos
    const integrations = await this.integrationDBService.findByProceduresIds(
      procedureIds,
      payload,
    );
    
    // Retorna dados transformados para o próximo pipe
    return {
      orderId,
      integrations,
      fields,
      token,
    };
  } catch (e) {
    throw new WsException('Erro ao procurar os procedimentos da integração');
  }
}
O que faz: intercepta requisição antes do handler (primeiro pipe)
Validação 1: valida e decodifica token JWT
Validação 2: valida se orderId é válido e numérico
Busca dados: busca pedido completo e integrações dos procedimentos
Retorno: { orderId, integrations, fields, token }
3

Pipe 2 (FieldValidatorPipe) valida e filtra integrações

O segundo Pipe valida o token novamente e filtra apenas as integrações válidas com os campos fornecidos:

// field-validator.pipe.ts
async transform({
  orderId,
  integrations,
  fields,
  token,
}: {
  orderId: number;
  integrations: Array<IntegrationDiagnosticsBrazil & { procedure: Procedures }>;
  fields: CamposAdicionais;
  token: string;
}) {
  try {
    // Valida token JWT novamente
    const payload = await this.jwtService.verifyAsync(token, {
      secret: this.jwtConfiguration.access.secret,
      audience: this.jwtConfiguration.access.audience,
      issuer: this.jwtConfiguration.access.issuer,
    });
    
    // Filtra integrações válidas
    const integrationsFiltred = await Promise.all(
      integrations.flatMap(async (integration) => {
        // Busca campos específicos do procedimento em fields.procedures
        const fieldFind = fields.procedures.find(
          (field) => field.procedureId === integration.procedureId,
        );
        
        // Valida se a integração está configurada corretamente com os campos fornecidos
        const find = await this.integrationService.findByProcedureIdAndFields(
          integration.procedureId,
          {
            ...fieldFind,
            height: fields.height,
            weight: fields.weight,
          },
          payload,
        );
        
        // Mantém apenas integrações que passam na validação
        return find ? [integration] : [];
      }),
    );
    
    // Retorna dados filtrados
    return {
      orderId,
      integrations: integrationsFiltred.flat(),
      fields: fields,
    };
  } catch (e) {
    throw new WsException('Erro ao validar os procedimentos da integração');
  }
}
O que faz: segundo pipe na cadeia (executa após ProceduresRequiredPipe)
Validação: valida token JWT novamente
Filtragem: mantém apenas integrações que têm campos válidos configurados
Validação por procedimento: verifica se cada procedimento tem os campos necessários (volume, region, height, weight)
Retorno: { orderId, integrations: filtradas, fields }
4

Gateway verifica integrações e emite evento interno se necessário

O handler recebe os dados transformados pelos dois pipes e verifica se deve gerar etiquetas:

// accounts.gateways.ts
@SubscribeMessage('create.label')
async handleGenerateLabel(
  @ConnectedSocket() client: Socket,
  @MessageBody(ProceduresRequiredPipe, FieldValidatorPipe)
  data: {
    orderId: number;
    integrations: Array<
      IntegrationDiagnosticsBrazil & {
        procedure: Procedures;
      }
    >;
    fields: CamposAdicionais;
  },
): Promise<ResponseWs> {
  try {
    // Se não há integrações pendentes (todas foram validadas), gera etiquetas
    if (data.integrations.length === 0) {
      // Emite evento interno para gerar etiquetas (processamento assíncrono)
      this.eventEmitter.emit(
        'create.label',
        new LabelEvent(
          data.orderId,
          client.data.payload,
          data.fields,
        ),
      );
    }
    
    // Retorna resposta sempre (independente se gerou etiquetas ou não)
    return {
      status: 'receive',
      integrations: data.integrations, // Array vazio se todas foram validadas
      timestamp: new Date().toISOString(),
    };
  } catch (e) {
    // ... tratamento de erro
  }
}
Quem recebe: servidor (Gateway WS)
Pipes em sequência: ProceduresRequiredPipe → FieldValidatorPipe
Dados recebidos: já transformados e filtrados pelos pipes
Lógica condicional: só gera etiquetas se data.integrations.length === 0
Evento interno: emit('create.label', LabelEvent) para processamento assíncrono
Resposta: retorna sempre via callback (com integrações filtradas)
5

Listener gera etiquetas via serviço SOAP

O listener escuta o evento interno e chama o serviço SOAP para gerar as etiquetas:

// integration.listener.ts
@OnEvent('create.label')
async handleOnGenerateLabel(labelEvent: LabelEvent) {
  try {
    // Gera etiquetas via API SOAP externa (processamento assíncrono)
    const confirm = await this.soapService.generateLabel(
      labelEvent.orderId,
      labelEvent.payload,
      labelEvent.additionals, // Campos adicionais (priority, height, weight, procedures)
    );
  } catch (e) {
    this.logger.debug(e);
  }
}
O que faz: escuta evento interno e gera etiquetas via SOAP
Processamento: assíncrono (não bloqueia resposta do gateway)
Dados enviados: orderId, payload (autenticação), additionals (campos validados)
6

Serviço SOAP gera etiquetas e completa pedido automaticamente

O serviço SOAP busca o pedido, valida status, monta dados, chama API externa e completa o pedido se sucesso:

// soap-diagnostics-brazil.service.ts
async generateLabel(id: number, payload: PayloadTokenDto, additionals?: CamposAdicionais) {
  // Busca o pedido completo
  const order = await this.ordersService.findById(id, payload);
  
  // Valida se o pedido está IN_PROGRESS (só gera etiquetas de pedidos em coleta)
  this.checkOrderStatus(order, OrdersStatusEnum.IN_PROGRESS);
  
  // Monta dados do pedido com campos adicionais
  const requestData = {
    // Dados do paciente
    patient: order.patientExams.patient,
    // Procedimentos com campos adicionais (volume, region por procedimento)
    procedures: order.patientExams.procedureSupplier.map((ps) => ({
      ...ps,
      volume: additionals?.procedures?.find(p => p.procedureId === ps.procedureId)?.volume,
      region: additionals?.procedures?.find(p => p.procedureId === ps.procedureId)?.region,
    })),
    // Campos globais
    priority: additionals?.priority,
    height: additionals?.height,
    weight: additionals?.weight,
  };
  
  // Chama API SOAP externa (DiagnosticsBrazil)
  const [response] = await this.diagnosticsBrazilWrapper.recebeAtendimento({
    request: requestData,
  });
  
  // Valida resposta da API
  const validationResult = await this.validationService.validate(response);
  
  if (!validationResult.isValid) {
    // Se validação falhar, emite evento de erro
    this.eventEmitter.emit('orders.failed', {
      orderId: id,
      errors: validationResult.errors,
    });
    return;
  }
  
  // Se validação passar, completa o pedido automaticamente
  this.eventEmitter.emit('orders.finish', {
    orderId: id,
    status: OrdersStatusEnum.COMPLETED,
    payload,
  });
  
  // Formata resposta das etiquetas geradas
  return this.formatConfirm(response.Confirmacao);
}
Validação: verifica se pedido está IN_PROGRESS antes de gerar
Montagem de dados: combina dados do pedido com campos adicionais validados
API externa: consulta DiagnosticsBrazil via SOAP
Validação de resposta: valida resposta da API antes de completar pedido
Completar pedido: emite 'orders.finish' com status COMPLETED se sucesso
Tratamento de erro: emite 'orders.failed' se validação falhar
7

Cliente processa resposta e gerencia loading

No frontend, o callback processa a resposta e gerencia o estado de loading baseado nas integrações pendentes:

// Label.tsx - Callback do socket.emit
(data: ValiditionsResponseWs) => {
  console.log('Validation response:', data);
  
  // Se há integrações pendentes, remove loading (usuário precisa ajustar dados)
  if (data.integrations.length > 0) {
    setIsLoading(false);
  }
  // Se não há integrações pendentes, loading continua (aguardando geração de etiquetas)

  // Trata erros
  if (data.status === 'refused') {
    setIsLoading(false);
    addNotification({
      title: 'Atenção',
      message: data.message,
      bg: 'danger',
      className: 'text-white',
      autohide: true,
    });
  }
}
Integrações pendentes: se data.integrations.length > 0, remove loading (usuário precisa ajustar)
Todas validadas: se data.integrations.length === 0, mantém loading (aguardando etiquetas)
Tratamento de erro: exibe notificação se status === 'refused'
8

Cliente escuta evento 'completed' e busca etiquetas automaticamente

O componente escuta o evento 'completed' e busca as etiquetas automaticamente quando o pedido é completado:

// Label.tsx
socket.on('completed', (completed: Order) => {
  // Só busca se estiver aguardando impressão
  if (!isWindowPrint.current) return;
  
  // Valida condições antes de buscar
  if (
    order.id !== completed.id ||           // Deve ser o mesmo pedido
    labels.length !== 0 ||                 // Não deve ter etiquetas já carregadas
    completed.status.name !== OrdersStatusEnum.COMPLETED // Status deve ser COMPLETED
  ) return;
  
  // Busca etiquetas automaticamente
  getLabel();
});
Quando é chamado: quando pedido é completado automaticamente após gerar etiquetas
Condições: isWindowPrint.current === true, mesmo pedido, sem etiquetas, status COMPLETED
Automação: busca etiquetas automaticamente quando pedido é finalizado
Flag: isWindowPrint.current foi definida como true no passo 1

Request URL

ws://api-dev.imagemais.com
{
  "orderId": 123,
  "fields": {
  ...
{
  "orderId": 123,
  "fields": {
    "priority": true,
    "height": "170cm",
    "weight": "70kg",
    "procedures": [
      {
        "procedureId": 456,
        "volume": "5ml",
        "region": "Braço direito"
      }
    ]
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Respostas

{
  "status": "receive",
  "integrations": [],
  ...
{
  "status": "receive",
  "integrations": [],
  "timestamp": "2025-01-23T10:30:00.000Z"
}
{
  "status": "refused",
  "message": "Erro ao validar os procedimentos da integração",
  ...
{
  "status": "refused",
  "message": "Erro ao validar os procedimentos da integração",
  "timestamp": "2025-01-23T10:30:00.000Z"
}