Files
sar/scripts/sar-triggers.sql
julian 236dd69eba feat(web+api): tela de cadastro de cliente novo (CNPJ/CPF) com sync ERP
Web (NewClientPage):
- Segmentado PJ/CPF com campos específicos por categoria
- PJ: razão social, nome fantasia, CNPJ, IE, indicador IE, consumidor final
- PF: nome completo, CPF
- Endereço com auto-preenchimento via ViaCEP (CEP → rua/bairro/município)
- Município: Select com busca server-side (/catalog/municipios)
- Contato: DDD, telefone, email
- Comercial: forma pagamento, tabela de preços, limite crédito
- Feedback: código ERP exibido no sucesso; alerta de erro de sync sem bloquear

API:
- GET /catalog/municipios?q= — busca municipios da vw_municipios
- POST /clients/novo — INSERT em sar.clientes_novos; trigger sincroniza para sig.corrent
- Retorna { id, idCorrentErp, sincronizado, erroSync } após o trigger disparar
- Fix trigger fn_sync_cliente_to_erp: NULLIF para cgcpf vazio (evita violação
  do unique index key_corrent_cgcpf quando cliente não tem CPF/CNPJ)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 17:06:27 +00:00

462 lines
15 KiB
PL/PgSQL

-- =============================================================================
-- sar-triggers.sql
-- Triggers de integração SAR → ERP (schema sig/gestao no banco libreplast)
--
-- Execução: psql -U postgres -d libreplast -f scripts/sar-triggers.sql
--
-- CONTEÚDO:
-- 1. ALTER TABLE sar.pedidos — adiciona erp_id_pedido para rastrear ref ERP
-- 2. fn_sync_pedido_aprovado_to_erp — replica pedido aprovado em sig.pedidos
-- 3. trg_pedido_aprovado — dispara fn acima ao aprovar (situa 1→2)
-- 4. sar.clientes_novos — tabela staging de clientes criados pelo rep
-- 5. fn_sync_cliente_to_erp — replica cliente em sig.corrent
-- 6. trg_cliente_novo — dispara fn acima no INSERT em clientes_novos
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. Coluna de referência ERP no pedido SAR
-- ---------------------------------------------------------------------------
ALTER TABLE sar.pedidos
ADD COLUMN IF NOT EXISTS erp_id_pedido INTEGER;
CREATE INDEX IF NOT EXISTS idx_sar_ped_erp_id ON sar.pedidos(erp_id_pedido)
WHERE erp_id_pedido IS NOT NULL;
-- ---------------------------------------------------------------------------
-- 2. Função: replicar pedido aprovado → sig.pedidos + sig.peditens
--
-- Dispara quando sar.pedidos.situa muda de qualquer valor para 2 (Aprovado).
-- Insere em sig.pedidos com situa=1 (Pendente ERP) para que o operador
-- da fábrica finalize o lançamento. Grava o id_pedido ERP de volta em
-- sar.pedidos.erp_id_pedido.
--
-- id_tes=48 (Venda de Produção) — padrão SAR. Mudar para 53 se for bonificação.
-- numero: MAX+1 por empresa — mesmo padrão usado pelo SIG Desktop.
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION sar.fn_sync_pedido_aprovado_to_erp()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
DECLARE
v_numero INTEGER;
v_id_pedido_erp INTEGER;
v_cod_pauta INTEGER;
v_item RECORD;
v_id_st INTEGER;
v_endereco TEXT;
v_bairro TEXT;
v_municipio TEXT;
v_estado TEXT;
v_cep TEXT;
BEGIN
-- Só dispara na transição → 2 (Aprovado) e se ainda não sincronizado
IF NEW.situa <> 2 OR OLD.situa = 2 OR NEW.erp_id_pedido IS NOT NULL THEN
RETURN NEW;
END IF;
-- Lock por empresa para serializar geração do número (FOR UPDATE não
-- é permitido com funções de agregação no PG 18)
PERFORM pg_advisory_xact_lock(NEW.id_empresa::bigint);
SELECT COALESCE(MAX(numero), 0) + 1
INTO v_numero
FROM sig.pedidos
WHERE id_empresa = NEW.id_empresa;
-- Código da pauta (sig usa codigo, SAR armazena id_pauta)
SELECT codigo INTO v_cod_pauta
FROM gestao.pauta
WHERE id_pauta = NEW.id_pauta
LIMIT 1;
-- Endereço de entrega do cliente (do ERP)
SELECT
COALESCE(c.endereco, ''),
COALESCE(c.bairr, ''),
COALESCE(m.nome, ''),
COALESCE(m.estado, ''),
COALESCE(c.cep, '')
INTO v_endereco, v_bairro, v_municipio, v_estado, v_cep
FROM sig.corrent c
LEFT JOIN gestao.municipio m ON m.id_municipio = c.id_municipio
WHERE c.id_corrent = NEW.id_cliente
LIMIT 1;
-- Inserir cabeçalho do pedido no ERP
INSERT INTO sig.pedidos (
id_empresa,
tipo,
numero,
data,
data_emissao,
num_ped_vendedor,
id_tes,
situa,
id_local,
clien,
cod_vendedor,
cod_pauta,
cod_formapag,
e_ender,
e_bairr,
e_munic,
e_estad,
e_cep,
conta,
totpr,
ipi,
vl_icms_subst,
total,
descp,
descv,
com_fat,
com_rec,
obs,
prz_con,
tx_acrescimo,
num_ped_sar,
inf_usuario,
fconta,
frete,
cliente_retira,
pesob,
pesol,
ordem,
finalidade,
vol_qtd,
vol_especie,
vol_marca,
vol_numero,
obs_nf1,
obs_nf2,
num_oc,
usuario_credito,
num_lacre,
etiqueta_emitida,
canc_saldo,
rec_antecipado,
guid_sarped,
redesp_frete,
prioridade,
classificacao,
enviar_email,
tp_moeda,
vl_cotacao
) VALUES (
NEW.id_empresa,
'P',
v_numero,
NEW.dt_pedido,
NEW.dt_pedido,
NEW.num_ped_sar, -- num_ped_vendedor = SAR ref
48, -- id_tes: Venda de Produção
1, -- situa ERP: Pendente (operador finaliza)
0,
NEW.id_cliente,
NEW.cod_vendedor,
COALESCE(v_cod_pauta, 0),
COALESCE(NEW.cod_formapag, 0),
COALESCE(v_endereco, ''),
COALESCE(v_bairro, ''),
COALESCE(v_municipio, ''),
COALESCE(v_estado, ''),
COALESCE(v_cep, ''),
'',
NEW.total_produtos,
NEW.total_ipi,
NEW.total_icmsst,
NEW.total,
NEW.desconto_perc,
NEW.desconto_valor,
NEW.comissao,
0,
COALESCE(LEFT(NEW.obs, 1024), ''),
0,
COALESCE(NEW.acrescimo, 0),
NEW.num_ped_sar,
1, -- inf_usuario=1: criado pelo SAR
1, -- fconta: CHECK (1,2,3,4,5,6,9) — 1=padrão
0,
0,
0,
0,
0,
' ',
'',
'',
'',
'',
'',
'',
' ',
'',
'',
0,
0,
0,
'',
0,
2,
' ',
0,
0,
0
)
RETURNING id_pedido INTO v_id_pedido_erp;
-- Inserir itens no ERP
FOR v_item IN
SELECT pi.*
FROM sar.pedido_itens pi
WHERE pi.id_pedido = NEW.id
ORDER BY pi.ordem
LOOP
-- Situação tributária do produto
SELECT s.id_st INTO v_id_st
FROM gestao.produto p
JOIN gestao.st s ON s.codigo = p.cod_st
WHERE p.id_erp = v_item.id_produto
AND p.id_empresa = CASE
WHEN NEW.id_empresa > 9000
THEN NEW.id_empresa - 9000
ELSE NEW.id_empresa
END
LIMIT 1;
INSERT INTO sig.peditens (
id_pedido,
ordem,
produ,
id_grade,
qtd,
pruni,
descp,
descv,
ipi,
total,
comis,
obs,
id_st,
preco_pauta,
qtd_vasilhame_dev,
id_integrado,
qtd_bonificacao,
num_entrega,
vl_comissao_und,
id_acabamento,
id_vidro,
largura,
altura,
l1, l2, l3, l4,
h1, h2, h3, h4,
id_varprod,
id_vista,
num_op,
status,
vl_icmsst,
tp_operacao,
vl_acrescimo,
tempo_prod_prev,
preco_ipi,
vl_flex
) VALUES (
v_id_pedido_erp,
v_item.ordem,
v_item.id_produto,
NULL,
v_item.qtd,
v_item.preco_unitario,
v_item.desconto_perc,
v_item.desconto_valor,
v_item.vl_ipi,
v_item.total,
v_item.comissao,
'',
COALESCE(v_id_st, 0),
v_item.preco_pauta,
0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0,
0,
0,
1, -- status=1: item ativo/normal
v_item.vl_icmsst,
0,
0,
0,
v_item.preco_com_ipi,
v_item.vl_flex
);
END LOOP;
-- Gravar referência ERP de volta no pedido SAR
UPDATE sar.pedidos
SET erp_id_pedido = v_id_pedido_erp
WHERE id = NEW.id;
RETURN NEW;
END;
$$;
-- Criar (ou recriar) o trigger
DROP TRIGGER IF EXISTS trg_pedido_aprovado ON sar.pedidos;
CREATE TRIGGER trg_pedido_aprovado
AFTER UPDATE OF situa ON sar.pedidos
FOR EACH ROW
EXECUTE FUNCTION sar.fn_sync_pedido_aprovado_to_erp();
-- ---------------------------------------------------------------------------
-- 4. Tabela staging de clientes novos criados pelo SAR
--
-- O rep preenche os campos obrigatórios e o trigger replica em sig.corrent.
-- Após a replicação, id_corrent_erp é preenchido com o id gerado pelo ERP.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS sar.clientes_novos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id_empresa INTEGER NOT NULL,
cod_vendedor INTEGER NOT NULL,
-- Dados principais
nome VARCHAR(40) NOT NULL,
razao VARCHAR(65) NOT NULL,
pesso SMALLINT NOT NULL DEFAULT 1, -- 0=PJ 1=PF
consfinal SMALLINT NOT NULL DEFAULT 0,
cgcpf VARCHAR(18),
suf_cgcpf VARCHAR(3) NOT NULL DEFAULT '',
inscr VARCHAR(18) NOT NULL DEFAULT '',
indicador_ie SMALLINT, -- 1=contribuinte 2=isento 9=não contribuinte
-- Endereço
endereco VARCHAR(60) NOT NULL DEFAULT '',
num_endereco VARCHAR(10) NOT NULL DEFAULT '',
bairro VARCHAR(60) NOT NULL DEFAULT '',
id_municipio INTEGER NOT NULL,
cep VARCHAR(9) NOT NULL DEFAULT '',
-- Contato
ddd VARCHAR(4) NOT NULL DEFAULT '',
telefone VARCHAR(35) NOT NULL DEFAULT '',
email VARCHAR(80) NOT NULL DEFAULT '',
-- Comercial
limite_credito NUMERIC(15,2) NOT NULL DEFAULT 0,
cod_formapag INTEGER,
cod_pauta INTEGER,
-- Resultado após sync
id_corrent_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_clinovos_empresa ON sar.clientes_novos(id_empresa);
CREATE INDEX IF NOT EXISTS idx_sar_clinovos_vend ON sar.clientes_novos(cod_vendedor);
CREATE INDEX IF NOT EXISTS idx_sar_clinovos_sync ON sar.clientes_novos(sincronizado);
-- ---------------------------------------------------------------------------
-- 5. Função: replicar cliente novo → sig.corrent
--
-- Campos obrigatórios NOT NULL sem default em sig.corrent recebem '' (char)
-- ou 0 (int). O ERP aceita esses defaults — é o mesmo padrão do SIG Desktop
-- ao cadastrar cliente mínimo.
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION sar.fn_sync_cliente_to_erp()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
DECLARE
v_id_corrent INTEGER;
v_cgcpf CHAR(18);
BEGIN
BEGIN
-- NULLIF evita conflito no unique index key_corrent_cgcpf quando cgcpf é vazio
v_cgcpf := NULLIF(TRIM(COALESCE(NEW.cgcpf, '')), '');
INSERT INTO sig.corrent (
ativo, pesso, consfinal, cgcpf, suf_cgcpf, inscr, nome, razao,
endereco, cxpostal, compl, bairr, id_municipio, cep,
e_ender, e_bairr, e_cep, c_ender, c_bairr, c_cep,
e_mail, grupo_eco, no_reg, hor_descarga,
fat_anul, cap_social, cap_realiz, cap_aberto, con_ac_privado, con_ac_nacional,
ramo_ativ, cod_ativ_ir, no_empregados,
importa, p_importa, exporta, p_exporta,
princ_produ1, princ_produ2, princ_produ3, perc_vendas1, perc_vendas2, perc_vendas3,
www, banc1, banc2, banc3, agen1, agen2, agen3, nagen1, nagen2, nagen3,
ct_nu, ct_se, ci1, ci2, orgex, uf,
ddd, telef, fax, data, cod_vendedor, conta,
c_dm_nascto, c_fone, con_f, f_dm_nascto, f_fone, c_ctb, c_ctb_consig,
-- campos pessoais (obrigatórios sem default)
sexo, e_civ, pai, mae, conju, ocupa, c_spc, natur,
t_ser, l_tra, t_end, inscr_pro, classe,
tolatr, inscr_suframa, lei_livre, codigo,
aviso_protesto, codigo_vend_prep, id_tipo,
i_ban, i_com, obs,
-- comercial
limcred, indicador_ie, cod_formapag, cod_pauta
) VALUES (
1, NEW.pesso, NEW.consfinal,
v_cgcpf, COALESCE(NEW.suf_cgcpf, ''), COALESCE(NEW.inscr, ''),
COALESCE(LEFT(NEW.nome, 40), ''), LEFT(NEW.razao, 65),
COALESCE(LEFT(NEW.endereco, 60), ''), 0, '',
COALESCE(LEFT(NEW.bairro, 60), ''), NEW.id_municipio, COALESCE(NEW.cep, ''),
COALESCE(LEFT(NEW.endereco, 60), ''), COALESCE(LEFT(NEW.bairro, 60), ''), COALESCE(NEW.cep, ''),
COALESCE(LEFT(NEW.endereco, 60), ''), COALESCE(LEFT(NEW.bairro, 60), ''), COALESCE(NEW.cep, ''),
COALESCE(LEFT(NEW.email, 80), ''), '', '', '',
0, 0, 0, 0, 0, 0,
'', 0, 0,
0, 0, 0, 0,
'', '', '', 0, 0, 0,
'', 0, 0, 0, '', '', '', '', '', '',
0, 0, '', 0, '', '',
COALESCE(NEW.ddd, ''), COALESCE(LEFT(NEW.telefone, 35), ''), '', CURRENT_DATE,
NEW.cod_vendedor, '',
NULL, '', '', NULL, '', 0, 0,
-- campos pessoais com defaults neutros
0, 0, '', '', '', '', '', '',
0, '', 0, '', '',
0, '', '', '',
0, 0, 0,
'', '', '',
-- comercial
COALESCE(NEW.limite_credito, 0), NEW.indicador_ie, NEW.cod_formapag, NEW.cod_pauta
)
RETURNING id_corrent INTO v_id_corrent;
UPDATE sar.clientes_novos
SET id_corrent_erp = v_id_corrent,
sincronizado = TRUE,
erro_sync = NULL
WHERE id = NEW.id;
EXCEPTION WHEN OTHERS THEN
-- Registra o erro sem abortar — rep vê o status na UI
UPDATE sar.clientes_novos
SET sincronizado = FALSE,
erro_sync = SQLERRM
WHERE id = NEW.id;
END;
RETURN NEW;
END;
$$;
-- Criar (ou recriar) o trigger
DROP TRIGGER IF EXISTS trg_cliente_novo ON sar.clientes_novos;
CREATE TRIGGER trg_cliente_novo
AFTER INSERT ON sar.clientes_novos
FOR EACH ROW
EXECUTE FUNCTION sar.fn_sync_cliente_to_erp();
-- ---------------------------------------------------------------------------
-- Verificação final
-- ---------------------------------------------------------------------------
SELECT
trigger_name,
event_object_schema || '.' || event_object_table AS tabela,
event_manipulation AS evento,
action_timing AS timing
FROM information_schema.triggers
WHERE trigger_schema = 'sar'
ORDER BY tabela, trigger_name;