import axios from "axios"; import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp"; export type FechamentoTarefaItem = { id: string; fechamentoId: string; asanaTaskGid?: string | null; tipo: string; numeroTicket: string | null; descricao: string; cliente: string | null; linkAsana: string | null; etiquetas: { gid: string; name: string }[] | null; tempoMinutos: number | null; pontuacao: number; pontuacaoOriginal: number; estaRevisada: boolean; dataInicio: string | null; dataVencimento: string | null; dataConclusao: string | null; editadoEm: string | null; editadoPorId: string | null; criadoEm: string; }; type ListarTarefasResponse = { data: FechamentoTarefaItem[]; }; type PatchTarefaResponse = { data: FechamentoTarefaItem; }; type CriarLancamentoResponse = { data: FechamentoTarefaItem; }; export type FechamentoFechadoResponse = { id: string; competenciaId: string; parceiroId: string; status: "fechado"; versao: number; pontuacaoTotalEntregue: number; pontuacaoMeta: number; pontuacaoPaga: number; pontuacaoBanco: number; exportadoFinanceiro: boolean; fechadoPorId: string | null; fechadoEm: string | null; criadoEm: string; atualizadoEm: string; }; export type FechamentoReabertoResponse = { id: string; competenciaId: string; parceiroId: string; status: "em_aberto"; versao: number; pontuacaoTotalEntregue: number; pontuacaoMeta: number; pontuacaoPaga: number; pontuacaoBanco: number; exportadoFinanceiro: boolean; fechadoPorId: string | null; fechadoEm: string | null; criadoEm: string; atualizadoEm: string; }; type ConcluirFechamentoResponse = { data: FechamentoFechadoResponse; }; type ReabrirFechamentoResponse = { data: FechamentoReabertoResponse; }; type ReprocessarAsanaResponse = { data: { fechamentoId: string; tarefasRecebidas: number; tarefasImportadas: number; tarefasIgnoradasSemParceiro: number; chamadasSubtarefasAsana: number; }; }; export type ExportarPlanilhaResponse = { buffer: ArrayBuffer; filename: string | null; }; type ApiErrorShape = { error?: { message?: string; }; }; class FechamentoFechamentosService { private normalizeEtiquetas( value: FechamentoTarefaItem["etiquetas"] | undefined, tarefaId: string, ): { gid: string; name: string }[] | null { if (!Array.isArray(value) || value.length === 0) { return null; } const out: { gid: string; name: string }[] = []; for (let i = 0; i < value.length; i += 1) { const item = value[i]; const name = typeof item?.name === "string" ? item.name.trim() : ""; if (!name) continue; const gidRaw = typeof item?.gid === "string" ? item.gid.trim() : ""; out.push({ gid: gidRaw || `${tarefaId}-etiqueta-${i}`, name }); } return out.length > 0 ? out : null; } private extractFilenameFromContentDisposition(value: string | undefined): string | null { if (!value) return null; const utf8Match = value.match(/filename\*=UTF-8''([^;]+)/i); if (utf8Match?.[1]) { try { return decodeURIComponent(utf8Match[1]); } catch { return utf8Match[1]; } } const regularMatch = value.match(/filename="?([^";]+)"?/i); return regularMatch?.[1] ?? null; } private handleAxiosError(error: unknown, fallback: string): never { if (axios.isAxiosError(error)) { if (!error.response) { throw new Error( "Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.", ); } const message = ((error.response.data as ApiErrorShape | undefined)?.error?.message ?? error.message ?? fallback) as string; throw new Error(message); } throw new Error(fallback); } async listarTarefas(fechamentoId: string): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.get(`${baseUrl}/fechamentos/${fechamentoId}/tarefas`, { headers, }); const tarefas = response.data.data ?? []; return tarefas.map((tarefa) => ({ ...tarefa, etiquetas: this.normalizeEtiquetas(tarefa.etiquetas, tarefa.id), })); } catch (error) { this.handleAxiosError(error, "Erro ao listar tarefas do fechamento."); } } async patchTarefa( fechamentoId: string, tarefaId: string, input: { estaRevisada?: boolean; pontuacao?: number; numeroTicket?: string | null; descricao?: string; cliente?: string | null; tempoMinutos?: number | null; editadoPorId: string; }, ): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.patch( `${baseUrl}/fechamentos/${fechamentoId}/tarefas/${tarefaId}`, { ...(input.estaRevisada !== undefined ? { esta_revisada: input.estaRevisada } : {}), ...(input.pontuacao !== undefined ? { pontuacao: input.pontuacao } : {}), ...(input.numeroTicket !== undefined ? { numero_ticket: input.numeroTicket } : {}), ...(input.descricao !== undefined ? { descricao: input.descricao } : {}), ...(input.cliente !== undefined ? { cliente: input.cliente } : {}), ...(input.tempoMinutos !== undefined ? { tempo_minutos: input.tempoMinutos } : {}), editado_por_id: input.editadoPorId, }, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao atualizar aprovação da tarefa."); } } async criarLancamento( fechamentoId: string, input: { tipo: "bonus" | "desconto"; descricao: string; pontuacao: number }, ): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.post( `${baseUrl}/fechamentos/${fechamentoId}/tarefas`, { tipo: input.tipo, descricao: input.descricao, pontuacao: input.pontuacao, }, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao criar lançamento no fechamento."); } } async excluirLancamento(fechamentoId: string, tarefaId: string): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.delete( `${baseUrl}/fechamentos/${fechamentoId}/tarefas/${tarefaId}`, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao excluir lançamento."); } } async concluirFechamento( fechamentoId: string, input: { pontuacaoPaga: number; fechadoPorId?: string; motivoAjuste?: string }, ): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.post( `${baseUrl}/fechamentos/${fechamentoId}/concluir`, { pontuacao_paga: input.pontuacaoPaga, ...(input.fechadoPorId ? { fechado_por_id: input.fechadoPorId } : {}), ...(input.motivoAjuste?.trim() ? { motivo_ajuste: input.motivoAjuste.trim() } : {}), }, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao concluir fechamento."); } } async exportarPlanilha(fechamentoId: string): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.get(`${baseUrl}/fechamentos/${fechamentoId}/exportar`, { headers, responseType: "arraybuffer", }); return { buffer: response.data, filename: this.extractFilenameFromContentDisposition(response.headers["content-disposition"]), }; } catch (error) { this.handleAxiosError(error, "Erro ao exportar planilha do fechamento."); } } async reabrirFechamento( fechamentoId: string, input: { reabertoPorId: string; motivo?: string }, ): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.post( `${baseUrl}/fechamentos/${fechamentoId}/reabrir`, { reaberto_por_id: input.reabertoPorId, ...(input.motivo?.trim() ? { motivo: input.motivo.trim() } : {}), }, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao reabrir fechamento."); } } async reprocessarAsana(fechamentoId: string): Promise { try { const headers = await buildCommanderHeaders(); const baseUrl = resolveCommanderBaseUrl(); const response = await axios.post( `${baseUrl}/fechamentos/${fechamentoId}/reprocessar-asana`, {}, { headers }, ); return response.data.data; } catch (error) { this.handleAxiosError(error, "Erro ao reprocessar tarefas do Asana."); } } } export const fechamentoFechamentosService = new FechamentoFechamentosService();