-
Notifications
You must be signed in to change notification settings - Fork 0
fix(quotes): alinhar shipping_type=fob vs fob_pre em UI/RPC/dados #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,8 +68,9 @@ export function useQuoteViewData(id: string | undefined) { | |
| const discountValue = quote.discount_percent | ||
| ? Math.round(fullSubtotal * (quote.discount_percent / 100) * 100) / 100 | ||
| : quote.discount_amount || 0; | ||
| // Apenas 'fob_pre' (FOB Pré-negociado) tem custo no total. 'fob' = cliente paga, sem cost. | ||
| const shipValue = | ||
| quote.shipping_type === 'fob' || quote.shipping_type === 'fob_pre' | ||
| quote.shipping_type === 'fob_pre' | ||
| ? quote.shipping_cost || 0 | ||
|
Comment on lines
+73
to
74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This now excludes Useful? React with 👍 / 👎. |
||
| : 0; | ||
| const computedTotal = fullSubtotal - discountValue + shipValue; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| -- ============================================================================ | ||
| -- FIX: fn_quotes_recalc_subtotal_from_items — alinhar shipping_type com helper | ||
| -- ============================================================================ | ||
| -- | ||
| -- CONTEXTO: | ||
| -- Em 18/mai (commit 72c8639), o frontend (src/hooks/quotes/quoteHelpers.ts:45) | ||
| -- foi alterado para considerar shipping_cost APENAS quando shipping_type='fob_pre'. | ||
| -- Antes era ('fob' OR 'fob_pre'). A semântica adotada pelo refactor é: | ||
| -- | ||
| -- 'cif' → Cortesia (sem custo no orçamento) | ||
| -- 'fob' → Cliente paga frete diretamente (sem cost no orçamento) | ||
| -- 'fob_pre' → FOB Pré-negociado (cost no orçamento, repassado ao cliente) | ||
| -- | ||
| -- PROBLEMA RESOLVIDO POR ESTA MIGRATION: | ||
| -- A funcao trigger fn_quotes_recalc_subtotal_from_items (criada em Onda 17, | ||
| -- migration 20260515000000) ainda usa o critério antigo: | ||
| -- shipping := if shipping_type in ('fob','fob_pre') then shipping_cost else 0 | ||
| -- | ||
| -- Resultado: ao alterar quote_items de um orçamento com shipping_type='fob' e | ||
| -- shipping_cost>0, o trigger AFTER recalcula o total INCLUINDO o cost, gerando | ||
| -- divergência com o que o frontend salva (que não inclui cost para 'fob'). | ||
| -- | ||
| -- FIX: alinhar a função trigger com a nova regra (só 'fob_pre' soma cost). | ||
| -- | ||
| -- IMPACTO EM DADOS EXISTENTES: | ||
| -- Orçamentos legados com shipping_type='fob' AND shipping_cost>0 precisam ser | ||
| -- migrados manualmente para 'fob_pre' (semanticamente o que eram). Isso é feito | ||
| -- na migration separada 20260519225100_backfill_legacy_fob_with_cost.sql | ||
| -- (executada em sequência). | ||
| -- | ||
| -- VALIDACAO: a função foi reescrita preservando 100% da lógica de markup, disc | ||
| -- pct/amount e immutability check; apenas a expressao IN foi substituida. | ||
| -- ============================================================================ | ||
|
|
||
| CREATE OR REPLACE FUNCTION public.fn_quotes_recalc_subtotal_from_items() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| SECURITY DEFINER | ||
| SET search_path TO 'public' | ||
| AS $function$ | ||
| DECLARE | ||
| _quote_id uuid; | ||
| _quote_status text; | ||
| _markup numeric; | ||
| _disc_amount_db numeric; | ||
| _disc_pct numeric; | ||
| _ship_type text; | ||
| _ship_cost numeric; | ||
| _real_subtotal numeric(12,2); | ||
| _new_subtotal numeric(12,2); | ||
| _ship_value numeric(12,2); | ||
| _disc_value numeric(12,2); | ||
| _new_total numeric(12,2); | ||
| BEGIN | ||
| _quote_id := COALESCE(NEW.quote_id, OLD.quote_id); | ||
| IF _quote_id IS NULL THEN RETURN COALESCE(NEW, OLD); END IF; | ||
|
|
||
| SELECT | ||
| status, | ||
| LEAST(50, GREATEST(0, COALESCE(negotiation_markup_percent, 0))), | ||
| COALESCE(discount_amount, 0), | ||
| COALESCE(discount_percent, 0), | ||
| shipping_type, | ||
| COALESCE(shipping_cost, 0) | ||
| INTO _quote_status, _markup, _disc_amount_db, _disc_pct, _ship_type, _ship_cost | ||
| FROM public.quotes WHERE id = _quote_id; | ||
|
|
||
| -- Nao mexer em quotes aprovados/convertidos (imutaveis) | ||
| IF _quote_status IN ('approved', 'converted') THEN | ||
| RETURN COALESCE(NEW, OLD); | ||
| END IF; | ||
|
|
||
| -- REAL: soma pura dos itens (sem markup) | ||
| SELECT COALESCE(SUM(quantity * unit_price + COALESCE(personalization_cost, 0)), 0) | ||
| INTO _real_subtotal | ||
| FROM public.quote_items | ||
| WHERE quote_id = _quote_id; | ||
|
|
||
| -- APRESENTADO ao cliente: aplica markup | ||
| _new_subtotal := ROUND(_real_subtotal * (1 + _markup / 100.0), 2); | ||
|
|
||
| -- DESCONTO: discount_percent tem prioridade (espelha logica do frontend) | ||
| IF _disc_pct > 0 THEN | ||
| _disc_value := ROUND(_new_subtotal * (_disc_pct / 100.0), 2); | ||
| ELSE | ||
| _disc_value := _disc_amount_db; | ||
| END IF; | ||
|
|
||
| -- FRETE: apenas 'fob_pre' (FOB Pré-negociado) tem custo no orçamento. | ||
| -- 'fob' = cliente paga diretamente. 'cif' = cortesia. | ||
| -- Alinhado com quoteHelpers.ts:45 e useQuoteBuilderState.ts:695 desde 18/mai. | ||
| IF _ship_type = 'fob_pre' THEN | ||
| _ship_value := _ship_cost; | ||
| ELSE | ||
| _ship_value := 0; | ||
| END IF; | ||
|
|
||
| _new_total := ROUND(_new_subtotal - _disc_value + _ship_value, 2); | ||
|
|
||
| -- Persistir | ||
| UPDATE public.quotes | ||
| SET | ||
| subtotal = _new_subtotal, | ||
| discount_amount = _disc_value, | ||
| total = _new_total, | ||
| updated_at = now() | ||
| WHERE id = _quote_id; | ||
|
Comment on lines
+100
to
+107
Comment on lines
+103
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This replacement removed the prior Useful? React with 👍 / 👎. |
||
|
|
||
| RETURN COALESCE(NEW, OLD); | ||
| END; | ||
| $function$; | ||
|
|
||
| COMMENT ON FUNCTION public.fn_quotes_recalc_subtotal_from_items() IS | ||
| 'Recalcula subtotal/discount_amount/total de quotes após mudanças em quote_items. Apenas shipping_type=fob_pre soma cost no total (alinhado com quoteHelpers.ts desde 18/mai/2026).'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| -- ============================================================================ | ||
| -- BACKFILL: orçamentos legados com shipping_type='fob' AND shipping_cost>0 | ||
| -- ============================================================================ | ||
| -- | ||
| -- CONTEXTO: | ||
| -- Ate 18/mai (commit 72c8639), o frontend salvava orcamentos com | ||
| -- shipping_type='fob' E shipping_cost>0 — entendia 'fob' como "FOB com custo | ||
| -- repassado ao cliente". | ||
| -- | ||
| -- A partir de 18/mai, a semantica mudou: | ||
| -- 'fob' = cliente paga frete diretamente (cost = 0 no orcamento) | ||
| -- 'fob_pre' = FOB Pré-negociado (cost no orcamento, repassado ao cliente) | ||
| -- | ||
| -- Orçamentos legados que tinham shipping_type='fob' AND shipping_cost>0 | ||
| -- representam, na nova semantica, exatamente 'fob_pre'. Esta migration os | ||
| -- converte para manter o cost no calculo do total (preservando o valor | ||
| -- combinado com o cliente). | ||
| -- | ||
| -- SEGURANÇA: | ||
| -- - Não toca orçamentos com status 'approved' ou 'converted' (imutaveis). | ||
| -- - Não recalcula totals (sera feito pelo trigger fn_quotes_recalc na proxima | ||
| -- alteração de quote_items, ou pode ser feito manualmente via touch). | ||
|
Comment on lines
+21
to
+22
|
||
| -- - Idempotente: na 2a execução, retorna 0 rows (todos já convertidos). | ||
| -- | ||
| -- VALIDACAO PRE-AVISO: | ||
| -- Antes de aplicar, executar em transação para inspecionar: | ||
| -- BEGIN; | ||
| -- SELECT id, status, shipping_type, shipping_cost, total | ||
| -- FROM public.quotes | ||
| -- WHERE shipping_type = 'fob' AND shipping_cost > 0; | ||
| -- ROLLBACK; | ||
| -- ============================================================================ | ||
|
|
||
| UPDATE public.quotes | ||
| SET shipping_type = 'fob_pre', | ||
| updated_at = now() | ||
| WHERE shipping_type = 'fob' | ||
| AND shipping_cost > 0 | ||
| AND status NOT IN ('approved', 'converted'); | ||
|
|
||
| -- Audit log: registrar conversoes para forensics | ||
| DO $$ | ||
| DECLARE | ||
| _converted_count integer; | ||
| BEGIN | ||
| GET DIAGNOSTICS _converted_count = ROW_COUNT; | ||
| RAISE NOTICE '[backfill_legacy_fob] Converted % quotes from fob+cost to fob_pre', _converted_count; | ||
| END $$; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The summary now drops freight whenever
shippingType === 'fob', but the backfill intentionally skipsapproved/convertedrows, so legacy immutable quotes can still befobwith positiveshipping_costand persisted totals that include freight. In those cases this card shows a lower recomputed total than the stored approved amount, which can misstate the contracted value on the quote view.Useful? React with 👍 / 👎.