create.labelCriar 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
| Nome | Tipo | Obrig. | Descrição |
|---|---|---|---|
Authorization | string | Sim | Token de autenticação no formato Bearer {accessToken}. O token deve ser enviado durante o handshake de conexão WebSocket. |
AuthorizationOBRIGATÓRIOParâmetros
| Nome | Tipo | Obrig. | Descrição |
|---|---|---|---|
orderId | number | Sim | 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. | |||
fields | object | Sim | Campos adicionais validados pelo usuário |
token | string | Sim | Token JWT do usuário para validação e autenticação |
orderIdOBRIGATÓRIOID 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ÓRIOtokenOBRIGATÓRIOFluxo passo a passo: create.label
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]);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');
}
}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');
}
}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
}
}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);
}
}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);
}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,
});
}
}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();
});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"
}