🚀 Initial commit: Versão atual do TrackSteel APP

This commit is contained in:
2026-03-18 21:17:53 +00:00
commit bde410c9ad
633 changed files with 108150 additions and 0 deletions
+410
View File
@@ -0,0 +1,410 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
export interface RomaneioExpedicao {
id: string;
numero_romaneio: string;
of_number: string;
data_romaneio: string;
data_criacao: string;
revisao: number;
motivo_revisao?: string;
data_prevista_entrega?: string;
prioridade: string;
status: string;
observacoes?: string;
peso_total_romaneio: number;
previsao_kg?: number;
maior_dimensao?: string;
tipo_transporte?: string;
frete_tipo?: string;
nome_motorista?: string;
created_at: string;
updated_at: string;
created_by?: string;
itens_pecas?: ItemRomaneioPeca[];
itens_insumos?: ItemRomaneioInsumo[];
}
export interface ItemRomaneioPeca {
id: string;
romaneio_id: string;
peca_id: string;
marca: string;
fase?: string;
descricao?: string;
comprimento?: number;
peso_unitario: number;
quantidade_expedida: number;
peso_total: number;
quantidade_faltante: number;
created_at: string;
}
export interface ItemRomaneioInsumo {
id: string;
romaneio_id: string;
tipo_insumo: string;
descricao: string;
unidade: string;
quantidade_expedida: number;
peso_unitario?: number;
peso_total?: number;
observacoes?: string;
created_at: string;
}
export interface PecaPintura {
id: string;
marca: string;
etapa_fase?: string;
descricao?: string;
peso_unitario: number;
quantidade_produzida_pintura: number;
quantidade_ja_expedida: number;
quantidade_disponivel: number;
of_number: string;
}
export const useRomaneios = (ofNumberFilter?: string) => {
return useQuery({
queryKey: ['romaneios', ofNumberFilter],
queryFn: async () => {
let query = supabase
.from('romaneios_expedicao')
.select(`
*,
itens_pecas:itens_romaneio_pecas(*),
itens_insumos:itens_romaneio_insumos(*)
`)
.order('created_at', { ascending: false });
if (ofNumberFilter && ofNumberFilter !== 'all') {
query = query.eq('of_number', ofNumberFilter);
}
const { data, error } = await query;
if (error) throw error;
// Calcular peso total correto das peças para cada romaneio
const normalizedData = data?.map(romaneio => {
const pesoTotalPecas = romaneio.itens_pecas?.reduce((sum: number, item: any) => sum + (item.peso_total || 0), 0) || 0;
const pesoTotalInsumos = romaneio.itens_insumos?.reduce((sum: number, item: any) => sum + (item.peso_total || 0), 0) || 0;
return {
...romaneio,
peso_total_romaneio: pesoTotalPecas + pesoTotalInsumos,
prioridade: romaneio.prioridade === 'Normal' || romaneio.prioridade === 'Urgente'
? romaneio.prioridade
: 'Normal',
status: ['Em planejamento', 'Confirmado', 'Expedido', 'Entregue', 'Conferido em Obra'].includes(romaneio.status)
? romaneio.status
: 'Em planejamento',
frete_tipo: romaneio.frete_tipo === 'terceiros' || romaneio.frete_tipo === 'proprio'
? romaneio.frete_tipo
: undefined
};
});
return normalizedData as RomaneioExpedicao[];
},
});
};
export const usePecasPintura = (ofNumber?: string) => {
return useQuery({
queryKey: ['pecas-pintura', ofNumber],
queryFn: async () => {
if (!ofNumber) return [];
// Buscar peças que passaram pelo processo de pintura
const { data: apontamentosPintura, error: apontamentosError } = await supabase
.from('apontamentos_producao')
.select(`
peca_id,
quantidade_produzida,
pecas (
id,
marca,
etapa_fase,
descricao,
peso_unitario,
of_number
),
processos_fabricacao (
nome
)
`)
.eq('of_number', ofNumber)
.ilike('processos_fabricacao.nome', '%pintura%');
if (apontamentosError) throw apontamentosError;
// Buscar quantidades já expedidas
const { data: itensExpedidos, error: expedidosError } = await supabase
.from('itens_romaneio_pecas')
.select('peca_id, quantidade_expedida')
.in('peca_id', apontamentosPintura?.map(a => a.peca_id) || []);
if (expedidosError) throw expedidosError;
// Calcular disponibilidade
const pecasPintura: PecaPintura[] = [];
const pecasMap = new Map();
apontamentosPintura?.forEach(apontamento => {
const pecaId = apontamento.peca_id;
const peca = apontamento.pecas;
if (!peca) return;
if (pecasMap.has(pecaId)) {
pecasMap.get(pecaId).quantidade_produzida_pintura += apontamento.quantidade_produzida;
} else {
pecasMap.set(pecaId, {
id: peca.id,
marca: peca.marca,
etapa_fase: peca.etapa_fase,
descricao: peca.descricao,
peso_unitario: peca.peso_unitario,
quantidade_produzida_pintura: apontamento.quantidade_produzida,
of_number: peca.of_number
});
}
});
pecasMap.forEach((peca, pecaId) => {
const totalExpedido = itensExpedidos
?.filter(item => item.peca_id === pecaId)
.reduce((sum, item) => sum + item.quantidade_expedida, 0) || 0;
pecasPintura.push({
...peca,
quantidade_ja_expedida: totalExpedido,
quantidade_disponivel: peca.quantidade_produzida_pintura - totalExpedido
});
});
return pecasPintura.filter(p => p.quantidade_disponivel > 0);
},
enabled: !!ofNumber,
});
};
const generateRomaneioNumber = (ofNumber: string, existingRomaneios: RomaneioExpedicao[]) => {
// Extrair apenas o número da OF (sem o B)
const ofNumOnly = ofNumber.replace('B', '');
// Contar romaneios existentes para esta OF
const existingCount = existingRomaneios.filter(r => r.of_number === ofNumber).length;
const nextSequence = existingCount + 1;
return `RO${ofNumOnly}-${nextSequence.toString().padStart(2, '0')}`;
};
export const useCriarRomaneio = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (romaneio: Omit<RomaneioExpedicao, 'id' | 'numero_romaneio' | 'created_at' | 'updated_at'>) => {
const { data: existingRomaneios } = await supabase
.from('romaneios_expedicao')
.select('*')
.eq('of_number', romaneio.of_number);
const numeroRomaneio = generateRomaneioNumber(romaneio.of_number, existingRomaneios || []);
const { data, error } = await supabase
.from('romaneios_expedicao')
.insert([{
numero_romaneio: numeroRomaneio,
of_number: romaneio.of_number,
data_romaneio: romaneio.data_romaneio,
data_criacao: romaneio.data_criacao,
revisao: romaneio.revisao || 1,
motivo_revisao: romaneio.motivo_revisao,
data_prevista_entrega: romaneio.data_prevista_entrega,
prioridade: romaneio.prioridade,
status: romaneio.status,
observacoes: romaneio.observacoes,
peso_total_romaneio: romaneio.peso_total_romaneio,
previsao_kg: romaneio.previsao_kg,
maior_dimensao: romaneio.maior_dimensao,
tipo_transporte: romaneio.tipo_transporte,
frete_tipo: romaneio.frete_tipo,
nome_motorista: romaneio.nome_motorista,
created_by: (await supabase.auth.getUser()).data.user?.id
}])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Romaneio criado com sucesso!');
},
onError: (error) => {
console.error('Erro ao criar romaneio:', error);
toast.error('Erro ao criar romaneio');
},
});
};
export const useAtualizarRomaneio = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, ...romaneio }: Partial<RomaneioExpedicao> & { id: string }) => {
// Buscar status atual antes da atualização
const { data: romaneioAtual, error: errorAtual } = await supabase
.from('romaneios_expedicao')
.select('status, numero_romaneio, of_number')
.eq('id', id)
.single();
if (errorAtual) {
console.error('❌ Erro ao buscar romaneio atual:', errorAtual);
throw errorAtual;
}
console.log('📋 Status atual do romaneio:', romaneioAtual?.status);
console.log('📋 Novo status a ser definido:', romaneio.status);
const { data, error } = await supabase
.from('romaneios_expedicao')
.update(romaneio)
.eq('id', id)
.select(`
*,
itens_pecas:itens_romaneio_pecas(*)
`)
.single();
if (error) throw error;
// VERIFICAÇÃO ESPECÍFICA: Disparar apontamento automático APENAS quando status muda PARA "Entregue"
const statusAnterior = romaneioAtual?.status;
const novoStatus = romaneio.status;
console.log('🔍 Verificando mudança de status:');
console.log('- Status anterior:', statusAnterior);
console.log('- Novo status:', novoStatus);
if (novoStatus === 'Entregue' && statusAnterior !== 'Entregue') {
console.log('🚀 CONDIÇÃO ATENDIDA: Status mudou PARA "Entregue" - iniciando apontamento automático');
console.log('📦 Dados do romaneio atualizado:', data);
// Buscar dados completos do romaneio incluindo itens
const { data: romaneioCompleto, error: errorCompleto } = await supabase
.from('romaneios_expedicao')
.select(`
*,
itens_pecas:itens_romaneio_pecas(*)
`)
.eq('id', id)
.single();
if (errorCompleto) {
console.error('❌ Erro ao buscar dados completos do romaneio:', errorCompleto);
return data;
}
console.log('📋 Dados completos do romaneio para apontamento:', romaneioCompleto);
console.log('🔢 Número de peças no romaneio:', romaneioCompleto?.itens_pecas?.length || 0);
// Verificar se há peças para apontar
if (!romaneioCompleto?.itens_pecas || romaneioCompleto.itens_pecas.length === 0) {
console.log('⚠️ Romaneio sem peças - não será processado apontamento automático');
return data;
}
// Disparar evento customizado para apontamento automático
const eventDetail = {
romaneioId: id,
status: novoStatus,
romaneioData: romaneioCompleto,
statusAnterior: statusAnterior
};
console.log('🎯 Disparando evento romaneio-entregue para apontamento automático:', eventDetail);
// Usar setTimeout para garantir que o evento seja disparado após a atualização
setTimeout(() => {
window.dispatchEvent(new CustomEvent('romaneio-entregue', {
detail: eventDetail
}));
}, 100);
} else if (novoStatus === 'Entregue' && statusAnterior === 'Entregue') {
console.log('ℹ️ Status já era "Entregue" - apontamento automático não será disparado');
} else {
console.log(`ℹ️ Status atualizado de "${statusAnterior}" para "${novoStatus}" - apontamento automático não será disparado (só para mudança PARA "Entregue")`);
}
return data;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Romaneio atualizado com sucesso!');
},
onError: (error) => {
console.error('Erro ao atualizar romaneio:', error);
toast.error('Erro ao atualizar romaneio');
},
});
};
export const useAdicionarItemPeca = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (item: Omit<ItemRomaneioPeca, 'id' | 'created_at'>) => {
const { data, error } = await supabase
.from('itens_romaneio_pecas')
.insert([item])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Item adicionado com sucesso!');
},
onError: (error) => {
console.error('Erro ao adicionar item:', error);
toast.error('Erro ao adicionar item');
},
});
};
export const useAdicionarItemInsumo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (item: Omit<ItemRomaneioInsumo, 'id' | 'created_at'>) => {
const { data, error } = await supabase
.from('itens_romaneio_insumos')
.insert([item])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Insumo adicionado com sucesso!');
},
onError: (error) => {
console.error('Erro ao adicionar insumo:', error);
toast.error('Erro ao adicionar insumo');
},
});
};