15 KiB
15 KiB
🚀 Sistema Compute - Campos Calculados Automáticos
📚 Índice
- Visão Geral
- Arquitetura
- Implementação
- Como Usar
- Exemplos Práticos
- Configuração Avançada
- Troubleshooting
- Melhorias Futuras
🎯 Visão Geral
O sistema compute é uma solução dinâmica e reutilizável para campos calculados automaticamente em formulários Angular. Ele permite que campos sejam calculados em tempo real baseado em outros campos do formulário, sem necessidade de código específico por domínio.
✨ Características Principais
- ✅ 100% Reutilizável para qualquer domínio
- ✅ Detecção automática de dependências
- ✅ Recálculo em tempo real quando campos relacionados mudam
- ✅ Configuração declarativa por campo
- ✅ Performance otimizada com listeners inteligentes
- ✅ Tratamento de erros robusto
🏗️ Arquitetura
📁 Estrutura de Arquivos
projects/idt_app/src/app/shared/
├── interfaces/
│ └── generic-tab-form.interface.ts # Interface TabFormField estendida
└── components/
└── generic-tab-form/
└── generic-tab-form.component.ts # Lógica de campos computados
🔧 Componentes do Sistema
- Interface Estendida:
TabFormFieldcom propriedadescomputeecomputeDependencies - Detector de Dependências: Analisa funções
computeautomaticamente - Listener Dinâmico: Escuta mudanças apenas nos campos relevantes
- Calculador: Executa funções
computee atualiza valores - Gerenciador de Estado: Preserva valores calculados durante edição
🚀 Implementação
1. Interface TabFormField Estendida
// projects/idt_app/src/app/shared/interfaces/generic-tab-form.interface.ts
export interface TabFormField {
// ... propriedades existentes ...
// 🎯 NOVA: Propriedade para campos calculados
compute?: (model: any) => any;
// 🎯 NOVA: Dependências para campos computados
computeDependencies?: string[];
}
Propriedades:
compute: Função que calcula o valor do campocomputeDependencies: Array de nomes dos campos que afetam o cálculo
2. Sistema de Detecção Automática
// projects/idt_app/src/app/shared/components/generic-tab-form/generic-tab-form.component.ts
private getAllComputedFieldDependencies(): string[] {
const allFields = this.getAllFieldsFromSubTabs();
const dependencies = new Set<string>();
allFields.forEach(field => {
if (field.compute) {
// 🎯 Dependências explícitas (declaradas pelo desenvolvedor)
if (field.computeDependencies) {
field.computeDependencies.forEach(dep => dependencies.add(dep));
}
// 🚀 DETECÇÃO AUTOMÁTICA: Analisar a função compute
const autoDependencies = this.detectDependenciesFromComputeFunction(field);
autoDependencies.forEach(dep => dependencies.add(dep));
}
});
return Array.from(dependencies);
}
3. Detecção Inteligente de Dependências
private detectDependenciesFromComputeFunction(field: TabFormField): string[] {
if (!field.compute) return [];
try {
const functionString = field.compute.toString();
const dependencies: string[] = [];
// 🎯 Padrões comuns de acesso a propriedades
const patterns = [
/model\.(\w+)/g, // model.propertyName
/model\?\.(\w+)/g, // model?.propertyName
/model\[['"`](\w+)['"`]\]/g, // model['propertyName']
/model\[`(\w+)`\]/g // model[`propertyName`]
];
patterns.forEach(pattern => {
let match;
while ((match = pattern.exec(functionString)) !== null) {
const propertyName = match[1];
if (propertyName && !dependencies.includes(propertyName)) {
dependencies.push(propertyName);
}
}
});
return dependencies;
} catch (error) {
console.warn(`⚠️ Erro ao detectar dependências automáticas para ${field.key}:`, error);
return [];
}
}
4. Listener Dinâmico
private setupComputedFieldsListener(): void {
if (!this.form) return;
// 🚀 DINÂMICO: Detectar automaticamente todas as dependências
const allDependencies = this.getAllComputedFieldDependencies();
if (allDependencies.length === 0) {
console.log('ℹ️ [COMPUTE] Nenhum campo computado encontrado');
return;
}
console.log('🎯 [COMPUTE] Dependências detectadas:', allDependencies);
// Escutar mudanças em TODOS os campos que afetam cálculos
this.form.valueChanges.subscribe(value => {
const hasRelevantChange = allDependencies.some(field =>
value[field] !== undefined && value[field] !== null
);
if (hasRelevantChange) {
this.updateComputedFields();
}
});
}
5. Atualização de Campos Computados
private updateComputedFields(): void {
if (!this.form) return;
const formValue = this.form.value;
this.getAllFieldsFromSubTabs().forEach(field => {
if (field.compute) {
try {
const computedValue = field.compute(formValue);
if (computedValue !== undefined && computedValue !== null) {
this.form.get(field.key)?.setValue(computedValue, { emitEvent: false });
}
} catch (error) {
console.warn(`⚠️ Erro ao recalcular campo ${field.key}:`, error);
}
}
});
}
📝 Como Usar
1. Configuração Básica de Campo Computado
{
key: 'campo_calculado',
label: 'Campo Calculado',
type: 'number',
readOnly: true,
compute: (model: any) => {
// Lógica de cálculo aqui
return resultado;
},
computeDependencies: ['campo1', 'campo2']
}
2. Tipos de Campo Suportados
// ✅ Tipos que funcionam bem com compute
type: 'number' // Para valores numéricos
type: 'currency-input' // Para valores monetários
type: 'text' // Para texto calculado
type: 'date' // Para datas calculadas
3. Estrutura da Função Compute
compute: (model: any) => {
// 🎯 model contém todos os valores do formulário
// 🎯 Acessar campos específicos
const valor1 = Number(model?.campo1) || 0;
const valor2 = Number(model?.campo2) || 0;
// 🎯 Lógica de cálculo
const resultado = valor1 + valor2;
// 🎯 Retornar o valor calculado
return resultado;
}
💡 Exemplos Práticos
1. Cálculo Financeiro (Veículos)
{
key: 'calc_total_pago',
label: 'Valor Total Pago',
type: 'currency-input',
readOnly: true,
compute: (model: any) => {
const parcela = Number(model?.alienation_payment_installment_value) || 0;
const pagas = Number(model?.alienation_payment_installment_total) || 0;
return parcela * pagas;
},
computeDependencies: ['alienation_payment_installment_value', 'alienation_payment_installment_total'],
formatOptions: { locale: 'pt-BR', useGrouping: true, suffix: ' R$' }
}
2. Cálculo de Idade (Motoristas)
{
key: 'idade_calculada',
label: 'Idade',
type: 'number',
readOnly: true,
compute: (model: any) => {
if (!model?.data_nascimento) return null;
const nascimento = new Date(model.data_nascimento);
const hoje = new Date();
const idade = hoje.getFullYear() - nascimento.getFullYear();
// Ajustar para mês/dia
const mesAtual = hoje.getMonth();
const mesNascimento = nascimento.getMonth();
if (mesAtual < mesNascimento ||
(mesAtual === mesNascimento && hoje.getDate() < nascimento.getDate())) {
return idade - 1;
}
return idade;
},
computeDependencies: ['data_nascimento']
}
3. Formatação de CNPJ (Empresas)
{
key: 'cnpj_formatado',
label: 'CNPJ Formatado',
type: 'text',
readOnly: true,
compute: (model: any) => {
const cnpj = model?.cnpj;
if (!cnpj) return '';
// Remover caracteres não numéricos
const cleanCnpj = cnpj.replace(/\D/g, '');
if (cleanCnpj.length !== 14) return cnpj;
// Formatar: XX.XXX.XXX/XXXX-XX
return cleanCnpj.replace(
/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/,
'$1.$2.$3/$4-$5'
);
},
computeDependencies: ['cnpj']
}
4. Cálculo de Distância (Rotas)
{
key: 'distancia_total',
label: 'Distância Total',
type: 'number',
readOnly: true,
compute: (model: any) => {
const paradas = model?.stops || [];
if (paradas.length < 2) return 0;
let distanciaTotal = 0;
for (let i = 1; i < paradas.length; i++) {
const anterior = paradas[i - 1];
const atual = paradas[i];
const distancia = this.calcularDistancia(
anterior.latitude, anterior.longitude,
atual.latitude, atual.longitude
);
distanciaTotal += distancia;
}
return Math.round(distanciaTotal * 100) / 100; // 2 casas decimais
},
computeDependencies: ['stops']
}
⚙️ Configuração Avançada
1. Dependências Múltiplas
{
key: 'campo_complexo',
compute: (model: any) => {
// Campo depende de vários outros campos
const valor1 = model?.campo1 || 0;
const valor2 = model?.campo2 || 0;
const valor3 = model?.campo3 || 0;
return (valor1 + valor2) * valor3;
},
// 🎯 Declarar TODAS as dependências
computeDependencies: ['campo1', 'campo2', 'campo3']
}
2. Validação Condicional
{
key: 'campo_validado',
compute: (model: any) => {
const valor = model?.campo_base;
// 🎯 Validação antes do cálculo
if (!valor || valor <= 0) {
return null; // Retornar null para indicar valor inválido
}
return valor * 2;
},
computeDependencies: ['campo_base']
}
3. Cálculos com Formatação
{
key: 'valor_formatado',
type: 'text',
readOnly: true,
compute: (model: any) => {
const valor = Number(model?.valor_base) || 0;
if (valor === 0) return 'R$ 0,00';
// 🎯 Formatação brasileira
return valor.toLocaleString('pt-BR', {
style: 'currency',
currency: 'BRL',
minimumFractionDigits: 2
});
},
computeDependencies: ['valor_base']
}
🔧 Troubleshooting
❌ Problemas Comuns e Soluções
1. Campo não está sendo recalculado
Sintomas:
- Campo computado não atualiza quando dependências mudam
- Valor inicial não é calculado
Soluções:
// ✅ Verificar se computeDependencies está correto
computeDependencies: ['campo1', 'campo2']
// ✅ Verificar se a função compute retorna valor válido
compute: (model: any) => {
const valor = model?.campo1 || 0;
return valor > 0 ? valor * 2 : 0; // Sempre retornar valor válido
}
2. Erro "Cannot read property of undefined"
Sintomas:
- Erro no console ao tentar calcular campo
- Campo fica vazio
Soluções:
// ✅ Usar operador de coalescência nula
compute: (model: any) => {
const valor = model?.campo1 ?? 0; // Usar ?? em vez de ||
return valor * 2;
}
// ✅ Verificar se campos existem antes de usar
compute: (model: any) => {
if (!model?.campo1 || !model?.campo2) return 0;
return model.campo1 + model.campo2;
}
3. Performance lenta com muitos campos computados
Sintomas:
- Formulário lento ao digitar
- Muitos recálculos desnecessários
Soluções:
// ✅ Limitar dependências apenas aos campos essenciais
computeDependencies: ['campo_essencial'] // Não incluir campos opcionais
// ✅ Usar debounce para campos que mudam frequentemente
// (Implementar no futuro se necessário)
🔍 Debug e Logs
// ✅ Adicionar logs para debug
compute: (model: any) => {
console.log('🔍 [DEBUG] Modelo recebido:', model);
const valor1 = Number(model?.campo1) || 0;
const valor2 = Number(model?.campo2) || 0;
console.log('🔍 [DEBUG] Valores:', { valor1, valor2 });
const resultado = valor1 + valor2;
console.log('🔍 [DEBUG] Resultado:', resultado);
return resultado;
}
🚀 Melhorias Futuras
1. Sistema de Cache Inteligente
// 🎯 Cache configurável por campo
computeCache?: {
enabled?: boolean;
ttl?: number; // Time to live em ms
strategy?: 'memory' | 'localStorage';
}
2. Debounce para Campos Frequentes
// 🎯 Debounce para campos que mudam muito
computeDebounce?: {
enabled?: boolean;
delay?: number; // ms
}
3. Validação de Dependências
// 🎯 Validação automática de dependências
computeValidation?: {
required?: boolean;
minValue?: number;
maxValue?: number;
pattern?: RegExp;
}
4. Métricas de Performance
// 🎯 Monitoramento de performance
computeMetrics?: {
trackExecutionTime?: boolean;
trackDependencyChanges?: boolean;
logPerformance?: boolean;
}
✅ Checklist de Implementação
✅ Para Desenvolvedores
- Campo tem propriedade
computedefinida - Campo tem
computeDependenciesdeclaradas - Campo é
readOnly: true - Função
computeretorna valor válido - Dependências estão corretas
- Tratamento de erros implementado
- Testado com valores vazios/nulos
- Performance aceitável
✅ Para Testes
- Campo é calculado ao carregar formulário
- Campo é recalculado quando dependências mudam
- Campo não é recalculado desnecessariamente
- Tratamento de erros funciona
- Performance é aceitável
- Funciona em diferentes domínios
🎯 Conclusão
O sistema compute para campos calculados é uma solução robusta, escalável e reutilizável que elimina a necessidade de código específico por domínio para cálculos automáticos.
🌟 Benefícios
- Produtividade: Desenvolvimento mais rápido
- Manutenibilidade: Código centralizado e limpo
- Reutilização: Funciona em qualquer domínio
- Performance: Otimizado e eficiente
- Flexibilidade: Configurável por campo
🎯 Próximos Passos
- Implementar em novos domínios
- Adicionar testes automatizados
- Monitorar performance em produção
- Coletar feedback dos usuários
- Implementar melhorias baseadas em uso real
📚 Referências
Arquivos do Sistema
- Interface:
projects/idt_app/src/app/shared/interfaces/generic-tab-form.interface.ts - Componente:
projects/idt_app/src/app/shared/components/generic-tab-form/generic-tab-form.component.ts - Exemplo de Uso:
projects/idt_app/src/app/domain/vehicles/vehicles.component.ts
Padrões Utilizados
- Registry Pattern: Para configuração de formulários
- Observer Pattern: Para listeners de mudanças
- Strategy Pattern: Para diferentes tipos de cálculo
- Factory Pattern: Para criação de campos computados
Autor: PraFrota Development Team
Data: $(date)
Versão: 2.0.0 - Padronização Completa