-
Notifications
You must be signed in to change notification settings - Fork 0
feat(crm): Fase 8 — sync bidirecional etapa↔etiqueta, auto-tag de nov… #56
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ import { revalidatePath } from "next/cache" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getSupabaseAdmin } from "./supabase" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getUsuarioAtual } from "./auth" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { CrmEtiquetaResumo } from "./crm-leads" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { sincronizarEtiquetaDaEtapa } from "./crm-etapas" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { sincronizarEtiquetaDaEtapa, aplicarFaseAoLead } from "./crm-etapas" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface ResultadoEtiqueta { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok: boolean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -83,10 +83,36 @@ export async function atribuirEtiquetaAction( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // um lead só pode ganhar etiquetas do MESMO dono). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [{ data: lead }, { data: etiqueta }] = await Promise.all([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.from("crm_leads").select("id").eq("id", leadId).eq("usuario_id", usuario.id).maybeSingle(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db.from("crm_etiquetas").select("id").eq("id", etiquetaId).eq("usuario_id", usuario.id).maybeSingle(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from("crm_etiquetas") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select("id, etapa_id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .eq("id", etiquetaId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .eq("usuario_id", usuario.id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .maybeSingle(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!lead || !etiqueta) return { ok: false, erro: "Lead ou etiqueta não encontrado." } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fase 8: etiqueta espelha uma etapa do Kanban — atribuir aqui move o card | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // pra coluna correspondente também (mesma troca, não acumula: remove | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // qualquer outra etiqueta-de-etapa que o lead tivesse). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (etiqueta.etapa_id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: etapa } = await db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from("crm_etapas") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select("id, nome, tipo, cor") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .eq("id", etiqueta.etapa_id as string) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .maybeSingle() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (etapa) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await aplicarFaseAoLead(db, usuario.id, leadId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: etapa.id as string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nome: etapa.nome as string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tipo: etapa.tipo as "aberta" | "ganho" | "perdido", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cor: (etapa.cor as string) ?? null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| revalidarCrm() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { ok: true } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+98
to
+114
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Etapa lookup doesn't filter
🛡️ Guard against applying an inactive etapa const { data: etapa } = await db
.from("crm_etapas")
.select("id, nome, tipo, cor")
.eq("id", etiqueta.etapa_id as string)
+ .eq("ativo", true)
.maybeSingle()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error } = await db | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from("crm_lead_etiquetas") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .upsert({ lead_id: leadId, etiqueta_id: etiquetaId }, { onConflict: "lead_id,etiqueta_id" }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Silent failure contract can misrepresent success to end users.
aplicarFaseAoLeadnever throws and never returns a status — it only logs on error (Line 173-176) or when the internal sync fails. Every current caller treats theawaitas "done" and reportsok: trueregardless. This is defensible for callers where the phase move is a secondary/best-effort side effect (e.g.criarFollowUpAction), but inatribuirEtiquetaAction(lib/crm-etiquetas-actions.ts, Lines 104-113) the phase move is the entire purpose of that early-return branch — if the underlyingcrm_leadsupdate fails, the action still returns{ ok: true }, misleading the user into thinking the tag/stage change succeeded.Consider having
aplicarFaseAoLeadreturn a boolean (or throw) so at least the primary-purpose callers can propagate the real outcome.🤖 Prompt for AI Agents