Migração do Sistema de Preço Cortado para Políticas de Corte - NH-42
Data de Implementação: 2025-11-17 Jira Issue: NH-42 Módulo: Comercial Tela: Calcular Preço do Corte Tipo: Refactoring - Migração de Sistema Legado
Índice
- Resumo
- Contexto e Motivação
- Mudanças Implementadas
- Detalhes Técnicos
- Como Testar
- Impacto no Sistema
- Arquivos Modificados
- Benefícios da Migração
- Notas Importantes
Resumo
Migração completa do sistema de Cálculo de Preço Cortado do modelo legado MercadoriaTipoServico para o novo modelo Políticas de Corte.
Mudança Principal:
- ❌ Antes: Sistema usava tabela MercadoriaTipoServico (modelo legado) com fallback
- ✅ Depois: Sistema usa exclusivamente a tabela PoliticaCorte (modelo atual)
Objetivos Alcançados:
- Eliminação de dependências do sistema legado MercadoriaTipoServico
- Unificação de fonte de dados para tipos de serviço
- Cálculos baseados exclusivamente em Políticas de Corte configuradas
- Interface mais clara e consistente
Contexto e Motivação
Problema Anterior
O sistema de cálculo de preço cortado estava dividido entre dois modelos diferentes:
- Modelo Legado (
MercadoriaTipoServico): - Tabela antiga que associava mercadorias a tipos de serviço
- Continha configurações de corte por família de mercadorias
- Sistema de fallback quando política de corte não existia
-
Difícil manutenção e duplicação de dados
-
Modelo Novo (
PoliticaCorte): - Tabela moderna com políticas específicas por mercadoria e tipo de serviço
- Mais flexível e granular
- Melhor organização de dados
Inconsistências Encontradas
- 🔀 Lógica duplicada: Cálculos podiam usar valores de dois lugares diferentes
- ⚠️ Fallback problemático: Sistema priorizava política de corte mas recaía para modelo legado
- 🎯 Falta de clareza: Usuário não sabia qual fonte de dados estava sendo usada
- 🔧 Manutenção complexa: Alterações precisavam ser feitas em dois lugares
Solicitação
Migrar completamente para o novo modelo de Políticas de Corte, eliminando dependências do sistema legado e simplificando a lógica de cálculo.
Mudanças Implementadas
1. Modelo de Dados (PrecoCortado)
Antes:
public class PrecoCortado
{
// Nova FK para PoliticaCorte (preferencial)
public int? PoliticaCorteId { get; set; }
public PoliticaCorte? PoliticaCorte { get; set; }
// FK legada (compatibilidade durante migração)
public int? MercadoriaTipoServicoId { get; set; }
public MercadoriaTipoServico? MercadoriaTipoServico { get; set; }
public decimal PercentualMargemComercial { get; set; }
// ...
}
Depois:
public class PrecoCortado
{
// FK para TipoServico (usado para buscar a política de corte)
public int? TipoServicoId { get; set; }
public TipoServico? TipoServico { get; set; }
// Política de Corte encontrada (usada internamente nos cálculos)
public int? PoliticaCorteId { get; set; }
public PoliticaCorte? PoliticaCorte { get; set; }
public decimal ValorQuilo { get; set; }
public decimal PercentualMargemComercial { get; set; }
// ...
}
Mudanças:
- ✅ Removida referência a MercadoriaTipoServico
- ✅ Adicionado TipoServicoId para seleção do tipo de serviço
- ✅ Adicionado ValorQuilo para armazenar valor da política
- ✅ PoliticaCorte é encontrada pela combinação (MercadoriaId + TipoServicoId)
2. Lógica de Cálculo (TomadaPrecoService)
Antes:
// Priorizar PoliticaCorte (novo sistema), com fallback para MercadoriaTipoServico (legado)
if (precoCortado.PoliticaCorte != null)
{
indicePerca = precoCortado.PoliticaCorte.IndicePerca ?? 1;
valorPorQuilo = (decimal)precoCortado.PoliticaCorte.ValorQuilo;
percentualMargemComercial = precoCortado.PoliticaCorte.PercentualMargemComercial ?? 0;
}
else // FALLBACK para modelo legado
{
indicePerca = precoCortado.MercadoriaTipoServico?.IndicePerca ?? 1;
valorPorQuilo = (decimal)(precoCortado.MercadoriaTipoServico?.ValorPorQuilo ?? 0);
percentualMargemComercial = precoCortado.MercadoriaTipoServico?.PercentualMargemComercial ?? 0;
}
Depois:
// Buscar a Política de Corte baseado em TipoServicoId + MercadoriaId
if (precoCortado.TipoServicoId.HasValue)
{
var politicasCorte = await _politicaCorteRepository.ObterPoliticasCortePorMercadoria(precoCortado.MercadoriaId);
precoCortado.PoliticaCorte = politicasCorte.FirstOrDefault(p => p.TipoServicoId == precoCortado.TipoServicoId.Value);
if (precoCortado.PoliticaCorte != null)
{
precoCortado.PoliticaCorteId = precoCortado.PoliticaCorte.Id;
}
}
// Obter valores da Política de Corte (SEM FALLBACK)
var indicePerca = precoCortado.PoliticaCorte!.IndicePerca ?? 1;
var valorPorQuilo = (decimal)precoCortado.PoliticaCorte!.ValorQuilo;
var percentualMargemComercial = precoCortado.PoliticaCorte!.PercentualMargemComercial ?? 0;
// Copiar valores para o retorno
precoCortado.ValorQuilo = valorPorQuilo;
precoCortado.PercentualMargemComercial = percentualMargemComercial;
Mudanças:
- ✅ Removido fallback para MercadoriaTipoServico
- ✅ Política de corte encontrada por busca (MercadoriaId + TipoServicoId)
- ✅ Validação garante que política de corte existe
- ✅ Cálculos usam exclusivamente valores da política de corte
3. Interface de Usuário
Menu de Serviços (_Layout.cshtml):
- ✅ "Políticas de Corte" → "Cadastro de Políticas de Corte"
- ✅ "Cadastro de Tipos de Serviços (old)" → Oculto (mantido no código)
Dropdown de Tipo de Serviço (PrecoCortado.cshtml):
Antes:
<select asp-for="MercadoriaTipoServicoId" class="form-control">
@{
List<MercadoriaTipoServico> listaMercadoriaTipoServico = ViewBag.ListaMercadoriaTipoServico;
foreach (var item in listaMercadoriaTipoServico.OrderBy(x => x.Servico))
{
<option value="@item.Id">@item.Servico</option>
}
}
</select>
Depois:
<select asp-for="TipoServicoId" class="form-control">
@{
var listaTiposServico = ViewBag.ListaTiposServico as IEnumerable<TipoServico>;
if (listaTiposServico != null && listaTiposServico.Any())
{
foreach (var item in listaTiposServico)
{
<option value="@item.Id">@item.Nome</option>
}
}
else
{
<option value="">Nenhum tipo de serviço disponível para esta mercadoria</option>
}
}
</select>
Mudanças: - ✅ Dropdown carrega tipos de serviço das políticas de corte da mercadoria - ✅ Mostra apenas tipos de serviço que têm política configurada - ✅ Mensagem clara quando não há tipos disponíveis
4. Fonte de Dados para Tipos de Serviço
Método Novo (TomadaPrecoController):
private async Task<IEnumerable<TipoServico>> PopularTiposServicoPorMercadoria(int mercadoriaId)
{
var politicasCorte = await _politicaCorteRepository.ObterPoliticasCortePorMercadoria(mercadoriaId);
return politicasCorte
.Where(p => p.TipoServico != null)
.Select(p => p.TipoServico!)
.DistinctBy(ts => ts.Id)
.OrderBy(ts => ts.Nome);
}
Comportamento:
1. Busca todas as políticas de corte da mercadoria selecionada
2. Extrai os tipos de serviço únicos dessas políticas
3. Remove duplicatas (usa DistinctBy)
4. Ordena por nome do serviço
5. Retorna apenas tipos de serviço que têm política configurada
Detalhes Técnicos
Fluxo de Funcionamento
┌─────────────────────────────────────────────────────────────┐
│ 1. Usuário seleciona Mercadoria │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Sistema busca Políticas de Corte da Mercadoria │
│ Repository: ObterPoliticasCortePorMercadoria() │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Extrai Tipos de Serviço únicos das Políticas │
│ - DistinctBy(ts => ts.Id) │
│ - OrderBy(ts => ts.Nome) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Popula dropdown com Tipos de Serviço disponíveis │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Usuário seleciona Tipo de Serviço e Quantidade │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Sistema encontra Política de Corte correspondente │
│ Busca: MercadoriaId + TipoServicoId │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 7. Validação: Política de Corte DEVE existir │
│ Se não existir: ERRO (sem fallback) │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 8. Cálculo usando valores da Política de Corte │
│ - ValorQuilo │
│ - IndicePerca │
│ - PercentualMargemComercial │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 9. Retorna PrecoCortado calculado │
└─────────────────────────────────────────────────────────────┘
Validações Implementadas
FluentValidation (PrecoCortadoValidation):
RuleFor(x => x.MercadoriaId)
.NotEmpty()
.WithName("Mercadoria");
RuleFor(x => x.TipoServicoId) // Mudou de MercadoriaTipoServicoId
.NotEmpty()
.WithName("Tipo Serviço");
RuleFor(x => x.QuantidadePeca)
.NotEmpty()
.GreaterThan(0)
.WithName("Quantidade Peças");
RuleFor(x => x.ComprimentoPeca)
.GreaterThan(0)
.WithName("Comprimento Peças (mm)");
Validação de Negócio (TomadaPrecoService):
// Validar que a política de corte foi encontrada (SEM FALLBACK)
if (precoCortado.PoliticaCorte is null)
{
Notify("Política de Corte não encontrada para o Tipo de Serviço selecionado.");
isValid = false;
}
else
{
// Validar SolicitarComprimento
bool solicitarComprimento = precoCortado.PoliticaCorte.SolicitarComprimento;
if (solicitarComprimento && !precoCortado.ComprimentoPeca.HasValue)
{
Notify("Comprimento Peças (mm) não informado.");
isValid = false;
}
}
Dependências Removidas
TomadaPrecoService:
- ❌ Removido: IMercadoriaTipoServicoRepository
- ✅ Mantido: IPoliticaCorteRepository
TomadaPrecoController:
- ✅ Adicionado: IPoliticaCorteRepository
- ✅ Adicionado: ITipoServicoRepository
Como Testar
Pré-requisitos
- ✅ Ter ao menos uma Política de Corte cadastrada
- ✅ Política deve ter:
- Mercadoria associada
- Tipo de Serviço configurado
- ValorQuilo definido
- PercentualMargemComercial (opcional)
- IndicePerca (opcional)
Cenário 1: Cálculo com Política de Corte Existente
Passos: 1. Acessar: Comercial → Tomada de Preço → Selecionar Mercadoria → "Calcular Preço do Corte" 2. Verificar que o dropdown "Tipo Serviço" mostra apenas serviços com política configurada 3. Selecionar um Tipo de Serviço 4. Informar Quantidade de Peças 5. Informar Comprimento de Peças (se solicitado) 6. Clicar em "Calcular"
Resultado Esperado: - ✅ Cálculo executado com sucesso - ✅ Valores exibidos na tabela (ValorQuilo, PercentualMargemComercial, etc.) - ✅ Nenhum erro de validação
Cenário 2: Mercadoria sem Política de Corte
Passos: 1. Selecionar mercadoria que não tem política de corte cadastrada 2. Observar dropdown "Tipo Serviço"
Resultado Esperado: - ✅ Mensagem: "Nenhum tipo de serviço disponível para esta mercadoria" - ✅ Dropdown vazio (sem opções)
Cenário 3: Tipo de Serviço sem Política para Mercadoria
Passos: 1. Selecionar mercadoria X 2. Verificar que apenas tipos de serviço com política configurada aparecem 3. Tipos de serviço sem política não devem aparecer no dropdown
Resultado Esperado: - ✅ Dropdown mostra apenas tipos de serviço válidos - ✅ Sistema previne seleção de tipos sem política
Cenário 4: Validação de Comprimento
Passos:
1. Selecionar política de corte com SolicitarComprimento = true
2. Informar Quantidade de Peças
3. NÃO informar Comprimento de Peças
4. Clicar em "Calcular"
Resultado Esperado: - ❌ Erro de validação: "Comprimento Peças (mm) não informado." - ✅ Campo deve ser preenchido obrigatoriamente
Impacto no Sistema
Impacto Positivo
✅ Simplificação de Código: - Remoção de lógica de fallback complexa - Menos dependências entre módulos - Código mais limpo e fácil de manter
✅ Consistência de Dados: - Única fonte de verdade: Políticas de Corte - Eliminação de duplicação de configurações - Dados sempre atualizados
✅ Melhor UX: - Dropdown mostra apenas opções válidas - Mensagens claras quando não há políticas - Interface mais intuitiva
✅ Facilidade de Manutenção: - Alterações centralizadas em um único lugar - Menos chance de erros de sincronização
Breaking Changes
⚠️ Dependência Crítica:
- Sistema requer políticas de corte cadastradas
- Sem política configurada, cálculo não funciona
- Modelo legado MercadoriaTipoServico não é mais usado
⚠️ Migração de Dados:
- Se existiam dados em MercadoriaTipoServico, devem ser migrados para PoliticaCorte
- Verificar que todas as mercadorias têm políticas cadastradas
Compatibilidade
- ✅ Não afeta outras partes do sistema
- ✅ Apenas a funcionalidade "Calcular Preço do Corte" foi alterada
- ⚠️ Requer políticas de corte configuradas para funcionar
Arquivos Modificados
Camada de Negócio (Business)
- Models/Comercial/PrecoCortado.cs
- Removido:
MercadoriaTipoServicoId,MercadoriaTipoServico -
Adicionado:
TipoServicoId,TipoServico,ValorQuilo -
Services/Comercial/TomadaPrecoService.cs
- Removido: Dependência
IMercadoriaTipoServicoRepository - Removido: Lógica de fallback para
MercadoriaTipoServico - Modificado: Busca de política por
MercadoriaId + TipoServicoId -
Modificado: Validação para exigir política de corte obrigatoriamente
-
Validations/Comercial/PrecoCortadoValidation.cs
- Modificado: Validação de
TipoServicoId(eraMercadoriaTipoServicoId)
Camada de Apresentação (WebApp)
- Areas/Comercial/Controllers/TomadaPrecoController.cs
- Adicionado: Dependências
IPoliticaCorteRepository,ITipoServicoRepository - Adicionado: Método
PopularPoliticasCorte() - Adicionado: Método
PopularTiposServicoPorMercadoria() -
Modificado:
PopularListasDropDownPrecoCortado()para usar novo método -
Areas/Comercial/ViewModels/PrecoCortadoViewModel.cs
- Removido:
MercadoriaTipoServicoId,MercadoriaTipoServico -
Adicionado:
TipoServicoId,TipoServico,ValorQuilo -
Areas/Comercial/Views/TomadaPreco/PrecoCortado.cshtml
- Modificado: Dropdown para usar
ListaTiposServico(eraListaMercadoriaTipoServico) - Adicionado: Cast explícito para
IEnumerable<TipoServico> - Adicionado: Mensagem quando não há tipos de serviço disponíveis
- Modificado: Exibição de
ValorQuilodo Model (era navigation property) -
Modificado: Exibição de
PercentualMargemComercialdo Model -
Views/Shared/_Layout.cshtml
- Modificado: Label "Políticas de Corte" → "Cadastro de Políticas de Corte"
- Adicionado:
style="display: none;"no item legado "Cadastro de Tipos de Serviços (old)"
Benefícios da Migração
Para o Negócio
✅ Configuração Centralizada: - Todas as políticas gerenciadas em um único lugar - Alterações refletidas imediatamente
✅ Maior Flexibilidade: - Políticas específicas por mercadoria e tipo de serviço - Configurações granulares e personalizáveis
✅ Redução de Erros: - Impossível usar configurações desatualizadas - Validações garantem integridade dos dados
Para Desenvolvimento
✅ Código Mais Limpo: - Remoção de 33 linhas de código legado - Eliminação de lógica condicional complexa
✅ Melhor Testabilidade: - Fluxo único e previsível - Menos casos de teste necessários
✅ Manutenção Simplificada: - Único ponto de alteração - Menos dependências circulares
Notas Importantes
Ações Necessárias Pós-Migração
🔧 Cadastro de Políticas: - Verificar que todas as mercadorias que precisam de cálculo de preço cortado têm políticas cadastradas - Garantir que cada política tem todos os campos obrigatórios preenchidos
📊 Migração de Dados (se aplicável):
- Se existiam configurações em MercadoriaTipoServico, migrar para PoliticaCorte
- Script de migração pode ser necessário
⚠️ Treinamento de Usuários: - Informar que políticas de corte devem ser cadastradas antes de calcular preços - Explicar que tipos de serviço aparecem apenas se houver política configurada
Código Legado Preservado
O item de menu "Cadastro de Tipos de Serviços (old)" foi ocultado mas não removido:
- Mantido no código para referência futura
- Pode ser restaurado removendo style="display: none;"
- Funcionalidade antiga ainda acessível via URL direta (se necessário)
Considerações de Performance
✅ Otimizações:
- Uso de DistinctBy para evitar duplicatas
- Query única para buscar políticas da mercadoria
- Menos joins no banco de dados
Referências
- Jira Issue: NH-42
- Branch:
feature/migration-politica-corte-NH-42 - Commits:
4fadc880- Migrate PrecoCortado model to use cutting policies846555c5- Remove legacy service type fallback from cut price calculation23508952- Update PrecoCortadoViewModel for cutting policiese8601b8c- Load service types from cutting policies in cut price445af821- Update cut price view to use cutting policies122d1da7- Update services menu labels and hide legacy option
Documentação criada em: 2025-11-17 Última atualização: 2025-11-17 Autor: Bruno Martins