Pular para conteúdo

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

  1. Resumo
  2. Contexto e Motivação
  3. Mudanças Implementadas
  4. Detalhes Técnicos
  5. Como Testar
  6. Impacto no Sistema
  7. Arquivos Modificados
  8. Benefícios da Migração
  9. 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:

  1. Modelo Legado (MercadoriaTipoServico):
  2. Tabela antiga que associava mercadorias a tipos de serviço
  3. Continha configurações de corte por família de mercadorias
  4. Sistema de fallback quando política de corte não existia
  5. Difícil manutenção e duplicação de dados

  6. Modelo Novo (PoliticaCorte):

  7. Tabela moderna com políticas específicas por mercadoria e tipo de serviço
  8. Mais flexível e granular
  9. 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

  1. ✅ Ter ao menos uma Política de Corte cadastrada
  2. ✅ Política deve ter:
  3. Mercadoria associada
  4. Tipo de Serviço configurado
  5. ValorQuilo definido
  6. PercentualMargemComercial (opcional)
  7. 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)

  1. Models/Comercial/PrecoCortado.cs
  2. Removido: MercadoriaTipoServicoId, MercadoriaTipoServico
  3. Adicionado: TipoServicoId, TipoServico, ValorQuilo

  4. Services/Comercial/TomadaPrecoService.cs

  5. Removido: Dependência IMercadoriaTipoServicoRepository
  6. Removido: Lógica de fallback para MercadoriaTipoServico
  7. Modificado: Busca de política por MercadoriaId + TipoServicoId
  8. Modificado: Validação para exigir política de corte obrigatoriamente

  9. Validations/Comercial/PrecoCortadoValidation.cs

  10. Modificado: Validação de TipoServicoId (era MercadoriaTipoServicoId)

Camada de Apresentação (WebApp)

  1. Areas/Comercial/Controllers/TomadaPrecoController.cs
  2. Adicionado: Dependências IPoliticaCorteRepository, ITipoServicoRepository
  3. Adicionado: Método PopularPoliticasCorte()
  4. Adicionado: Método PopularTiposServicoPorMercadoria()
  5. Modificado: PopularListasDropDownPrecoCortado() para usar novo método

  6. Areas/Comercial/ViewModels/PrecoCortadoViewModel.cs

  7. Removido: MercadoriaTipoServicoId, MercadoriaTipoServico
  8. Adicionado: TipoServicoId, TipoServico, ValorQuilo

  9. Areas/Comercial/Views/TomadaPreco/PrecoCortado.cshtml

  10. Modificado: Dropdown para usar ListaTiposServico (era ListaMercadoriaTipoServico)
  11. Adicionado: Cast explícito para IEnumerable<TipoServico>
  12. Adicionado: Mensagem quando não há tipos de serviço disponíveis
  13. Modificado: Exibição de ValorQuilo do Model (era navigation property)
  14. Modificado: Exibição de PercentualMargemComercial do Model

  15. Views/Shared/_Layout.cshtml

  16. Modificado: Label "Políticas de Corte" → "Cadastro de Políticas de Corte"
  17. 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 policies
  • 846555c5 - Remove legacy service type fallback from cut price calculation
  • 23508952 - Update PrecoCortadoViewModel for cutting policies
  • e8601b8c - Load service types from cutting policies in cut price
  • 445af821 - Update cut price view to use cutting policies
  • 122d1da7 - Update services menu labels and hide legacy option

Documentação criada em: 2025-11-17 Última atualização: 2025-11-17 Autor: Bruno Martins