feat(web+api): contatos vinculados a clientes com sync ERP e base WhatsApp

DB:
- sar.vw_contatos: view sobre gestao.contato com join sig.corrent
  (id_entidade CHAR20 = id_corrent; cod_vendedor para filtro por rep)
- sar.contatos_novos: staging com nome, cargo, departamento, telefone,
  celular, whatsapp, email, dt_aniversario, anotacoes
- trg_contato_novo: AFTER INSERT → gestao.contato; grava id_contato_erp

API:
- GET  /clients/:id/contacts  — lista contatos ativos do cliente
- POST /clients/:id/contacts  — cria via staging; retorna { idContatoErp, sincronizado }

Web:
- ClientContacts: tabela de contatos no detalhe do cliente
- Links diretos WhatsApp (wa.me) + mailto por contato
- Modal "Novo Contato" com todos os campos; feedback de código ERP no sucesso
- whatsapp é o campo principal para envio futuro via ChatWoot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 17:25:48 +00:00
parent 236dd69eba
commit 34368e8006
8 changed files with 574 additions and 0 deletions

View File

@@ -503,6 +503,37 @@ SELECT
nome
FROM gestao.marca;
-- -----------------------------------------------------------------------------
-- Contatos vinculados a clientes (gestao.contato)
-- id_entidade = id_corrent::text (CHAR 20, auto-pad)
-- Usado para envio via WhatsApp / ChatWoot futuramente
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW sar.vw_contatos AS
SELECT
c.id_contato,
c.id_empresa,
TRIM(c.id_entidade)::integer AS id_corrent,
c.id_chatwoot,
TRIM(c.nome) AS nome,
TRIM(c.empresa) AS empresa,
TRIM(c.cargo) AS cargo,
TRIM(c.departamento) AS departamento,
c.dt_aniversario,
TRIM(c.telefone) AS telefone,
TRIM(c.ramal) AS ramal,
TRIM(c.celular) AS celular,
TRIM(c.whatsapp) AS whatsapp,
TRIM(c.email) AS email,
COALESCE(c.ativo, 1) AS ativo,
c.anotacoes,
TRIM(cr.nome) AS nome_cliente,
TRIM(cr.razao::text) AS razao_cliente,
cr.cod_vendedor
FROM gestao.contato c
LEFT JOIN sig.corrent cr
ON cr.id_corrent = TRIM(c.id_entidade)::integer
WHERE TRIM(c.id_entidade) ~ '^\d+$';
-- =============================================================================
-- PARTE 2 — TABELAS DE ESCRITA DO SAR
-- Dados gerados pelo SAR — não existem no ERP.

View File

@@ -448,6 +448,97 @@ CREATE TRIGGER trg_cliente_novo
FOR EACH ROW
EXECUTE FUNCTION sar.fn_sync_cliente_to_erp();
-- ---------------------------------------------------------------------------
-- 7. Tabela staging de contatos criados pelo rep
--
-- id_entidade em gestao.contato é CHAR(20) — recebe id_corrent::text (auto-pad).
-- whatsapp aqui é o número que será usado para envio via ChatWoot.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sar.contatos_novos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id_empresa INTEGER NOT NULL,
cod_vendedor INTEGER NOT NULL,
id_corrent INTEGER NOT NULL,
nome VARCHAR(100) NOT NULL,
empresa VARCHAR(100),
cargo VARCHAR(100),
departamento VARCHAR(100),
dt_aniversario DATE,
telefone VARCHAR(20),
ramal VARCHAR(10),
celular VARCHAR(20),
whatsapp VARCHAR(20),
email VARCHAR(150),
anotacoes TEXT,
id_contato_erp INTEGER,
sincronizado BOOLEAN NOT NULL DEFAULT FALSE,
erro_sync TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_sar_ctnov_corrent ON sar.contatos_novos(id_corrent);
CREATE INDEX IF NOT EXISTS idx_sar_ctnov_empresa ON sar.contatos_novos(id_empresa);
CREATE INDEX IF NOT EXISTS idx_sar_ctnov_vend ON sar.contatos_novos(cod_vendedor);
CREATE INDEX IF NOT EXISTS idx_sar_ctnov_sync ON sar.contatos_novos(sincronizado);
-- ---------------------------------------------------------------------------
-- 8. Função: replicar contato → gestao.contato
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION sar.fn_sync_contato_to_erp()
RETURNS TRIGGER LANGUAGE plpgsql AS $fn$
DECLARE
v_id_contato INTEGER;
BEGIN
BEGIN
INSERT INTO gestao.contato (
id_empresa, id_entidade,
nome, empresa, cargo, departamento,
dt_aniversario,
telefone, ramal, celular, whatsapp, email,
ativo, anotacoes
) VALUES (
NEW.id_empresa,
NEW.id_corrent::text,
COALESCE(LEFT(NEW.nome, 100), ''),
LEFT(NEW.empresa, 100),
LEFT(NEW.cargo, 100),
LEFT(NEW.departamento, 100),
NEW.dt_aniversario,
LEFT(NEW.telefone, 20),
LEFT(NEW.ramal, 10),
LEFT(NEW.celular, 20),
LEFT(NEW.whatsapp, 20),
LEFT(NEW.email, 150),
1,
NEW.anotacoes
)
RETURNING id_contato INTO v_id_contato;
UPDATE sar.contatos_novos
SET id_contato_erp = v_id_contato,
sincronizado = TRUE,
erro_sync = NULL
WHERE id = NEW.id;
EXCEPTION WHEN OTHERS THEN
UPDATE sar.contatos_novos
SET sincronizado = FALSE,
erro_sync = SQLERRM
WHERE id = NEW.id;
END;
RETURN NEW;
END;
$fn$;
-- 9. Trigger
DROP TRIGGER IF EXISTS trg_contato_novo ON sar.contatos_novos;
CREATE TRIGGER trg_contato_novo
AFTER INSERT ON sar.contatos_novos
FOR EACH ROW EXECUTE FUNCTION sar.fn_sync_contato_to_erp();
-- ---------------------------------------------------------------------------
-- Verificação final
-- ---------------------------------------------------------------------------