Bug comum em sistema caseiro de cotação: o PDF mostra o preço do momento em que o cliente abre. Você ajusta o catálogo na terça, o cliente abre a cotação na quinta, o preço impresso está errado, o cliente fica bravo, e ninguém explica o que aconteceu.
Esse bug é insidioso porque não parece bug. O sistema está funcionando corretamente — está mostrando o preço atual. O problema é que 'preço atual' é a resposta errada para um documento que foi criado na semana passada. A cotação é um snapshot de um acordo comercial num ponto no tempo. O catálogo é um documento vivo. Confundir os dois é um erro de categoria, e é um que custa dinheiro de verdade quando um cliente te cobra uma cotação que você não consegue honrar pelo preço original.
Snapshot na criação da linha
Todo QuoteItem carrega um unit_price capturado do Product na hora de criar a linha. Update de catálogo nunca reescreve o histórico. D2 e D11 no decision log são explícitas: snapshot é o contrato.
A implementação é direta: quando uma linha de cotação é criada, o service lê o unit_price atual do produto e grava no QuoteItem.unit_price. Depois desse momento, os dois valores são independentes. O preço do produto pode mudar cem vezes — a linha da cotação mantém o preço que estava vigente quando a linha foi criada. Se o comercial quiser usar o preço atualizado, precisa explicitamente re-precificar a linha.
Por que não simplesmente re-consultar? Re-consultar faz cada reload mostrar um total sutilmente diferente. Seu PDF impresso e o banco discordam. Dois clientes diferentes terminam com preços diferentes pela mesma cotação histórica. O snapshot tira tudo isso.
A dimensão jurídica torna isso inegociável. Na maioria das jurisdições, uma cotação é uma oferta. Uma vez aceita, vira contrato no preço cotado. Se o seu sistema silenciosamente muda o preço entre o momento em que você enviou a cotação e o momento em que o cliente aceitou, você ou arca com a diferença ou discute com um cliente que tem um PDF com um número diferente. Nenhum dos dois é bom para o negócio.
Desconto também vive na linha
discount_pct é por linha, capturado ao mesmo tempo. line_total é propriedade computada — quantidade × unit_price × (1 − discount_pct / 100). A matemática inteira é reprodutível da linha.
Como todo input do total da linha está armazenado na linha, o total é determinístico. Duas pessoas diferentes olhando a mesma cotação, com seis meses de diferença, veem os mesmos números. Um auditor pode recalcular cada linha dos campos armazenados e verificar que o total bate. Não tem fórmula escondida, não tem lookup dinâmico, não tem dado de referência que pode ter mudado desde que a cotação foi criada. A linha é autocontida e autocomprovante.
