fix(db): Onda 17 — fn_quotes_recalc aplica markup + discount_percent (item 5.1)#212
Conversation
… + disc_pct (item 5.1) Corrige bug onde a funcao recalculadora nao aplicava negotiation_markup_percent nem discount_percent, causando drift no real_subtotal usado em validacao de alcada. Replica formula completa do frontend (calculateQuoteTotals). Validado em PROD via BEGIN/ROLLBACK em 5 cenarios. Aplicada via apply_migration. Ref: docs/AUDITORIA-PROFUNDA-PROMOGIFTS-PRE-PROD.md (item 5.1)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
Visão geralAdicionada migration com função trigger PL/pgSQL ( MudançasRecálculo automático de subtotais
Pontos de validação
🎯 2 (Simple) | ⏱️ ~10 minutos 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7bc072eab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| END IF; | ||
|
|
||
| -- REAL: soma pura dos itens (sem markup) | ||
| SELECT COALESCE(SUM(quantity * unit_price + COALESCE(personalization_cost, 0)), 0) |
There was a problem hiding this comment.
Read personalization totals from the persisted table
For quotes with personalization charges, this recalculation no longer matches the current data model: the app inserts those charges into quote_item_personalizations.total_cost after inserting quote_items (buildItemsInsertPayload does not write a personalization_cost field, and the generated quote_items type has no such column). When the quote-items trigger runs, this query either errors with column personalization_cost does not exist on the current schema or, on schemas that still have the legacy column, recalculates subtotals without the personalization rows and overwrites quotes.subtotal/total too low after any item change.
Useful? React with 👍 / 👎.
| -- Ref: docs/AUDITORIA-PROFUNDA-PROMOGIFTS-PRE-PROD.md (item 5.1) | ||
| -- ============================================================================ | ||
|
|
||
| CREATE OR REPLACE FUNCTION public.fn_quotes_recalc_subtotal_from_items() |
There was a problem hiding this comment.
Wire the recalculation function to quote_items
When this migration is replayed from the repository in a fresh or staging database, it only defines the trigger function; I checked supabase/migrations for fn_quotes_recalc_subtotal_from_items/trg_quotes_recalc and found no CREATE TRIGGER that attaches it to quote_items (only an earlier REVOKE). In those environments, item INSERT/UPDATE/DELETE operations will never invoke this recalculation, so client-supplied quotes.subtotal, discount_amount, and total can remain stale despite the new function existing.
Useful? React with 👍 / 👎.
| _disc_value numeric(12,2); | ||
| _new_total numeric(12,2); | ||
| BEGIN | ||
| _quote_id := COALESCE(NEW.quote_id, OLD.quote_id); |
There was a problem hiding this comment.
Recalculate both quotes when quote_id changes
If an admin/API update moves an existing quote_items row from one draft quote to another, this picks only NEW.quote_id and recalculates the destination quote while leaving the source quote totals with the moved item still included. The column is updatable in the generated Supabase type and the quote-items policy allows updates by quote ownership, so a reassignment can leave the old quote's subtotal, discount_amount, and total stale until another item change happens on that old quote.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR updates the server-side quote subtotal recalculation function so quote totals are recomputed from items with markup, discount percent, and FOB shipping logic.
Changes:
- Replaces
fn_quotes_recalc_subtotal_from_itemswith fuller quote total calculation logic. - Reconciles
discount_amountfromdiscount_percentwhen item changes trigger recalculation. - Adds migration/audit notes documenting the Onda 17 financial-integrity fix.
Comments suppressed due to low confidence (1)
supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql:90
- This recalc is tied to
quote_items, but the app saves personalization costs inquote_item_personalizationsafter inserting the item rows. Without recalculating again when those personalization rows are inserted/updated/deleted, quotes created or edited with personalization can be recalculated before those costs exist and end up with totals that exclude them.
FROM public.quote_items
WHERE quote_id = _quote_id;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SELECT COALESCE(SUM(quantity * unit_price + COALESCE(personalization_cost, 0)), 0) | ||
| INTO _real_subtotal | ||
| FROM public.quote_items | ||
| WHERE quote_id = _quote_id; |
| IF _disc_pct > 0 THEN | ||
| _disc_value := ROUND(_new_subtotal * (_disc_pct / 100.0), 2); | ||
| ELSE | ||
| _disc_value := _disc_amount_db; |
| _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 FOB (somente FOB entra no total) | ||
| _ship_value := CASE WHEN _ship_type IN ('fob', 'fob_pre') THEN _ship_cost ELSE 0 END; | ||
|
|
||
| -- TOTAL final | ||
| _new_total := _new_subtotal - _disc_value + _ship_value; | ||
|
|
||
| -- UPDATE apenas se mudou (evita loop com trigger BEFORE em quotes) | ||
| UPDATE public.quotes | ||
| SET subtotal = _new_subtotal, | ||
| total = _new_total, | ||
| discount_amount = _disc_value, | ||
| updated_at = now() | ||
| WHERE id = _quote_id | ||
| AND (subtotal IS DISTINCT FROM _new_subtotal | ||
| OR total IS DISTINCT FROM _new_total | ||
| OR discount_amount IS DISTINCT FROM _disc_value); | ||
|
|
| SELECT COALESCE(SUM(quantity * unit_price + COALESCE(personalization_cost, 0)), 0) | ||
| INTO _real_subtotal | ||
| FROM public.quote_items | ||
| WHERE quote_id = _quote_id; |
| -- _new_subtotal := SUM(qty*price + perso); -- sem markup | ||
| -- _new_total := _new_subtotal - discount_amount; -- sem disc_pct, sem shipping | ||
| -- | ||
| -- Como trg_quotes_calc_real_values (BEFORE em quotes) calcula |
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql">
<violation number="1" location="supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql:48">
P1: This migration only defines the function (`CREATE OR REPLACE FUNCTION`) but does not include a `CREATE TRIGGER` statement attaching it to `quote_items`. On fresh or staging databases built from the migration history, the recalculation will never fire because no trigger invokes it. Consider adding a `CREATE TRIGGER ... AFTER INSERT OR UPDATE OR DELETE ON quote_items` (with `IF NOT EXISTS` or `DROP TRIGGER IF EXISTS` guard) to ensure the function is wired up in all environments.</violation>
<violation number="2" location="supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql:87">
P0: The query references `personalization_cost` on `quote_items`, but the current schema stores personalization amounts in `quote_item_personalizations.total_cost`. If this column doesn't exist on `quote_items`, the trigger will fail with a runtime error on every item INSERT/UPDATE/DELETE. If it exists as a legacy column but is never populated, personalization costs will be silently excluded from recalculated totals. The aggregate should join or subquery against `quote_item_personalizations` to pull `total_cost`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| END IF; | ||
|
|
||
| -- REAL: soma pura dos itens (sem markup) | ||
| SELECT COALESCE(SUM(quantity * unit_price + COALESCE(personalization_cost, 0)), 0) |
There was a problem hiding this comment.
P0: The query references personalization_cost on quote_items, but the current schema stores personalization amounts in quote_item_personalizations.total_cost. If this column doesn't exist on quote_items, the trigger will fail with a runtime error on every item INSERT/UPDATE/DELETE. If it exists as a legacy column but is never populated, personalization costs will be silently excluded from recalculated totals. The aggregate should join or subquery against quote_item_personalizations to pull total_cost.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql, line 87:
<comment>The query references `personalization_cost` on `quote_items`, but the current schema stores personalization amounts in `quote_item_personalizations.total_cost`. If this column doesn't exist on `quote_items`, the trigger will fail with a runtime error on every item INSERT/UPDATE/DELETE. If it exists as a legacy column but is never populated, personalization costs will be silently excluded from recalculated totals. The aggregate should join or subquery against `quote_item_personalizations` to pull `total_cost`.</comment>
<file context>
@@ -0,0 +1,126 @@
+ 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
</file context>
| -- Ref: docs/AUDITORIA-PROFUNDA-PROMOGIFTS-PRE-PROD.md (item 5.1) | ||
| -- ============================================================================ | ||
|
|
||
| CREATE OR REPLACE FUNCTION public.fn_quotes_recalc_subtotal_from_items() |
There was a problem hiding this comment.
P1: This migration only defines the function (CREATE OR REPLACE FUNCTION) but does not include a CREATE TRIGGER statement attaching it to quote_items. On fresh or staging databases built from the migration history, the recalculation will never fire because no trigger invokes it. Consider adding a CREATE TRIGGER ... AFTER INSERT OR UPDATE OR DELETE ON quote_items (with IF NOT EXISTS or DROP TRIGGER IF EXISTS guard) to ensure the function is wired up in all environments.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql, line 48:
<comment>This migration only defines the function (`CREATE OR REPLACE FUNCTION`) but does not include a `CREATE TRIGGER` statement attaching it to `quote_items`. On fresh or staging databases built from the migration history, the recalculation will never fire because no trigger invokes it. Consider adding a `CREATE TRIGGER ... AFTER INSERT OR UPDATE OR DELETE ON quote_items` (with `IF NOT EXISTS` or `DROP TRIGGER IF EXISTS` guard) to ensure the function is wired up in all environments.</comment>
<file context>
@@ -0,0 +1,126 @@
+-- Ref: docs/AUDITORIA-PROFUNDA-PROMOGIFTS-PRE-PROD.md (item 5.1)
+-- ============================================================================
+
+CREATE OR REPLACE FUNCTION public.fn_quotes_recalc_subtotal_from_items()
+RETURNS trigger
+LANGUAGE plpgsql
</file context>
🎯 Objetivo
Atacar o item 5.1 da auditoria pré-prod (integridade financeira) corrigindo bug descoberto durante inspeção dos triggers existentes.
🔍 Estado descoberto durante pré-flight
subtotalgravado pelo cliente sem recálculodiscount_amountinconsistente comdiscount_percentnumeric(12,2)server-sidestatus='approved'real_subtotal🚨 Bug concreto que esta PR corrige
Cenário do bug:
real_subtotalé usado para validar alçada de desconto do vendedor, então o bug poderia bloquear ou liberar descontos incorretamente.📦 Fix — replicar fórmula completa do frontend
Bonus: grava
discount_amountderivado dediscount_percent(reconcilia automaticamente — resolve issue 2 da auditoria).🧪 Validação em PROD via BEGIN/ROLLBACK (5 cenários)
Quotes existentes em PROD (3 quotes, todos markup=0 sem desconto): estado idêntico antes/depois (snapshot validado).
🌐 Impacto
real_subtotalagora consistente entre fluxo via wizard E via INSERT/UPDATE direto em items🎯 Próximas ondas
numeric(12,2)em todas as colunas monetárias (audit 5.4)📚 Ref
Summary by cubic
Fixes quote total recalculation by applying markup and discount percent, preventing real_subtotal drift after item changes. The server function now mirrors the frontend formula to keep subtotal, total, and discount consistent.
fn_quotes_recalc_subtotal_from_itemsto applynegotiation_markup_percent, preferdiscount_percentoverdiscount_amount, include FOB shipping, and updatesubtotal,total, anddiscount_amount.subtotalwithout markup, corruptingreal_subtotalused in discount authorization.approved/convertedquotes and only updates when values change. Resolves audit item 5.1.Written for commit b7bc072. Summary will update on new commits.
Summary by CodeRabbit