diff --git a/python/dataload.ipynb b/python/dataload.ipynb index 57d8902..689b71d 100644 --- a/python/dataload.ipynb +++ b/python/dataload.ipynb @@ -22,7 +22,7 @@ "# fazendo importações\n", "import os\n", "from random import choice, choices, randint, triangular\n", - "from datetime import date, timedelta\n", + "from datetime import datetime, date, timedelta, timezone\n", "from dotenv import load_dotenv\n", "from faker import Faker\n", "import pandas as pd\n", @@ -774,6 +774,36 @@ "dados_meta_responsavel = []\n", "dados_usuario_treinamento = []\n", "dados_priorizacao = []\n", + "dados_notificacao = []\n", + "\n", + "ciclos_empresa = {}\n", + "for id_ciclo_notificacao, ciclo_notificacao in enumerate(dados_ciclo, start=1):\n", + " ciclos_empresa.setdefault(ciclo_notificacao['id_empresa'], []).append(id_ciclo_notificacao)\n", + "\n", + "agora = datetime.now(timezone.utc)\n", + "for _ in range(100):\n", + " id_empresa = choice(lista_id_empresa)\n", + " id_usuario_destino = choice(usuarios_empresa[id_empresa])\n", + " id_ciclo_notificacao = choice(ciclos_empresa[id_empresa])\n", + " criado_em = faker.date_time_between(start_date='-365d', end_date='now', tzinfo=timezone.utc)\n", + " lido_em = None\n", + " if choice([True, False]):\n", + " intervalo_leitura = int((agora - criado_em).total_seconds())\n", + " lido_em = criado_em + timedelta(seconds=randint(1, intervalo_leitura)) if intervalo_leitura else criado_em\n", + "\n", + " dados_notificacao.append({\n", + " 'id_usuario_destino': id_usuario_destino,\n", + " 'id_empresa': id_empresa,\n", + " 'tipo': 'CICLO_ATUALIZADO',\n", + " 'categoria': 'PDCA',\n", + " 'tipo_entidade': 'CICLO',\n", + " 'id_entidade': str(id_ciclo_notificacao),\n", + " 'deep_link': f'/ciclos/{id_ciclo_notificacao}',\n", + " 'chave_deduplicacao': f'dataload:{uuid4()}',\n", + " 'criado_em': criado_em,\n", + " 'lido_em': lido_em,\n", + " 'expira_em': criado_em + timedelta(days=90)\n", + " })\n", "\n", "for id_ciclo, ciclo in enumerate(dados_ciclo, start=1):\n", " colaborador_empresa = usuarios_empresa[ciclo['id_empresa']]\n", @@ -825,7 +855,8 @@ "df_meta_responsavel = pd.DataFrame(dados_meta_responsavel)\n", "df_priorizacao_problema = pd.DataFrame(dados_priorizacao)\n", "df_usuario_treinamento = pd.DataFrame(dados_usuario_treinamento)\n", - "df_usuario_ciclo = pd.DataFrame(dados_usuario_ciclo)" + "df_usuario_ciclo = pd.DataFrame(dados_usuario_ciclo)\n", + "df_notificacao = pd.DataFrame(dados_notificacao)\n" ] }, { @@ -849,6 +880,7 @@ "tabelas = {\n", " ('public', 'empresa'): df_empresa,\n", " ('public', 'usuario_sistema'): df_usuario,\n", + " ('auditoria', 'notificacao'): df_notificacao,\n", " ('public', 'colaborador'): df_colaborador,\n", " ('public', 'email_empresa'): df_email_empresa,\n", " ('public', 'telefone_empresa'): df_telefone_empresa,\n", diff --git a/sql/001_script.sql b/sql/001_script.sql index 36bfe8e..437fd1b 100644 --- a/sql/001_script.sql +++ b/sql/001_script.sql @@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS usuario_sistema ( nome VARCHAR(160) NOT NULL, email_login VARCHAR(254) NOT NULL UNIQUE CHECK (email_login ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$'), firebase_uid VARCHAR(128) UNIQUE, + firebase_fid TEXT, tipo_usuario VARCHAR(40) NOT NULL CHECK (tipo_usuario IN ('ADMIN', 'GESTOR', 'COLABORADOR')), status VARCHAR(40) NOT NULL CHECK (status IN ('ATIVO', 'INATIVO', 'PENDENTE', 'BLOQUEADO', 'ARQUIVADO')), criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), @@ -270,16 +271,6 @@ CREATE TABLE IF NOT EXISTS pdca.tarefa ( CONSTRAINT tarefa_datas_check CHECK ((data_inicio_real IS NULL OR data_fim_prevista >= data_inicio_real) AND (data_fim_real IS NULL OR data_inicio_real IS NULL OR data_fim_real >= data_inicio_real)) ); - -CREATE TABLE IF NOT EXISTS pdca.alerta_prazo ( - id BIGSERIAL PRIMARY KEY, - id_tarefa BIGINT NOT NULL REFERENCES pdca.tarefa(id) ON DELETE CASCADE, - id_usuario_destino BIGINT NOT NULL REFERENCES usuario_sistema(id) ON DELETE CASCADE, - mensagem TEXT NOT NULL, - enviado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), - lido_em TIMESTAMPTZ -); - CREATE TABLE IF NOT EXISTS pdca.tarefa_dependencia ( id_tarefa BIGINT NOT NULL REFERENCES pdca.tarefa(id) ON DELETE CASCADE, id_tarefa_dependencia BIGINT NOT NULL REFERENCES pdca.tarefa(id) ON DELETE CASCADE, @@ -393,9 +384,21 @@ CREATE TABLE IF NOT EXISTS auditoria.log_tarefa ( CONSTRAINT log_tarefa_json_check CHECK (jsonb_typeof(dados_antes) = 'object' AND jsonb_typeof(dados_depois) = 'object') ); - - ------------Anexos (alterar no modelo lógico) +CREATE TABLE IF NOT EXISTS auditoria.notificacao ( + id BIGSERIAL PRIMARY KEY, + id_usuario_destino BIGINT NOT NULL REFERENCES public.usuario_sistema(id) ON DELETE CASCADE, + id_empresa BIGINT NOT NULL REFERENCES public.empresa(id) ON DELETE CASCADE, + tipo VARCHAR(60) NOT NULL, + categoria VARCHAR(40) NOT NULL, + tipo_entidade VARCHAR(40) NOT NULL, + id_entidade VARCHAR(100) NOT NULL, + deep_link VARCHAR(240) NOT NULL, + chave_deduplicacao VARCHAR(160) NOT NULL, + criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), + lido_em TIMESTAMPTZ, + expira_em TIMESTAMPTZ, + CONSTRAINT uk_notificacao_destinatario_deduplicacao UNIQUE (id_usuario_destino, chave_deduplicacao) +); CREATE TABLE IF NOT EXISTS pdca.anexo ( id BIGSERIAL PRIMARY KEY, @@ -446,4 +449,4 @@ CREATE TABLE IF NOT EXISTS pdca.anexo ( ) OR tipo_arquivo LIKE 'image/%' ) -); \ No newline at end of file +); diff --git a/sql/002_indices.sql b/sql/002_indices.sql index 4b9d15e..bc568e2 100644 --- a/sql/002_indices.sql +++ b/sql/002_indices.sql @@ -173,17 +173,17 @@ CREATE INDEX IF NOT EXISTS idx_problema_status ON pdca.problema (status); -- ANTES: execution time -> 0.401 -- DEPOIS: execution time -> 0.081 --- Alerta Prazo por Tarefa -EXPLAIN ANALYZE SELECT * FROM pdca.alerta_prazo WHERE id_tarefa = 1; -CREATE INDEX IF NOT EXISTS idx_alerta_tarefa ON pdca.alerta_prazo (id_tarefa); --- ANTES: execution time -> 0.681 +-- Notificações por destinatário +EXPLAIN ANALYZE SELECT * FROM auditoria.notificacao WHERE id_usuario_destino = 64 ORDER BY criado_em DESC, id DESC; +CREATE INDEX IF NOT EXISTS ix_notificacao_destinatario_criado ON auditoria.notificacao (id_usuario_destino, criado_em DESC, id DESC); +-- ANTES: execution time -> 0.091 -- DEPOIS: execution time -> 0.027 --- Alerta Prazo por Usuário -EXPLAIN ANALYZE SELECT * FROM pdca.alerta_prazo WHERE id_usuario_destino = 1; -CREATE INDEX IF NOT EXISTS idx_alerta_usuario ON pdca.alerta_prazo (id_usuario_destino); --- ANTES: execution time -> 0.329 --- DEPOIS: execution time -> 0.025 +-- Notificações não lidas por destinatário +EXPLAIN ANALYZE SELECT * FROM auditoria.notificacao WHERE id_usuario_destino = 65 AND lido_em IS NULL ORDER BY criado_em DESC, id DESC; +CREATE INDEX IF NOT EXISTS ix_notificacao_destinatario_nao_lida ON auditoria.notificacao (id_usuario_destino, criado_em DESC, id DESC) WHERE lido_em IS NULL; +-- ANTES: execution time -> 0.027 +-- DEPOIS: execution time -> 0.026 -- Colaborador por Empresa EXPLAIN ANALYZE SELECT * FROM colaborador WHERE id_empresa = 1; @@ -276,7 +276,6 @@ CREATE INDEX IF NOT EXISTS idx_email_colaborador ON email_colaborador (id_colabo -- DEPOIS: execution time -> 0.035 -- Índices Únicos - CREATE UNIQUE INDEX IF NOT EXISTS idx_telefone_colaborador_principal ON public.telefone_colaborador (id_colaborador) WHERE principal = TRUE; diff --git a/sql/003_functions_triggers_procedures.sql b/sql/003_functions_triggers_procedures.sql index f699591..b7d1022 100644 --- a/sql/003_functions_triggers_procedures.sql +++ b/sql/003_functions_triggers_procedures.sql @@ -815,7 +815,7 @@ EXECUTE FUNCTION public.fn_preencher_nickname_colaborador(); -- PROCEDURES -- ###################### --- 1 (gera alertas de atraso para tarefas que estão atrasadas e atualiza o status das mesmas para 'ATRASADA') +-- 1 (gera notificações de atraso para tarefas vencidas e atualiza o status para 'ATRASADA') CREATE OR REPLACE PROCEDURE pdca.pr_gerar_alertas_atraso() LANGUAGE plpgsql @@ -824,16 +824,30 @@ DECLARE r RECORD; BEGIN FOR r IN - SELECT id, id_responsavel, titulo, data_fim_prevista FROM pdca.tarefa - WHERE data_fim_prevista < CURRENT_DATE AND status NOT IN ('CONCLUIDA', 'CANCELADA') + SELECT t.id, t.id_responsavel, t.titulo, t.data_fim_prevista, c.id_empresa, c.id AS id_ciclo + FROM pdca.tarefa t + JOIN pdca.plano_acao pa ON pa.id = t.id_plano_acao + JOIN pdca.ciclo c ON c.id = pa.id_ciclo + WHERE t.data_fim_prevista < CURRENT_DATE AND t.status NOT IN ('CONCLUIDA', 'CANCELADA') LOOP PERFORM set_config('app.current_user_id', r.id_responsavel::text, true); - INSERT INTO pdca.alerta_prazo (id_tarefa, id_usuario_destino, mensagem) VALUES (r.id, r.id_responsavel, - FORMAT('A tarefa "%s" está atrasada desde %s.', r.titulo, TO_CHAR(r.data_fim_prevista, 'DD/MM/YYYY'))); + INSERT INTO auditoria.notificacao ( + id_usuario_destino, id_empresa, tipo, categoria, tipo_entidade, + id_entidade, deep_link, chave_deduplicacao + ) VALUES ( + r.id_responsavel, + r.id_empresa, + 'TAREFA_ATRASADA', + 'PRAZO', + 'TAREFA', + r.id::TEXT, + FORMAT('/ciclos/%s/tarefas/%s', r.id_ciclo, r.id), + FORMAT('tarefa-atrasada:%s', r.id) + ) + ON CONFLICT (id_usuario_destino, chave_deduplicacao) DO NOTHING; UPDATE pdca.tarefa SET status = 'ATRASADA' WHERE id = r.id; - END LOOP; END; $$; diff --git a/sql/007_comments.sql b/sql/007_comments.sql index f4e1d64..e08ca9d 100644 --- a/sql/007_comments.sql +++ b/sql/007_comments.sql @@ -16,6 +16,7 @@ COMMENT ON COLUMN public.usuario_sistema.id_empresa IS 'Empresa a qual o usuario COMMENT ON COLUMN public.usuario_sistema.nome IS 'Nome do usuario.'; COMMENT ON COLUMN public.usuario_sistema.email_login IS 'E-mail utilizado para login no sistema.'; COMMENT ON COLUMN public.usuario_sistema.firebase_uid IS 'Identificador unico do usuario no Firebase Authentication.'; +COMMENT ON COLUMN public.usuario_sistema.firebase_fid IS 'Firebase Installation ID (FID) atual associado a um dispositivo do usuario; atualizar quando renovado e limpar no logout ou quando invalido.'; COMMENT ON COLUMN public.usuario_sistema.tipo_usuario IS 'Perfil de acesso do usuario: ADMIN, GESTOR ou COLABORADOR.'; COMMENT ON COLUMN public.usuario_sistema.status IS 'Situacao do usuario no sistema.'; COMMENT ON COLUMN public.usuario_sistema.criado_em IS 'Data e hora de criacao do registro.'; @@ -225,14 +226,6 @@ COMMENT ON COLUMN pdca.tarefa.data_fim_real IS 'Data real de conclusao da tarefa COMMENT ON COLUMN pdca.tarefa.criado_em IS 'Data e hora de criacao do registro.'; COMMENT ON COLUMN pdca.tarefa.atualizado_em IS 'Data e hora da ultima atualizacao do registro.'; -COMMENT ON TABLE pdca.alerta_prazo IS 'Alertas enviados a usuarios sobre prazos de tarefas.'; -COMMENT ON COLUMN pdca.alerta_prazo.id IS 'Identificador unico do alerta.'; -COMMENT ON COLUMN pdca.alerta_prazo.id_tarefa IS 'Tarefa relacionada ao alerta.'; -COMMENT ON COLUMN pdca.alerta_prazo.id_usuario_destino IS 'Usuario destinatario do alerta.'; -COMMENT ON COLUMN pdca.alerta_prazo.mensagem IS 'Mensagem enviada no alerta.'; -COMMENT ON COLUMN pdca.alerta_prazo.enviado_em IS 'Data e hora de envio do alerta.'; -COMMENT ON COLUMN pdca.alerta_prazo.lido_em IS 'Data e hora de leitura do alerta.'; - COMMENT ON TABLE pdca.tarefa_dependencia IS 'Dependencias entre tarefas de planos de acao.'; COMMENT ON COLUMN pdca.tarefa_dependencia.id_tarefa IS 'Tarefa que depende de outra tarefa.'; COMMENT ON COLUMN pdca.tarefa_dependencia.id_tarefa_dependencia IS 'Tarefa que deve ser considerada como dependencia.'; @@ -341,4 +334,18 @@ COMMENT ON COLUMN auditoria.log_tarefa.id_usuario IS 'Usuario responsavel pela o COMMENT ON COLUMN auditoria.log_tarefa.dados_antes IS 'Estado da tarefa antes da operacao.'; COMMENT ON COLUMN auditoria.log_tarefa.dados_depois IS 'Estado da tarefa depois da operacao.'; COMMENT ON COLUMN auditoria.log_tarefa.data_log IS 'Data e hora de criacao do log.'; -COMMENT ON COLUMN auditoria.log_tarefa.operacao IS 'Tipo de operacao auditada.'; \ No newline at end of file +COMMENT ON COLUMN auditoria.log_tarefa.operacao IS 'Tipo de operacao auditada.'; + +COMMENT ON TABLE auditoria.notificacao IS 'Notificacoes destinadas aos usuarios da plataforma.'; +COMMENT ON COLUMN auditoria.notificacao.id IS 'Identificador unico da notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.id_usuario_destino IS 'Usuario que recebera a notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.id_empresa IS 'Empresa associada a notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.tipo IS 'Tipo do evento que originou a notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.categoria IS 'Categoria funcional da notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.tipo_entidade IS 'Tipo do registro relacionado a notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.id_entidade IS 'Identificador do registro relacionado, armazenado como texto.'; +COMMENT ON COLUMN auditoria.notificacao.deep_link IS 'Caminho para abrir o contexto relacionado na aplicacao.'; +COMMENT ON COLUMN auditoria.notificacao.chave_deduplicacao IS 'Chave unica por destinatario usada para evitar notificacoes duplicadas.'; +COMMENT ON COLUMN auditoria.notificacao.criado_em IS 'Data e hora de criacao da notificacao.'; +COMMENT ON COLUMN auditoria.notificacao.lido_em IS 'Data e hora em que o destinatario leu a notificacao, quando aplicavel.'; +COMMENT ON COLUMN auditoria.notificacao.expira_em IS 'Data e hora em que a notificacao deixa de ser valida, quando aplicavel.'; diff --git a/sql/008_catalogo.sql b/sql/008_catalogo.sql index 04410e8..af828d9 100644 --- a/sql/008_catalogo.sql +++ b/sql/008_catalogo.sql @@ -22,6 +22,7 @@ INSERT INTO auditoria.catalogo_dados ( ('public', 'usuario_sistema', 'nome', 'VARCHAR(160)', FALSE, FALSE, NULL, TRUE, 'Nome do usuário.', 'SENSIVEL', 'Nome do usuário.'), ('public', 'usuario_sistema', 'email_login', 'VARCHAR(254)', FALSE, FALSE, NULL, TRUE, 'Deve corresponder ao padrão de e-mail válido. Único no sistema.', 'SENSIVEL', 'E-mail utilizado para login no sistema.'), ('public', 'usuario_sistema', 'firebase_uid', 'VARCHAR(128)', FALSE, FALSE, NULL, FALSE, 'Obrigatório caso o status seja ATIVO.', 'RESTRITO', 'Identificador único do usuário no Firebase Authentication.'), +('public', 'usuario_sistema', 'firebase_fid', 'TEXT', FALSE, FALSE, NULL, FALSE, 'Firebase Installation ID atual associado a um único dispositivo do usuário; atualizar quando renovado e limpar quando inválido ou no logout.', 'RESTRITO', 'Identificador de destino usado pelo acta-fcm-api para enviar push ao usuário.'), ('public', 'usuario_sistema', 'tipo_usuario', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'Valores aceitos: ADMIN, GESTOR ou COLABORADOR.', 'INTERNO', 'Perfil de acesso do usuário: ADMIN, GESTOR ou COLABORADOR.'), ('public', 'usuario_sistema', 'status', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'Valores aceitos: ATIVO, INATIVO, PENDENTE, BLOQUEADO, ARQUIVADO.', 'INTERNO', 'Situação do usuário no sistema.'), ('public', 'usuario_sistema', 'criado_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, TRUE, 'Valor padrão obtido com NOW().', 'INTERNO', 'Data e hora de criação do registro.'), @@ -232,14 +233,6 @@ INSERT INTO auditoria.catalogo_dados ( ('pdca', 'tarefa', 'criado_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, TRUE, 'Valor padrão NOW().', 'INTERNO', 'Data e hora de criação do registro.'), ('pdca', 'tarefa', 'atualizado_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, FALSE, 'Última modificação.', 'INTERNO', 'Data e hora da última atualização do registro.'), --- pdca.alerta_prazo -('pdca', 'alerta_prazo', 'id', 'BIGSERIAL', TRUE, FALSE, NULL, TRUE, 'Gerado automaticamente.', 'INTERNO', 'Identificador único do alerta.'), -('pdca', 'alerta_prazo', 'id_tarefa', 'BIGINT', FALSE, TRUE, 'pdca.tarefa(id)', TRUE, 'ON DELETE CASCADE.', 'INTERNO', 'Tarefa relacionada ao alerta.'), -('pdca', 'alerta_prazo', 'id_usuario_destino', 'BIGINT', FALSE, TRUE, 'public.usuario_sistema(id)', TRUE, 'ON DELETE CASCADE.', 'INTERNO', 'Usuário destinatário do alerta.'), -('pdca', 'alerta_prazo', 'mensagem', 'TEXT', FALSE, FALSE, NULL, TRUE, 'Texto do alerta.', 'INTERNO', 'Mensagem enviada no alerta.'), -('pdca', 'alerta_prazo', 'enviado_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, TRUE, 'Valor padrão NOW().', 'INTERNO', 'Data e hora de envio do alerta.'), -('pdca', 'alerta_prazo', 'lido_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, FALSE, 'Registrado na confirmação de leitura.', 'INTERNO', 'Data e hora de leitura do alerta.'), - -- pdca.tarefa_dependencia ('pdca', 'tarefa_dependencia', 'id_tarefa', 'BIGINT', TRUE, TRUE, 'pdca.tarefa(id)', TRUE, 'ON DELETE CASCADE. Parte da PK.', 'INTERNO', 'Tarefa que depende de outra tarefa.'), ('pdca', 'tarefa_dependencia', 'id_tarefa_dependencia', 'BIGINT', TRUE, TRUE, 'pdca.tarefa(id)', TRUE, 'ON DELETE CASCADE. Parte da PK. Deve ser diferente de id_tarefa.', 'INTERNO', 'Tarefa que deve ser considerada como dependência.'), @@ -349,4 +342,18 @@ INSERT INTO auditoria.catalogo_dados ( ('auditoria', 'log_tarefa', 'dados_antes', 'JSONB', FALSE, FALSE, NULL, TRUE, 'Snapshot anterior (Objeto JSONB).', 'RESTRITO', 'Estado da tarefa antes da operação.'), ('auditoria', 'log_tarefa', 'dados_depois', 'JSONB', FALSE, FALSE, NULL, TRUE, 'Snapshot posterior (Objeto JSONB).', 'RESTRITO', 'Estado da tarefa depois da operação.'), ('auditoria', 'log_tarefa', 'data_log', 'TIMESTAMPTZ', FALSE, FALSE, NULL, TRUE, 'Valor padrão NOW().', 'RESTRITO', 'Data e hora de criação do log.'), -('auditoria', 'log_tarefa', 'operacao', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'INSERT, UPDATE, DELETE, LOGIN, LOGOUT, READ, EXPORT.', 'RESTRITO', 'Tipo de operação auditada.'); \ No newline at end of file +('auditoria', 'log_tarefa', 'operacao', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'INSERT, UPDATE, DELETE, LOGIN, LOGOUT, READ, EXPORT.', 'RESTRITO', 'Tipo de operação auditada.'), + +-- auditoria.notificacao +('auditoria', 'notificacao', 'id', 'BIGSERIAL', TRUE, FALSE, NULL, TRUE, 'Gerado automaticamente pelo banco de dados.', 'INTERNO', 'Identificador único da notificação.'), +('auditoria', 'notificacao', 'id_usuario_destino', 'BIGINT', FALSE, TRUE, 'public.usuario_sistema(id)', TRUE, 'ON DELETE CASCADE.', 'INTERNO', 'Usuário que receberá a notificação.'), +('auditoria', 'notificacao', 'id_empresa', 'BIGINT', FALSE, TRUE, 'public.empresa(id)', TRUE, 'ON DELETE CASCADE.', 'INTERNO', 'Empresa associada à notificação.'), +('auditoria', 'notificacao', 'tipo', 'VARCHAR(60)', FALSE, FALSE, NULL, TRUE, 'Tipo do evento que originou a notificação.', 'INTERNO', 'Tipo do evento que originou a notificação.'), +('auditoria', 'notificacao', 'categoria', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'Categoria funcional definida pela aplicação.', 'INTERNO', 'Categoria funcional da notificação.'), +('auditoria', 'notificacao', 'tipo_entidade', 'VARCHAR(40)', FALSE, FALSE, NULL, TRUE, 'Tipo do registro relacionado.', 'INTERNO', 'Tipo do registro relacionado à notificação.'), +('auditoria', 'notificacao', 'id_entidade', 'VARCHAR(100)', FALSE, FALSE, NULL, TRUE, 'Identificador do registro relacionado, armazenado como texto.', 'INTERNO', 'Identificador do registro relacionado.'), +('auditoria', 'notificacao', 'deep_link', 'VARCHAR(240)', FALSE, FALSE, NULL, TRUE, 'Caminho interno para o contexto relacionado na aplicação.', 'INTERNO', 'Caminho para abrir o contexto da notificação.'), +('auditoria', 'notificacao', 'chave_deduplicacao', 'VARCHAR(160)', FALSE, FALSE, NULL, TRUE, 'Única por destinatário; impede duplicidade para a mesma chave.', 'INTERNO', 'Chave de deduplicação da notificação por destinatário.'), +('auditoria', 'notificacao', 'criado_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, TRUE, 'Valor padrão NOW().', 'INTERNO', 'Data e hora de criação da notificação.'), +('auditoria', 'notificacao', 'lido_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, FALSE, 'NULL enquanto não lida.', 'INTERNO', 'Data e hora da leitura pelo destinatário.'), +('auditoria', 'notificacao', 'expira_em', 'TIMESTAMPTZ', FALSE, FALSE, NULL, FALSE, 'Data de expiração opcional; a aplicação deve filtrar ou remover registros expirados.', 'INTERNO', 'Data e hora de expiração da notificação, quando aplicável.'); \ No newline at end of file diff --git a/sql/009_views.sql b/sql/009_views.sql index f6e24a8..9f01946 100644 --- a/sql/009_views.sql +++ b/sql/009_views.sql @@ -131,24 +131,24 @@ FROM pdca.usuario_treinamento ut JOIN pdca.treinamento tr ON tr.id = ut.id_treinamento JOIN public.usuario_sistema u ON u.id = ut.id_usuario; --- 4 (fato de alertas) +-- 4 (fato de alertas de prazo, originados como notificações) CREATE OR REPLACE VIEW pdca.vw_fato_alertas AS SELECT - a.id AS id_alerta, - a.id_usuario_destino AS id_dim_usuario, - u.id_empresa AS id_dim_empresa, + n.id AS id_alerta, + n.id_usuario_destino AS id_dim_usuario, + n.id_empresa AS id_dim_empresa, pa.id_ciclo AS id_dim_ciclo, - a.id_tarefa, - a.enviado_em AS id_dim_data, + t.id AS id_tarefa, + n.criado_em AS id_dim_data, 1 AS qtd_alertas, - CASE WHEN a.lido_em IS NULL THEN 1 ELSE 0 END AS qtd_nao_lidos, - CASE WHEN a.lido_em IS NOT NULL THEN 1 ELSE 0 END AS qtd_lidos + CASE WHEN n.lido_em IS NULL THEN 1 ELSE 0 END AS qtd_nao_lidos, + CASE WHEN n.lido_em IS NOT NULL THEN 1 ELSE 0 END AS qtd_lidos -FROM pdca.alerta_prazo a -JOIN pdca.tarefa t ON t.id = a.id_tarefa -LEFT JOIN pdca.plano_acao pa ON pa.id = t.id_plano_acao -JOIN public.usuario_sistema u ON u.id = a.id_usuario_destino; +FROM auditoria.notificacao n +JOIN pdca.tarefa t ON t.id::TEXT = n.id_entidade +JOIN pdca.plano_acao pa ON pa.id = t.id_plano_acao +WHERE n.categoria = 'PRAZO' AND n.tipo_entidade = 'TAREFA' AND n.tipo = 'TAREFA_ATRASADA'; -- 5 (fato de logs de auditoria) CREATE OR REPLACE VIEW auditoria.vw_fato_logs_auditoria AS @@ -293,20 +293,20 @@ FROM pdca.treinamento tr JOIN pdca.ciclo c ON c.id = tr.id_ciclo JOIN public.usuario_sistema u ON u.id = tr.id_responsavel; --- 4 (alertas não lidos) +-- 4 (alertas de prazo não lidos, originados como notificações) CREATE OR REPLACE VIEW pdca.vw_alertas_nao_lidos AS SELECT - a.id AS id_alerta, - a.id_tarefa, - a.id_usuario_destino, + n.id AS id_alerta, + t.id AS id_tarefa, + n.id_usuario_destino, u.nome AS usuario_nome, - a.mensagem, - a.enviado_em, + FORMAT('A tarefa "%s" está atrasada desde %s.', t.titulo, TO_CHAR(t.data_fim_prevista, 'DD/MM/YYYY')) AS mensagem, + n.criado_em AS enviado_em, t.titulo AS tarefa_titulo -FROM pdca.alerta_prazo a -JOIN public.usuario_sistema u ON u.id = a.id_usuario_destino -JOIN pdca.tarefa t ON t.id = a.id_tarefa -WHERE a.lido_em IS NULL; +FROM auditoria.notificacao n +JOIN public.usuario_sistema u ON u.id = n.id_usuario_destino +JOIN pdca.tarefa t ON t.id::TEXT = n.id_entidade +WHERE n.lido_em IS NULL AND n.categoria = 'PRAZO' AND n.tipo_entidade = 'TAREFA' AND n.tipo = 'TAREFA_ATRASADA'; -- 5 (treinamentos pendentes por colaborador) CREATE OR REPLACE VIEW pdca.vw_treinamentos_pendentes AS