🚀 Initial commit: Versão atual do TrackSteel APP

This commit is contained in:
2026-03-18 21:17:53 +00:00
commit bde410c9ad
633 changed files with 108150 additions and 0 deletions

40
supabase/config.toml Normal file
View File

@@ -0,0 +1,40 @@
project_id = "lwjppiicofojfcdfjsto"
[api]
enabled = true
port = 54321
schemas = ["public", "storage", "graphql_public"]
extra_search_path = ["public", "extensions"]
max_rows = 1000
[db]
port = 54322
shadow_port = 54320
major_version = 15
[studio]
enabled = true
port = 54323
[inbucket]
enabled = true
port = 54324
[storage]
enabled = true
file_size_limit = "50MiB"
[auth]
enabled = true
site_url = "http://localhost:3000"
additional_redirect_urls = ["https://lwjppiicofojfcdfjsto.supabase.co"]
jwt_expiry = 3600
[auth.email]
enable_signup = true
double_confirm_changes = true
enable_confirmations = false
[functions.cleanup-duplicates]
verify_jwt = false

View File

@@ -0,0 +1,282 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { JSZip } from 'https://deno.land/x/jszip@0.11.0/mod.ts'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const authHeader = req.headers.get('Authorization')!
const token = authHeader.replace('Bearer ', '')
const { data: { user } } = await supabaseClient.auth.getUser(token)
if (!user) {
return new Response(
JSON.stringify({ error: 'Unauthorized' }),
{
status: 401,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Verificar se o usuário é admin
const { data: userRole } = await supabaseClient
.from('user_roles')
.select('role')
.eq('user_id', user.id)
.eq('role', 'admin')
.single()
if (!userRole) {
return new Response(
JSON.stringify({ error: 'Forbidden' }),
{
status: 403,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Criar timestamp para o nome do arquivo
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const backupFileName = `backup_${timestamp}.zip`
// Criar registro de log inicial
const { data: logEntry } = await supabaseClient
.from('backup_logs')
.insert({
operation_type: 'backup',
file_name: backupFileName,
created_by: user.id
})
.select()
.single()
if (!logEntry) {
return new Response(
JSON.stringify({ error: 'Failed to create backup log' }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
try {
// Lista das principais tabelas para backup
const tablesToBackup = [
'profiles',
'functions',
'privileges',
'user_roles',
'pecas',
'ordens_fabricacao',
'processos_fabricacao',
'apontamentos_producao',
'componentes_peca',
'estoque_materiais',
'movimentacoes_estoque',
'romaneios_expedicao',
'itens_romaneio_pecas',
'itens_romaneio_insumos',
'cronogramas_of',
'tasks',
'contratos_obra',
'catalogos',
'api_keys',
'json_codes',
'webhook_configs',
'prompts',
'session_logs',
'password_reset_requests',
'sugestoes',
'backup_logs',
'ficha_tecnica_contratos',
'diarios_producao',
'empenhos_material'
]
// Criar ZIP
const zip = new JSZip()
let totalRecords = 0
let tablesCount = 0
const backupStartTime = new Date()
const tablesSchema: any = {}
const operationLogs: any[] = []
// Fazer backup de cada tabela
for (const tableName of tablesToBackup) {
try {
operationLogs.push({
timestamp: new Date().toISOString(),
operation: 'backup_table_start',
table: tableName
})
const { data: tableData, error } = await supabaseClient
.from(tableName)
.select('*')
if (!error && tableData) {
// Adicionar dados da tabela na pasta data/
zip.addFile(`data/${tableName}.json`, JSON.stringify(tableData, null, 2))
// Coletar informações do schema (simplificado)
if (tableData.length > 0) {
const sampleRecord = tableData[0]
tablesSchema[tableName] = {
columns: Object.keys(sampleRecord).map(key => ({
name: key,
type: typeof sampleRecord[key]
})),
record_count: tableData.length
}
} else {
tablesSchema[tableName] = {
columns: [],
record_count: 0
}
}
totalRecords += tableData.length
tablesCount++
operationLogs.push({
timestamp: new Date().toISOString(),
operation: 'backup_table_success',
table: tableName,
records: tableData.length
})
console.log(`Backup da tabela ${tableName}: ${tableData.length} registros`)
} else {
operationLogs.push({
timestamp: new Date().toISOString(),
operation: 'backup_table_error',
table: tableName,
error: error?.message || 'Unknown error'
})
console.warn(`Erro ao fazer backup da tabela ${tableName}:`, error)
}
} catch (error) {
operationLogs.push({
timestamp: new Date().toISOString(),
operation: 'backup_table_error',
table: tableName,
error: error.message
})
console.error(`Erro ao fazer backup da tabela ${tableName}:`, error)
}
}
// Criar metadata.json
const metadata = {
backup_info: {
created_at: backupStartTime.toISOString(),
completed_at: new Date().toISOString(),
version: '1.0',
created_by: user.email,
database_name: 'TrackSteel Production Database'
},
statistics: {
total_tables: tablesCount,
total_records: totalRecords,
backup_size_estimate: 'calculated_after_compression'
}
}
// Criar schema.json
const schema = {
version: '1.0',
created_at: new Date().toISOString(),
tables: tablesSchema
}
// Criar logs.json
const logs = {
backup_start: backupStartTime.toISOString(),
backup_end: new Date().toISOString(),
operations: operationLogs,
summary: {
total_operations: operationLogs.length,
successful_tables: operationLogs.filter(log => log.operation === 'backup_table_success').length,
failed_tables: operationLogs.filter(log => log.operation === 'backup_table_error').length
}
}
// Adicionar arquivos de controle ao ZIP
zip.addFile('metadata.json', JSON.stringify(metadata, null, 2))
zip.addFile('schema.json', JSON.stringify(schema, null, 2))
zip.addFile('logs.json', JSON.stringify(logs, null, 2))
// Gerar o arquivo ZIP
const zipData = await zip.generateAsync({ type: 'uint8array' })
// Atualizar log como concluído
await supabaseClient
.from('backup_logs')
.update({
status: 'completed',
file_size: zipData.length,
tables_count: tablesCount,
records_count: totalRecords
})
.eq('id', logEntry.id)
console.log(`Backup concluído: ${tablesCount} tabelas, ${totalRecords} registros, ${zipData.length} bytes`)
return new Response(zipData, {
headers: {
...corsHeaders,
'Content-Type': 'application/zip',
'Content-Disposition': `attachment; filename="${backupFileName}"`,
}
})
} catch (error) {
console.error('Erro durante o backup:', error)
// Atualizar log com erro
await supabaseClient
.from('backup_logs')
.update({
status: 'failed',
error_message: error.message
})
.eq('id', logEntry.id)
return new Response(
JSON.stringify({ error: `Erro durante o backup: ${error.message}` }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
} catch (error) {
console.error('Erro geral no backup:', error)
return new Response(
JSON.stringify({ error: `Erro no backup: ${error.message}` }),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
})

View File

@@ -0,0 +1,304 @@
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
interface DuplicateGroup {
chave_agrupamento: string;
of_number: string;
marca: string;
etapa_fase: string;
processo_nome: string;
quantidade_total_peca: number;
apontamentos: Array<{
id: string;
data_apontamento: string;
created_at: string;
quantidade_produzida: number;
tipo_apontamento: string;
}>;
total_apontado: number;
excesso: number;
}
Deno.serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const { of_number, action = 'execute' } = await req.json();
if (!of_number) {
return new Response(
JSON.stringify({
success: false,
error: 'Número da OF é obrigatório'
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400
}
);
}
console.log(`🔍 Iniciando ${action === 'analyze' ? 'análise' : 'limpeza'} de duplicatas para OF ${of_number}...`);
// Buscar todos os apontamentos da OF com dados completos
const { data: apontamentos, error } = await supabase
.from('apontamentos_producao')
.select(`
id,
of_number,
processo_id,
quantidade_produzida,
data_apontamento,
created_at,
tipo_apontamento,
peca_id,
componente_id,
peca:pecas(id, marca, etapa_fase, quantidade),
componente:componentes_peca(id, marca_componente),
processo:processos_fabricacao(nome)
`)
.eq('of_number', of_number)
.order('data_apontamento', { ascending: true })
.order('created_at', { ascending: true });
if (error) {
console.error('❌ Erro ao buscar apontamentos:', error);
throw error;
}
console.log(`📋 Encontrados ${apontamentos?.length || 0} apontamentos para OF ${of_number}`);
if (!apontamentos || apontamentos.length === 0) {
return new Response(
JSON.stringify({
success: true,
message: `Nenhum apontamento encontrado para OF ${of_number}`,
duplicatesRemoved: 0,
totalGroups: 0,
groupsWithDuplicates: 0,
details: []
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200
}
);
}
// Agrupar apontamentos por peça+fase+processo
const grupos = new Map<string, DuplicateGroup>();
apontamentos.forEach(apt => {
let marca = '';
let etapa_fase = '';
let quantidade_total_peca = 0;
if (apt.tipo_apontamento === 'peca' && apt.peca) {
marca = apt.peca.marca;
etapa_fase = apt.peca.etapa_fase || '0';
quantidade_total_peca = apt.peca.quantidade || 0;
} else if (apt.tipo_apontamento === 'componente' && apt.componente) {
marca = apt.componente.marca_componente;
etapa_fase = '0';
quantidade_total_peca = 0;
return; // Pular componentes nesta análise
} else {
console.warn('⚠️ Apontamento sem peça válida:', apt.id);
return;
}
// Chave única: OF + Marca + Fase + Processo
const chave = `${apt.of_number}|${marca}|${etapa_fase}|${apt.processo_id}`;
const processo_nome = apt.processo?.nome || 'Desconhecido';
console.log(`📝 Processando: OF=${apt.of_number}, Marca=${marca}, Fase=${etapa_fase}, Processo=${processo_nome}, Data=${apt.data_apontamento}, Qtd=${apt.quantidade_produzida}`);
if (!grupos.has(chave)) {
grupos.set(chave, {
chave_agrupamento: chave,
of_number: apt.of_number,
marca,
etapa_fase,
processo_nome,
quantidade_total_peca,
apontamentos: [],
total_apontado: 0,
excesso: 0
});
}
const grupo = grupos.get(chave)!;
grupo.apontamentos.push({
id: apt.id,
data_apontamento: apt.data_apontamento,
created_at: apt.created_at,
quantidade_produzida: apt.quantidade_produzida,
tipo_apontamento: apt.tipo_apontamento
});
});
console.log(`🔍 Identificados ${grupos.size} grupos únicos de OF+Marca+Fase+Processo`);
// Identificar duplicatas REAIS
const idsParaExcluir: string[] = [];
const detalhesLimpeza: DuplicateGroup[] = [];
grupos.forEach((grupo) => {
// Calcular total apontado para este grupo
grupo.total_apontado = grupo.apontamentos.reduce((sum, apt) => sum + apt.quantidade_produzida, 0);
grupo.excesso = Math.max(0, grupo.total_apontado - grupo.quantidade_total_peca);
console.log(`\n🔄 Analisando grupo: ${grupo.marca} | Fase: ${grupo.etapa_fase} | Processo: ${grupo.processo_nome}`);
console.log(` 📊 Quantidade total da peça: ${grupo.quantidade_total_peca}`);
console.log(` 📈 Total apontado: ${grupo.total_apontado}`);
console.log(` 📝 Número de apontamentos: ${grupo.apontamentos.length}`);
// Se só tem 1 apontamento, não há duplicata
if (grupo.apontamentos.length <= 1) {
console.log(` ✅ Apenas 1 apontamento - OK`);
return;
}
// Ordenar apontamentos por data (mais antigo primeiro)
grupo.apontamentos.sort((a, b) => {
const dataA = new Date(a.data_apontamento);
const dataB = new Date(b.data_apontamento);
if (dataA.getTime() !== dataB.getTime()) {
return dataA.getTime() - dataB.getTime();
}
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
console.log(` 📅 Apontamentos ordenados por data:`);
grupo.apontamentos.forEach((apt, idx) => {
console.log(` ${idx + 1}. Data: ${apt.data_apontamento}, Qtd: ${apt.quantidade_produzida}, ID: ${apt.id}`);
});
// Verificar se há excesso real de apontamentos ou múltiplas datas
const datasUnicas = [...new Set(grupo.apontamentos.map(a => a.data_apontamento))];
const temExcesso = grupo.total_apontado > grupo.quantidade_total_peca;
const temMultiplasDatas = datasUnicas.length > 1;
if (temExcesso || temMultiplasDatas) {
console.log(` 🚨 DUPLICATA DETECTADA!`);
if (temExcesso) {
console.log(` ⚠️ EXCESSO: Total apontado (${grupo.total_apontado}) > Quantidade da peça (${grupo.quantidade_total_peca})`);
}
if (temMultiplasDatas) {
console.log(` 📅 MÚLTIPLAS DATAS: Mesma peça apontada em ${datasUnicas.length} datas diferentes: ${datasUnicas.join(', ')}`);
}
let quantidadeAcumulada = 0;
// Processar apontamentos em ordem cronológica (manter os mais antigos)
for (let i = 0; i < grupo.apontamentos.length; i++) {
const apt = grupo.apontamentos[i];
if (quantidadeAcumulada + apt.quantidade_produzida <= grupo.quantidade_total_peca) {
// Este apontamento ainda cabe na quantidade da peça
quantidadeAcumulada += apt.quantidade_produzida;
console.log(` ✅ MANTIDO: ${apt.data_apontamento} - Qtd: ${apt.quantidade_produzida} (Acum: ${quantidadeAcumulada})`);
} else {
// Este apontamento é excesso - REMOVER
idsParaExcluir.push(apt.id);
console.log(` 🗑️ REMOVIDO: ${apt.data_apontamento} - Qtd: ${apt.quantidade_produzida} - MOTIVO: Excesso`);
}
}
detalhesLimpeza.push(grupo);
}
});
const resultado = {
success: true,
message: action === 'analyze'
? `Análise concluída para OF ${of_number}. ${detalhesLimpeza.length} duplicatas encontradas.`
: `Limpeza concluída para OF ${of_number}. ${idsParaExcluir.length} duplicatas/excessos removidos.`,
duplicatesRemoved: action === 'execute' ? idsParaExcluir.length : 0,
totalGroups: grupos.size,
groupsWithDuplicates: detalhesLimpeza.length,
details: detalhesLimpeza
};
// Se for apenas análise, não executar exclusões
if (action === 'analyze') {
console.log('\n📊 Análise concluída - não executando exclusões');
return new Response(
JSON.stringify(resultado),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200
}
);
}
// Executar exclusões em lotes (apenas se action === 'execute')
let duplicatasRemovidas = 0;
if (idsParaExcluir.length > 0) {
console.log(`\n🗑 Iniciando exclusão de ${idsParaExcluir.length} apontamentos duplicados/excessivos`);
const loteSize = 50;
for (let i = 0; i < idsParaExcluir.length; i += loteSize) {
const lote = idsParaExcluir.slice(i, i + loteSize);
console.log(`🔄 Processando lote ${Math.floor(i/loteSize) + 1}/${Math.ceil(idsParaExcluir.length/loteSize)} com ${lote.length} registros...`);
const { error: deleteError } = await supabase
.from('apontamentos_producao')
.delete()
.in('id', lote);
if (deleteError) {
console.error(`❌ Erro ao excluir lote:`, deleteError);
throw deleteError;
}
duplicatasRemovidas += lote.length;
console.log(`✅ Lote processado: ${lote.length} registros excluídos`);
}
}
resultado.duplicatesRemoved = duplicatasRemovidas;
console.log('\n✅ Operação concluída:', resultado);
return new Response(
JSON.stringify(resultado),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200
}
);
} catch (error) {
console.error('❌ Erro durante operação:', error);
return new Response(
JSON.stringify({
success: false,
error: error.message || 'Erro interno do servidor',
duplicatesRemoved: 0
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 500
}
);
}
});

View File

@@ -0,0 +1,139 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const authHeader = req.headers.get('Authorization')!
const token = authHeader.replace('Bearer ', '')
const { data: { user } } = await supabaseClient.auth.getUser(token)
if (!user) {
return new Response('Unauthorized', { status: 401, headers: corsHeaders })
}
// Verificar se o usuário é admin
const { data: userRole } = await supabaseClient
.from('user_roles')
.select('role')
.eq('user_id', user.id)
.eq('role', 'admin')
.single()
if (!userRole) {
return new Response('Forbidden', { status: 403, headers: corsHeaders })
}
// Ler arquivo do request
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
return new Response('No file provided', { status: 400, headers: corsHeaders })
}
// Criar registro de log inicial
const { data: logEntry } = await supabaseClient
.from('backup_logs')
.insert({
operation_type: 'restore',
file_name: file.name,
file_size: file.size,
created_by: user.id
})
.select()
.single()
try {
// Ler conteúdo do arquivo
const fileContent = await file.text()
const backupData = JSON.parse(fileContent)
// Validar estrutura do backup
if (!backupData.version || !backupData.tables) {
throw new Error('Formato de backup inválido')
}
let totalRecords = 0
// Restaurar cada tabela
for (const [tableName, tableData] of Object.entries(backupData.tables)) {
if (Array.isArray(tableData) && tableData.length > 0) {
try {
// Limpar tabela antes de restaurar (cuidado!)
await supabaseClient
.from(tableName)
.delete()
.not('id', 'is', null)
// Inserir dados em lotes
const batchSize = 100
for (let i = 0; i < tableData.length; i += batchSize) {
const batch = tableData.slice(i, i + batchSize)
await supabaseClient
.from(tableName)
.insert(batch)
}
totalRecords += tableData.length
} catch (error) {
console.error(`Erro ao restaurar tabela ${tableName}:`, error)
// Continuar com outras tabelas mesmo se uma falhar
}
}
}
// Atualizar log como concluído
await supabaseClient
.from('backup_logs')
.update({
status: 'completed',
tables_count: Object.keys(backupData.tables).length,
records_count: totalRecords
})
.eq('id', logEntry.id)
return new Response(JSON.stringify({
success: true,
message: 'Restauração concluída com sucesso',
tablesRestored: Object.keys(backupData.tables).length,
recordsRestored: totalRecords
}), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})
} catch (error) {
// Atualizar log com erro
await supabaseClient
.from('backup_logs')
.update({
status: 'failed',
error_message: error.message
})
.eq('id', logEntry.id)
throw error
}
} catch (error) {
console.error('Erro na restauração:', error)
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
})
}
})

View File

@@ -0,0 +1,47 @@
-- Criar tabela de perfis de usuários
CREATE TABLE public.profiles (
id UUID NOT NULL REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
email TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar RLS na tabela profiles
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Política para permitir que usuários vejam apenas seus próprios perfis
CREATE POLICY "Users can view their own profile"
ON public.profiles
FOR SELECT
USING (auth.uid() = id);
-- Política para permitir que usuários insiram seus próprios perfis
CREATE POLICY "Users can insert their own profile"
ON public.profiles
FOR INSERT
WITH CHECK (auth.uid() = id);
-- Política para permitir que usuários atualizem seus próprios perfis
CREATE POLICY "Users can update their own profile"
ON public.profiles
FOR UPDATE
USING (auth.uid() = id);
-- Função para criar perfil automaticamente quando um usuário se registra
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER SET search_path = ''
AS $$
BEGIN
INSERT INTO public.profiles (id, email)
VALUES (new.id, new.email);
RETURN new;
END;
$$;
-- Trigger para executar a função quando um novo usuário é criado
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();

View File

@@ -0,0 +1,73 @@
-- Create enum for user roles
CREATE TYPE public.app_role AS ENUM ('admin', 'user');
-- Create user_roles table
CREATE TABLE public.user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
role app_role NOT NULL DEFAULT 'user',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
UNIQUE (user_id, role)
);
-- Enable RLS on user_roles table
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;
-- Create security definer function to check user roles
CREATE OR REPLACE FUNCTION public.has_role(_user_id UUID, _role app_role)
RETURNS BOOLEAN
LANGUAGE SQL
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
SELECT EXISTS (
SELECT 1
FROM public.user_roles
WHERE user_id = _user_id
AND role = _role
)
$$;
-- RLS policies for user_roles table
CREATE POLICY "Users can view their own roles"
ON public.user_roles
FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Only admins can manage roles"
ON public.user_roles
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Add missing DELETE policy for profiles table
CREATE POLICY "Users can delete their own profile"
ON public.profiles
FOR DELETE
USING (auth.uid() = id);
-- Create trigger to assign default 'user' role to new users
CREATE OR REPLACE FUNCTION public.handle_new_user_role()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER SET search_path = public
AS $$
BEGIN
INSERT INTO public.user_roles (user_id, role)
VALUES (new.id, 'user');
RETURN new;
END;
$$;
CREATE TRIGGER on_auth_user_created_role
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user_role();
-- Insert admin role for existing users (you can modify this as needed)
-- This will make the first user an admin - adjust the email as needed
INSERT INTO public.user_roles (user_id, role)
SELECT id, 'admin'
FROM auth.users
WHERE email = 'm.reifonas@gmail.com'
ON CONFLICT (user_id, role) DO NOTHING;

View File

@@ -0,0 +1,161 @@
-- Criar tabela para ficha técnica de contratos
CREATE TABLE public.ficha_tecnica_contratos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
of_number TEXT UNIQUE NOT NULL,
gestor TEXT,
revisao TEXT,
quantidade NUMERIC,
data_criacao DATE DEFAULT CURRENT_DATE,
-- Dados do Cliente
cliente TEXT,
cnpj TEXT,
ie TEXT,
endereco TEXT,
cidade TEXT,
estado TEXT,
cep TEXT,
contato_contrato TEXT,
fone_contrato TEXT,
cel_contrato TEXT,
email_contrato TEXT,
contato_obra TEXT,
fone_obra TEXT,
cel_obra TEXT,
email_obra TEXT,
contato_qualid TEXT,
fone_qualid TEXT,
cel_qualid TEXT,
email_qualid TEXT,
-- Dados do Projeto
descricao_resumida TEXT,
endereco_projeto TEXT,
bairro_projeto TEXT,
cep_projeto TEXT,
cidade_projeto TEXT,
estado_projeto TEXT,
horarios_trabalho TEXT,
condicoes_acesso TEXT,
-- Tipo de Projeto (checkboxes)
tipo_estrutural BOOLEAN DEFAULT FALSE,
tipo_residencial BOOLEAN DEFAULT FALSE,
tipo_espacial BOOLEAN DEFAULT FALSE,
tipo_comercial BOOLEAN DEFAULT FALSE,
tipo_grades BOOLEAN DEFAULT FALSE,
tipo_industrial BOOLEAN DEFAULT FALSE,
tipo_cobertura BOOLEAN DEFAULT FALSE,
tipo_com_montagem BOOLEAN DEFAULT FALSE,
-- Documentos fornecidos pelo cliente (checkboxes)
doc_calculo BOOLEAN DEFAULT FALSE,
doc_projeto BOOLEAN DEFAULT FALSE,
doc_detalhamento BOOLEAN DEFAULT FALSE,
doc_cronograma BOOLEAN DEFAULT FALSE,
doc_normas BOOLEAN DEFAULT FALSE,
doc_especif_tecnicas BOOLEAN DEFAULT FALSE,
doc_catalogo BOOLEAN DEFAULT FALSE,
doc_fotos BOOLEAN DEFAULT FALSE,
-- Informações do projeto (E/C/NA para cada item)
info_calculo_estrutural JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_projeto_basico JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_detalhamento JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_materia_prima JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_fabricacao JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_grades_piso JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_jateamento JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_pintura_base JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_pintura_inter JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_pintura_acabamento JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_galvanizacao JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_embalagem JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_transporte JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_inspecao JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_ensaios_lab JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_databook JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_pre_montagem JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_placa_engenetal JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_parafusos JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_chumbadores JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_stud_bolt JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_fornec_telhas JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_montagem_telhas JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_forn_calhas JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_mont_calhas JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_steel_deck JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_fornec_wall JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_mont_wall JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
info_outros_materiais JSONB DEFAULT '{"e": false, "c": false, "na": false, "info": ""}',
-- Validação pós detalhamento
necessita_validacao_pos_detalh BOOLEAN DEFAULT FALSE,
-- Grades de piso
grades_modelo TEXT,
grades_padrao_comercial BOOLEAN DEFAULT FALSE,
grades_padrao_sa2 BOOLEAN DEFAULT FALSE,
grades_padrao_sa2_meio BOOLEAN DEFAULT FALSE,
grades_padrao_sa3 BOOLEAN DEFAULT FALSE,
-- Requisitos ambientais
req_ambientais_existem BOOLEAN DEFAULT FALSE,
req_ambientais_quais TEXT,
req_saude_seguranca_existem BOOLEAN DEFAULT FALSE,
req_saude_seguranca_quais TEXT,
-- Alteração no contrato
alteracao_descritivo TEXT,
alteracao_motivo TEXT,
alteracao_impacto TEXT,
alteracao_custo NUMERIC,
alteracao_cronograma TEXT,
alteracao_pecas_prontas TEXT,
alteracao_detalh_projeto TEXT,
-- Cronograma (semanas)
cronograma_semanas JSONB DEFAULT '{}',
-- Visto dos responsáveis
visto_gestor TEXT,
visto_pcp TEXT,
visto_eng TEXT,
visto_fab TEXT,
visto_exp TEXT,
visto_qual TEXT,
visto_colunas JSONB DEFAULT '{"1": false, "2": false, "3": false, "4": false}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
user_id UUID REFERENCES auth.users(id)
);
-- Enable RLS
ALTER TABLE public.ficha_tecnica_contratos ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY "Users can view their own fichas"
ON public.ficha_tecnica_contratos
FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can create their own fichas"
ON public.ficha_tecnica_contratos
FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own fichas"
ON public.ficha_tecnica_contratos
FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete their own fichas"
ON public.ficha_tecnica_contratos
FOR DELETE
USING (auth.uid() = user_id);
-- Create index for better performance
CREATE INDEX idx_ficha_tecnica_of_number ON public.ficha_tecnica_contratos(of_number);
CREATE INDEX idx_ficha_tecnica_user_id ON public.ficha_tecnica_contratos(user_id);

View File

@@ -0,0 +1,111 @@
-- Create enum for user status
CREATE TYPE public.user_status AS ENUM ('pending', 'active', 'inactive', 'rejected');
-- Create functions table for user roles/functions
CREATE TABLE public.functions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create privileges table for access control
CREATE TABLE public.privileges (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL,
description TEXT,
permissions JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Update the existing profiles table to include the new fields
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS full_name TEXT;
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS profile_image_url TEXT;
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS function_id UUID REFERENCES public.functions(id);
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS privilege_id UUID REFERENCES public.privileges(id);
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS status public.user_status DEFAULT 'pending';
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS requested_at TIMESTAMP WITH TIME ZONE DEFAULT NOW();
-- Enable RLS on new tables
ALTER TABLE public.functions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.privileges ENABLE ROW LEVEL SECURITY;
-- Create policies for functions table
CREATE POLICY "Admins can manage functions"
ON public.functions
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "All authenticated users can view functions"
ON public.functions
FOR SELECT
TO authenticated
USING (true);
-- Create policies for privileges table
CREATE POLICY "Admins can manage privileges"
ON public.privileges
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "All authenticated users can view privileges"
ON public.privileges
FOR SELECT
TO authenticated
USING (true);
-- Update profiles policies
DROP POLICY IF EXISTS "Users can view their own profile" ON public.profiles;
DROP POLICY IF EXISTS "Users can update their own profile" ON public.profiles;
CREATE POLICY "Admins can view all profiles"
ON public.profiles
FOR SELECT
USING (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "Users can view their own profile"
ON public.profiles
FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Admins can update all profiles"
ON public.profiles
FOR UPDATE
USING (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "Users can update their own profile image"
ON public.profiles
FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
-- Insert default functions
INSERT INTO public.functions (name, description) VALUES
('Desenvolvedor', 'Responsável pelo desenvolvimento de software'),
('Gerente de Produção/Engenharia', 'Gerencia processos de produção e engenharia'),
('Projetista', 'Responsável por projetos técnicos'),
('Vendedora', 'Responsável por vendas'),
('Compradora', 'Responsável por compras')
ON CONFLICT (name) DO NOTHING;
-- Insert default privileges
INSERT INTO public.privileges (name, description, permissions) VALUES
('Admin', 'Acesso total ao sistema', '{"can_manage_users": true, "can_edit_settings": true, "can_view_all": true}'),
('Viewer', 'Apenas visualização', '{"can_manage_users": false, "can_edit_settings": false, "can_view_all": false}')
ON CONFLICT (name) DO NOTHING;
-- Update existing user registration trigger to set status as pending
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER SET search_path = public
AS $$
BEGIN
INSERT INTO public.profiles (id, email, status, requested_at)
VALUES (new.id, new.email, 'pending', NOW());
RETURN new;
END;
$$;

View File

@@ -0,0 +1,247 @@
-- Create enum for task status
CREATE TYPE public.task_status AS ENUM ('a_fazer', 'em_andamento', 'revisao', 'pendente', 'bloqueado', 'concluido');
-- Create enum for task priority
CREATE TYPE public.task_priority AS ENUM ('baixa', 'media', 'alta', 'urgente');
-- Create tasks table
CREATE TABLE public.tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_ref TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
description TEXT,
of_number TEXT NOT NULL REFERENCES public.ficha_tecnica_contratos(of_number),
created_by UUID REFERENCES auth.users(id) NOT NULL,
assigned_to UUID[] DEFAULT '{}',
due_date TIMESTAMP WITH TIME ZONE,
status public.task_status DEFAULT 'a_fazer',
priority public.task_priority DEFAULT 'media',
category TEXT,
is_completed BOOLEAN DEFAULT false,
completed_at TIMESTAMP WITH TIME ZONE,
completed_by UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create task comments table
CREATE TABLE public.task_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_id UUID REFERENCES public.tasks(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES auth.users(id) NOT NULL,
comment TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create task attachments table
CREATE TABLE public.task_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_id UUID REFERENCES public.tasks(id) ON DELETE CASCADE NOT NULL,
file_name TEXT NOT NULL,
file_url TEXT NOT NULL,
file_size BIGINT,
file_type TEXT,
uploaded_by UUID REFERENCES auth.users(id) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create task subtasks table
CREATE TABLE public.task_subtasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_id UUID REFERENCES public.tasks(id) ON DELETE CASCADE NOT NULL,
title TEXT NOT NULL,
is_completed BOOLEAN DEFAULT false,
order_index INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Enable RLS on all task tables
ALTER TABLE public.tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.task_comments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.task_attachments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.task_subtasks ENABLE ROW LEVEL SECURITY;
-- Create policies for tasks table
CREATE POLICY "Users can view tasks assigned to them or created by them"
ON public.tasks
FOR SELECT
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Authenticated users can create tasks"
ON public.tasks
FOR INSERT
WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update their own tasks or assigned tasks"
ON public.tasks
FOR UPDATE
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Users can delete their own tasks"
ON public.tasks
FOR DELETE
USING (
auth.uid() = created_by OR
public.has_role(auth.uid(), 'admin')
);
-- Create policies for task comments
CREATE POLICY "Users can view comments on accessible tasks"
ON public.task_comments
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
CREATE POLICY "Authenticated users can create comments on accessible tasks"
ON public.task_comments
FOR INSERT
WITH CHECK (
auth.uid() = user_id AND
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
-- Create policies for task attachments
CREATE POLICY "Users can view attachments on accessible tasks"
ON public.task_attachments
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
CREATE POLICY "Authenticated users can upload attachments to accessible tasks"
ON public.task_attachments
FOR INSERT
WITH CHECK (
auth.uid() = uploaded_by AND
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
-- Create policies for task subtasks
CREATE POLICY "Users can view subtasks on accessible tasks"
ON public.task_subtasks
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
CREATE POLICY "Users can manage subtasks on accessible tasks"
ON public.task_subtasks
FOR ALL
USING (
EXISTS (
SELECT 1 FROM public.tasks
WHERE id = task_id AND (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
)
)
);
-- Create function to generate task reference
CREATE OR REPLACE FUNCTION public.generate_task_ref()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
next_num INTEGER;
task_ref TEXT;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(task_ref FROM 'TASK-(\d+)') AS INTEGER)), 0) + 1
INTO next_num
FROM public.tasks
WHERE task_ref ~ '^TASK-\d+$';
task_ref := 'TASK-' || LPAD(next_num::TEXT, 3, '0');
RETURN task_ref;
END;
$$;
-- Create trigger to auto-generate task reference
CREATE OR REPLACE FUNCTION public.handle_task_ref()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.task_ref IS NULL OR NEW.task_ref = '' THEN
NEW.task_ref := public.generate_task_ref();
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER set_task_ref_trigger
BEFORE INSERT ON public.tasks
FOR EACH ROW
EXECUTE FUNCTION public.handle_task_ref();
-- Create trigger to update task completion
CREATE OR REPLACE FUNCTION public.handle_task_completion()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.status = 'concluido' AND OLD.status != 'concluido' THEN
NEW.is_completed := true;
NEW.completed_at := NOW();
NEW.completed_by := auth.uid();
ELSIF NEW.status != 'concluido' AND OLD.status = 'concluido' THEN
NEW.is_completed := false;
NEW.completed_at := NULL;
NEW.completed_by := NULL;
END IF;
NEW.updated_at := NOW();
RETURN NEW;
END;
$$;
CREATE TRIGGER handle_task_completion_trigger
BEFORE UPDATE ON public.tasks
FOR EACH ROW
EXECUTE FUNCTION public.handle_task_completion();

View File

@@ -0,0 +1,38 @@
-- Drop existing policies if they exist
DROP POLICY IF EXISTS "Users can view tasks assigned to them or created by them" ON public.tasks;
DROP POLICY IF EXISTS "Authenticated users can create tasks" ON public.tasks;
DROP POLICY IF EXISTS "Users can update their own tasks or assigned tasks" ON public.tasks;
DROP POLICY IF EXISTS "Users can delete their own tasks" ON public.tasks;
-- Create new policies for tasks table
CREATE POLICY "Users can view tasks assigned to them or created by them"
ON public.tasks
FOR SELECT
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Authenticated users can create tasks"
ON public.tasks
FOR INSERT
WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update their own tasks or assigned tasks"
ON public.tasks
FOR UPDATE
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Users can delete their own tasks"
ON public.tasks
FOR DELETE
USING (
auth.uid() = created_by OR
public.has_role(auth.uid(), 'admin')
);

View File

@@ -0,0 +1,47 @@
-- Primeiro, vamos alterar a tabela tasks para remover a restrição de chave estrangeira com ficha_tecnica_contratos
-- e tornar of_number apenas um campo de texto para vinculação
ALTER TABLE public.tasks DROP CONSTRAINT IF EXISTS tasks_of_number_fkey;
-- Vamos ajustar a coluna of_number para permitir valores que podem não existir em ficha_tecnica_contratos
-- (caso o usuário digite um número de OF que ainda não foi cadastrado)
ALTER TABLE public.tasks ALTER COLUMN of_number DROP NOT NULL;
ALTER TABLE public.tasks ALTER COLUMN of_number SET NOT NULL;
-- Vamos criar um índice para melhorar a performance de busca por OF
CREATE INDEX IF NOT EXISTS idx_tasks_of_number ON public.tasks(of_number);
-- Vamos também criar um índice para o task_ref para buscas rápidas
CREATE INDEX IF NOT EXISTS idx_tasks_task_ref ON public.tasks(task_ref);
-- Vamos atualizar a função de geração de referência para um formato mais claro
CREATE OR REPLACE FUNCTION public.generate_task_ref()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
next_num INTEGER;
task_ref TEXT;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(task_ref FROM 'T-(\d+)') AS INTEGER)), 0) + 1
INTO next_num
FROM public.tasks
WHERE task_ref ~ '^T-\d+$';
task_ref := 'T-' || LPAD(next_num::TEXT, 4, '0');
RETURN task_ref;
END;
$$;
-- Vamos atualizar a função trigger para usar o novo formato
CREATE OR REPLACE FUNCTION public.handle_task_ref()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.task_ref IS NULL OR NEW.task_ref = '' OR NEW.task_ref = 'TEMP' THEN
NEW.task_ref := public.generate_task_ref();
END IF;
RETURN NEW;
END;
$$;

View File

@@ -0,0 +1,43 @@
-- Criar bucket para fotos de perfil
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'profile-images',
'profile-images',
true,
5242880, -- 5MB limit
ARRAY['image/jpeg', 'image/png', 'image/gif', 'image/webp']
);
-- Política para permitir que usuários façam upload de suas próprias fotos
CREATE POLICY "Users can upload their own profile images"
ON storage.objects
FOR INSERT
WITH CHECK (
bucket_id = 'profile-images'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- Política para permitir que usuários atualizem suas próprias fotos
CREATE POLICY "Users can update their own profile images"
ON storage.objects
FOR UPDATE
USING (
bucket_id = 'profile-images'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- Política para permitir que usuários deletem suas próprias fotos
CREATE POLICY "Users can delete their own profile images"
ON storage.objects
FOR DELETE
USING (
bucket_id = 'profile-images'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- Política para permitir que todos vejam as fotos de perfil (públicas)
CREATE POLICY "Profile images are publicly viewable"
ON storage.objects
FOR SELECT
USING (bucket_id = 'profile-images');

View File

@@ -0,0 +1,28 @@
-- Create brand_settings table
CREATE TABLE public.brand_settings (
id SERIAL PRIMARY KEY,
company_name TEXT NOT NULL DEFAULT 'TrackSteel',
logo_url TEXT,
font_family TEXT NOT NULL DEFAULT 'Arial',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Create storage bucket for brand assets
INSERT INTO storage.buckets (id, name, public)
VALUES ('brand-assets', 'brand-assets', true);
-- Create storage policy for brand assets
CREATE POLICY "Allow public access to brand assets" ON storage.objects
FOR ALL USING (bucket_id = 'brand-assets');
-- Enable Row Level Security
ALTER TABLE public.brand_settings ENABLE ROW LEVEL SECURITY;
-- Create policy for brand_settings (public read, admin write)
CREATE POLICY "Anyone can view brand settings" ON public.brand_settings
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can modify brand settings" ON public.brand_settings
FOR ALL USING (auth.role() = 'authenticated');

View File

@@ -0,0 +1,57 @@
-- Corrigir a função generate_task_ref para evitar ambiguidade na coluna task_ref
CREATE OR REPLACE FUNCTION public.generate_task_ref()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
next_num INTEGER;
new_task_ref TEXT;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(tasks.task_ref FROM 'T-(\d+)') AS INTEGER)), 0) + 1
INTO next_num
FROM public.tasks
WHERE tasks.task_ref ~ '^T-\d+$';
new_task_ref := 'T-' || LPAD(next_num::TEXT, 4, '0');
RETURN new_task_ref;
END;
$$;
-- Verificar e recriar as políticas RLS para garantir que estão funcionando corretamente
DROP POLICY IF EXISTS "Users can view tasks assigned to them or created by them" ON public.tasks;
DROP POLICY IF EXISTS "Authenticated users can create tasks" ON public.tasks;
DROP POLICY IF EXISTS "Users can update their own tasks or assigned tasks" ON public.tasks;
DROP POLICY IF EXISTS "Users can delete their own tasks" ON public.tasks;
-- Recriar as políticas RLS
CREATE POLICY "Users can view tasks assigned to them or created by them"
ON public.tasks
FOR SELECT
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Authenticated users can create tasks"
ON public.tasks
FOR INSERT
WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update their own tasks or assigned tasks"
ON public.tasks
FOR UPDATE
USING (
auth.uid() = created_by OR
auth.uid() = ANY(assigned_to) OR
public.has_role(auth.uid(), 'admin')
);
CREATE POLICY "Users can delete their own tasks"
ON public.tasks
FOR DELETE
USING (
auth.uid() = created_by OR
public.has_role(auth.uid(), 'admin')
);

View File

@@ -0,0 +1,77 @@
-- Criar tabela para recursos de interface (telas/menus)
CREATE TABLE public.interface_resources (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
resource_key TEXT NOT NULL UNIQUE,
resource_name TEXT NOT NULL,
parent_key TEXT NULL,
icon_name TEXT NULL,
route_path TEXT NULL,
is_submenu BOOLEAN DEFAULT false,
order_index INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela de relação N:M entre privilégios e recursos de interface
CREATE TABLE public.privilege_interface_resources (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
privilege_id UUID NOT NULL REFERENCES public.privileges(id) ON DELETE CASCADE,
resource_key TEXT NOT NULL REFERENCES public.interface_resources(resource_key) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
UNIQUE(privilege_id, resource_key)
);
-- Inserir os recursos de interface baseados no menu atual
INSERT INTO public.interface_resources (resource_key, resource_name, icon_name, route_path, is_submenu, order_index) VALUES
('dashboard', 'Dashboard', 'LayoutDashboard', '/', false, 1),
('grupos', 'Grupos e Equipe', 'Users', '/grupos', false, 2),
('tarefas', 'Tarefas', 'CheckSquare', '/tarefas', false, 3),
('cadastro', 'Cadastro', 'UserPlus', '/cadastro', false, 4),
('cadastro-of', 'Cadastro de OF', 'FileText', '/cadastro-of', true, 1),
('producao', 'Produção', 'Factory', '/producao', false, 5),
('sistema', 'Sistema', 'Settings', '/sistema', false, 6),
('configuracoes', 'Configurações', 'Settings', '/configuracoes', false, 7),
('admin', 'Painel Admin', 'Shield', '/admin', false, 8),
('user-management', 'Usuários e Privilégios', 'Users', '/user-management', false, 9);
-- Definir relações pai-filho para submenus
UPDATE public.interface_resources
SET parent_key = 'cadastro'
WHERE resource_key = 'cadastro-of';
-- Habilitar RLS nas novas tabelas
ALTER TABLE public.interface_resources ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.privilege_interface_resources ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para interface_resources (todos podem ler, só admins podem modificar)
CREATE POLICY "Anyone can view interface resources"
ON public.interface_resources
FOR SELECT
USING (true);
CREATE POLICY "Only admins can modify interface resources"
ON public.interface_resources
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Políticas RLS para privilege_interface_resources (só admins podem gerenciar)
CREATE POLICY "Only admins can manage privilege interface resources"
ON public.privilege_interface_resources
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Função para verificar se usuário tem acesso a um recurso específico
CREATE OR REPLACE FUNCTION public.user_has_interface_access(_user_id UUID, _resource_key TEXT)
RETURNS BOOLEAN
LANGUAGE sql
STABLE SECURITY DEFINER
AS $$
SELECT EXISTS (
SELECT 1
FROM public.profiles p
JOIN public.privilege_interface_resources pir ON p.privilege_id = pir.privilege_id
WHERE p.id = _user_id
AND pir.resource_key = _resource_key
) OR public.has_role(_user_id, 'admin');
$$;

View File

@@ -0,0 +1,40 @@
-- Criar tabela para configurações de tema
CREATE TABLE public.theme_config (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
light_theme jsonb NOT NULL DEFAULT '{}',
dark_theme jsonb NOT NULL DEFAULT '{}',
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now()
);
-- Habilitar RLS
ALTER TABLE public.theme_config ENABLE ROW LEVEL SECURITY;
-- Política para permitir que apenas admins possam gerenciar temas
CREATE POLICY "Only admins can manage themes"
ON public.theme_config
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Inserir registro inicial com tema padrão
INSERT INTO public.theme_config (id, light_theme, dark_theme)
VALUES (
'00000000-0000-0000-0000-000000000001',
'{"background": "0 0% 100%", "foreground": "222.2 84% 4.9%", "primary": "222.2 47.4% 11.2%", "primary-foreground": "210 40% 98%", "secondary": "210 40% 96.1%", "secondary-foreground": "222.2 47.4% 11.2%", "accent": "210 40% 96.1%", "accent-foreground": "222.2 47.4% 11.2%", "card": "0 0% 100%", "card-foreground": "222.2 84% 4.9%", "border": "214.3 31.8% 91.4%", "input": "214.3 31.8% 91.4%", "ring": "222.2 84% 4.9%"}',
'{"background": "222.2 84% 4.9%", "foreground": "210 40% 98%", "primary": "210 40% 98%", "primary-foreground": "222.2 47.4% 11.2%", "secondary": "217.2 32.6% 17.5%", "secondary-foreground": "210 40% 98%", "accent": "217.2 32.6% 17.5%", "accent-foreground": "210 40% 98%", "card": "222.2 84% 4.9%", "card-foreground": "210 40% 98%", "border": "217.2 32.6% 17.5%", "input": "217.2 32.6% 17.5%", "ring": "212.7 26.8% 83.9%"}'
);
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_theme_config_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_theme_config_updated_at
BEFORE UPDATE ON public.theme_config
FOR EACH ROW
EXECUTE FUNCTION update_theme_config_updated_at();

View File

@@ -0,0 +1,95 @@
-- Adicionar campos de data início e término à tabela de fichas técnicas
ALTER TABLE ficha_tecnica_contratos
ADD COLUMN data_inicio date,
ADD COLUMN data_termino_prev date;
-- Atualizar os dados existentes para migrar data_criacao para data_inicio
UPDATE ficha_tecnica_contratos
SET data_inicio = data_criacao
WHERE data_inicio IS NULL;
-- Criar tabela para ordens de fabricação
CREATE TABLE public.ordens_fabricacao (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
num_of text NOT NULL,
descritivo text,
data_abertura date,
data_prazo date,
peso_total numeric,
criterio_qualidade text,
tratamento_final text,
local_uf text,
status text DEFAULT 'ativa',
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
user_id uuid,
ficha_tecnica_id uuid REFERENCES ficha_tecnica_contratos(id)
);
-- Criar tabela para OFs arquivadas/concluídas
CREATE TABLE public.ofs_concluidas (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
num_of text NOT NULL,
descritivo text,
data_abertura date,
data_prazo date,
peso_total numeric,
criterio_qualidade text,
tratamento_final text,
local_uf text,
data_arquivamento timestamp with time zone DEFAULT now(),
arquivado_por uuid,
ficha_tecnica_data jsonb, -- Dados da ficha técnica arquivados
created_at timestamp with time zone NOT NULL DEFAULT now(),
user_id uuid
);
-- Adicionar RLS às novas tabelas
ALTER TABLE public.ordens_fabricacao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ofs_concluidas ENABLE ROW LEVEL SECURITY;
-- Políticas para ordens_fabricacao (usuários autenticados podem ver todas)
CREATE POLICY "Authenticated users can view ordens_fabricacao"
ON public.ordens_fabricacao
FOR SELECT
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can insert ordens_fabricacao"
ON public.ordens_fabricacao
FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can update ordens_fabricacao"
ON public.ordens_fabricacao
FOR UPDATE
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can delete ordens_fabricacao"
ON public.ordens_fabricacao
FOR DELETE
USING (auth.role() = 'authenticated');
-- Políticas para ofs_concluidas (usuários autenticados podem ver todas)
CREATE POLICY "Authenticated users can view ofs_concluidas"
ON public.ofs_concluidas
FOR SELECT
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can insert ofs_concluidas"
ON public.ofs_concluidas
FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_ordens_fabricacao_updated_at
BEFORE UPDATE ON ordens_fabricacao
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

View File

@@ -0,0 +1,9 @@
-- Add gestor_id and projetista_id columns to ficha_tecnica_contratos table
ALTER TABLE public.ficha_tecnica_contratos
ADD COLUMN gestor_id uuid REFERENCES auth.users(id),
ADD COLUMN projetista_id uuid REFERENCES auth.users(id);
-- Create indexes for better performance
CREATE INDEX idx_ficha_tecnica_gestor_id ON public.ficha_tecnica_contratos(gestor_id);
CREATE INDEX idx_ficha_tecnica_projetista_id ON public.ficha_tecnica_contratos(projetista_id);

View File

@@ -0,0 +1,59 @@
-- Criar tabela para cadastro de peças
CREATE TABLE public.pecas (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
of_number TEXT NOT NULL,
etapa_fase TEXT,
marca TEXT NOT NULL,
descricao TEXT,
quantidade INTEGER DEFAULT 0,
peso_unitario NUMERIC(10,3) DEFAULT 0,
peso_total NUMERIC(10,3) DEFAULT 0,
tratamento_superficial TEXT,
material TEXT,
perfil_principal TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
user_id UUID REFERENCES auth.users
);
-- Habilitar RLS (Row Level Security)
ALTER TABLE public.pecas ENABLE ROW LEVEL SECURITY;
-- Política para permitir que usuários vejam todas as peças
CREATE POLICY "Users can view all pecas"
ON public.pecas
FOR SELECT
USING (true);
-- Política para permitir que usuários criem peças
CREATE POLICY "Users can create pecas"
ON public.pecas
FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Política para permitir que usuários atualizem suas próprias peças
CREATE POLICY "Users can update their own pecas"
ON public.pecas
FOR UPDATE
USING (auth.uid() = user_id);
-- Política para permitir que usuários deletem suas próprias peças
CREATE POLICY "Users can delete their own pecas"
ON public.pecas
FOR DELETE
USING (auth.uid() = user_id);
-- Trigger para atualizar updated_at automaticamente
CREATE OR REPLACE FUNCTION update_pecas_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_pecas_updated_at_trigger
BEFORE UPDATE ON public.pecas
FOR EACH ROW
EXECUTE FUNCTION update_pecas_updated_at();

View File

@@ -0,0 +1,52 @@
-- Criar a tabela sugestoes
CREATE TABLE public.sugestoes (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
sugestao TEXT NOT NULL,
user_id UUID REFERENCES auth.users(id) NOT NULL,
user_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'Pendente' CHECK (status IN ('Pendente', 'Implementada', 'Rejeitada')),
developer_notes TEXT
);
-- Habilitar Row Level Security
ALTER TABLE public.sugestoes ENABLE ROW LEVEL SECURITY;
-- Política para INSERT - qualquer usuário autenticado pode inserir
CREATE POLICY "Usuários autenticados podem inserir sugestões"
ON public.sugestoes
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Política para SELECT - qualquer usuário autenticado pode ler todas as sugestões
CREATE POLICY "Usuários autenticados podem ver todas as sugestões"
ON public.sugestoes
FOR SELECT
TO authenticated
USING (true);
-- Política para UPDATE - apenas admins podem atualizar status e developer_notes
CREATE POLICY "Apenas admins podem atualizar sugestões"
ON public.sugestoes
FOR UPDATE
TO authenticated
USING (public.has_role(auth.uid(), 'admin'))
WITH CHECK (public.has_role(auth.uid(), 'admin'));
-- Criar Edge Function para obter informações do banco de dados
CREATE OR REPLACE FUNCTION public.get_database_info()
RETURNS TABLE (
database_size TEXT,
table_count BIGINT
)
LANGUAGE SQL
SECURITY DEFINER
AS $$
SELECT
pg_size_pretty(pg_database_size(current_database())) as database_size,
COUNT(*) as table_count
FROM information_schema.tables
WHERE table_schema = 'public';
$$;

View File

@@ -0,0 +1,51 @@
-- Criar tabela para armazenar os catálogos
CREATE TABLE public.catalogos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titulo TEXT NOT NULL,
categoria TEXT NOT NULL,
disciplina TEXT NOT NULL,
palavras_chave TEXT[],
conteudo TEXT NOT NULL,
numero_paginas INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Habilitar RLS
ALTER TABLE public.catalogos ENABLE ROW LEVEL SECURITY;
-- Política para leitura (todos os usuários autenticados podem ler)
CREATE POLICY "Todos podem ver catalogos"
ON public.catalogos
FOR SELECT
TO authenticated
USING (true);
-- Política para inserção (apenas admins)
CREATE POLICY "Admins podem criar catalogos"
ON public.catalogos
FOR INSERT
TO authenticated
WITH CHECK (public.has_role(auth.uid(), 'admin'));
-- Política para atualização (apenas admins)
CREATE POLICY "Admins podem atualizar catalogos"
ON public.catalogos
FOR UPDATE
TO authenticated
USING (public.has_role(auth.uid(), 'admin'));
-- Política para exclusão (apenas admins)
CREATE POLICY "Admins podem excluir catalogos"
ON public.catalogos
FOR DELETE
TO authenticated
USING (public.has_role(auth.uid(), 'admin'));
-- Trigger para atualizar updated_at
CREATE TRIGGER update_catalogos_updated_at
BEFORE UPDATE ON public.catalogos
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();

View File

@@ -0,0 +1,70 @@
-- Adicionar coluna para arquivamento na tabela sugestoes
ALTER TABLE public.sugestoes
ADD COLUMN archived_at TIMESTAMP WITH TIME ZONE,
ADD COLUMN archived_by UUID REFERENCES auth.users(id);
-- Criar tabela para notificações de sugestões
CREATE TABLE public.sugestao_notifications (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) NOT NULL,
sugestao_id UUID REFERENCES public.sugestoes(id) NOT NULL,
message TEXT NOT NULL,
is_read BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Habilitar RLS para notificações
ALTER TABLE public.sugestao_notifications ENABLE ROW LEVEL SECURITY;
-- Política para que usuários vejam apenas suas próprias notificações
CREATE POLICY "Usuários podem ver suas próprias notificações"
ON public.sugestao_notifications
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Política para que usuários possam marcar suas notificações como lidas
CREATE POLICY "Usuários podem atualizar suas próprias notificações"
ON public.sugestao_notifications
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id);
-- Função para criar notificação quando sugestão é implementada/rejeitada
CREATE OR REPLACE FUNCTION public.create_sugestao_notification()
RETURNS TRIGGER AS $$
BEGIN
-- Verificar se o status mudou para 'Implementada' ou 'Rejeitada'
IF NEW.status IN ('Implementada', 'Rejeitada') AND OLD.status = 'Pendente' THEN
INSERT INTO public.sugestao_notifications (user_id, sugestao_id, message)
VALUES (
NEW.user_id,
NEW.id,
CASE
WHEN NEW.status = 'Implementada' THEN 'Sua sugestão foi implementada!'
WHEN NEW.status = 'Rejeitada' THEN 'Sua sugestão foi rejeitada.'
END
);
-- Arquivar automaticamente a sugestão
NEW.archived_at = now();
NEW.archived_by = auth.uid();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Criar trigger para notificações
CREATE TRIGGER sugestao_status_notification
BEFORE UPDATE ON public.sugestoes
FOR EACH ROW
EXECUTE FUNCTION public.create_sugestao_notification();
-- Política para inserir notificações (via trigger)
CREATE POLICY "Sistema pode criar notificações"
ON public.sugestao_notifications
FOR INSERT
TO authenticated
WITH CHECK (true);

View File

@@ -0,0 +1,73 @@
-- Inserir todos os recursos de interface atuais da sidebar
INSERT INTO public.interface_resources (resource_key, resource_name, parent_key, icon_name, route_path, is_submenu, order_index) VALUES
-- Menu principal
('dashboard', 'Dashboard', NULL, 'BarChart3', '/dashboard', false, 1),
('cadastro', 'Cadastro', NULL, 'FileText', NULL, false, 2),
('ferramentas', 'Ferramentas', NULL, 'Wrench', NULL, false, 3),
('estoque', 'Estoque', NULL, 'Warehouse', '/estoque', false, 4),
('ofs', 'OFs', NULL, 'FolderOpen', NULL, false, 5),
('producao', 'Produção', NULL, 'Building2', NULL, false, 6),
('expedicao', 'Expedição', NULL, 'Truck', '/expedicao', false, 7),
('obra', 'Obra', NULL, 'HardHat', NULL, false, 8),
('tarefas', 'Tarefas', NULL, 'CheckSquare', NULL, false, 9),
('biblioteca', 'Biblioteca', NULL, 'Book', NULL, false, 10),
('sistema', 'Sistema', NULL, 'Clipboard', '/sistema', false, 11),
('sugestoes', 'Sugestões', NULL, 'MessageSquare', '/sugestoes', false, 12),
('configuracoes', 'Configurações', NULL, 'Settings', NULL, false, 13),
-- Submenus do Cadastro
('cadastro-of', 'Ficha Técnica da OF', 'cadastro', NULL, '/cadastro-of', true, 1),
('cadastro-pecas', 'Cadastro de Peças', 'cadastro', NULL, '/cadastro-pecas', true, 2),
-- Submenus das Ferramentas
('ferramentas-conversores', 'Conversores de dados', 'ferramentas', NULL, '/ferramentas/conversores', true, 1),
-- Submenus das OFs
('ofs-lista', 'Ordens de Fabricação', 'ofs', NULL, '/ofs', true, 1),
('ofs-cronograma', 'Cronograma', 'ofs', NULL, '/ofs/cronograma', true, 2),
('ofs-concluidas', 'OFs Concluídas', 'ofs', NULL, '/cadastro/ofs-concluidas', true, 3),
-- Submenus da Produção
('producao-visao', 'Visão Geral', 'producao', NULL, '/producao', true, 1),
('producao-apontamento', 'Apontamento de Produção', 'producao', NULL, '/apontamento-producao', true, 2),
('producao-dashboard', 'Dashboard de Produção', 'producao', NULL, '/dashboard-producao', true, 3),
-- Submenus da Obra
('obra-dashboard', 'Dashboard de Obras', 'obra', NULL, '/obra', true, 1),
('obra-configuracoes', 'Configurações da Obra', 'obra', NULL, '/obra/configuracoes', true, 2),
-- Submenus das Tarefas
('tarefas-lista', 'Tarefas', 'tarefas', NULL, '/tarefas', true, 1),
('tarefas-historico', 'Histórico', 'tarefas', NULL, '/tarefas/historico', true, 2),
-- Submenus da Biblioteca
('biblioteca-catalogos', 'Catálogos', 'biblioteca', NULL, '/biblioteca/catalogos', true, 1),
('biblioteca-normas', 'Normas', 'biblioteca', NULL, '/biblioteca/normas', true, 2),
('biblioteca-referencias', 'Referências', 'biblioteca', NULL, '/biblioteca/referencias', true, 3),
-- Submenus das Configurações
('configuracoes-gerais', 'Configurações Gerais', 'configuracoes', NULL, '/configuracoes', true, 1),
('theme-customization', 'Personalização de Tema', 'configuracoes', NULL, '/admin/theme-customization', true, 2),
-- Menus de Admin
('admin', 'Admin', NULL, 'Shield', '/admin', false, 14),
('user-management', 'Gerenciar Usuários', NULL, 'UserCog', '/user-management', false, 15)
ON CONFLICT (resource_key) DO UPDATE SET
resource_name = EXCLUDED.resource_name,
parent_key = EXCLUDED.parent_key,
icon_name = EXCLUDED.icon_name,
route_path = EXCLUDED.route_path,
is_submenu = EXCLUDED.is_submenu,
order_index = EXCLUDED.order_index,
updated_at = now();
-- Atualizar recursos existentes para atribuí-los aos grupos padrão
UPDATE public.interface_resources
SET group_id = (SELECT id FROM public.menu_groups WHERE name = 'Principal' LIMIT 1)
WHERE resource_key NOT IN ('admin', 'user-management') AND group_id IS NULL;
UPDATE public.interface_resources
SET group_id = (SELECT id FROM public.menu_groups WHERE name = 'Administração' LIMIT 1)
WHERE resource_key IN ('admin', 'user-management') AND group_id IS NULL;

View File

@@ -0,0 +1,47 @@
-- Criar bucket para documentos dos catálogos
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'catalogo-documents',
'catalogo-documents',
true,
10485760, -- 10MB limit
ARRAY['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/tiff', 'image/bmp']
);
-- Política para permitir que usuários façam upload de documentos
CREATE POLICY "Users can upload catalog documents"
ON storage.objects
FOR INSERT
WITH CHECK (
bucket_id = 'catalogo-documents'
AND auth.uid() IS NOT NULL
);
-- Política para permitir que usuários atualizem seus próprios documentos
CREATE POLICY "Users can update catalog documents"
ON storage.objects
FOR UPDATE
USING (
bucket_id = 'catalogo-documents'
AND auth.uid() IS NOT NULL
);
-- Política para permitir que usuários deletem seus próprios documentos
CREATE POLICY "Users can delete catalog documents"
ON storage.objects
FOR DELETE
USING (
bucket_id = 'catalogo-documents'
AND auth.uid() IS NOT NULL
);
-- Política para permitir que todos vejam os documentos dos catálogos (públicos)
CREATE POLICY "Catalog documents are publicly viewable"
ON storage.objects
FOR SELECT
USING (bucket_id = 'catalogo-documents');
-- Adicionar coluna para armazenar URLs dos arquivos na tabela catalogos
ALTER TABLE public.catalogos
ADD COLUMN IF NOT EXISTS arquivo_urls TEXT[];

View File

@@ -0,0 +1,79 @@
-- Criar tabela para cronogramas das OFs
CREATE TABLE public.cronogramas_of (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
of_id uuid REFERENCES public.ordens_fabricacao(id) ON DELETE CASCADE,
gestor_id uuid REFERENCES public.profiles(id),
revisao integer DEFAULT 1,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid REFERENCES public.profiles(id),
UNIQUE(of_id) -- Uma OF pode ter apenas um cronograma ativo
);
-- Criar tabela para processos do cronograma
CREATE TABLE public.processos_cronograma (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
cronograma_id uuid REFERENCES public.cronogramas_of(id) ON DELETE CASCADE,
nome_processo text NOT NULL,
data_inicio date NOT NULL,
data_fim date NOT NULL,
ordem integer NOT NULL DEFAULT 0,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now()
);
-- Adicionar RLS às novas tabelas
ALTER TABLE public.cronogramas_of ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.processos_cronograma ENABLE ROW LEVEL SECURITY;
-- Políticas para cronogramas_of
CREATE POLICY "Authenticated users can view cronogramas_of"
ON public.cronogramas_of
FOR SELECT
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can insert cronogramas_of"
ON public.cronogramas_of
FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can update cronogramas_of"
ON public.cronogramas_of
FOR UPDATE
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can delete cronogramas_of"
ON public.cronogramas_of
FOR DELETE
USING (auth.role() = 'authenticated');
-- Políticas para processos_cronograma
CREATE POLICY "Authenticated users can view processos_cronograma"
ON public.processos_cronograma
FOR SELECT
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can insert processos_cronograma"
ON public.processos_cronograma
FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can update processos_cronograma"
ON public.processos_cronograma
FOR UPDATE
USING (auth.role() = 'authenticated');
CREATE POLICY "Authenticated users can delete processos_cronograma"
ON public.processos_cronograma
FOR DELETE
USING (auth.role() = 'authenticated');
-- Trigger para atualizar updated_at
CREATE TRIGGER update_cronogramas_of_updated_at
BEFORE UPDATE ON cronogramas_of
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_processos_cronograma_updated_at
BEFORE UPDATE ON processos_cronograma
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

View File

@@ -0,0 +1,54 @@
-- Criar tabela para componentes das peças
CREATE TABLE public.componentes_peca (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
peca_id UUID NOT NULL REFERENCES public.pecas(id) ON DELETE CASCADE,
marca_componente TEXT NOT NULL,
descricao TEXT,
perfil TEXT,
peso_unitario NUMERIC(10,3) DEFAULT 0,
quantidade_por_peca INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
user_id UUID REFERENCES auth.users,
-- Constraint para garantir que marcas de componentes sejam numéricas entre 1000-9999
CONSTRAINT check_marca_componente_range CHECK (
marca_componente ~ '^\d+$' AND
CAST(marca_componente AS INTEGER) BETWEEN 1000 AND 9999
)
);
-- Habilitar RLS
ALTER TABLE public.componentes_peca ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para componentes
CREATE POLICY "Users can view all componentes"
ON public.componentes_peca
FOR SELECT
USING (true);
CREATE POLICY "Users can create componentes"
ON public.componentes_peca
FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own componentes"
ON public.componentes_peca
FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete their own componentes"
ON public.componentes_peca
FOR DELETE
USING (auth.uid() = user_id);
-- Trigger para atualizar updated_at
CREATE TRIGGER update_componentes_peca_updated_at_trigger
BEFORE UPDATE ON public.componentes_peca
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Índices para melhor performance
CREATE INDEX idx_componentes_peca_peca_id ON public.componentes_peca(peca_id);
CREATE INDEX idx_componentes_peca_marca ON public.componentes_peca(marca_componente);

View File

@@ -0,0 +1,6 @@
-- Adicionar campo para indicar se a peça tem componentes
ALTER TABLE public.pecas ADD COLUMN tem_componentes BOOLEAN DEFAULT FALSE;
-- Criar índice para melhor performance nas consultas
CREATE INDEX idx_pecas_tem_componentes ON public.pecas(tem_componentes);

View File

@@ -0,0 +1,182 @@
-- Criar tabela para processos de fabricação
CREATE TABLE public.processos_fabricacao (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
nome TEXT NOT NULL,
descricao TEXT,
ordem INTEGER DEFAULT 0,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Criar tabela para apontamentos de produção
CREATE TABLE public.apontamentos_producao (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
of_number TEXT NOT NULL,
peca_id UUID REFERENCES public.pecas(id),
processo_id UUID NOT NULL REFERENCES public.processos_fabricacao(id),
quantidade_produzida NUMERIC NOT NULL DEFAULT 0,
data_apontamento DATE NOT NULL DEFAULT CURRENT_DATE,
observacoes TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by UUID REFERENCES auth.users(id),
CONSTRAINT quantidade_positiva CHECK (quantidade_produzida > 0)
);
-- Criar tabela para controle de datas reais dos processos por peça
CREATE TABLE public.processos_pecas_datas (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
peca_id UUID NOT NULL REFERENCES public.pecas(id),
processo_id UUID NOT NULL REFERENCES public.processos_fabricacao(id),
data_inicio_real DATE,
data_conclusao_real DATE,
quantidade_total_planejada NUMERIC NOT NULL DEFAULT 0,
quantidade_total_produzida NUMERIC NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
UNIQUE(peca_id, processo_id)
);
-- Criar índices para performance
CREATE INDEX idx_apontamentos_of_number ON public.apontamentos_producao(of_number);
CREATE INDEX idx_apontamentos_peca_processo ON public.apontamentos_producao(peca_id, processo_id);
CREATE INDEX idx_apontamentos_data ON public.apontamentos_producao(data_apontamento);
CREATE INDEX idx_processos_pecas_datas_peca ON public.processos_pecas_datas(peca_id);
-- Habilitar RLS nas tabelas
ALTER TABLE public.processos_fabricacao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.apontamentos_producao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.processos_pecas_datas ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para processos_fabricacao (todos podem ver, apenas usuários autenticados podem modificar)
CREATE POLICY "Todos podem visualizar processos" ON public.processos_fabricacao
FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir processos" ON public.processos_fabricacao
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar processos" ON public.processos_fabricacao
FOR UPDATE USING (auth.uid() IS NOT NULL);
-- Políticas RLS para apontamentos_producao
CREATE POLICY "Usuários podem visualizar apontamentos" ON public.apontamentos_producao
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários podem inserir apontamentos" ON public.apontamentos_producao
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários podem atualizar seus apontamentos" ON public.apontamentos_producao
FOR UPDATE USING (auth.uid() = created_by);
-- Políticas RLS para processos_pecas_datas
CREATE POLICY "Usuários podem visualizar processos_pecas_datas" ON public.processos_pecas_datas
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários podem inserir processos_pecas_datas" ON public.processos_pecas_datas
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários podem atualizar processos_pecas_datas" ON public.processos_pecas_datas
FOR UPDATE USING (auth.uid() IS NOT NULL);
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_processos_fabricacao()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_processos_fabricacao_updated_at
BEFORE UPDATE ON public.processos_fabricacao
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_processos_fabricacao();
CREATE TRIGGER trigger_update_apontamentos_producao_updated_at
BEFORE UPDATE ON public.apontamentos_producao
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER trigger_update_processos_pecas_datas_updated_at
BEFORE UPDATE ON public.processos_pecas_datas
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Função para atualizar datas reais após inserção de apontamento
CREATE OR REPLACE FUNCTION atualizar_datas_reais_processo()
RETURNS TRIGGER AS $$
DECLARE
total_produzido NUMERIC;
total_planejado NUMERIC;
registro_existente BOOLEAN;
BEGIN
-- Verificar se já existe registro na tabela processos_pecas_datas
SELECT EXISTS(
SELECT 1 FROM public.processos_pecas_datas
WHERE peca_id = NEW.peca_id AND processo_id = NEW.processo_id
) INTO registro_existente;
-- Se não existe, criar registro inicial
IF NOT registro_existente THEN
-- Buscar quantidade total planejada da peça
SELECT COALESCE(quantidade, 0) INTO total_planejado
FROM public.pecas
WHERE id = NEW.peca_id;
INSERT INTO public.processos_pecas_datas (
peca_id,
processo_id,
data_inicio_real,
quantidade_total_planejada,
quantidade_total_produzida
) VALUES (
NEW.peca_id,
NEW.processo_id,
NEW.data_apontamento,
total_planejado,
NEW.quantidade_produzida
);
ELSE
-- Calcular total produzido para esta peça+processo
SELECT COALESCE(SUM(quantidade_produzida), 0) INTO total_produzido
FROM public.apontamentos_producao
WHERE peca_id = NEW.peca_id AND processo_id = NEW.processo_id;
-- Buscar quantidade total planejada
SELECT quantidade_total_planejada INTO total_planejado
FROM public.processos_pecas_datas
WHERE peca_id = NEW.peca_id AND processo_id = NEW.processo_id;
-- Atualizar registro existente
UPDATE public.processos_pecas_datas
SET
quantidade_total_produzida = total_produzido,
data_conclusao_real = CASE
WHEN total_produzido >= total_planejado THEN NEW.data_apontamento
ELSE data_conclusao_real
END,
updated_at = now()
WHERE peca_id = NEW.peca_id AND processo_id = NEW.processo_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para atualizar datas reais
CREATE TRIGGER trigger_atualizar_datas_reais
AFTER INSERT ON public.apontamentos_producao
FOR EACH ROW
EXECUTE FUNCTION atualizar_datas_reais_processo();
-- Inserir alguns processos padrão
INSERT INTO public.processos_fabricacao (nome, descricao, ordem) VALUES
('Corte', 'Processo de corte de materiais', 1),
('Solda', 'Processo de soldagem', 2),
('Usinagem', 'Processo de usinagem e acabamento', 3),
('Pintura', 'Processo de pintura e acabamento', 4),
('Montagem', 'Processo de montagem final', 5),
('Inspeção', 'Inspeção de qualidade', 6);

View File

@@ -0,0 +1,7 @@
-- Adicionar colunas que estão faltando na tabela ficha_tecnica_contratos
ALTER TABLE public.ficha_tecnica_contratos
ADD COLUMN IF NOT EXISTS bairro_obra TEXT,
ADD COLUMN IF NOT EXISTS email_obra_responsavel TEXT,
ADD COLUMN IF NOT EXISTS telefone_obra TEXT,
ADD COLUMN IF NOT EXISTS projetista TEXT;

View File

@@ -0,0 +1,19 @@
-- Adicionar todas as colunas que estão faltando na tabela ficha_tecnica_contratos
ALTER TABLE public.ficha_tecnica_contratos
ADD COLUMN IF NOT EXISTS eng_responsavel TEXT,
ADD COLUMN IF NOT EXISTS endereco_obra TEXT,
ADD COLUMN IF NOT EXISTS cep_obra TEXT,
ADD COLUMN IF NOT EXISTS cidade_obra TEXT,
ADD COLUMN IF NOT EXISTS estado_obra TEXT,
ADD COLUMN IF NOT EXISTS observacoes_obra TEXT;
-- Verificar se existe a coluna data_criacao, se não, adicionar
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'ficha_tecnica_contratos'
AND column_name = 'data_criacao') THEN
ALTER TABLE public.ficha_tecnica_contratos ADD COLUMN data_criacao DATE DEFAULT CURRENT_DATE;
END IF;
END $$;

View File

@@ -0,0 +1,21 @@
-- Alterar campos de peso na tabela pecas para aceitar decimais com precisão adequada
ALTER TABLE public.pecas
ALTER COLUMN peso_unitario TYPE NUMERIC(10,3),
ALTER COLUMN peso_total TYPE NUMERIC(10,3);
-- Permitir valores nulos nos campos de texto opcionais da tabela pecas
ALTER TABLE public.pecas
ALTER COLUMN tratamento_superficial DROP NOT NULL,
ALTER COLUMN material DROP NOT NULL,
ALTER COLUMN perfil_principal DROP NOT NULL,
ALTER COLUMN descricao DROP NOT NULL;
-- Permitir valores nulos nos campos de texto opcionais da tabela componentes_peca
ALTER TABLE public.componentes_peca
ALTER COLUMN descricao DROP NOT NULL,
ALTER COLUMN perfil DROP NOT NULL;
-- Alterar campo peso_unitario na tabela componentes_peca para aceitar decimais
ALTER TABLE public.componentes_peca
ALTER COLUMN peso_unitario TYPE NUMERIC(10,3);

View File

@@ -0,0 +1,103 @@
-- Criar função RPC para calcular o range dinâmico de datas do dashboard
CREATE OR REPLACE FUNCTION get_dashboard_date_range(of_number_param text)
RETURNS TABLE(
data_inicio_grafico date,
data_fim_grafico date
)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
min_data_inicio date;
max_data_fim date;
max_apontamento date;
data_atual date := CURRENT_DATE;
BEGIN
-- Buscar data de início e fim da OF
SELECT
COALESCE(data_abertura, CURRENT_DATE),
COALESCE(data_prazo, CURRENT_DATE + INTERVAL '30 days')
INTO min_data_inicio, max_data_fim
FROM ordens_fabricacao
WHERE num_of = of_number_param;
-- Buscar data máxima de apontamento para esta OF
SELECT MAX(data_apontamento)
INTO max_apontamento
FROM apontamentos_producao
WHERE of_number = of_number_param;
-- Calcular data final do gráfico
data_fim_grafico := GREATEST(
COALESCE(max_data_fim, data_atual),
COALESCE(max_apontamento, data_atual),
data_atual
);
-- Retornar o range
data_inicio_grafico := min_data_inicio;
RETURN NEXT;
END;
$$;
-- Criar função para buscar dados consolidados de todos os processos
CREATE OR REPLACE FUNCTION get_dashboard_consolidated_data(of_number_param text)
RETURNS TABLE(
processo_id uuid,
processo_nome text,
processo_cor text,
data_apontamento date,
peso_acumulado numeric
)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
WITH apontamentos_ordenados AS (
SELECT
ap.processo_id,
pf.nome as processo_nome,
COALESCE(pf.cor, '#8884d8') as processo_cor,
ap.data_apontamento,
ap.quantidade_produzida * COALESCE(p.peso_unitario, 0) as peso_produzido,
ROW_NUMBER() OVER (PARTITION BY ap.processo_id ORDER BY ap.data_apontamento) as rn
FROM apontamentos_producao ap
JOIN processos_fabricacao pf ON ap.processo_id = pf.id
LEFT JOIN pecas p ON ap.peca_id = p.id
WHERE ap.of_number = of_number_param
ORDER BY ap.processo_id, ap.data_apontamento
),
peso_acumulado_por_processo AS (
SELECT
a1.processo_id,
a1.processo_nome,
a1.processo_cor,
a1.data_apontamento,
SUM(a2.peso_produzido) as peso_acumulado
FROM apontamentos_ordenados a1
JOIN apontamentos_ordenados a2 ON a1.processo_id = a2.processo_id AND a2.rn <= a1.rn
GROUP BY a1.processo_id, a1.processo_nome, a1.processo_cor, a1.data_apontamento, a1.rn
ORDER BY a1.processo_id, a1.data_apontamento
)
SELECT * FROM peso_acumulado_por_processo;
END;
$$;
-- Adicionar coluna de cor na tabela processos_fabricacao se não existir
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'processos_fabricacao' AND column_name = 'cor') THEN
ALTER TABLE processos_fabricacao ADD COLUMN cor text DEFAULT '#8884d8';
END IF;
END $$;
-- Atualizar alguns processos com cores diferentes para melhor visualização
UPDATE processos_fabricacao SET cor = '#8884d8' WHERE nome ILIKE '%corte%';
UPDATE processos_fabricacao SET cor = '#82ca9d' WHERE nome ILIKE '%solda%';
UPDATE processos_fabricacao SET cor = '#ffc658' WHERE nome ILIKE '%pintura%';
UPDATE processos_fabricacao SET cor = '#ff7300' WHERE nome ILIKE '%montagem%';
UPDATE processos_fabricacao SET cor = '#00ff88' WHERE nome ILIKE '%expedi%';

View File

@@ -0,0 +1,30 @@
-- Criar tabela para armazenar chaves API
CREATE TABLE public.api_keys (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
key TEXT NOT NULL,
is_primary BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar Row Level Security
ALTER TABLE public.api_keys ENABLE ROW LEVEL SECURITY;
-- Criar políticas RLS - apenas administradores podem gerenciar chaves API
CREATE POLICY "Only admins can manage API keys"
ON public.api_keys
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Criar trigger para atualizar updated_at automaticamente
CREATE TRIGGER update_api_keys_updated_at
BEFORE UPDATE ON public.api_keys
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Garantir que apenas uma chave seja primary por vez
CREATE UNIQUE INDEX idx_api_keys_single_primary
ON public.api_keys (is_primary)
WHERE is_primary = true;

View File

@@ -0,0 +1,31 @@
-- Criar tabela para armazenar códigos JSON
CREATE TABLE public.json_codes (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
json_code JSONB NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Habilitar Row Level Security
ALTER TABLE public.json_codes ENABLE ROW LEVEL SECURITY;
-- Criar políticas RLS - apenas administradores podem gerenciar códigos JSON
CREATE POLICY "Only admins can manage JSON codes"
ON public.json_codes
FOR ALL
USING (public.has_role(auth.uid(), 'admin'));
-- Criar trigger para atualizar updated_at automaticamente
CREATE TRIGGER update_json_codes_updated_at
BEFORE UPDATE ON public.json_codes
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Criar índice para melhor performance nas consultas por nome
CREATE INDEX idx_json_codes_name ON public.json_codes(name);
CREATE INDEX idx_json_codes_active ON public.json_codes(is_active);

View File

@@ -0,0 +1,79 @@
-- Criar tabela para configurações de webhook dos conversores
CREATE TABLE public.webhook_configs (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
converter_name text NOT NULL,
link_envio text NOT NULL,
link_recebimento text NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid REFERENCES auth.users(id)
);
-- Criar tabela para rastrear processamentos de arquivos
CREATE TABLE public.file_processings (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
webhook_config_id uuid REFERENCES public.webhook_configs(id),
file_name text NOT NULL,
file_type text NOT NULL,
status text NOT NULL DEFAULT 'enviado',
sent_at timestamp with time zone DEFAULT now(),
completed_at timestamp with time zone,
download_url text,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now()
);
-- Adicionar RLS às tabelas
ALTER TABLE public.webhook_configs ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.file_processings ENABLE ROW LEVEL SECURITY;
-- Políticas para webhook_configs
CREATE POLICY "Users can view webhook configs" ON public.webhook_configs
FOR SELECT USING (true);
CREATE POLICY "Users can create webhook configs" ON public.webhook_configs
FOR INSERT WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update webhook configs" ON public.webhook_configs
FOR UPDATE USING (auth.uid() = created_by);
CREATE POLICY "Users can delete webhook configs" ON public.webhook_configs
FOR DELETE USING (auth.uid() = created_by);
-- Políticas para file_processings
CREATE POLICY "Users can view file processings" ON public.file_processings
FOR SELECT USING (true);
CREATE POLICY "Users can create file processings" ON public.file_processings
FOR INSERT WITH CHECK (true);
CREATE POLICY "Users can update file processings" ON public.file_processings
FOR UPDATE USING (true);
-- Trigger para atualizar updated_at automaticamente
CREATE OR REPLACE FUNCTION update_updated_at_webhook_configs()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_webhook_configs_updated_at
BEFORE UPDATE ON public.webhook_configs
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_webhook_configs();
CREATE OR REPLACE FUNCTION update_updated_at_file_processings()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_file_processings_updated_at
BEFORE UPDATE ON public.file_processings
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_file_processings();

View File

@@ -0,0 +1,40 @@
-- Criar tabela para armazenar prompts de instrução
CREATE TABLE public.prompts (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL,
content text NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid REFERENCES auth.users(id) NOT NULL
);
-- Adicionar RLS à tabela
ALTER TABLE public.prompts ENABLE ROW LEVEL SECURITY;
-- Políticas para prompts
CREATE POLICY "Users can view their own prompts" ON public.prompts
FOR SELECT USING (auth.uid() = created_by);
CREATE POLICY "Users can create their own prompts" ON public.prompts
FOR INSERT WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Users can update their own prompts" ON public.prompts
FOR UPDATE USING (auth.uid() = created_by);
CREATE POLICY "Users can delete their own prompts" ON public.prompts
FOR DELETE USING (auth.uid() = created_by);
-- Trigger para atualizar updated_at automaticamente
CREATE OR REPLACE FUNCTION update_updated_at_prompts()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_prompts_updated_at
BEFORE UPDATE ON public.prompts
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_prompts();

View File

@@ -0,0 +1,17 @@
-- Adicionar coluna para identificar se o apontamento é de um componente
ALTER TABLE public.apontamentos_producao
ADD COLUMN componente_id uuid REFERENCES public.componentes_peca(id);
-- Adicionar coluna para identificar o tipo do apontamento
ALTER TABLE public.apontamentos_producao
ADD COLUMN tipo_apontamento text DEFAULT 'peca' CHECK(tipo_apontamento IN ('peca', 'componente'));
-- Criar índice para melhor performance
CREATE INDEX idx_apontamentos_componente_id ON public.apontamentos_producao(componente_id);
CREATE INDEX idx_apontamentos_tipo ON public.apontamentos_producao(tipo_apontamento);
-- Atualizar registros existentes para definir o tipo como 'peca'
UPDATE public.apontamentos_producao
SET tipo_apontamento = 'peca'
WHERE tipo_apontamento IS NULL;

View File

@@ -0,0 +1,45 @@
-- Atualizar a função que calcula dados consolidados para usar pesos corretamente
CREATE OR REPLACE FUNCTION public.get_dashboard_consolidated_data(of_number_param text)
RETURNS TABLE(processo_id uuid, processo_nome text, processo_cor text, data_apontamento date, peso_acumulado numeric)
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
RETURN QUERY
WITH apontamentos_com_peso AS (
SELECT
ap.processo_id,
pf.nome as processo_nome,
COALESCE(pf.cor, '#8884d8') as processo_cor,
ap.data_apontamento,
-- Calcular peso corretamente baseado no tipo de apontamento
CASE
WHEN ap.tipo_apontamento = 'componente' THEN
ap.quantidade_produzida * COALESCE(cp.peso_unitario, 0)
ELSE
ap.quantidade_produzida * COALESCE(p.peso_unitario, 0)
END as peso_produzido,
ROW_NUMBER() OVER (PARTITION BY ap.processo_id ORDER BY ap.data_apontamento) as rn
FROM apontamentos_producao ap
JOIN processos_fabricacao pf ON ap.processo_id = pf.id
LEFT JOIN pecas p ON ap.peca_id = p.id AND ap.tipo_apontamento = 'peca'
LEFT JOIN componentes_peca cp ON ap.componente_id = cp.id AND ap.tipo_apontamento = 'componente'
WHERE ap.of_number = of_number_param
ORDER BY ap.processo_id, ap.data_apontamento
),
peso_acumulado_por_processo AS (
SELECT
a1.processo_id,
a1.processo_nome,
a1.processo_cor,
a1.data_apontamento,
SUM(a2.peso_produzido) as peso_acumulado
FROM apontamentos_com_peso a1
JOIN apontamentos_com_peso a2 ON a1.processo_id = a2.processo_id AND a2.rn <= a1.rn
GROUP BY a1.processo_id, a1.processo_nome, a1.processo_cor, a1.data_apontamento, a1.rn
ORDER BY a1.processo_id, a1.data_apontamento
)
SELECT * FROM peso_acumulado_por_processo;
END;
$function$

View File

@@ -0,0 +1,182 @@
-- Criar tabela para tipos de matéria-prima
CREATE TABLE public.tipos_materia_prima (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome TEXT NOT NULL,
descricao TEXT,
caracteristicas JSONB DEFAULT '{}',
controles JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID,
ativo BOOLEAN DEFAULT true
);
-- Criar tabela principal de estoque
CREATE TABLE public.estoque_materiais (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
codigo TEXT NOT NULL UNIQUE,
descricao TEXT NOT NULL,
tipo_material_id UUID REFERENCES tipos_materia_prima(id),
unidade TEXT NOT NULL DEFAULT 'PC',
quantidade_total NUMERIC DEFAULT 0,
quantidade_disponivel NUMERIC DEFAULT 0,
quantidade_empenhada NUMERIC DEFAULT 0,
quantidade_minima NUMERIC DEFAULT 0,
quantidade_maxima NUMERIC,
peso_unitario NUMERIC DEFAULT 0,
valor_unitario NUMERIC DEFAULT 0,
lote_atual TEXT,
fornecedor TEXT,
localizacao TEXT,
status TEXT DEFAULT 'Normal' CHECK (status IN ('Normal', 'Crítico', 'Excesso')),
certificado TEXT,
observacoes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID
);
-- Criar tabela de movimentações de estoque
CREATE TABLE public.movimentacoes_estoque (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
material_id UUID REFERENCES estoque_materiais(id) NOT NULL,
tipo_movimentacao TEXT NOT NULL CHECK (tipo_movimentacao IN ('entrada', 'saida', 'transferencia', 'ajuste', 'empenho', 'desempenho')),
quantidade NUMERIC NOT NULL,
valor_unitario NUMERIC,
valor_total NUMERIC,
lote TEXT,
fornecedor TEXT,
nota_fiscal TEXT,
of_vinculada TEXT,
observacoes TEXT,
data_movimentacao DATE DEFAULT CURRENT_DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID
);
-- Criar tabela de empenhos de material
CREATE TABLE public.empenhos_material (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
of_number TEXT NOT NULL,
material_id UUID REFERENCES estoque_materiais(id) NOT NULL,
quantidade_empenhada NUMERIC NOT NULL,
quantidade_utilizada NUMERIC DEFAULT 0,
status TEXT DEFAULT 'Empenhado' CHECK (status IN ('Empenhado', 'Parcial', 'Finalizado')),
data_empenho DATE DEFAULT CURRENT_DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID,
UNIQUE(of_number, material_id)
);
-- Criar tabela de rastreabilidade
CREATE TABLE public.rastreabilidade_materiais (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
material_id UUID REFERENCES estoque_materiais(id) NOT NULL,
lote TEXT NOT NULL,
quantidade NUMERIC NOT NULL,
certificado TEXT,
fornecedor TEXT,
data_entrada DATE,
data_validade DATE,
status TEXT DEFAULT 'Ativo',
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID
);
-- Habilitar RLS
ALTER TABLE public.tipos_materia_prima ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.estoque_materiais ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.movimentacoes_estoque ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.empenhos_material ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.rastreabilidade_materiais ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para tipos_materia_prima
CREATE POLICY "Todos podem visualizar tipos MP" ON tipos_materia_prima FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir tipos MP" ON tipos_materia_prima FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar tipos MP" ON tipos_materia_prima FOR UPDATE USING (auth.uid() IS NOT NULL);
-- Políticas RLS para estoque_materiais
CREATE POLICY "Todos podem visualizar estoque" ON estoque_materiais FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir estoque" ON estoque_materiais FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar estoque" ON estoque_materiais FOR UPDATE USING (auth.uid() IS NOT NULL);
-- Políticas RLS para movimentacoes_estoque
CREATE POLICY "Todos podem visualizar movimentações" ON movimentacoes_estoque FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir movimentações" ON movimentacoes_estoque FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
-- Políticas RLS para empenhos_material
CREATE POLICY "Todos podem visualizar empenhos" ON empenhos_material FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir empenhos" ON empenhos_material FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar empenhos" ON empenhos_material FOR UPDATE USING (auth.uid() IS NOT NULL);
-- Políticas RLS para rastreabilidade_materiais
CREATE POLICY "Todos podem visualizar rastreabilidade" ON rastreabilidade_materiais FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir rastreabilidade" ON rastreabilidade_materiais FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
-- Criar triggers para updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_tipos_materia_prima_updated_at BEFORE UPDATE ON tipos_materia_prima FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
CREATE TRIGGER update_estoque_materiais_updated_at BEFORE UPDATE ON estoque_materiais FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
-- Criar função para atualizar status do estoque baseado nas quantidades
CREATE OR REPLACE FUNCTION atualizar_status_estoque()
RETURNS TRIGGER AS $$
BEGIN
-- Atualizar status baseado na quantidade disponível vs mínima
IF NEW.quantidade_disponivel <= NEW.quantidade_minima THEN
NEW.status = 'Crítico';
ELSIF NEW.quantidade_maxima IS NOT NULL AND NEW.quantidade_disponivel >= NEW.quantidade_maxima THEN
NEW.status = 'Excesso';
ELSE
NEW.status = 'Normal';
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER trigger_atualizar_status_estoque
BEFORE INSERT OR UPDATE ON estoque_materiais
FOR EACH ROW EXECUTE PROCEDURE atualizar_status_estoque();
-- Criar função para atualizar quantidades do estoque após movimentações
CREATE OR REPLACE FUNCTION processar_movimentacao_estoque()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER trigger_processar_movimentacao
AFTER INSERT ON movimentacoes_estoque
FOR EACH ROW EXECUTE PROCEDURE processar_movimentacao_estoque();

View File

@@ -0,0 +1,152 @@
-- Criar tabela para romaneios de expedição
CREATE TABLE public.romaneios_expedicao (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
numero_romaneio TEXT NOT NULL UNIQUE,
of_number TEXT NOT NULL,
data_romaneio DATE NOT NULL DEFAULT CURRENT_DATE,
prioridade INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'Em Preparação',
observacoes TEXT,
peso_total_romaneio NUMERIC(10,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Criar tabela para itens do romaneio (peças)
CREATE TABLE public.itens_romaneio_pecas (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
romaneio_id UUID NOT NULL REFERENCES public.romaneios_expedicao(id) ON DELETE CASCADE,
peca_id UUID NOT NULL REFERENCES public.pecas(id),
marca TEXT NOT NULL,
fase TEXT,
descricao TEXT,
comprimento NUMERIC(10,2),
peso_unitario NUMERIC(10,3) NOT NULL,
quantidade_expedida NUMERIC(10,2) NOT NULL,
peso_total NUMERIC(10,2) NOT NULL,
quantidade_faltante NUMERIC(10,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela para insumos do romaneio
CREATE TABLE public.itens_romaneio_insumos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
romaneio_id UUID NOT NULL REFERENCES public.romaneios_expedicao(id) ON DELETE CASCADE,
tipo_insumo TEXT NOT NULL, -- 'parafusos', 'tintas', 'eletrodos', 'cola', etc
descricao TEXT NOT NULL,
unidade TEXT NOT NULL DEFAULT 'PC',
quantidade_expedida NUMERIC(10,2) NOT NULL,
peso_unitario NUMERIC(10,3),
peso_total NUMERIC(10,2),
observacoes TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar índices para melhorar performance
CREATE INDEX idx_romaneios_of_number ON public.romaneios_expedicao(of_number);
CREATE INDEX idx_romaneios_data ON public.romaneios_expedicao(data_romaneio);
CREATE INDEX idx_itens_romaneio_pecas ON public.itens_romaneio_pecas(romaneio_id);
CREATE INDEX idx_itens_romaneio_insumos ON public.itens_romaneio_insumos(romaneio_id);
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_romaneios_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_romaneios_expedicao_updated_at
BEFORE UPDATE ON public.romaneios_expedicao
FOR EACH ROW EXECUTE FUNCTION update_romaneios_updated_at();
-- Políticas RLS
ALTER TABLE public.romaneios_expedicao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.itens_romaneio_pecas ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.itens_romaneio_insumos ENABLE ROW LEVEL SECURITY;
-- Políticas para romaneios_expedicao
CREATE POLICY "Usuários autenticados podem visualizar romaneios"
ON public.romaneios_expedicao FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem inserir romaneios"
ON public.romaneios_expedicao FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar romaneios"
ON public.romaneios_expedicao FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem deletar romaneios"
ON public.romaneios_expedicao FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Políticas para itens_romaneio_pecas
CREATE POLICY "Usuários autenticados podem visualizar itens peças"
ON public.itens_romaneio_pecas FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem inserir itens peças"
ON public.itens_romaneio_pecas FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar itens peças"
ON public.itens_romaneio_pecas FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem deletar itens peças"
ON public.itens_romaneio_pecas FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Políticas para itens_romaneio_insumos
CREATE POLICY "Usuários autenticados podem visualizar itens insumos"
ON public.itens_romaneio_insumos FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem inserir itens insumos"
ON public.itens_romaneio_insumos FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar itens insumos"
ON public.itens_romaneio_insumos FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem deletar itens insumos"
ON public.itens_romaneio_insumos FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Função para gerar número do romaneio automaticamente
CREATE OR REPLACE FUNCTION generate_romaneio_number()
RETURNS TEXT AS $$
DECLARE
next_num INTEGER;
new_romaneio_number TEXT;
BEGIN
SELECT COALESCE(MAX(CAST(SUBSTRING(numero_romaneio FROM 'ROM-(\d+)') AS INTEGER)), 0) + 1
INTO next_num
FROM public.romaneios_expedicao
WHERE numero_romaneio ~ '^ROM-\d+$';
new_romaneio_number := 'ROM-' || LPAD(next_num::TEXT, 4, '0');
RETURN new_romaneio_number;
END;
$$ LANGUAGE plpgsql;
-- Trigger para gerar número do romaneio automaticamente
CREATE OR REPLACE FUNCTION handle_romaneio_number()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.numero_romaneio IS NULL OR NEW.numero_romaneio = '' THEN
NEW.numero_romaneio := generate_romaneio_number();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER generate_romaneio_number_trigger
BEFORE INSERT ON public.romaneios_expedicao
FOR EACH ROW EXECUTE FUNCTION handle_romaneio_number();

View File

@@ -0,0 +1,32 @@
-- Adicionar novos campos à tabela romaneios_expedicao
ALTER TABLE public.romaneios_expedicao
ADD COLUMN data_criacao date DEFAULT CURRENT_DATE,
ADD COLUMN revisao integer DEFAULT 1,
ADD COLUMN motivo_revisao text,
ADD COLUMN data_prevista_entrega date,
ADD COLUMN previsao_kg numeric,
ADD COLUMN maior_dimensao text,
ADD COLUMN tipo_transporte text,
ADD COLUMN frete_tipo text,
ADD COLUMN nome_motorista text;
-- Alterar o tipo da coluna prioridade de integer para text
ALTER TABLE public.romaneios_expedicao
ALTER COLUMN prioridade TYPE text USING
CASE
WHEN prioridade = 1 THEN 'Normal'
WHEN prioridade = 2 THEN 'Urgente'
ELSE 'Normal'
END;
-- Definir valor padrão para prioridade
ALTER TABLE public.romaneios_expedicao
ALTER COLUMN prioridade SET DEFAULT 'Normal';
-- Atualizar status existentes para os novos valores
UPDATE public.romaneios_expedicao
SET status = CASE
WHEN status = 'Em Preparação' THEN 'Em planejamento'
ELSE status
END;

View File

@@ -0,0 +1,27 @@
-- Adicionar campos que estão faltando na tabela estoque_materiais
ALTER TABLE estoque_materiais
ADD COLUMN IF NOT EXISTS comprimento numeric,
ADD COLUMN IF NOT EXISTS largura numeric,
ADD COLUMN IF NOT EXISTS espessura numeric,
ADD COLUMN IF NOT EXISTS qualidade_aco text;
-- Atualizar trigger para incluir os novos campos
CREATE OR REPLACE FUNCTION public.atualizar_status_estoque()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Atualizar status baseado na quantidade disponível vs mínima
IF NEW.quantidade_disponivel <= NEW.quantidade_minima THEN
NEW.status = 'Crítico';
ELSIF NEW.quantidade_maxima IS NOT NULL AND NEW.quantidade_disponivel >= NEW.quantidade_maxima THEN
NEW.status = 'Excesso';
ELSE
NEW.status = 'Normal';
END IF;
NEW.updated_at = now();
RETURN NEW;
END;
$function$;

View File

@@ -0,0 +1,118 @@
-- Primeiro, vamos alterar a tabela estoque_materiais para garantir que o lote seja obrigatório e único por material
ALTER TABLE estoque_materiais
ALTER COLUMN lote_atual SET NOT NULL,
ADD CONSTRAINT unique_material_lote UNIQUE (descricao, lote_atual);
-- Criar índice para melhor performance nas consultas por lote
CREATE INDEX idx_estoque_materiais_lote ON estoque_materiais(lote_atual);
-- Alterar a tabela movimentacoes_estoque para incluir o campo certificado
ALTER TABLE movimentacoes_estoque
ADD COLUMN certificado text,
ADD COLUMN user_name text;
-- Criar função para buscar o nome do usuário logado
CREATE OR REPLACE FUNCTION get_user_name(user_id uuid)
RETURNS text
LANGUAGE sql
STABLE
AS $$
SELECT COALESCE(full_name, email, 'Usuário não identificado')
FROM profiles
WHERE id = user_id;
$$;
-- Atualizar função de processamento de movimentação para incluir validações
CREATE OR REPLACE FUNCTION processar_movimentacao_estoque()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
material_record estoque_materiais%ROWTYPE;
BEGIN
-- Buscar o material
SELECT * INTO material_record
FROM estoque_materiais
WHERE id = NEW.material_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Material não encontrado';
END IF;
-- Validar e processar cada tipo de movimentação
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
-- Validar se há quantidade suficiente
IF material_record.quantidade_disponivel < NEW.quantidade THEN
RAISE EXCEPTION 'Quantidade insuficiente para saída. Disponível: %, Solicitado: %',
material_record.quantidade_disponivel, NEW.quantidade;
END IF;
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
-- Para ajuste, definir a quantidade total como o valor informado
UPDATE estoque_materiais
SET quantidade_total = NEW.quantidade,
quantidade_disponivel = NEW.quantidade - quantidade_empenhada
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
-- Validar se há quantidade disponível suficiente
IF material_record.quantidade_disponivel < NEW.quantidade THEN
RAISE EXCEPTION 'Quantidade insuficiente para empenho. Disponível: %, Solicitado: %',
material_record.quantidade_disponivel, NEW.quantidade;
END IF;
-- Validar se OF foi informada
IF NEW.of_vinculada IS NULL OR NEW.of_vinculada = '' THEN
RAISE EXCEPTION 'OF vinculada é obrigatória para movimentações de empenho';
END IF;
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
-- Validar se há quantidade empenhada suficiente
IF material_record.quantidade_empenhada < NEW.quantidade THEN
RAISE EXCEPTION 'Quantidade insuficiente para desempenho. Empenhado: %, Solicitado: %',
material_record.quantidade_empenhada, NEW.quantidade;
END IF;
-- Validar se OF foi informada
IF NEW.of_vinculada IS NULL OR NEW.of_vinculada = '' THEN
RAISE EXCEPTION 'OF vinculada é obrigatória para movimentações de desempenho';
END IF;
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
-- Para transferência, apenas atualizamos a localização se informada
-- A quantidade permanece a mesma
NULL; -- Não altera quantidades na transferência
END IF;
RETURN NEW;
END;
$function$;
-- Garantir que o trigger existe
DROP TRIGGER IF EXISTS trigger_processar_movimentacao_estoque ON movimentacoes_estoque;
CREATE TRIGGER trigger_processar_movimentacao_estoque
AFTER INSERT ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION processar_movimentacao_estoque();

View File

@@ -0,0 +1,15 @@
-- Corrigir dados existentes: definir lote padrão para materiais sem lote
UPDATE estoque_materiais
SET lote_atual = 'LOTE-001'
WHERE lote_atual IS NULL OR lote_atual = '';
-- Corrigir a regra de quantidade disponível = quantidade total - quantidade empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_total - quantidade_empenhada
WHERE quantidade_disponivel != (quantidade_total - quantidade_empenhada);
-- Garantir que não há valores negativos
UPDATE estoque_materiais
SET quantidade_disponivel = 0
WHERE quantidade_disponivel < 0;

View File

@@ -0,0 +1,10 @@
-- Adicionar o campo "corrida" à tabela rastreabilidade_materiais
ALTER TABLE public.rastreabilidade_materiais
ADD COLUMN corrida text;
-- Adicionar política de UPDATE para rastreabilidade_materiais (estava faltando)
CREATE POLICY "Usuários autenticados podem atualizar rastreabilidade"
ON public.rastreabilidade_materiais
FOR UPDATE
USING (auth.uid() IS NOT NULL);

View File

@@ -0,0 +1,8 @@
-- Adicionar coluna kg_por_metro na tabela estoque_materiais
ALTER TABLE public.estoque_materiais
ADD COLUMN kg_por_metro numeric;
-- Adicionar coluna nota_fiscal na tabela rastreabilidade_materiais
ALTER TABLE public.rastreabilidade_materiais
ADD COLUMN nota_fiscal text;

View File

@@ -0,0 +1,172 @@
-- Criar tabelas para o módulo de Obra
-- Tabela de contratos de obra (baseada nas OFs existentes)
CREATE TABLE public.contratos_obra (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
of_number TEXT NOT NULL UNIQUE,
nome_obra TEXT,
cliente TEXT,
data_inicio_contratual DATE,
data_termino_prevista DATE,
status TEXT DEFAULT 'Ativo' CHECK (status IN ('Ativo', 'Pausado', 'Concluído')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Tabela de recursos de obra (CRUD)
CREATE TABLE public.recursos_obra (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
tipo_recurso TEXT NOT NULL, -- "Mão de Obra", "Equipamento"
nome_recurso TEXT NOT NULL, -- "Montador", "Guindaste 50t"
descricao TEXT,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID REFERENCES auth.users(id)
);
-- Tabela de condições climáticas (CRUD)
CREATE TABLE public.condicoes_climaticas (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
nome TEXT NOT NULL UNIQUE, -- "Ensolarado", "Nublado", "Chuvoso", "Vento Forte", "Garoa"
icone TEXT, -- nome do ícone para exibição
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela de motivos improdutivos (CRUD)
CREATE TABLE public.motivos_improdutivos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
motivo TEXT NOT NULL UNIQUE, -- "Chuva", "Vento Acima do Limite", etc.
descricao TEXT,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela principal do Diário de Obra (RDO)
CREATE TABLE public.diario_obra_rdo (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
of_number TEXT NOT NULL,
usuario_rdo UUID REFERENCES auth.users(id),
data DATE NOT NULL DEFAULT CURRENT_DATE,
condicao_climatica_id UUID REFERENCES public.condicoes_climaticas(id),
temperatura_aproximada INTEGER, -- em °C
observacoes_gerais TEXT,
finalizado BOOLEAN DEFAULT false,
sincronizado BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
UNIQUE(of_number, data) -- Um RDO por OF por dia
);
-- Tabela de apontamentos de peças
CREATE TABLE public.apontamentos_peca_obra (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
rdo_id UUID NOT NULL REFERENCES public.diario_obra_rdo(id) ON DELETE CASCADE,
marca_peca TEXT NOT NULL,
status TEXT DEFAULT 'Recebida em Canteiro' CHECK (status IN ('Recebida em Canteiro', 'Preparada', 'Montada', 'Inspecionada')),
periodo TEXT DEFAULT 'manha' CHECK (periodo IN ('manha', 'tarde', 'noite')),
quantidade INTEGER DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela de apontamentos de recursos
CREATE TABLE public.apontamentos_recursos_obra (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
rdo_id UUID NOT NULL REFERENCES public.diario_obra_rdo(id) ON DELETE CASCADE,
recurso_id UUID NOT NULL REFERENCES public.recursos_obra(id),
horas_trabalhadas NUMERIC(5,2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela de apontamentos improdutivos
CREATE TABLE public.apontamentos_improdutivos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
rdo_id UUID NOT NULL REFERENCES public.diario_obra_rdo(id) ON DELETE CASCADE,
hora_inicio TIME NOT NULL,
hora_fim TIME NOT NULL,
duracao_total INTERVAL GENERATED ALWAYS AS (hora_fim - hora_inicio) STORED,
motivo_id UUID NOT NULL REFERENCES public.motivos_improdutivos(id),
descricao TEXT, -- obrigatória se motivo = "Outro"
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela de registros fotográficos
CREATE TABLE public.registros_fotograficos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
fk_associada UUID NOT NULL, -- pode ser RDO_ID, apontamento_peca_id ou desvio_id
tipo_associacao TEXT NOT NULL CHECK (tipo_associacao IN ('rdo', 'apontamento_peca', 'desvio')),
arquivo_url TEXT NOT NULL,
legenda TEXT,
timestamp_foto TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Tabela de registros de desvios
CREATE TABLE public.registros_desvios (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
rdo_id UUID NOT NULL REFERENCES public.diario_obra_rdo(id) ON DELETE CASCADE,
descricao_desvio TEXT NOT NULL,
referencia_projeto TEXT, -- ex: "Folha de Desenho 15-B"
status TEXT DEFAULT 'Aberto' CHECK (status IN ('Aberto', 'Em Análise', 'Resolvido')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Inserir dados iniciais para as tabelas de configuração
INSERT INTO public.condicoes_climaticas (nome, icone) VALUES
('Ensolarado', 'sun'),
('Nublado', 'cloud'),
('Chuvoso', 'cloud-rain'),
('Vento Forte', 'wind'),
('Garoa', 'cloud-drizzle');
INSERT INTO public.motivos_improdutivos (motivo, descricao) VALUES
('Chuva', 'Parada devido à chuva'),
('Vento Acima do Limite', 'Vento acima dos limites de segurança'),
('Falha de Equipamento', 'Problema técnico em equipamentos'),
('Falta de Material', 'Material não disponível no canteiro'),
('Instrução de Segurança (DDS)', 'Diálogo Diário de Segurança'),
('Problema de Acesso', 'Dificuldade de acesso ao local de trabalho'),
('Esperando Liberação', 'Aguardando autorização para prosseguir'),
('Outro', 'Outros motivos não listados');
-- Habilitar RLS para todas as tabelas
ALTER TABLE public.contratos_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.recursos_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.condicoes_climaticas ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.motivos_improdutivos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.diario_obra_rdo ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.apontamentos_peca_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.apontamentos_recursos_obra ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.apontamentos_improdutivos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.registros_fotograficos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.registros_desvios ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para permitir acesso a usuários autenticados
CREATE POLICY "Authenticated users can manage contratos_obra" ON public.contratos_obra FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage recursos_obra" ON public.recursos_obra FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Everyone can view condicoes_climaticas" ON public.condicoes_climaticas FOR SELECT USING (true);
CREATE POLICY "Everyone can view motivos_improdutivos" ON public.motivos_improdutivos FOR SELECT USING (true);
CREATE POLICY "Authenticated users can manage diario_obra_rdo" ON public.diario_obra_rdo FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage apontamentos_peca_obra" ON public.apontamentos_peca_obra FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage apontamentos_recursos_obra" ON public.apontamentos_recursos_obra FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage apontamentos_improdutivos" ON public.apontamentos_improdutivos FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage registros_fotograficos" ON public.registros_fotograficos FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage registros_desvios" ON public.registros_desvios FOR ALL USING (auth.uid() IS NOT NULL);
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_contratos_obra_updated_at BEFORE UPDATE ON public.contratos_obra FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
CREATE TRIGGER update_recursos_obra_updated_at BEFORE UPDATE ON public.recursos_obra FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
CREATE TRIGGER update_diario_obra_rdo_updated_at BEFORE UPDATE ON public.diario_obra_rdo FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
CREATE TRIGGER update_registros_desvios_updated_at BEFORE UPDATE ON public.registros_desvios FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();

View File

@@ -0,0 +1,17 @@
-- Adicionar coluna de categoria aos motivos improdutivos
ALTER TABLE public.motivos_improdutivos
ADD COLUMN categoria TEXT DEFAULT 'Empresa Montadora' CHECK (categoria IN ('Cliente', 'Empresa Montadora', 'Contratada', 'Terceiros Indiretos'));
-- Atualizar os registros existentes com categorias apropriadas
UPDATE public.motivos_improdutivos
SET categoria = CASE
WHEN motivo IN ('Chuva', 'Vento Acima do Limite', 'Garoa') THEN 'Terceiros Indiretos'
WHEN motivo IN ('Falha de Equipamento', 'Instrução de Segurança (DDS)') THEN 'Empresa Montadora'
WHEN motivo IN ('Problema de Acesso', 'Esperando Liberação') THEN 'Cliente'
WHEN motivo = 'Falta de Material' THEN 'Contratada'
ELSE 'Empresa Montadora'
END;
-- Permitir que admins gerenciem motivos improdutivos
CREATE POLICY "Admins can manage motivos_improdutivos" ON public.motivos_improdutivos FOR ALL USING (has_role(auth.uid(), 'admin'::app_role));

View File

@@ -0,0 +1,43 @@
-- Adicionar colunas necessárias na tabela diario_obra_rdo
ALTER TABLE public.diario_obra_rdo
ADD COLUMN numero_rdo TEXT,
ADD COLUMN usuario_nome TEXT;
-- Função para gerar numeração automática do RDO
CREATE OR REPLACE FUNCTION generate_rdo_number(of_number_param TEXT)
RETURNS TEXT AS $$
DECLARE
next_num INTEGER;
new_rdo_number TEXT;
of_digits TEXT;
BEGIN
-- Extrair apenas os dígitos da OF
of_digits := regexp_replace(of_number_param, '[^0-9]', '', 'g');
-- Buscar próximo número sequencial para esta OF
SELECT COALESCE(MAX(CAST(SUBSTRING(numero_rdo FROM '\-(\d+)$') AS INTEGER)), 0) + 1
INTO next_num
FROM public.diario_obra_rdo
WHERE of_number = of_number_param AND numero_rdo IS NOT NULL;
-- Gerar número do RDO no formato RDO[digits]-[seq]
new_rdo_number := 'RDO' || of_digits || '-' || LPAD(next_num::TEXT, 2, '0');
RETURN new_rdo_number;
END;
$$ LANGUAGE plpgsql;
-- Trigger para gerar automaticamente o número do RDO
CREATE OR REPLACE FUNCTION handle_rdo_number()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.numero_rdo IS NULL OR NEW.numero_rdo = '' THEN
NEW.numero_rdo := generate_rdo_number(NEW.of_number);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_generate_rdo_number
BEFORE INSERT ON public.diario_obra_rdo
FOR EACH ROW EXECUTE FUNCTION handle_rdo_number();

View File

@@ -0,0 +1,37 @@
-- Adicionar novos campos à tabela ordens_fabricacao
ALTER TABLE public.ordens_fabricacao
ADD COLUMN prioridade text DEFAULT 'Normal',
ADD COLUMN nivel_qualidade text DEFAULT 'Normal',
ADD COLUMN gestor text,
ADD COLUMN data_termino_prev date;
-- Criar índices para otimizar consultas por of_number
CREATE INDEX IF NOT EXISTS idx_ordens_fabricacao_num_of ON public.ordens_fabricacao(num_of);
CREATE INDEX IF NOT EXISTS idx_ficha_tecnica_of_number ON public.ficha_tecnica_contratos(of_number);
CREATE INDEX IF NOT EXISTS idx_cronogramas_of_num ON public.cronogramas_of(of_id);
-- Função para sincronizar dados entre tabelas relacionadas
CREATE OR REPLACE FUNCTION sync_of_data()
RETURNS TRIGGER AS $$
BEGIN
-- Atualizar dados na tabela ordens_fabricacao quando ficha_tecnica_contratos for alterada
IF TG_TABLE_NAME = 'ficha_tecnica_contratos' THEN
UPDATE ordens_fabricacao
SET
gestor = NEW.gestor,
data_termino_prev = NEW.data_termino_prev,
peso_total = NEW.quantidade,
descritivo = NEW.descricao_resumida
WHERE num_of = NEW.of_number;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para sincronizar dados
CREATE TRIGGER sync_ficha_to_of
AFTER UPDATE ON ficha_tecnica_contratos
FOR EACH ROW
EXECUTE FUNCTION sync_of_data();

View File

@@ -0,0 +1,129 @@
-- Verificar estrutura atual da tabela tipos_materia_prima
-- Se a coluna categoria não existir, vamos adicioná-la para distinguir entre material direto e indireto
-- Adicionar coluna categoria se não existir
ALTER TABLE tipos_materia_prima
ADD COLUMN IF NOT EXISTS categoria TEXT DEFAULT 'direto' CHECK (categoria IN ('direto', 'indireto'));
-- Criar tabelas para os outros CRUDs se não existirem
-- Tabela para unidades de medida
CREATE TABLE IF NOT EXISTS unidades_medida (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome TEXT NOT NULL UNIQUE,
abreviacao TEXT NOT NULL UNIQUE,
descricao TEXT,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Tabela para localizações
CREATE TABLE IF NOT EXISTS localizacoes_estoque (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome TEXT NOT NULL UNIQUE,
descricao TEXT,
codigo TEXT UNIQUE,
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Tabela para qualidades de aço
CREATE TABLE IF NOT EXISTS qualidades_aco (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
nome TEXT NOT NULL UNIQUE,
norma TEXT,
descricao TEXT,
propriedades JSONB DEFAULT '{}',
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Triggers para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_unidades_medida()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_updated_at_localizacoes()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION update_updated_at_qualidades_aco()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Criar triggers se não existirem
DROP TRIGGER IF EXISTS trigger_update_updated_at_unidades_medida ON unidades_medida;
CREATE TRIGGER trigger_update_updated_at_unidades_medida
BEFORE UPDATE ON unidades_medida
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_unidades_medida();
DROP TRIGGER IF EXISTS trigger_update_updated_at_localizacoes ON localizacoes_estoque;
CREATE TRIGGER trigger_update_updated_at_localizacoes
BEFORE UPDATE ON localizacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_localizacoes();
DROP TRIGGER IF EXISTS trigger_update_updated_at_qualidades_aco ON qualidades_aco;
CREATE TRIGGER trigger_update_updated_at_qualidades_aco
BEFORE UPDATE ON qualidades_aco
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_qualidades_aco();
-- Inserir alguns dados iniciais para unidades de medida
INSERT INTO unidades_medida (nome, abreviacao, descricao) VALUES
('Peça', 'PC', 'Unidade por peça'),
('Metro', 'M', 'Unidade de comprimento em metros'),
('Quilograma', 'KG', 'Unidade de peso em quilogramas'),
('Tonelada', 'TON', 'Unidade de peso em toneladas'),
('Metro Quadrado', '', 'Unidade de área em metros quadrados'),
('Litro', 'L', 'Unidade de volume em litros')
ON CONFLICT (nome) DO NOTHING;
-- Inserir algumas localizações iniciais
INSERT INTO localizacoes_estoque (nome, codigo, descricao) VALUES
('Galpão Principal', 'GP001', 'Galpão principal de armazenamento'),
('Área Externa', 'AE001', 'Área externa coberta'),
('Depósito Pequeno', 'DP001', 'Depósito para materiais pequenos'),
('Área de Recebimento', 'AR001', 'Área para recebimento de materiais'),
('Área de Expedição', 'AX001', 'Área para expedição de materiais')
ON CONFLICT (nome) DO NOTHING;
-- Inserir algumas qualidades de aço iniciais
INSERT INTO qualidades_aco (nome, norma, descricao, propriedades) VALUES
('ASTM A36', 'ASTM A36', 'Aço carbono estrutural', '{"resistencia": "250 MPa", "uso": "estrutural"}'),
('ASTM A572 Gr50', 'ASTM A572', 'Aço carbono alta resistência', '{"resistencia": "345 MPa", "uso": "estrutural"}'),
('SAE 1020', 'SAE 1020', 'Aço carbono baixo teor', '{"carbono": "0.20%", "uso": "geral"}'),
('SAE 1045', 'SAE 1045', 'Aço carbono médio teor', '{"carbono": "0.45%", "uso": "eixos"}'),
('ASTM A500 Gr B', 'ASTM A500', 'Aço carbono para tubos', '{"resistencia": "290 MPa", "uso": "tubular"}')
ON CONFLICT (nome) DO NOTHING;
-- RLS Policies
ALTER TABLE unidades_medida ENABLE ROW LEVEL SECURITY;
ALTER TABLE localizacoes_estoque ENABLE ROW LEVEL SECURITY;
ALTER TABLE qualidades_aco ENABLE ROW LEVEL SECURITY;
-- Políticas para visualização (todos podem ver)
CREATE POLICY "Todos podem visualizar unidades" ON unidades_medida FOR SELECT USING (true);
CREATE POLICY "Todos podem visualizar localizações" ON localizacoes_estoque FOR SELECT USING (true);
CREATE POLICY "Todos podem visualizar qualidades aço" ON qualidades_aco FOR SELECT USING (true);
-- Políticas para modificação (apenas usuários autenticados)
CREATE POLICY "Usuários autenticados podem gerenciar unidades" ON unidades_medida FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem gerenciar localizações" ON localizacoes_estoque FOR ALL USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem gerenciar qualidades aço" ON qualidades_aco FOR ALL USING (auth.uid() IS NOT NULL);

View File

@@ -0,0 +1,50 @@
-- Create table for menu groups
CREATE TABLE public.menu_groups (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#6366f1',
order_index INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Enable RLS
ALTER TABLE public.menu_groups ENABLE ROW LEVEL SECURITY;
-- Create policies for menu groups
CREATE POLICY "Only admins can manage menu groups"
ON public.menu_groups
FOR ALL
USING (has_role(auth.uid(), 'admin'::app_role));
-- Add group_id to interface_resources table
ALTER TABLE public.interface_resources
ADD COLUMN group_id UUID REFERENCES public.menu_groups(id);
-- Create trigger for updating updated_at
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_menu_groups_updated_at
BEFORE UPDATE ON public.menu_groups
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- Insert default menu groups
INSERT INTO public.menu_groups (name, color, order_index) VALUES
('Principal', '#6366f1', 0),
('Administração', '#ef4444', 1);
-- Update existing interface resources to assign them to default group
UPDATE public.interface_resources
SET group_id = (SELECT id FROM public.menu_groups WHERE name = 'Principal' LIMIT 1)
WHERE resource_key NOT IN ('admin', 'user-management');
UPDATE public.interface_resources
SET group_id = (SELECT id FROM public.menu_groups WHERE name = 'Administração' LIMIT 1)
WHERE resource_key IN ('admin', 'user-management');

View File

@@ -0,0 +1,20 @@
-- Remover a constraint de chave estrangeira que impede a exclusão de peças
ALTER TABLE processos_pecas_datas DROP CONSTRAINT IF EXISTS processos_pecas_datas_peca_id_fkey;
-- Recriar a constraint com CASCADE para permitir exclusão automática dos registros relacionados
ALTER TABLE processos_pecas_datas
ADD CONSTRAINT processos_pecas_datas_peca_id_fkey
FOREIGN KEY (peca_id) REFERENCES pecas(id) ON DELETE CASCADE;
-- Fazer o mesmo para a tabela componentes_peca se necessário
ALTER TABLE componentes_peca DROP CONSTRAINT IF EXISTS componentes_peca_peca_id_fkey;
ALTER TABLE componentes_peca
ADD CONSTRAINT componentes_peca_peca_id_fkey
FOREIGN KEY (peca_id) REFERENCES pecas(id) ON DELETE CASCADE;
-- E para apontamentos_producao
ALTER TABLE apontamentos_producao DROP CONSTRAINT IF EXISTS apontamentos_producao_peca_id_fkey;
ALTER TABLE apontamentos_producao
ADD CONSTRAINT apontamentos_producao_peca_id_fkey
FOREIGN KEY (peca_id) REFERENCES pecas(id) ON DELETE CASCADE;

View File

@@ -0,0 +1,7 @@
-- Adicionar coluna de prioridade na tabela pecas
ALTER TABLE public.pecas
ADD COLUMN prioridade text DEFAULT 'P4';
-- Adicionar comentário para documentar os valores possíveis
COMMENT ON COLUMN public.pecas.prioridade IS 'Prioridade da peça: P1 (alta), P2 (média-alta), P3 (média-baixa), P4 (baixa)';

View File

@@ -0,0 +1,6 @@
-- Adicionar política para permitir que usuários deletem seus próprios apontamentos
CREATE POLICY "Usuários podem deletar seus apontamentos"
ON public.apontamentos_producao
FOR DELETE
USING (auth.uid() = created_by);

View File

@@ -0,0 +1,49 @@
-- Criar tabela para configurações de prioridades
CREATE TABLE public.prioridades_config (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
codigo text NOT NULL UNIQUE,
nome text NOT NULL,
cor text NOT NULL,
ordem integer NOT NULL DEFAULT 0,
ativo boolean DEFAULT true,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now()
);
-- Inserir as prioridades padrão
INSERT INTO public.prioridades_config (codigo, nome, cor, ordem) VALUES
('P1', 'Crítica', '#dc2626', 1),
('P2', 'Alta', '#ea580c', 2),
('P3', 'Média', '#ca8a04', 3),
('P4', 'Baixa', '#16a34a', 4);
-- Habilitar RLS
ALTER TABLE public.prioridades_config ENABLE ROW LEVEL SECURITY;
-- Políticas RLS
CREATE POLICY "Anyone can view prioridades config"
ON public.prioridades_config
FOR SELECT
USING (true);
CREATE POLICY "Only admins can modify prioridades config"
ON public.prioridades_config
FOR ALL
USING (has_role(auth.uid(), 'admin'::app_role));
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_prioridades_config_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$function$;
CREATE TRIGGER trigger_update_prioridades_config_updated_at
BEFORE UPDATE ON public.prioridades_config
FOR EACH ROW
EXECUTE FUNCTION update_prioridades_config_updated_at();

View File

@@ -0,0 +1,94 @@
-- Adicionar campo status_detalhado à tabela ofs_concluidas para diferenciar entre concluídas e entregues
ALTER TABLE public.ofs_concluidas
ADD COLUMN status_detalhado text DEFAULT 'concluida';
-- Atualizar registros existentes
UPDATE public.ofs_concluidas
SET status_detalhado = 'concluida'
WHERE status_detalhado IS NULL;
-- Adicionar índice para melhor performance
CREATE INDEX IF NOT EXISTS idx_ofs_concluidas_status ON public.ofs_concluidas(status_detalhado);
-- Função para mover OF de ativa para concluída
CREATE OR REPLACE FUNCTION concluir_of(of_id_param uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
of_record ordens_fabricacao%ROWTYPE;
ficha_data jsonb;
BEGIN
-- Buscar dados da OF ativa
SELECT * INTO of_record
FROM ordens_fabricacao
WHERE id = of_id_param;
IF NOT FOUND THEN
RAISE EXCEPTION 'OF não encontrada';
END IF;
-- Buscar dados da ficha técnica
SELECT to_jsonb(ft.*) INTO ficha_data
FROM ficha_tecnica_contratos ft
WHERE of_number = of_record.num_of;
-- Inserir na tabela ofs_concluidas com status 'concluida'
INSERT INTO ofs_concluidas (
num_of,
descritivo,
peso_total,
data_abertura,
data_prazo,
criterio_qualidade,
tratamento_final,
local_uf,
user_id,
ficha_tecnica_data,
status_detalhado,
arquivado_por
) VALUES (
of_record.num_of,
of_record.descritivo,
of_record.peso_total,
of_record.data_abertura,
of_record.data_prazo,
of_record.criterio_qualidade,
of_record.tratamento_final,
of_record.local_uf,
of_record.user_id,
ficha_data,
'concluida',
auth.uid()
);
-- Remover da tabela de ordens ativas
DELETE FROM ordens_fabricacao WHERE id = of_id_param;
END;
$function$;
-- Função para mover OF de concluída para entregue
CREATE OR REPLACE FUNCTION entregar_of(of_concluida_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
-- Atualizar status para 'entregue'
UPDATE ofs_concluidas
SET
status_detalhado = 'entregue',
data_arquivamento = now(),
arquivado_por = auth.uid()
WHERE id = of_concluida_id AND status_detalhado = 'concluida';
IF NOT FOUND THEN
RAISE EXCEPTION 'OF concluída não encontrada ou já entregue';
END IF;
END;
$function$;
-- Atualizar políticas RLS para incluir o novo campo
-- (As políticas existentes já cobrem o acesso, apenas garantindo que estão corretas)

View File

@@ -0,0 +1,78 @@
-- Função para reverter OF de entregue para concluída (apenas admin)
CREATE OR REPLACE FUNCTION reverter_of_entregue_para_concluida(of_concluida_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
-- Verificar se o usuário é admin
IF NOT has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Apenas administradores podem reverter OFs entregues';
END IF;
-- Atualizar status para 'concluida'
UPDATE ofs_concluidas
SET
status_detalhado = 'concluida',
data_arquivamento = NULL,
arquivado_por = NULL
WHERE id = of_concluida_id AND status_detalhado = 'entregue';
IF NOT FOUND THEN
RAISE EXCEPTION 'OF entregue não encontrada';
END IF;
END;
$function$;
-- Função para reverter OF de concluída para ativa (apenas admin)
CREATE OR REPLACE FUNCTION reverter_of_concluida_para_ativa(of_concluida_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
of_record ofs_concluidas%ROWTYPE;
BEGIN
-- Verificar se o usuário é admin
IF NOT has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Apenas administradores podem reverter OFs concluídas';
END IF;
-- Buscar dados da OF concluída
SELECT * INTO of_record
FROM ofs_concluidas
WHERE id = of_concluida_id AND status_detalhado = 'concluida';
IF NOT FOUND THEN
RAISE EXCEPTION 'OF concluída não encontrada';
END IF;
-- Inserir de volta na tabela ordens_fabricacao como ativa
INSERT INTO ordens_fabricacao (
num_of,
descritivo,
peso_total,
data_abertura,
data_prazo,
criterio_qualidade,
tratamento_final,
local_uf,
user_id,
status
) VALUES (
of_record.num_of,
of_record.descritivo,
of_record.peso_total,
of_record.data_abertura,
of_record.data_prazo,
of_record.criterio_qualidade,
of_record.tratamento_final,
of_record.local_uf,
of_record.user_id,
'ativa'
);
-- Remover da tabela ofs_concluidas
DELETE FROM ofs_concluidas WHERE id = of_concluida_id;
END;
$function$;

View File

@@ -0,0 +1,39 @@
-- Add revision field to tasks table and archived fields
ALTER TABLE public.tasks
ADD COLUMN revision INTEGER DEFAULT 1,
ADD COLUMN archived_at TIMESTAMP WITH TIME ZONE,
ADD COLUMN archived_by UUID REFERENCES auth.users(id);
-- Function to increment revision when task is updated
CREATE OR REPLACE FUNCTION increment_task_revision()
RETURNS TRIGGER AS $$
BEGIN
-- Only increment revision if the task content has actually changed
IF (OLD.title != NEW.title OR
OLD.description != NEW.description OR
OLD.of_number != NEW.of_number OR
OLD.assigned_to != NEW.assigned_to OR
OLD.due_date != NEW.due_date OR
OLD.priority != NEW.priority OR
OLD.category != NEW.category) THEN
NEW.revision = COALESCE(OLD.revision, 1) + 1;
-- Update task_ref to include revision number if > 1
IF NEW.revision > 1 THEN
-- Extract base task ref (remove any existing revision)
NEW.task_ref = regexp_replace(OLD.task_ref, '/\d+$', '') || '/' || NEW.revision;
END IF;
END IF;
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create trigger for revision increment
DROP TRIGGER IF EXISTS task_revision_trigger ON public.tasks;
CREATE TRIGGER task_revision_trigger
BEFORE UPDATE ON public.tasks
FOR EACH ROW
EXECUTE FUNCTION increment_task_revision();

View File

@@ -0,0 +1,94 @@
-- Fase 2: Índices Estratégicos para Performance
-- Identificados através da análise dos hooks principais
-- Índices para queries frequentes em peças
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_of_number ON pecas(of_number);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_marca ON pecas(marca);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_user_id ON pecas(user_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_created_at ON pecas(created_at DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_prioridade ON pecas(prioridade);
-- Índices para componentes de peças
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_componentes_peca_peca_id ON componentes_peca(peca_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_componentes_marca ON componentes_peca(marca_componente);
-- Índices para apontamentos de produção (queries críticas)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_of_number ON apontamentos_producao(of_number);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_processo_id ON apontamentos_producao(processo_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_peca_id ON apontamentos_producao(peca_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_componente_id ON apontamentos_producao(componente_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_data ON apontamentos_producao(data_apontamento DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_created_by ON apontamentos_producao(created_by);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_tipo ON apontamentos_producao(tipo_apontamento);
-- Índice composto para busca de quantidade processada (query frequente)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_busca_quantidade
ON apontamentos_producao(processo_id, of_number, tipo_apontamento, peca_id, componente_id);
-- Índices para estoque (queries pesadas)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_estoque_codigo ON estoque_materiais(codigo);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_estoque_status ON estoque_materiais(status);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_estoque_tipo_material ON estoque_materiais(tipo_material_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_estoque_localizacao ON estoque_materiais(localizacao);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_estoque_fornecedor ON estoque_materiais(fornecedor);
-- Índices para movimentações de estoque
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_movimentacoes_material_id ON movimentacoes_estoque(material_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_movimentacoes_data ON movimentacoes_estoque(data_movimentacao DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_movimentacoes_tipo ON movimentacoes_estoque(tipo_movimentacao);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_movimentacoes_created_at ON movimentacoes_estoque(created_at DESC);
-- Índices para tasks (performance crítica)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_is_completed ON tasks(is_completed);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_assigned_to ON tasks(assigned_to);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_created_by ON tasks(created_by);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_completed_by ON tasks(completed_by);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_due_date ON tasks(due_date);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_completed_at ON tasks(completed_at DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_priority ON tasks(priority);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_status ON tasks(status);
-- Índice composto para tasks ativas ordenadas por prioridade e prazo
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_active_priority
ON tasks(is_completed, priority, due_date) WHERE is_completed = false;
-- Índice composto para tasks concluídas recentes
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_completed_recent
ON tasks(is_completed, completed_at DESC) WHERE is_completed = true;
-- Índices para ordens de fabricação
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_of_num_of ON ordens_fabricacao(num_of);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_of_status ON ordens_fabricacao(status);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_of_data_prazo ON ordens_fabricacao(data_prazo);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_of_gestor ON ordens_fabricacao(gestor);
-- Índices para ficha técnica
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ficha_tecnica_of_number ON ficha_tecnica_contratos(of_number);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ficha_tecnica_cliente ON ficha_tecnica_contratos(cliente);
-- Índices para processos de fabricação
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_processos_ativo ON processos_fabricacao(ativo);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_processos_ordem ON processos_fabricacao(ordem);
-- Índices para profiles (autenticação e autorização)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_profiles_email ON profiles(email);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_profiles_status ON profiles(status);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_profiles_privilege_id ON profiles(privilege_id);
-- Índices para melhor performance de join entre apontamentos e processos
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_apontamentos_join_processo
ON apontamentos_producao(processo_id, data_apontamento DESC);
-- Índices para melhor performance de join entre peças e componentes
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pecas_join_componentes
ON pecas(id, tem_componentes) WHERE tem_componentes = true;
-- Estatísticas para melhor planejamento de queries
ANALYZE pecas;
ANALYZE componentes_peca;
ANALYZE apontamentos_producao;
ANALYZE estoque_materiais;
ANALYZE movimentacoes_estoque;
ANALYZE tasks;
ANALYZE ordens_fabricacao;
ANALYZE processos_fabricacao;

View File

@@ -0,0 +1,73 @@
-- Create function to allow admins to create new users with default password
CREATE OR REPLACE FUNCTION public.admin_create_user(
user_email TEXT,
user_full_name TEXT DEFAULT NULL,
user_function_id UUID DEFAULT NULL,
user_privilege_id UUID DEFAULT NULL
)
RETURNS UUID
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
new_user_id UUID;
temp_password TEXT := '1234';
BEGIN
-- Check if current user is admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Only admins can create new users';
END IF;
-- Check if email already exists
IF EXISTS (SELECT 1 FROM auth.users WHERE email = user_email) THEN
RAISE EXCEPTION 'User with this email already exists';
END IF;
-- Create user in auth.users (this will trigger the profile creation)
INSERT INTO auth.users (
instance_id,
id,
aud,
role,
email,
encrypted_password,
email_confirmed_at,
created_at,
updated_at,
confirmation_token,
email_change,
email_change_token_new,
recovery_token
) VALUES (
'00000000-0000-0000-0000-000000000000',
gen_random_uuid(),
'authenticated',
'authenticated',
user_email,
crypt(temp_password, gen_salt('bf')),
now(),
now(),
now(),
'',
'',
'',
''
) RETURNING id INTO new_user_id;
-- Update the profile with additional information
UPDATE public.profiles
SET
full_name = user_full_name,
function_id = user_function_id,
privilege_id = user_privilege_id,
status = 'active'
WHERE id = new_user_id;
RETURN new_user_id;
END;
$$;
-- Grant execute permission to authenticated users (the function itself checks for admin role)
GRANT EXECUTE ON FUNCTION public.admin_create_user TO authenticated;

View File

@@ -0,0 +1,74 @@
-- Fix the admin_create_user function to use proper password hashing
CREATE OR REPLACE FUNCTION public.admin_create_user(
user_email TEXT,
user_full_name TEXT DEFAULT NULL,
user_function_id UUID DEFAULT NULL,
user_privilege_id UUID DEFAULT NULL
)
RETURNS UUID
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
new_user_id UUID;
temp_password TEXT := '1234';
hashed_password TEXT;
BEGIN
-- Check if current user is admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Only admins can create new users';
END IF;
-- Check if email already exists
IF EXISTS (SELECT 1 FROM auth.users WHERE email = user_email) THEN
RAISE EXCEPTION 'User with this email already exists';
END IF;
-- Generate a simple hash for the password (Supabase will handle proper hashing)
hashed_password := encode(digest(temp_password, 'sha256'), 'hex');
-- Create user in auth.users (this will trigger the profile creation)
INSERT INTO auth.users (
instance_id,
id,
aud,
role,
email,
encrypted_password,
email_confirmed_at,
created_at,
updated_at,
confirmation_token,
email_change,
email_change_token_new,
recovery_token
) VALUES (
'00000000-0000-0000-0000-000000000000',
gen_random_uuid(),
'authenticated',
'authenticated',
user_email,
hashed_password,
now(),
now(),
now(),
'',
'',
'',
''
) RETURNING id INTO new_user_id;
-- Update the profile with additional information
UPDATE public.profiles
SET
full_name = user_full_name,
function_id = user_function_id,
privilege_id = user_privilege_id,
status = 'active'
WHERE id = new_user_id;
RETURN new_user_id;
END;
$$;

View File

@@ -0,0 +1,34 @@
-- Criar tabela para logs de apontamentos automáticos
CREATE TABLE public.logs_apontamentos_automaticos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
romaneio_id UUID NOT NULL REFERENCES public.romaneios_expedicao(id),
numero_romaneio TEXT NOT NULL,
of_number TEXT NOT NULL,
total_pecas_processadas INTEGER NOT NULL DEFAULT 0,
status_operacao TEXT NOT NULL DEFAULT 'sucesso',
detalhes_erro TEXT,
usuario_executou UUID REFERENCES auth.users(id),
data_execucao TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar RLS
ALTER TABLE public.logs_apontamentos_automaticos ENABLE ROW LEVEL SECURITY;
-- Política para visualizar logs
CREATE POLICY "Authenticated users can view logs"
ON public.logs_apontamentos_automaticos
FOR SELECT
USING (auth.uid() IS NOT NULL);
-- Política para inserir logs
CREATE POLICY "Authenticated users can insert logs"
ON public.logs_apontamentos_automaticos
FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
-- Criar índices para performance
CREATE INDEX idx_logs_apontamentos_romaneio ON public.logs_apontamentos_automaticos(romaneio_id);
CREATE INDEX idx_logs_apontamentos_of ON public.logs_apontamentos_automaticos(of_number);
CREATE INDEX idx_logs_apontamentos_data ON public.logs_apontamentos_automaticos(data_execucao);

View File

@@ -0,0 +1,173 @@
-- Criar tabela para armazenar os diários de produção
CREATE TABLE public.diarios_producao (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
data DATE NOT NULL DEFAULT CURRENT_DATE,
tecnico_responsavel TEXT NOT NULL,
turno TEXT NOT NULL CHECK (turno IN ('1-manha', '1-tarde', '2-turno')),
observacoes_gerais TEXT,
fotos_urls TEXT[],
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
created_by UUID REFERENCES auth.users(id),
finalizado BOOLEAN DEFAULT false
);
-- Criar tabela para recursos (máquinas e operários)
CREATE TABLE public.recursos_producao (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
nome TEXT NOT NULL,
tipo TEXT NOT NULL CHECK (tipo IN ('maquina', 'operario')),
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela para apontamentos de recursos por OF no diário
CREATE TABLE public.apontamentos_diario_recursos (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
diario_id UUID NOT NULL REFERENCES public.diarios_producao(id) ON DELETE CASCADE,
of_number TEXT NOT NULL,
recurso_id UUID NOT NULL REFERENCES public.recursos_producao(id),
qtd_inicio NUMERIC DEFAULT 0,
qtd_meio NUMERIC DEFAULT 0,
qtd_fim NUMERIC DEFAULT 0,
qtd_segundo_turno NUMERIC DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela para lotes de solda por OF no diário
CREATE TABLE public.lotes_solda_diario (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
diario_id UUID NOT NULL REFERENCES public.diarios_producao(id) ON DELETE CASCADE,
of_number TEXT NOT NULL,
lote_solda TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela para ocorrências de improdutividade
CREATE TABLE public.ocorrencias_improdutividade (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
descricao TEXT NOT NULL,
categoria TEXT DEFAULT 'Geral',
ativo BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar tabela para ocorrências marcadas no diário
CREATE TABLE public.diario_ocorrencias (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
diario_id UUID NOT NULL REFERENCES public.diarios_producao(id) ON DELETE CASCADE,
ocorrencia_id UUID NOT NULL REFERENCES public.ocorrencias_improdutividade(id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar RLS nas tabelas
ALTER TABLE public.diarios_producao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.recursos_producao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.apontamentos_diario_recursos ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.lotes_solda_diario ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ocorrencias_improdutividade ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.diario_ocorrencias ENABLE ROW LEVEL SECURITY;
-- Políticas RLS para diarios_producao
CREATE POLICY "Authenticated users can view diarios_producao"
ON public.diarios_producao
FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can insert diarios_producao"
ON public.diarios_producao
FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can update diarios_producao"
ON public.diarios_producao
FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can delete diarios_producao"
ON public.diarios_producao
FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Políticas RLS para recursos_producao
CREATE POLICY "Authenticated users can view recursos_producao"
ON public.recursos_producao
FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage recursos_producao"
ON public.recursos_producao
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Políticas RLS para apontamentos_diario_recursos
CREATE POLICY "Authenticated users can manage apontamentos_diario_recursos"
ON public.apontamentos_diario_recursos
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Políticas RLS para lotes_solda_diario
CREATE POLICY "Authenticated users can manage lotes_solda_diario"
ON public.lotes_solda_diario
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Políticas RLS para ocorrencias_improdutividade
CREATE POLICY "Authenticated users can view ocorrencias_improdutividade"
ON public.ocorrencias_improdutividade
FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users can manage ocorrencias_improdutividade"
ON public.ocorrencias_improdutividade
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Políticas RLS para diario_ocorrencias
CREATE POLICY "Authenticated users can manage diario_ocorrencias"
ON public.diario_ocorrencias
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Inserir dados iniciais para recursos
INSERT INTO public.recursos_producao (nome, tipo) VALUES
('Robo', 'maquina'),
('Serra', 'maquina'),
('Maq. Cantoneira', 'maquina'),
('Maq. Solda', 'maquina'),
('Plasma', 'maquina'),
('Jato', 'maquina'),
('Montadores', 'operario'),
('Soldadores', 'operario'),
('Endireitadores', 'operario'),
('Op. Acabamento', 'operario'),
('Jatista', 'operario'),
('Pintores', 'operario'),
('Op. Carga', 'operario');
-- Inserir dados iniciais para ocorrências de improdutividade
INSERT INTO public.ocorrencias_improdutividade (descricao) VALUES
('Ponte rolante em manutenção'),
('Falta de insumo'),
('Quebra/Falta de caminhão para expedição'),
('Falta de energia'),
('Equipamento quebrado'),
('Falta de matéria prima'),
('Problema de qualidade'),
('Atraso de fornecedor'),
('Falta de pessoal'),
('Condições climáticas adversas');
-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_diarios_producao_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_diarios_producao_updated_at
BEFORE UPDATE ON diarios_producao
FOR EACH ROW EXECUTE FUNCTION update_diarios_producao_updated_at();

View File

@@ -0,0 +1,95 @@
-- Create function to check if user can be safely deleted
CREATE OR REPLACE FUNCTION public.can_delete_user(_user_id uuid)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
has_dependencies boolean := false;
BEGIN
-- Check if user has any data in critical tables
SELECT EXISTS (
-- Check ficha_tecnica_contratos
SELECT 1 FROM public.ficha_tecnica_contratos WHERE user_id = _user_id
UNION ALL
-- Check ordens_fabricacao
SELECT 1 FROM public.ordens_fabricacao WHERE user_id = _user_id
UNION ALL
-- Check tasks created or assigned
SELECT 1 FROM public.tasks WHERE created_by = _user_id OR assigned_to = _user_id
UNION ALL
-- Check pecas
SELECT 1 FROM public.pecas WHERE user_id = _user_id
UNION ALL
-- Check componentes_peca
SELECT 1 FROM public.componentes_peca WHERE user_id = _user_id
UNION ALL
-- Check apontamentos_producao
SELECT 1 FROM public.apontamentos_producao WHERE created_by = _user_id
UNION ALL
-- Check estoque_materiais
SELECT 1 FROM public.estoque_materiais WHERE created_by = _user_id
UNION ALL
-- Check movimentacoes_estoque
SELECT 1 FROM public.movimentacoes_estoque WHERE created_by = _user_id
UNION ALL
-- Check empenhos_material
SELECT 1 FROM public.empenhos_material WHERE created_by = _user_id
UNION ALL
-- Check cronogramas_of
SELECT 1 FROM public.cronogramas_of WHERE created_by = _user_id OR gestor_id = _user_id
UNION ALL
-- Check diarios_producao
SELECT 1 FROM public.diarios_producao WHERE created_by = _user_id
UNION ALL
-- Check contratos_obra
SELECT 1 FROM public.contratos_obra WHERE created_by = _user_id
UNION ALL
-- Check diario_obra_rdo
SELECT 1 FROM public.diario_obra_rdo WHERE usuario_rdo = _user_id
UNION ALL
-- Check catalogos
SELECT 1 FROM public.catalogos WHERE created_by = _user_id
UNION ALL
-- Check json_codes
SELECT 1 FROM public.json_codes WHERE created_by = _user_id
) INTO has_dependencies;
RETURN NOT has_dependencies;
END;
$$;
-- Create function to delete user and all related data (only if safe)
CREATE OR REPLACE FUNCTION public.admin_delete_user(_user_id uuid)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Check if current user is admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Only admins can delete users';
END IF;
-- Check if user can be safely deleted
IF NOT public.can_delete_user(_user_id) THEN
RAISE EXCEPTION 'User cannot be deleted due to existing dependencies';
END IF;
-- Delete from user_roles table
DELETE FROM public.user_roles WHERE user_id = _user_id;
-- Delete from profiles table
DELETE FROM public.profiles WHERE id = _user_id;
-- Delete from auth.users table (this will cascade)
DELETE FROM auth.users WHERE id = _user_id;
RETURN true;
END;
$$;
-- Grant execute permission to authenticated users (the function itself checks for admin role)
GRANT EXECUTE ON FUNCTION public.can_delete_user TO authenticated;
GRANT EXECUTE ON FUNCTION public.admin_delete_user TO authenticated;

View File

@@ -0,0 +1,44 @@
-- Update the function to be more permissive and only check truly critical dependencies
CREATE OR REPLACE FUNCTION public.can_delete_user(_user_id uuid)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
has_critical_dependencies boolean := false;
BEGIN
-- Only check for truly critical data that would break system integrity
SELECT EXISTS (
-- Check ficha_tecnica_contratos (critical business data)
SELECT 1 FROM public.ficha_tecnica_contratos WHERE user_id = _user_id
UNION ALL
-- Check ordens_fabricacao (critical business data)
SELECT 1 FROM public.ordens_fabricacao WHERE user_id = _user_id
UNION ALL
-- Check tasks that are assigned to user (but not created by - creation is less critical)
SELECT 1 FROM public.tasks WHERE assigned_to = _user_id
UNION ALL
-- Check pecas (critical manufacturing data)
SELECT 1 FROM public.pecas WHERE user_id = _user_id
UNION ALL
-- Check apontamentos_producao (critical production data)
SELECT 1 FROM public.apontamentos_producao WHERE created_by = _user_id
UNION ALL
-- Check cronogramas_of where user is gestor (critical role)
SELECT 1 FROM public.cronogramas_of WHERE gestor_id = _user_id
UNION ALL
-- Check diarios_producao (critical production records)
SELECT 1 FROM public.diarios_producao WHERE created_by = _user_id
UNION ALL
-- Check contratos_obra (critical business data)
SELECT 1 FROM public.contratos_obra WHERE created_by = _user_id
UNION ALL
-- Check diario_obra_rdo (critical work records)
SELECT 1 FROM public.diario_obra_rdo WHERE usuario_rdo = _user_id
) INTO has_critical_dependencies;
-- User can be deleted if they don't have critical dependencies
RETURN NOT has_critical_dependencies;
END;
$$;

View File

@@ -0,0 +1,157 @@
-- Create function to get detailed user dependencies
CREATE OR REPLACE FUNCTION public.get_user_dependencies(_user_id uuid)
RETURNS TABLE(
table_name text,
dependency_type text,
count bigint,
description text
)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT 'ficha_tecnica_contratos'::text, 'created'::text, COUNT(*)::bigint, 'Fichas técnicas criadas'::text
FROM public.ficha_tecnica_contratos WHERE user_id = _user_id
UNION ALL
SELECT 'ordens_fabricacao'::text, 'created'::text, COUNT(*)::bigint, 'Ordens de fabricação criadas'::text
FROM public.ordens_fabricacao WHERE user_id = _user_id
UNION ALL
SELECT 'tasks'::text, 'assigned'::text, COUNT(*)::bigint, 'Tarefas atribuídas'::text
FROM public.tasks WHERE assigned_to = _user_id
UNION ALL
SELECT 'tasks'::text, 'created'::text, COUNT(*)::bigint, 'Tarefas criadas'::text
FROM public.tasks WHERE created_by = _user_id
UNION ALL
SELECT 'pecas'::text, 'created'::text, COUNT(*)::bigint, 'Peças criadas'::text
FROM public.pecas WHERE user_id = _user_id
UNION ALL
SELECT 'apontamentos_producao'::text, 'created'::text, COUNT(*)::bigint, 'Apontamentos de produção'::text
FROM public.apontamentos_producao WHERE created_by = _user_id
UNION ALL
SELECT 'cronogramas_of'::text, 'manager'::text, COUNT(*)::bigint, 'Cronogramas como gestor'::text
FROM public.cronogramas_of WHERE gestor_id = _user_id
UNION ALL
SELECT 'cronogramas_of'::text, 'created'::text, COUNT(*)::bigint, 'Cronogramas criados'::text
FROM public.cronogramas_of WHERE created_by = _user_id
UNION ALL
SELECT 'diarios_producao'::text, 'created'::text, COUNT(*)::bigint, 'Diários de produção'::text
FROM public.diarios_producao WHERE created_by = _user_id
UNION ALL
SELECT 'contratos_obra'::text, 'created'::text, COUNT(*)::bigint, 'Contratos de obra'::text
FROM public.contratos_obra WHERE created_by = _user_id
UNION ALL
SELECT 'diario_obra_rdo'::text, 'responsible'::text, COUNT(*)::bigint, 'RDOs como responsável'::text
FROM public.diario_obra_rdo WHERE usuario_rdo = _user_id
UNION ALL
SELECT 'estoque_materiais'::text, 'created'::text, COUNT(*)::bigint, 'Materiais de estoque criados'::text
FROM public.estoque_materiais WHERE created_by = _user_id
UNION ALL
SELECT 'movimentacoes_estoque'::text, 'created'::text, COUNT(*)::bigint, 'Movimentações de estoque'::text
FROM public.movimentacoes_estoque WHERE created_by = _user_id
UNION ALL
SELECT 'empenhos_material'::text, 'created'::text, COUNT(*)::bigint, 'Empenhos de material'::text
FROM public.empenhos_material WHERE created_by = _user_id
UNION ALL
SELECT 'componentes_peca'::text, 'created'::text, COUNT(*)::bigint, 'Componentes de peça'::text
FROM public.componentes_peca WHERE user_id = _user_id;
END;
$$;
-- Update the can_delete_user function to be more lenient for new users
CREATE OR REPLACE FUNCTION public.can_delete_user(_user_id uuid)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
has_critical_dependencies boolean := false;
user_created_at timestamp with time zone;
BEGIN
-- Get when the user was created
SELECT created_at INTO user_created_at
FROM public.profiles WHERE id = _user_id;
-- If user was created very recently (less than 1 day ago) and has no critical data, allow deletion
IF user_created_at > (now() - interval '1 day') THEN
SELECT EXISTS (
-- Only check the most critical data for new users
SELECT 1 FROM public.ficha_tecnica_contratos WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.ordens_fabricacao WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.pecas WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.apontamentos_producao WHERE created_by = _user_id
) INTO has_critical_dependencies;
ELSE
-- For older users, check all critical dependencies
SELECT EXISTS (
SELECT 1 FROM public.ficha_tecnica_contratos WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.ordens_fabricacao WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.tasks WHERE assigned_to = _user_id
UNION ALL
SELECT 1 FROM public.pecas WHERE user_id = _user_id
UNION ALL
SELECT 1 FROM public.apontamentos_producao WHERE created_by = _user_id
UNION ALL
SELECT 1 FROM public.cronogramas_of WHERE gestor_id = _user_id
UNION ALL
SELECT 1 FROM public.diarios_producao WHERE created_by = _user_id
UNION ALL
SELECT 1 FROM public.contratos_obra WHERE created_by = _user_id
UNION ALL
SELECT 1 FROM public.diario_obra_rdo WHERE usuario_rdo = _user_id
) INTO has_critical_dependencies;
END IF;
RETURN NOT has_critical_dependencies;
END;
$$;
-- Create function to replace user references with "UserDel"
CREATE OR REPLACE FUNCTION public.replace_user_with_deleted(_user_id uuid)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
deleted_user_id uuid;
BEGIN
-- Check if current user is admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Only admins can replace user references';
END IF;
-- Create or get "UserDel" user in profiles
SELECT id INTO deleted_user_id
FROM public.profiles
WHERE email = 'user.deleted@system.internal';
IF deleted_user_id IS NULL THEN
-- Create the deleted user placeholder
INSERT INTO public.profiles (id, email, full_name, status)
VALUES (gen_random_uuid(), 'user.deleted@system.internal', 'Usuário Excluído', 'inactive')
RETURNING id INTO deleted_user_id;
END IF;
-- Replace user references in non-critical tables
UPDATE public.tasks SET created_by = deleted_user_id WHERE created_by = _user_id;
UPDATE public.cronogramas_of SET created_by = deleted_user_id WHERE created_by = _user_id;
UPDATE public.estoque_materiais SET created_by = deleted_user_id WHERE created_by = _user_id;
UPDATE public.movimentacoes_estoque SET created_by = deleted_user_id WHERE created_by = _user_id;
UPDATE public.empenhos_material SET created_by = deleted_user_id WHERE created_by = _user_id;
UPDATE public.componentes_peca SET user_id = deleted_user_id WHERE user_id = _user_id;
-- Note: We don't replace references in critical business tables like:
-- ficha_tecnica_contratos, ordens_fabricacao, pecas, apontamentos_producao
-- These should prevent deletion if they exist
END;
$$;
-- Grant execute permissions
GRANT EXECUTE ON FUNCTION public.get_user_dependencies TO authenticated;
GRANT EXECUTE ON FUNCTION public.replace_user_with_deleted TO authenticated;

View File

@@ -0,0 +1,31 @@
-- Criar tabela para registrar pedidos de redefinição de senha
CREATE TABLE public.password_reset_requests (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
email text NOT NULL,
user_id uuid REFERENCES auth.users(id),
requested_at timestamp with time zone NOT NULL DEFAULT now(),
status text NOT NULL DEFAULT 'pending',
ip_address text,
user_agent text,
created_at timestamp with time zone NOT NULL DEFAULT now()
);
-- Adicionar RLS (Row Level Security)
ALTER TABLE public.password_reset_requests ENABLE ROW LEVEL SECURITY;
-- Política para admins visualizarem todos os pedidos
CREATE POLICY "Admins can view all password reset requests"
ON public.password_reset_requests
FOR SELECT
USING (has_role(auth.uid(), 'admin'::app_role));
-- Política para permitir inserção de pedidos (qualquer um pode solicitar)
CREATE POLICY "Anyone can create password reset requests"
ON public.password_reset_requests
FOR INSERT
WITH CHECK (true);
-- Índice para melhorar performance nas consultas por email e data
CREATE INDEX idx_password_reset_requests_email ON public.password_reset_requests(email);
CREATE INDEX idx_password_reset_requests_requested_at ON public.password_reset_requests(requested_at DESC);

View File

@@ -0,0 +1,90 @@
-- Criar tabela para logs de sessão dos usuários
CREATE TABLE public.user_session_logs (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
session_start TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
session_end TIMESTAMP WITH TIME ZONE NULL,
duration_minutes INTEGER NULL,
ip_address INET NULL,
user_agent TEXT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Criar índices para performance
CREATE INDEX idx_user_session_logs_user_id ON public.user_session_logs(user_id);
CREATE INDEX idx_user_session_logs_session_start ON public.user_session_logs(session_start);
CREATE INDEX idx_user_session_logs_is_active ON public.user_session_logs(is_active);
-- Habilitar RLS
ALTER TABLE public.user_session_logs ENABLE ROW LEVEL SECURITY;
-- Políticas RLS
CREATE POLICY "Admins can view all session logs"
ON public.user_session_logs
FOR SELECT
USING (has_role(auth.uid(), 'admin'::app_role));
CREATE POLICY "System can insert session logs"
ON public.user_session_logs
FOR INSERT
WITH CHECK (true);
CREATE POLICY "System can update session logs"
ON public.user_session_logs
FOR UPDATE
USING (true);
-- Função para atualizar updated_at
CREATE OR REPLACE FUNCTION public.update_user_session_logs_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger para atualizar updated_at
CREATE TRIGGER update_user_session_logs_updated_at
BEFORE UPDATE ON public.user_session_logs
FOR EACH ROW
EXECUTE FUNCTION public.update_user_session_logs_updated_at();
-- Função para finalizar sessão automaticamente
CREATE OR REPLACE FUNCTION public.end_user_session(session_id UUID)
RETURNS VOID AS $$
BEGIN
UPDATE public.user_session_logs
SET
session_end = now(),
duration_minutes = EXTRACT(EPOCH FROM (now() - session_start)) / 60,
is_active = false
WHERE id = session_id AND is_active = true;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Função para obter usuários online
CREATE OR REPLACE FUNCTION public.get_online_users()
RETURNS TABLE(
user_id UUID,
email TEXT,
full_name TEXT,
avatar_url TEXT,
session_start TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.email,
p.full_name,
p.avatar_url,
usl.session_start
FROM public.profiles p
INNER JOIN public.user_session_logs usl ON p.id = usl.user_id
WHERE usl.is_active = true
AND usl.session_start > (now() - INTERVAL '30 minutes')
ORDER BY usl.session_start DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

View File

@@ -0,0 +1,25 @@
-- Corrigir a função get_online_users para usar profile_image_url em vez de avatar_url
CREATE OR REPLACE FUNCTION public.get_online_users()
RETURNS TABLE(
user_id UUID,
email TEXT,
full_name TEXT,
avatar_url TEXT,
session_start TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.email,
p.full_name,
p.profile_image_url as avatar_url,
usl.session_start
FROM public.profiles p
INNER JOIN public.user_session_logs usl ON p.id = usl.user_id
WHERE usl.is_active = true
AND usl.session_start > (now() - INTERVAL '30 minutes')
ORDER BY usl.session_start DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

View File

@@ -0,0 +1,63 @@
-- Adicionar coluna online na tabela profiles
ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS online boolean DEFAULT false;
-- Criar função para marcar usuário como online
CREATE OR REPLACE FUNCTION public.set_user_online(user_id_param uuid)
RETURNS void AS $$
BEGIN
UPDATE public.profiles
SET online = true, updated_at = now()
WHERE id = user_id_param;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Criar função para marcar usuário como offline
CREATE OR REPLACE FUNCTION public.set_user_offline(user_id_param uuid)
RETURNS void AS $$
BEGIN
UPDATE public.profiles
SET online = false, updated_at = now()
WHERE id = user_id_param;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Atualizar função get_online_users para usar a nova coluna
CREATE OR REPLACE FUNCTION public.get_online_users()
RETURNS TABLE(
user_id UUID,
email TEXT,
full_name TEXT,
avatar_url TEXT,
session_start TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.email,
p.full_name,
p.profile_image_url as avatar_url,
p.updated_at as session_start
FROM public.profiles p
WHERE p.online = true
ORDER BY p.updated_at DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Criar função para verificar e limpar usuários offline (executada periodicamente)
CREATE OR REPLACE FUNCTION public.cleanup_offline_users()
RETURNS void AS $$
BEGIN
-- Marcar como offline usuários que não tem sessão ativa há mais de 5 minutos
UPDATE public.profiles
SET online = false, updated_at = now()
WHERE online = true
AND id NOT IN (
SELECT DISTINCT usl.user_id
FROM public.user_session_logs usl
WHERE usl.is_active = true
AND usl.session_start > (now() - INTERVAL '5 minutes')
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

View File

@@ -0,0 +1,33 @@
-- Atualizar função para filtrar admins e desenvolvedores dos usuários online
CREATE OR REPLACE FUNCTION public.get_online_users()
RETURNS TABLE(
user_id UUID,
email TEXT,
full_name TEXT,
avatar_url TEXT,
session_start TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.email,
p.full_name,
p.profile_image_url as avatar_url,
p.updated_at as session_start
FROM public.profiles p
WHERE p.online = true
-- Excluir usuários com role de admin
AND NOT EXISTS (
SELECT 1 FROM public.user_roles ur
WHERE ur.user_id = p.id AND ur.role = 'admin'
)
-- Excluir usuários com função de Desenvolvedor
AND NOT EXISTS (
SELECT 1 FROM public.functions f
WHERE f.id = p.function_id AND f.name = 'Desenvolvedor'
)
ORDER BY p.updated_at DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

View File

@@ -0,0 +1,29 @@
-- Corrigir políticas RLS da tabela user_session_logs para permitir inserção e atualização pelo sistema
DROP POLICY IF EXISTS "System can insert session logs" ON public.user_session_logs;
DROP POLICY IF EXISTS "System can update session logs" ON public.user_session_logs;
-- Permitir que qualquer usuário autenticado insira logs (o sistema controlará via código quem pode inserir)
CREATE POLICY "Authenticated users can insert session logs"
ON public.user_session_logs
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Permitir que usuários atualizem seus próprios logs de sessão
CREATE POLICY "Users can update their own session logs"
ON public.user_session_logs
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id);
-- Permitir que admins vejam todos os logs, e usuários vejam apenas os seus próprios
DROP POLICY IF EXISTS "Admins can view all session logs" ON public.user_session_logs;
CREATE POLICY "Users can view session logs"
ON public.user_session_logs
FOR SELECT
TO authenticated
USING (
has_role(auth.uid(), 'admin'::app_role) OR auth.uid() = user_id
);

View File

@@ -0,0 +1,104 @@
-- Criar função para admins alterarem senhas de usuários
CREATE OR REPLACE FUNCTION public.admin_change_user_password(
user_id_param uuid,
new_password text
)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Verificar se o usuário atual é admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Apenas administradores podem alterar senhas de usuários';
END IF;
-- Atualizar a senha do usuário na tabela auth.users
UPDATE auth.users
SET
encrypted_password = crypt(new_password, gen_salt('bf')),
updated_at = now()
WHERE id = user_id_param;
IF NOT FOUND THEN
RAISE EXCEPTION 'Usuário não encontrado';
END IF;
RETURN true;
END;
$$;
-- Atualizar função de criação de usuário para usar senha padrão "usuario"
CREATE OR REPLACE FUNCTION public.admin_create_user(
user_email text,
user_full_name text DEFAULT NULL::text,
user_function_id uuid DEFAULT NULL::uuid,
user_privilege_id uuid DEFAULT NULL::uuid
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $$
DECLARE
new_user_id UUID;
temp_password TEXT := 'usuario'; -- Alterado de '1234' para 'usuario'
hashed_password TEXT;
BEGIN
-- Check if current user is admin
IF NOT public.has_role(auth.uid(), 'admin'::app_role) THEN
RAISE EXCEPTION 'Only admins can create new users';
END IF;
-- Check if email already exists
IF EXISTS (SELECT 1 FROM auth.users WHERE email = user_email) THEN
RAISE EXCEPTION 'User with this email already exists';
END IF;
-- Generate a hash for the password
hashed_password := crypt(temp_password, gen_salt('bf'));
-- Create user in auth.users
INSERT INTO auth.users (
instance_id,
id,
aud,
role,
email,
encrypted_password,
email_confirmed_at,
created_at,
updated_at,
confirmation_token,
email_change,
email_change_token_new,
recovery_token
) VALUES (
'00000000-0000-0000-0000-000000000000',
gen_random_uuid(),
'authenticated',
'authenticated',
user_email,
hashed_password,
now(),
now(),
now(),
'',
'',
'',
''
) RETURNING id INTO new_user_id;
-- Update the profile with additional information
UPDATE public.profiles
SET
full_name = user_full_name,
function_id = user_function_id,
privilege_id = user_privilege_id,
status = 'active'
WHERE id = new_user_id;
RETURN new_user_id;
END;
$$;

View File

@@ -0,0 +1,84 @@
-- Criar função auxiliar para verificar se o usuário tem permissões administrativas ou de colaborador
CREATE OR REPLACE FUNCTION public.user_can_access_ofs(_user_id uuid)
RETURNS BOOLEAN
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
user_is_admin boolean := false;
user_permissions record;
BEGIN
-- Verificar se é admin
SELECT EXISTS (
SELECT 1 FROM public.user_roles
WHERE user_id = _user_id AND role = 'admin'
) INTO user_is_admin;
IF user_is_admin THEN
RETURN true;
END IF;
-- Verificar privilégios funcionais (Admin ou qualquer permissão que não seja apenas visualização)
SELECT p.permissions INTO user_permissions
FROM public.profiles pr
JOIN public.privileges p ON pr.privilege_id = p.id
WHERE pr.id = _user_id;
-- Se tem privilégios administrativos ou de colaborador (qualquer coisa além de view_only)
IF user_permissions.permissions IS NOT NULL THEN
IF (user_permissions.permissions->>'can_admin')::boolean = true OR
(user_permissions.permissions->>'can_create_update_delete')::boolean = true OR
(user_permissions.permissions->>'can_create_only')::boolean = true THEN
RETURN true;
END IF;
END IF;
RETURN false;
END;
$$;
-- Atualizar política SELECT para ficha_tecnica_contratos
DROP POLICY IF EXISTS "Users can view their own fichas" ON public.ficha_tecnica_contratos;
CREATE POLICY "Users with permissions can view fichas"
ON public.ficha_tecnica_contratos
FOR SELECT
USING (
auth.uid() = user_id OR
public.user_can_access_ofs(auth.uid())
);
-- Atualizar política INSERT para ficha_tecnica_contratos
DROP POLICY IF EXISTS "Users can create their own fichas" ON public.ficha_tecnica_contratos;
CREATE POLICY "Users with permissions can create fichas"
ON public.ficha_tecnica_contratos
FOR INSERT
WITH CHECK (
public.user_can_access_ofs(auth.uid())
);
-- Atualizar política UPDATE para ficha_tecnica_contratos
DROP POLICY IF EXISTS "Users can update their own fichas" ON public.ficha_tecnica_contratos;
CREATE POLICY "Users with permissions can update fichas"
ON public.ficha_tecnica_contratos
FOR UPDATE
USING (
auth.uid() = user_id OR
public.user_can_access_ofs(auth.uid())
);
-- Atualizar política DELETE para ficha_tecnica_contratos
DROP POLICY IF EXISTS "Users can delete their own fichas" ON public.ficha_tecnica_contratos;
CREATE POLICY "Users with permissions can delete fichas"
ON public.ficha_tecnica_contratos
FOR DELETE
USING (
auth.uid() = user_id OR
public.user_can_access_ofs(auth.uid())
);

View File

@@ -0,0 +1,3 @@
-- Adicionar nova coluna sem_componentes na tabela pecas
ALTER TABLE pecas ADD COLUMN sem_componentes boolean DEFAULT false;

View File

@@ -0,0 +1,43 @@
-- Criar tabela para logs de backup
CREATE TABLE public.backup_logs (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
operation_type text NOT NULL CHECK (operation_type IN ('backup', 'restore')),
status text NOT NULL DEFAULT 'in_progress' CHECK (status IN ('in_progress', 'completed', 'failed')),
file_name text NOT NULL,
file_size bigint,
tables_count integer,
records_count integer,
started_at timestamp with time zone NOT NULL DEFAULT now(),
completed_at timestamp with time zone,
error_message text,
created_by uuid REFERENCES auth.users(id) NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now()
);
-- Habilitar RLS
ALTER TABLE public.backup_logs ENABLE ROW LEVEL SECURITY;
-- Política para admins poderem gerenciar logs de backup
CREATE POLICY "Only admins can manage backup logs"
ON public.backup_logs
FOR ALL
USING (has_role(auth.uid(), 'admin'::app_role));
-- Função para atualizar updated_at
CREATE OR REPLACE FUNCTION public.update_backup_logs_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.completed_at = now();
RETURN NEW;
END;
$function$;
-- Trigger para atualizar completed_at quando status muda para completed ou failed
CREATE TRIGGER update_backup_logs_completed_at
BEFORE UPDATE ON public.backup_logs
FOR EACH ROW
WHEN (OLD.status = 'in_progress' AND NEW.status IN ('completed', 'failed'))
EXECUTE FUNCTION public.update_backup_logs_updated_at();

View File

@@ -0,0 +1,102 @@
-- Criar tabela principal para prioridades de fabricação
CREATE TABLE public.prioridades_fabricacao (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
of_number TEXT NOT NULL,
etapa_fase TEXT NOT NULL,
prioridade_id UUID REFERENCES prioridades_config(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
created_by UUID REFERENCES profiles(id),
nome_prioridade TEXT NOT NULL,
ativo BOOLEAN DEFAULT true
);
-- Criar tabela para itens da prioridade
CREATE TABLE public.itens_prioridade_fabricacao (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
prioridade_fabricacao_id UUID REFERENCES prioridades_fabricacao(id) ON DELETE CASCADE,
peca_id UUID REFERENCES pecas(id),
quantidade_priorizada NUMERIC NOT NULL DEFAULT 0,
ordem_fabricacao INTEGER NOT NULL DEFAULT 1,
peso_total NUMERIC DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
-- Habilitar RLS nas tabelas
ALTER TABLE public.prioridades_fabricacao ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.itens_prioridade_fabricacao ENABLE ROW LEVEL SECURITY;
-- Criar políticas RLS para prioridades_fabricacao
CREATE POLICY "Usuários autenticados podem visualizar prioridades"
ON public.prioridades_fabricacao
FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem inserir prioridades"
ON public.prioridades_fabricacao
FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar prioridades"
ON public.prioridades_fabricacao
FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem deletar prioridades"
ON public.prioridades_fabricacao
FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Criar políticas RLS para itens_prioridade_fabricacao
CREATE POLICY "Usuários autenticados podem visualizar itens prioridade"
ON public.itens_prioridade_fabricacao
FOR SELECT
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem inserir itens prioridade"
ON public.itens_prioridade_fabricacao
FOR INSERT
WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar itens prioridade"
ON public.itens_prioridade_fabricacao
FOR UPDATE
USING (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem deletar itens prioridade"
ON public.itens_prioridade_fabricacao
FOR DELETE
USING (auth.uid() IS NOT NULL);
-- Criar trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_prioridades_fabricacao_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_prioridades_fabricacao_updated_at
BEFORE UPDATE ON public.prioridades_fabricacao
FOR EACH ROW
EXECUTE FUNCTION update_prioridades_fabricacao_updated_at();
CREATE OR REPLACE FUNCTION update_itens_prioridade_fabricacao_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_itens_prioridade_fabricacao_updated_at
BEFORE UPDATE ON public.itens_prioridade_fabricacao
FOR EACH ROW
EXECUTE FUNCTION update_itens_prioridade_fabricacao_updated_at();
-- Criar índices para performance
CREATE INDEX idx_prioridades_fabricacao_of_fase ON public.prioridades_fabricacao(of_number, etapa_fase);
CREATE INDEX idx_itens_prioridade_ordem ON public.itens_prioridade_fabricacao(prioridade_fabricacao_id, ordem_fabricacao);

View File

@@ -0,0 +1,48 @@
-- Inserir o resource 'producao-prioridades' na tabela interface_resources
INSERT INTO public.interface_resources (
resource_key,
resource_name,
route_path,
icon_name,
parent_key,
is_submenu,
order_index
) VALUES (
'producao-prioridades',
'Prioridades de Fabricação',
'/producao/prioridades',
'PriorityHigh',
'producao',
true,
2
);
-- Buscar o privilege_id do admin (assumindo que existe um privilege padrão para admin)
DO $$
DECLARE
admin_privilege_id UUID;
BEGIN
-- Tentar encontrar um privilege de admin existente
SELECT id INTO admin_privilege_id
FROM public.privileges
WHERE name ILIKE '%admin%'
LIMIT 1;
-- Se não encontrou, criar um privilege de admin
IF admin_privilege_id IS NULL THEN
INSERT INTO public.privileges (name, description, permissions)
VALUES (
'Administrador',
'Acesso completo ao sistema',
'{"can_admin": true, "can_create_update_delete": true, "can_create_only": false, "can_view_only": false}'::jsonb
)
RETURNING id INTO admin_privilege_id;
END IF;
-- Inserir a relação privilege_interface_resources para admin
INSERT INTO public.privilege_interface_resources (privilege_id, resource_key)
VALUES (admin_privilege_id, 'producao-prioridades')
ON CONFLICT (privilege_id, resource_key) DO NOTHING;
RAISE NOTICE 'Resource producao-prioridades criado e associado ao privilege admin: %', admin_privilege_id;
END $$;

View File

@@ -0,0 +1,122 @@
-- Criar função para verificar disponibilidade de peças antes de inserir/atualizar itens de prioridade
CREATE OR REPLACE FUNCTION verificar_disponibilidade_peca(
p_peca_id uuid,
p_quantidade_adicional numeric,
p_item_id uuid DEFAULT NULL -- Para updates, excluir o próprio item do cálculo
)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
quantidade_total_peca numeric;
quantidade_ja_priorizada numeric;
quantidade_disponivel numeric;
BEGIN
-- Buscar quantidade total da peça
SELECT quantidade INTO quantidade_total_peca
FROM pecas
WHERE id = p_peca_id;
IF quantidade_total_peca IS NULL THEN
RAISE EXCEPTION 'Peça não encontrada';
END IF;
-- Calcular quantidade já priorizada (excluindo o item atual se for update)
SELECT COALESCE(SUM(quantidade_priorizada), 0) INTO quantidade_ja_priorizada
FROM itens_prioridade_fabricacao
WHERE peca_id = p_peca_id
AND (p_item_id IS NULL OR id != p_item_id);
-- Calcular quantidade disponível
quantidade_disponivel := quantidade_total_peca - quantidade_ja_priorizada;
-- Verificar se a quantidade adicional não excede o disponível
IF p_quantidade_adicional > quantidade_disponivel THEN
RAISE EXCEPTION 'Quantidade solicitada (%) excede o disponível (%) para esta peça',
p_quantidade_adicional, quantidade_disponivel;
END IF;
RETURN true;
END;
$$;
-- Criar trigger para validar inserções
CREATE OR REPLACE FUNCTION trigger_validar_item_prioridade()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
-- Validar na inserção
IF TG_OP = 'INSERT' THEN
PERFORM verificar_disponibilidade_peca(NEW.peca_id, NEW.quantidade_priorizada);
RETURN NEW;
END IF;
-- Validar na atualização
IF TG_OP = 'UPDATE' THEN
PERFORM verificar_disponibilidade_peca(NEW.peca_id, NEW.quantidade_priorizada, NEW.id);
RETURN NEW;
END IF;
RETURN NULL;
END;
$$;
-- Aplicar o trigger na tabela
DROP TRIGGER IF EXISTS trigger_validar_disponibilidade_item_prioridade ON itens_prioridade_fabricacao;
CREATE TRIGGER trigger_validar_disponibilidade_item_prioridade
BEFORE INSERT OR UPDATE ON itens_prioridade_fabricacao
FOR EACH ROW
EXECUTE FUNCTION trigger_validar_item_prioridade();
-- Criar função otimizada para buscar peças disponíveis
CREATE OR REPLACE FUNCTION buscar_pecas_disponiveis_para_prioridade(
p_of_number text,
p_etapa_fase text
)
RETURNS TABLE(
id uuid,
marca text,
descricao text,
peso_unitario numeric,
quantidade numeric,
of_number text,
etapa_fase text,
quantidade_disponivel numeric,
user_id uuid,
created_at timestamp with time zone,
updated_at timestamp with time zone
)
LANGUAGE sql
SECURITY DEFINER
AS $$
WITH pecas_com_prioridades AS (
SELECT
p.*,
COALESCE(SUM(ipf.quantidade_priorizada), 0) as quantidade_total_priorizada
FROM pecas p
LEFT JOIN itens_prioridade_fabricacao ipf ON p.id = ipf.peca_id
LEFT JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
WHERE p.of_number = p_of_number
AND p.etapa_fase = p_etapa_fase
GROUP BY p.id, p.marca, p.descricao, p.peso_unitario, p.quantidade,
p.of_number, p.etapa_fase, p.user_id, p.created_at, p.updated_at
)
SELECT
pcp.id,
pcp.marca,
pcp.descricao,
pcp.peso_unitario,
pcp.quantidade,
pcp.of_number,
pcp.etapa_fase,
(pcp.quantidade - pcp.quantidade_total_priorizada) as quantidade_disponivel,
pcp.user_id,
pcp.created_at,
pcp.updated_at
FROM pecas_com_prioridades pcp
WHERE (pcp.quantidade - pcp.quantidade_total_priorizada) > 0
ORDER BY pcp.marca ASC;
$$;

View File

@@ -0,0 +1,259 @@
-- 1. Função para calcular a prioridade mais alta de uma peça
CREATE OR REPLACE FUNCTION calcular_prioridade_mais_alta_peca(p_peca_id uuid)
RETURNS text
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
prioridade_mais_alta text := 'P4'; -- Default
BEGIN
-- Buscar a prioridade mais alta desta peça em todas as prioridades de fabricação
SELECT
CASE
WHEN EXISTS (
SELECT 1 FROM itens_prioridade_fabricacao ipf
JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
JOIN prioridades_config pc ON pf.prioridade_id = pc.id
WHERE ipf.peca_id = p_peca_id AND pc.codigo = 'P1'
) THEN 'P1'
WHEN EXISTS (
SELECT 1 FROM itens_prioridade_fabricacao ipf
JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
JOIN prioridades_config pc ON pf.prioridade_id = pc.id
WHERE ipf.peca_id = p_peca_id AND pc.codigo = 'P2'
) THEN 'P2'
WHEN EXISTS (
SELECT 1 FROM itens_prioridade_fabricacao ipf
JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
JOIN prioridades_config pc ON pf.prioridade_id = pc.id
WHERE ipf.peca_id = p_peca_id AND pc.codigo = 'P3'
) THEN 'P3'
ELSE 'P4'
END
INTO prioridade_mais_alta;
RETURN prioridade_mais_alta;
END;
$$;
-- 2. Função para sincronizar prioridade da peça
CREATE OR REPLACE FUNCTION sincronizar_prioridade_peca()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
peca_id_afetada uuid;
nova_prioridade text;
BEGIN
-- Determinar qual peça foi afetada
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
peca_id_afetada := NEW.peca_id;
ELSIF TG_OP = 'DELETE' THEN
peca_id_afetada := OLD.peca_id;
END IF;
-- Calcular a nova prioridade mais alta para esta peça
nova_prioridade := calcular_prioridade_mais_alta_peca(peca_id_afetada);
-- Atualizar a coluna prioridade na tabela pecas
UPDATE pecas
SET prioridade = nova_prioridade,
updated_at = now()
WHERE id = peca_id_afetada;
-- Retornar o registro apropriado
IF TG_OP = 'DELETE' THEN
RETURN OLD;
ELSE
RETURN NEW;
END IF;
END;
$$;
-- 3. Criar trigger na tabela itens_prioridade_fabricacao
DROP TRIGGER IF EXISTS trigger_sincronizar_prioridade_peca ON itens_prioridade_fabricacao;
CREATE TRIGGER trigger_sincronizar_prioridade_peca
AFTER INSERT OR UPDATE OR DELETE ON itens_prioridade_fabricacao
FOR EACH ROW
EXECUTE FUNCTION sincronizar_prioridade_peca();
-- 4. Função para auto-criar prioridade P4 para peças novas
CREATE OR REPLACE FUNCTION auto_criar_prioridade_p4()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
prioridade_config_p4_id uuid;
prioridade_fabricacao_id uuid;
BEGIN
-- Garantir que a peça nova tenha prioridade P4
NEW.prioridade := 'P4';
-- Buscar ID da configuração de prioridade P4
SELECT id INTO prioridade_config_p4_id
FROM prioridades_config
WHERE codigo = 'P4';
IF prioridade_config_p4_id IS NULL THEN
RAISE EXCEPTION 'Configuração de prioridade P4 não encontrada';
END IF;
-- Buscar ou criar prioridade_fabricacao para P4 desta OF+fase
SELECT id INTO prioridade_fabricacao_id
FROM prioridades_fabricacao
WHERE of_number = NEW.of_number
AND etapa_fase = NEW.etapa_fase
AND prioridade_id = prioridade_config_p4_id;
-- Se não existe, criar a prioridade_fabricacao
IF prioridade_fabricacao_id IS NULL THEN
INSERT INTO prioridades_fabricacao (
of_number,
etapa_fase,
prioridade_id,
nome_prioridade,
ativo
) VALUES (
NEW.of_number,
NEW.etapa_fase,
prioridade_config_p4_id,
'P4 - Baixa',
true
) RETURNING id INTO prioridade_fabricacao_id;
END IF;
-- Criar item na prioridade P4 (será executado após o INSERT da peça)
-- Usar pg_notify para processar após o commit da transação
PERFORM pg_notify('nova_peca_p4', json_build_object(
'peca_id', NEW.id,
'prioridade_fabricacao_id', prioridade_fabricacao_id,
'peso_unitario', NEW.peso_unitario,
'quantidade', NEW.quantidade
)::text);
RETURN NEW;
END;
$$;
-- 5. Criar trigger na tabela pecas para auto-criar prioridade P4
DROP TRIGGER IF EXISTS trigger_auto_criar_prioridade_p4 ON pecas;
CREATE TRIGGER trigger_auto_criar_prioridade_p4
BEFORE INSERT ON pecas
FOR EACH ROW
EXECUTE FUNCTION auto_criar_prioridade_p4();
-- 6. Função para processar peças novas após commit
CREATE OR REPLACE FUNCTION processar_peca_nova_p4()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
notification_data json;
peca_existe boolean;
BEGIN
-- Parse dos dados da notificação
notification_data := NEW.payload::json;
-- Verificar se a peça ainda não está na prioridade P4
SELECT EXISTS(
SELECT 1 FROM itens_prioridade_fabricacao
WHERE peca_id = (notification_data->>'peca_id')::uuid
AND prioridade_fabricacao_id = (notification_data->>'prioridade_fabricacao_id')::uuid
) INTO peca_existe;
-- Se não existe, criar o item
IF NOT peca_existe THEN
INSERT INTO itens_prioridade_fabricacao (
peca_id,
prioridade_fabricacao_id,
quantidade_priorizada,
peso_total,
ordem_fabricacao
) VALUES (
(notification_data->>'peca_id')::uuid,
(notification_data->>'prioridade_fabricacao_id')::uuid,
(notification_data->>'quantidade')::numeric,
(notification_data->>'quantidade')::numeric * (notification_data->>'peso_unitario')::numeric,
1
);
END IF;
RETURN NEW;
END;
$$;
-- 7. Atualizar RLS na tabela pecas para impedir alteração manual da prioridade
DROP POLICY IF EXISTS "Users can update their own pecas" ON pecas;
CREATE POLICY "Users can update their own pecas" ON pecas
FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (
auth.uid() = user_id AND
-- Impedir alteração da coluna prioridade manualmente
(OLD.prioridade = NEW.prioridade OR NEW.prioridade IS NULL)
);
-- 8. Migrar dados existentes - atualizar prioridades das peças com base no sistema atual
DO $$
DECLARE
peca_record record;
BEGIN
-- Para cada peça existente, calcular e atualizar sua prioridade
FOR peca_record IN
SELECT DISTINCT id FROM pecas
LOOP
UPDATE pecas
SET prioridade = calcular_prioridade_mais_alta_peca(peca_record.id)
WHERE id = peca_record.id;
END LOOP;
END $$;
-- 9. Criar peças em P4 para aquelas que não estão em nenhuma prioridade
INSERT INTO itens_prioridade_fabricacao (
peca_id,
prioridade_fabricacao_id,
quantidade_priorizada,
peso_total,
ordem_fabricacao
)
SELECT DISTINCT
p.id as peca_id,
pf.id as prioridade_fabricacao_id,
p.quantidade as quantidade_priorizada,
p.quantidade * p.peso_unitario as peso_total,
1 as ordem_fabricacao
FROM pecas p
JOIN prioridades_config pc ON pc.codigo = 'P4'
LEFT JOIN LATERAL (
SELECT pf.id
FROM prioridades_fabricacao pf
WHERE pf.of_number = p.of_number
AND pf.etapa_fase = p.etapa_fase
AND pf.prioridade_id = pc.id
LIMIT 1
) pf ON true
LEFT JOIN itens_prioridade_fabricacao ipf ON ipf.peca_id = p.id
WHERE ipf.id IS NULL -- Peças que não estão em nenhuma prioridade
AND pf.id IS NOT NULL; -- Apenas se existir a prioridade_fabricacao
-- 10. Criar prioridades_fabricacao P4 para OF+fase que não existem
INSERT INTO prioridades_fabricacao (
of_number,
etapa_fase,
prioridade_id,
nome_prioridade,
ativo
)
SELECT DISTINCT
p.of_number,
p.etapa_fase,
pc.id as prioridade_id,
'P4 - Baixa' as nome_prioridade,
true as ativo
FROM pecas p
JOIN prioridades_config pc ON pc.codigo = 'P4'
LEFT JOIN prioridades_fabricacao pf ON pf.of_number = p.of_number
AND pf.etapa_fase = p.etapa_fase
AND pf.prioridade_id = pc.id
WHERE pf.id IS NULL;

View File

@@ -0,0 +1,41 @@
-- Create function to calculate priorities for all pieces in bulk
CREATE OR REPLACE FUNCTION public.calcular_prioridades_pecas_bulk()
RETURNS TABLE(peca_id uuid, prioridade_mais_alta text)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
WITH prioridades_hierarquia AS (
SELECT 'P1' as prioridade, 1 as ordem
UNION ALL
SELECT 'P2' as prioridade, 2 as ordem
UNION ALL
SELECT 'P3' as prioridade, 3 as ordem
UNION ALL
SELECT 'P4' as prioridade, 4 as ordem
),
pecas_com_prioridades AS (
SELECT
p.id as peca_id,
COALESCE(
(
SELECT pf.prioridade
FROM itens_prioridade_fabricacao ipf
JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
JOIN prioridades_hierarquia ph ON pf.prioridade = ph.prioridade
WHERE ipf.peca_id = p.id
ORDER BY ph.ordem ASC
LIMIT 1
),
'P4'
) as prioridade_mais_alta
FROM pecas p
)
SELECT
pcp.peca_id,
pcp.prioridade_mais_alta
FROM pecas_com_prioridades pcp;
END;
$$;

View File

@@ -0,0 +1,42 @@
-- Corrigir a função para buscar prioridades corretamente pela configuração
CREATE OR REPLACE FUNCTION public.calcular_prioridades_pecas_bulk()
RETURNS TABLE(peca_id uuid, prioridade_mais_alta text)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
WITH prioridades_hierarquia AS (
SELECT 'P1' as prioridade, 1 as ordem
UNION ALL
SELECT 'P2' as prioridade, 2 as ordem
UNION ALL
SELECT 'P3' as prioridade, 3 as ordem
UNION ALL
SELECT 'P4' as prioridade, 4 as ordem
),
pecas_com_prioridades AS (
SELECT
p.id as peca_id,
COALESCE(
(
SELECT pc.codigo
FROM itens_prioridade_fabricacao ipf
JOIN prioridades_fabricacao pf ON ipf.prioridade_fabricacao_id = pf.id
JOIN prioridades_config pc ON pf.prioridade_id = pc.id
JOIN prioridades_hierarquia ph ON pc.codigo = ph.prioridade
WHERE ipf.peca_id = p.id
ORDER BY ph.ordem ASC
LIMIT 1
),
'P4'
) as prioridade_mais_alta
FROM pecas p
)
SELECT
pcp.peca_id,
pcp.prioridade_mais_alta
FROM pecas_com_prioridades pcp;
END;
$$;

View File

@@ -0,0 +1,36 @@
-- Adicionar coluna de versão/revisão na tabela prioridades_fabricacao
ALTER TABLE public.prioridades_fabricacao
ADD COLUMN revisao integer DEFAULT 0,
ADD COLUMN data_ultima_modificacao timestamp with time zone DEFAULT now(),
ADD COLUMN modificado_por uuid REFERENCES auth.users(id);
-- Criar índice para otimizar consultas por OF, fase e revisão
CREATE INDEX idx_prioridades_fabricacao_of_fase_revisao
ON public.prioridades_fabricacao (of_number, etapa_fase, revisao);
-- Função para incrementar revisão automaticamente quando houver mudanças
CREATE OR REPLACE FUNCTION public.increment_prioridade_fabricacao_revision()
RETURNS TRIGGER AS $$
BEGIN
-- Verifica se houve alteração nos itens de prioridade
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN
-- Atualizar a revisão da prioridade_fabricacao correspondente
UPDATE public.prioridades_fabricacao
SET
revisao = COALESCE(revisao, 0) + 1,
data_ultima_modificacao = now(),
modificado_por = auth.uid()
WHERE id = COALESCE(NEW.prioridade_fabricacao_id, OLD.prioridade_fabricacao_id);
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;
-- Trigger para incrementar revisão automaticamente
CREATE TRIGGER trigger_increment_prioridade_fabricacao_revision
AFTER INSERT OR UPDATE OR DELETE
ON public.itens_prioridade_fabricacao
FOR EACH ROW
EXECUTE FUNCTION public.increment_prioridade_fabricacao_revision();

View File

@@ -0,0 +1,15 @@
-- Adicionar política RLS para permitir DELETE na tabela movimentacoes_estoque
CREATE POLICY "Usuários autenticados podem excluir movimentações"
ON public.movimentacoes_estoque
FOR DELETE
TO authenticated
USING (auth.uid() IS NOT NULL);
-- Adicionar política RLS para permitir UPDATE na tabela movimentacoes_estoque (caso seja necessário no futuro)
CREATE POLICY "Usuários autenticados podem atualizar movimentações"
ON public.movimentacoes_estoque
FOR UPDATE
TO authenticated
USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);

View File

@@ -0,0 +1,97 @@
-- Atualizar o trigger para processar exclusões de movimentações
DROP TRIGGER IF EXISTS trigger_processar_movimentacao_estoque ON public.movimentacoes_estoque;
-- Recriar a função com lógica de reversão para DELETE
CREATE OR REPLACE FUNCTION public.processar_movimentacao_estoque()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Lógica para INSERT (nova movimentação)
IF TG_OP = 'INSERT' THEN
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
-- Para ajustes, a quantidade pode ser positiva ou negativa
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
-- Para transferências, apenas atualiza as quantidades disponíveis
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
END IF;
RETURN NEW;
END IF;
-- Lógica para DELETE (reversão da movimentação)
IF TG_OP = 'DELETE' THEN
IF OLD.tipo_movimentacao = 'entrada' THEN
-- Reverter entrada: diminuir do total e disponível
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'saida' THEN
-- Reverter saída: aumentar no total e disponível
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + OLD.quantidade,
quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'empenho' THEN
-- Reverter empenho: aumentar disponível e diminuir empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade,
quantidade_empenhada = quantidade_empenhada - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter desempenho: diminuir disponível e aumentar empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - OLD.quantidade,
quantidade_empenhada = quantidade_empenhada + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'ajuste' THEN
-- Reverter ajuste
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'transferencia' THEN
-- Reverter transferência
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
END IF;
RETURN OLD;
END IF;
RETURN NULL;
END;
$function$;
-- Recriar o trigger para INSERT e DELETE
CREATE TRIGGER trigger_processar_movimentacao_estoque
AFTER INSERT OR DELETE ON public.movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION processar_movimentacao_estoque();

View File

@@ -0,0 +1,118 @@
-- 1. Criar índices para otimizar consultas de empenhos
CREATE INDEX IF NOT EXISTS idx_empenhos_material_of_number ON empenhos_material(of_number);
CREATE INDEX IF NOT EXISTS idx_empenhos_material_material_id ON empenhos_material(material_id);
CREATE INDEX IF NOT EXISTS idx_empenhos_material_status ON empenhos_material(status);
CREATE INDEX IF NOT EXISTS idx_movimentacoes_estoque_material_of ON movimentacoes_estoque(material_id, of_vinculada);
-- 2. Adicionar campos faltantes na tabela empenhos_material para controle completo
ALTER TABLE empenhos_material
ADD COLUMN IF NOT EXISTS lote TEXT,
ADD COLUMN IF NOT EXISTS observacoes TEXT,
ADD COLUMN IF NOT EXISTS movimentacao_empenho_id UUID REFERENCES movimentacoes_estoque(id);
-- 3. Atualizar trigger para sincronizar movimentações de empenho com a tabela empenhos_material
CREATE OR REPLACE FUNCTION sincronizar_empenhos_movimentacoes()
RETURNS TRIGGER AS $$
BEGIN
-- Para INSERT de movimentação tipo empenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'empenho' THEN
-- Inserir registro na tabela empenhos_material
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
lote,
observacoes,
movimentacao_empenho_id,
created_by,
status
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
NEW.lote,
NEW.observacoes,
NEW.id,
NEW.created_by,
'Empenhado'
);
RETURN NEW;
END IF;
-- Para INSERT de movimentação tipo desempenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'desempenho' THEN
-- Atualizar quantidade utilizada no empenho correspondente
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + NEW.quantidade
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
AND quantidade_utilizada + NEW.quantidade <= quantidade_empenhada;
-- Se quantidade utilizada = quantidade empenhada, marcar como finalizado
UPDATE empenhos_material
SET status = 'Finalizado'
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND quantidade_utilizada = quantidade_empenhada;
RETURN NEW;
END IF;
-- Para DELETE de movimentação tipo empenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'empenho' THEN
-- Remover registro da tabela empenhos_material
DELETE FROM empenhos_material
WHERE movimentacao_empenho_id = OLD.id;
RETURN OLD;
END IF;
-- Para DELETE de movimentação tipo desempenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter quantidade utilizada no empenho
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada - OLD.quantidade,
status = 'Empenhado'
WHERE material_id = OLD.material_id
AND of_number = OLD.of_vinculada;
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 4. Criar trigger para sincronização automática
DROP TRIGGER IF EXISTS trigger_sincronizar_empenhos ON movimentacoes_estoque;
CREATE TRIGGER trigger_sincronizar_empenhos
AFTER INSERT OR DELETE ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION sincronizar_empenhos_movimentacoes();
-- 5. Função para validar disponibilidade antes de empenho
CREATE OR REPLACE FUNCTION validar_disponibilidade_empenho(
p_material_id UUID,
p_quantidade NUMERIC
) RETURNS BOOLEAN AS $$
DECLARE
quantidade_disponivel NUMERIC;
BEGIN
SELECT em.quantidade_disponivel INTO quantidade_disponivel
FROM estoque_materiais em
WHERE em.id = p_material_id;
IF quantidade_disponivel IS NULL OR quantidade_disponivel < p_quantidade THEN
RAISE EXCEPTION 'Quantidade insuficiente para empenho. Disponível: %, Solicitado: %',
COALESCE(quantidade_disponivel, 0), p_quantidade;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- 6. Atualizar RLS para a nova estrutura
DROP POLICY IF EXISTS "Usuários autenticados podem excluir empenhos" ON empenhos_material;
CREATE POLICY "Usuários autenticados podem excluir empenhos"
ON empenhos_material FOR DELETE
USING (auth.uid() IS NOT NULL);

View File

@@ -0,0 +1,213 @@
-- 1. Primeiro, vamos corrigir os triggers existentes e garantir consistência
-- Remover triggers existentes que podem estar causando problemas
DROP TRIGGER IF EXISTS trigger_processar_movimentacao_estoque ON movimentacoes_estoque;
DROP TRIGGER IF EXISTS trigger_sincronizar_empenhos_movimentacoes ON movimentacoes_estoque;
-- Criar função consolidada para processar movimentações de estoque
CREATE OR REPLACE FUNCTION public.processar_movimentacao_estoque_consolidada()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Para INSERT (nova movimentação)
IF TG_OP = 'INSERT' THEN
-- Atualizar quantidades no estoque baseado no tipo de movimentação
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
-- Para empenho: diminuir disponível, aumentar empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
-- Criar registro de empenho automaticamente
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
quantidade_utilizada,
lote,
observacoes,
status,
data_empenho,
created_by,
movimentacao_empenho_id
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
0,
NEW.lote,
NEW.observacoes,
'Empenhado',
NEW.data_movimentacao,
NEW.created_by,
NEW.id
);
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
-- Para desempenho: aumentar disponível, diminuir empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
-- Atualizar quantidade utilizada nos empenhos existentes
WITH empenhos_ordenados AS (
SELECT id, quantidade_empenhada, quantidade_utilizada,
(quantidade_empenhada - quantidade_utilizada) as disponivel_para_utilizar
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
AND quantidade_utilizada < quantidade_empenhada
ORDER BY data_empenho ASC
),
atualizacao AS (
SELECT id,
CASE
WHEN NEW.quantidade <= disponivel_para_utilizar THEN NEW.quantidade
ELSE disponivel_para_utilizar
END as qtd_a_utilizar
FROM empenhos_ordenados
LIMIT 1
)
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + atualizacao.qtd_a_utilizar,
status = CASE
WHEN quantidade_utilizada + atualizacao.qtd_a_utilizar >= quantidade_empenhada
THEN 'Finalizado'
ELSE 'Empenhado'
END
FROM atualizacao
WHERE empenhos_material.id = atualizacao.id;
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
END IF;
RETURN NEW;
END IF;
-- Para DELETE (reversão da movimentação)
IF TG_OP = 'DELETE' THEN
-- Reverter as operações baseado no tipo de movimentação
IF OLD.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + OLD.quantidade,
quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'empenho' THEN
-- Reverter empenho: aumentar disponível, diminuir empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade,
quantidade_empenhada = quantidade_empenhada - OLD.quantidade
WHERE id = OLD.material_id;
-- Excluir o empenho vinculado
DELETE FROM empenhos_material
WHERE movimentacao_empenho_id = OLD.id;
ELSIF OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter desempenho: diminuir disponível, aumentar empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - OLD.quantidade,
quantidade_empenhada = quantidade_empenhada + OLD.quantidade
WHERE id = OLD.material_id;
-- Reverter quantidade utilizada nos empenhos
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada - OLD.quantidade,
status = 'Empenhado'
WHERE material_id = OLD.material_id
AND of_number = OLD.of_vinculada
AND quantidade_utilizada >= OLD.quantidade;
ELSIF OLD.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'transferencia' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
END IF;
RETURN OLD;
END IF;
RETURN NULL;
END;
$function$;
-- Criar o trigger consolidado
CREATE TRIGGER trigger_processar_movimentacao_estoque_consolidada
BEFORE INSERT OR DELETE ON movimentacoes_estoque
FOR EACH ROW EXECUTE FUNCTION processar_movimentacao_estoque_consolidada();
-- Função para limpar dados inconsistentes
CREATE OR REPLACE FUNCTION public.limpar_dados_inconsistentes_estoque()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
-- Recalcular quantidades empenhadas baseado na tabela de empenhos
UPDATE estoque_materiais
SET quantidade_empenhada = COALESCE(
(SELECT SUM(quantidade_empenhada - quantidade_utilizada)
FROM empenhos_material
WHERE material_id = estoque_materiais.id
AND status = 'Empenhado'),
0
);
-- Remover empenhos órfãos (sem movimentação vinculada)
DELETE FROM empenhos_material
WHERE movimentacao_empenho_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM movimentacoes_estoque
WHERE id = empenhos_material.movimentacao_empenho_id
);
-- Remover movimentações órfãs de empenho (sem material)
DELETE FROM movimentacoes_estoque
WHERE tipo_movimentacao = 'empenho'
AND NOT EXISTS (
SELECT 1 FROM estoque_materiais
WHERE id = movimentacoes_estoque.material_id
);
END;
$function$;
-- Executar limpeza de dados inconsistentes
SELECT limpar_dados_inconsistentes_estoque();

View File

@@ -0,0 +1,95 @@
-- Primeiro, vamos dropar o trigger existente que está causando o problema
DROP TRIGGER IF EXISTS trigger_sincronizar_empenhos_movimentacoes ON movimentacoes_estoque;
-- Recriar o trigger para executar APÓS as operações (AFTER)
CREATE TRIGGER trigger_sincronizar_empenhos_movimentacoes
AFTER INSERT OR DELETE ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION sincronizar_empenhos_movimentacoes();
-- Também vamos atualizar a função para garantir que funcione corretamente
CREATE OR REPLACE FUNCTION public.sincronizar_empenhos_movimentacoes()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Para INSERT de movimentação tipo empenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'empenho' THEN
-- Inserir registro na tabela empenhos_material
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
quantidade_utilizada,
lote,
observacoes,
movimentacao_empenho_id,
created_by,
status,
data_empenho
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
0,
NEW.lote,
NEW.observacoes,
NEW.id,
NEW.created_by,
'Empenhado',
NEW.data_movimentacao
);
RETURN NEW;
END IF;
-- Para INSERT de movimentação tipo desempenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'desempenho' THEN
-- Atualizar quantidade utilizada no empenho correspondente
WITH empenhos_ordenados AS (
SELECT id, quantidade_empenhada, quantidade_utilizada,
(quantidade_empenhada - quantidade_utilizada) as disponivel_para_utilizar
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
AND quantidade_utilizada < quantidade_empenhada
ORDER BY data_empenho ASC
LIMIT 1
)
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar),
status = CASE
WHEN quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar) >= quantidade_empenhada
THEN 'Finalizado'
ELSE 'Empenhado'
END
FROM empenhos_ordenados
WHERE empenhos_material.id = empenhos_ordenados.id;
RETURN NEW;
END IF;
-- Para DELETE de movimentação tipo empenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'empenho' THEN
-- Remover registro da tabela empenhos_material
DELETE FROM empenhos_material
WHERE movimentacao_empenho_id = OLD.id;
RETURN OLD;
END IF;
-- Para DELETE de movimentação tipo desempenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter quantidade utilizada no empenho
UPDATE empenhos_material
SET quantidade_utilizada = GREATEST(0, quantidade_utilizada - OLD.quantidade),
status = 'Empenhado'
WHERE material_id = OLD.material_id
AND of_number = OLD.of_vinculada
AND quantidade_utilizada > 0;
RETURN OLD;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$function$;

View File

@@ -0,0 +1,106 @@
-- Primeiro, vamos verificar e corrigir a foreign key constraint
-- Vamos dropar a constraint problemática e recriar corretamente
-- Dropar a constraint existente se ela existir
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS empenhos_material_movimentacao_empenho_id_fkey;
-- Recriar a constraint como DEFERRABLE para permitir que seja verificada ao final da transação
ALTER TABLE empenhos_material
ADD CONSTRAINT empenhos_material_movimentacao_empenho_id_fkey
FOREIGN KEY (movimentacao_empenho_id)
REFERENCES movimentacoes_estoque(id)
ON DELETE SET NULL
DEFERRABLE INITIALLY DEFERRED;
-- Também vamos ajustar a função para ser mais robusta
CREATE OR REPLACE FUNCTION public.sincronizar_empenhos_movimentacoes()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Para INSERT de movimentação tipo empenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'empenho' THEN
-- Aguardar um momento para garantir que o registro está commitado
PERFORM pg_sleep(0.001);
-- Verificar se o registro da movimentação realmente existe
IF EXISTS (SELECT 1 FROM movimentacoes_estoque WHERE id = NEW.id) THEN
-- Inserir registro na tabela empenhos_material
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
quantidade_utilizada,
lote,
observacoes,
movimentacao_empenho_id,
created_by,
status,
data_empenho
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
0,
NEW.lote,
NEW.observacoes,
NEW.id,
NEW.created_by,
'Empenhado',
NEW.data_movimentacao
);
END IF;
RETURN NEW;
END IF;
-- Para INSERT de movimentação tipo desempenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'desempenho' THEN
-- Atualizar quantidade utilizada no empenho correspondente
WITH empenhos_ordenados AS (
SELECT id, quantidade_empenhada, quantidade_utilizada,
(quantidade_empenhada - quantidade_utilizada) as disponivel_para_utilizar
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
AND quantidade_utilizada < quantidade_empenhada
ORDER BY data_empenho ASC
LIMIT 1
)
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar),
status = CASE
WHEN quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar) >= quantidade_empenhada
THEN 'Finalizado'
ELSE 'Empenhado'
END
FROM empenhos_ordenados
WHERE empenhos_material.id = empenhos_ordenados.id;
RETURN NEW;
END IF;
-- Para DELETE de movimentação tipo empenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'empenho' THEN
-- Remover registro da tabela empenhos_material
DELETE FROM empenhos_material
WHERE movimentacao_empenho_id = OLD.id;
RETURN OLD;
END IF;
-- Para DELETE de movimentação tipo desempenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter quantidade utilizada no empenho
UPDATE empenhos_material
SET quantidade_utilizada = GREATEST(0, quantidade_utilizada - OLD.quantidade),
status = 'Empenhado'
WHERE material_id = OLD.material_id
AND of_number = OLD.of_vinculada
AND quantidade_utilizada > 0;
RETURN OLD;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$function$;

View File

@@ -0,0 +1,125 @@
-- Remover a constraint única que está causando o problema
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS empenhos_material_of_number_material_id_key;
-- Criar nova função que consolida empenhos ao invés de criar duplicados
CREATE OR REPLACE FUNCTION public.sincronizar_empenhos_movimentacoes()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
empenho_existente_id UUID;
quantidade_atual NUMERIC;
BEGIN
-- Para INSERT de movimentação tipo empenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'empenho' THEN
-- Verificar se já existe empenho para este material+OF
SELECT id, quantidade_empenhada INTO empenho_existente_id, quantidade_atual
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
LIMIT 1;
IF empenho_existente_id IS NOT NULL THEN
-- Atualizar empenho existente
UPDATE empenhos_material
SET quantidade_empenhada = quantidade_empenhada + NEW.quantidade,
movimentacao_empenho_id = NEW.id,
observacoes = COALESCE(observacoes, '') ||
CASE WHEN observacoes IS NOT NULL AND observacoes != ''
THEN '; ' || COALESCE(NEW.observacoes, '')
ELSE COALESCE(NEW.observacoes, '') END
WHERE id = empenho_existente_id;
ELSE
-- Criar novo empenho
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
quantidade_utilizada,
lote,
observacoes,
movimentacao_empenho_id,
created_by,
status,
data_empenho
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
0,
NEW.lote,
NEW.observacoes,
NEW.id,
NEW.created_by,
'Empenhado',
NEW.data_movimentacao
);
END IF;
RETURN NEW;
END IF;
-- Para INSERT de movimentação tipo desempenho
IF TG_OP = 'INSERT' AND NEW.tipo_movimentacao = 'desempenho' THEN
-- Atualizar quantidade utilizada no empenho correspondente
WITH empenhos_ordenados AS (
SELECT id, quantidade_empenhada, quantidade_utilizada,
(quantidade_empenhada - quantidade_utilizada) as disponivel_para_utilizar
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
AND quantidade_utilizada < quantidade_empenhada
ORDER BY data_empenho ASC
LIMIT 1
)
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar),
status = CASE
WHEN quantidade_utilizada + LEAST(NEW.quantidade, empenhos_ordenados.disponivel_para_utilizar) >= quantidade_empenhada
THEN 'Finalizado'
ELSE 'Empenhado'
END
FROM empenhos_ordenados
WHERE empenhos_material.id = empenhos_ordenados.id;
RETURN NEW;
END IF;
-- Para DELETE de movimentação tipo empenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'empenho' THEN
-- Diminuir quantidade empenhada ou remover se for a única
WITH empenho_atual AS (
SELECT id, quantidade_empenhada
FROM empenhos_material
WHERE movimentacao_empenho_id = OLD.id
)
UPDATE empenhos_material
SET quantidade_empenhada = quantidade_empenhada - OLD.quantidade
FROM empenho_atual
WHERE empenhos_material.id = empenho_atual.id;
-- Remover empenho se quantidade chegou a zero
DELETE FROM empenhos_material
WHERE quantidade_empenhada <= 0 AND movimentacao_empenho_id = OLD.id;
RETURN OLD;
END IF;
-- Para DELETE de movimentação tipo desempenho
IF TG_OP = 'DELETE' AND OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter quantidade utilizada no empenho
UPDATE empenhos_material
SET quantidade_utilizada = GREATEST(0, quantidade_utilizada - OLD.quantidade),
status = 'Empenhado'
WHERE material_id = OLD.material_id
AND of_number = OLD.of_vinculada
AND quantidade_utilizada > 0;
RETURN OLD;
END IF;
RETURN COALESCE(NEW, OLD);
END;
$function$;

View File

@@ -0,0 +1,223 @@
-- Primeiro, vamos remover os triggers existentes para evitar conflitos
DROP TRIGGER IF EXISTS trigger_processar_movimentacao ON movimentacoes_estoque;
DROP TRIGGER IF EXISTS trigger_sincronizar_empenhos ON movimentacoes_estoque;
-- Remover as funções antigas
DROP FUNCTION IF EXISTS public.processar_movimentacao_estoque_consolidada();
DROP FUNCTION IF EXISTS public.sincronizar_empenhos_movimentacoes();
-- Criar uma nova função consolidada e mais robusta
CREATE OR REPLACE FUNCTION public.processar_movimentacao_estoque()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
empenho_existente_id UUID;
quantidade_atual NUMERIC;
BEGIN
-- Lógica para INSERT (nova movimentação)
IF TG_OP = 'INSERT' THEN
-- Processar movimentações de estoque
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
-- Para empenho: diminuir disponível, aumentar empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
-- Verificar se já existe empenho para este material+OF
SELECT em.id, em.quantidade_empenhada
INTO empenho_existente_id, quantidade_atual
FROM empenhos_material em
WHERE em.material_id = NEW.material_id
AND em.of_number = NEW.of_vinculada
AND em.status = 'Empenhado'
LIMIT 1;
IF empenho_existente_id IS NOT NULL THEN
-- Atualizar empenho existente
UPDATE empenhos_material
SET quantidade_empenhada = quantidade_empenhada + NEW.quantidade,
observacoes = CASE
WHEN observacoes IS NOT NULL AND observacoes != ''
THEN observacoes || '; ' || COALESCE(NEW.observacoes, '')
ELSE COALESCE(NEW.observacoes, '')
END
WHERE id = empenho_existente_id;
ELSE
-- Criar novo empenho
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
quantidade_utilizada,
lote,
observacoes,
movimentacao_empenho_id,
created_by,
status,
data_empenho
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
0,
NEW.lote,
NEW.observacoes,
NEW.id,
NEW.created_by,
'Empenhado',
NEW.data_movimentacao
);
END IF;
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
-- Para desempenho: aumentar disponível, diminuir empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
-- Atualizar quantidade utilizada nos empenhos existentes (FIFO)
UPDATE empenhos_material em1
SET quantidade_utilizada = quantidade_utilizada + LEAST(
NEW.quantidade - COALESCE((
SELECT SUM(em2.quantidade_empenhada - em2.quantidade_utilizada)
FROM empenhos_material em2
WHERE em2.material_id = NEW.material_id
AND em2.of_number = NEW.of_vinculada
AND em2.status = 'Empenhado'
AND em2.data_empenho < em1.data_empenho
), 0),
em1.quantidade_empenhada - em1.quantidade_utilizada
),
status = CASE
WHEN quantidade_utilizada + LEAST(
NEW.quantidade - COALESCE((
SELECT SUM(em3.quantidade_empenhada - em3.quantidade_utilizada)
FROM empenhos_material em3
WHERE em3.material_id = NEW.material_id
AND em3.of_number = NEW.of_vinculada
AND em3.status = 'Empenhado'
AND em3.data_empenho < em1.data_empenho
), 0),
em1.quantidade_empenhada - em1.quantidade_utilizada
) >= em1.quantidade_empenhada
THEN 'Finalizado'
ELSE 'Empenhado'
END
WHERE em1.material_id = NEW.material_id
AND em1.of_number = NEW.of_vinculada
AND em1.status = 'Empenhado'
AND em1.quantidade_utilizada < em1.quantidade_empenhada;
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
END IF;
RETURN NEW;
END IF;
-- Lógica para DELETE (reversão da movimentação)
IF TG_OP = 'DELETE' THEN
IF OLD.tipo_movimentacao = 'entrada' THEN
-- Reverter entrada: diminuir do total e disponível
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'saida' THEN
-- Reverter saída: aumentar no total e disponível
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + OLD.quantidade,
quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'empenho' THEN
-- Reverter empenho: aumentar disponível e diminuir empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade,
quantidade_empenhada = quantidade_empenhada - OLD.quantidade
WHERE id = OLD.material_id;
-- Diminuir quantidade empenhada ou remover empenho
UPDATE empenhos_material
SET quantidade_empenhada = quantidade_empenhada - OLD.quantidade
WHERE movimentacao_empenho_id = OLD.id;
-- Remover empenhos com quantidade zero
DELETE FROM empenhos_material
WHERE quantidade_empenhada <= 0
AND movimentacao_empenho_id = OLD.id;
ELSIF OLD.tipo_movimentacao = 'desempenho' THEN
-- Reverter desempenho: diminuir disponível e aumentar empenhada
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - OLD.quantidade,
quantidade_empenhada = quantidade_empenhada + OLD.quantidade
WHERE id = OLD.material_id;
-- Reverter quantidade utilizada no empenho
UPDATE empenhos_material em
SET quantidade_utilizada = GREATEST(0, em.quantidade_utilizada - OLD.quantidade),
status = 'Empenhado'
WHERE em.material_id = OLD.material_id
AND em.of_number = OLD.of_vinculada
AND em.quantidade_utilizada > 0;
ELSIF OLD.tipo_movimentacao = 'ajuste' THEN
-- Reverter ajuste
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'transferencia' THEN
-- Reverter transferência
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
END IF;
RETURN OLD;
END IF;
RETURN NULL;
END;
$function$;
-- Recriar o trigger principal
CREATE TRIGGER trigger_processar_movimentacao_estoque
BEFORE INSERT OR DELETE ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION processar_movimentacao_estoque();
-- Garantir que não há conflitos de constraint
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS empenhos_material_of_number_material_id_key;
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS empenhos_material_movimentacao_empenho_id_key;
-- Criar índices para melhorar performance
CREATE INDEX IF NOT EXISTS idx_empenhos_material_id_of_status ON empenhos_material(material_id, of_number, status);
CREATE INDEX IF NOT EXISTS idx_movimentacoes_material_tipo ON movimentacoes_estoque(material_id, tipo_movimentacao);

View File

@@ -0,0 +1,162 @@
-- Primeiro, vamos remover todos os triggers e constraints que podem estar causando problemas
DROP TRIGGER IF EXISTS trigger_processar_movimentacao_estoque ON movimentacoes_estoque;
DROP TRIGGER IF EXISTS trigger_sincronizar_empenhos ON movimentacoes_estoque;
DROP TRIGGER IF EXISTS trigger_processar_movimentacao ON movimentacoes_estoque;
-- Remover constraints de foreign key se existirem
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS empenhos_material_movimentacao_empenho_id_fkey;
ALTER TABLE empenhos_material DROP CONSTRAINT IF EXISTS fk_movimentacao_empenho;
-- Limpar a coluna de referência na tabela empenhos_material
UPDATE empenhos_material SET movimentacao_empenho_id = NULL WHERE movimentacao_empenho_id IS NOT NULL;
-- Tentar limpar dados da tabela movimentacoes_estoque
DELETE FROM movimentacoes_estoque;
-- Se ainda não conseguir, vamos recriar a tabela completamente
DROP TABLE IF EXISTS movimentacoes_estoque CASCADE;
-- Recriar a tabela movimentacoes_estoque sem relacionamentos problemáticos
CREATE TABLE public.movimentacoes_estoque (
id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
material_id uuid NOT NULL,
tipo_movimentacao text NOT NULL CHECK (tipo_movimentacao IN ('entrada', 'saida', 'transferencia', 'ajuste', 'empenho', 'desempenho')),
quantidade numeric NOT NULL DEFAULT 0,
valor_unitario numeric,
valor_total numeric,
lote text,
fornecedor text,
nota_fiscal text,
of_vinculada text,
observacoes text,
data_movimentacao date DEFAULT CURRENT_DATE,
created_at timestamp with time zone DEFAULT now(),
created_by uuid
);
-- Recriar as políticas RLS
ALTER TABLE public.movimentacoes_estoque ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Todos podem visualizar movimentações" ON public.movimentacoes_estoque
FOR SELECT USING (true);
CREATE POLICY "Usuários autenticados podem inserir movimentações" ON public.movimentacoes_estoque
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem atualizar movimentações" ON public.movimentacoes_estoque
FOR UPDATE USING (auth.uid() IS NOT NULL) WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "Usuários autenticados podem excluir movimentações" ON public.movimentacoes_estoque
FOR DELETE USING (auth.uid() IS NOT NULL);
-- Remover a coluna movimentacao_empenho_id da tabela empenhos_material se existir
ALTER TABLE empenhos_material DROP COLUMN IF EXISTS movimentacao_empenho_id;
-- Limpar também os dados da tabela empenhos_material para evitar conflitos
DELETE FROM empenhos_material;
-- Recriar trigger mais simples apenas para estoque (sem empenhos automáticos)
CREATE OR REPLACE FUNCTION public.processar_movimentacao_estoque_simples()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Lógica para INSERT (nova movimentação)
IF TG_OP = 'INSERT' THEN
IF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'empenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade,
quantidade_empenhada = quantidade_empenhada + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + NEW.quantidade,
quantidade_empenhada = quantidade_empenhada - NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
END IF;
RETURN NEW;
END IF;
-- Lógica para DELETE (reversão da movimentação)
IF TG_OP = 'DELETE' THEN
IF OLD.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + OLD.quantidade,
quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'empenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade,
quantidade_empenhada = quantidade_empenhada - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'desempenho' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel - OLD.quantidade,
quantidade_empenhada = quantidade_empenhada + OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel - OLD.quantidade
WHERE id = OLD.material_id;
ELSIF OLD.tipo_movimentacao = 'transferencia' THEN
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
END IF;
RETURN OLD;
END IF;
RETURN NULL;
END;
$function$;
-- Recriar o trigger simplificado
CREATE TRIGGER trigger_processar_movimentacao_estoque_simples
BEFORE INSERT OR DELETE ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION processar_movimentacao_estoque_simples();
-- Limpar quantidades empenhadas no estoque para começar limpo
UPDATE estoque_materiais SET quantidade_empenhada = 0;
-- Recriar índices necessários
CREATE INDEX IF NOT EXISTS idx_movimentacoes_material_tipo ON movimentacoes_estoque(material_id, tipo_movimentacao);
CREATE INDEX IF NOT EXISTS idx_movimentacoes_data ON movimentacoes_estoque(data_movimentacao);

View File

@@ -0,0 +1,37 @@
-- Verificar relacionamento entre tabelas e criar trigger para reverter empenhos automaticamente
-- Primeiro, vamos limpar dados inconsistentes como solicitado
DELETE FROM empenhos_material;
DELETE FROM movimentacoes_estoque;
-- Criar trigger para reverter automaticamente empenhos quando movimentação de empenho é excluída
CREATE OR REPLACE FUNCTION reverter_empenho_na_exclusao()
RETURNS TRIGGER AS $$
BEGIN
-- Se a movimentação excluída for do tipo 'empenho', reverter o empenho correspondente
IF OLD.tipo_movimentacao = 'empenho' THEN
-- Buscar e cancelar empenho vinculado a esta movimentação
UPDATE empenhos_material
SET status = 'Cancelado',
movimentacao_empenho_id = NULL
WHERE movimentacao_empenho_id = OLD.id;
-- Reverter quantidades no estoque
UPDATE estoque_materiais
SET quantidade_empenhada = quantidade_empenhada - OLD.quantidade,
quantidade_disponivel = quantidade_disponivel + OLD.quantidade
WHERE id = OLD.material_id;
RAISE NOTICE 'Empenho revertido para movimentação %', OLD.id;
END IF;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
-- Criar trigger que será executado ANTES da exclusão
DROP TRIGGER IF EXISTS trigger_reverter_empenho_exclusao ON movimentacoes_estoque;
CREATE TRIGGER trigger_reverter_empenho_exclusao
BEFORE DELETE ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION reverter_empenho_na_exclusao();

View File

@@ -0,0 +1,119 @@
-- Corrigir lógica de empenhos e relacionamentos entre tabelas
-- Primeiro, corrigir a função trigger para atualizar estoque
CREATE OR REPLACE FUNCTION atualizar_estoque_movimentacao()
RETURNS TRIGGER AS $$
DECLARE
empenho_record RECORD;
new_empenho_id UUID;
BEGIN
-- Para movimentações do tipo empenho
IF NEW.tipo_movimentacao = 'empenho' THEN
-- Atualizar estoque
UPDATE estoque_materiais
SET quantidade_empenhada = quantidade_empenhada + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
-- Criar registro na tabela empenhos_material DEPOIS da movimentação ser criada
INSERT INTO empenhos_material (
material_id,
of_number,
quantidade_empenhada,
data_empenho,
lote,
observacoes,
created_by,
status,
movimentacao_empenho_id
) VALUES (
NEW.material_id,
NEW.of_vinculada,
NEW.quantidade,
NEW.data_movimentacao,
NEW.lote,
NEW.observacoes,
NEW.created_by,
'Empenhado',
NEW.id
) RETURNING id INTO new_empenho_id;
-- Para movimentações do tipo desempenho
ELSIF NEW.tipo_movimentacao = 'desempenho' THEN
UPDATE estoque_materiais
SET quantidade_empenhada = quantidade_empenhada - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
-- Buscar empenho mais antigo para atualizar
SELECT * INTO empenho_record
FROM empenhos_material
WHERE material_id = NEW.material_id
AND of_number = NEW.of_vinculada
AND status = 'Empenhado'
ORDER BY created_at ASC
LIMIT 1;
-- Atualizar empenho encontrado
IF FOUND THEN
UPDATE empenhos_material
SET quantidade_utilizada = quantidade_utilizada + NEW.quantidade,
status = CASE
WHEN quantidade_utilizada + NEW.quantidade >= quantidade_empenhada THEN 'Finalizado'
ELSE 'Empenhado'
END
WHERE id = empenho_record.id;
END IF;
-- Para entradas
ELSIF NEW.tipo_movimentacao = 'entrada' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
-- Para saídas
ELSIF NEW.tipo_movimentacao = 'saida' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total - NEW.quantidade,
quantidade_disponivel = quantidade_disponivel - NEW.quantidade
WHERE id = NEW.material_id;
-- Para ajustes
ELSIF NEW.tipo_movimentacao = 'ajuste' THEN
UPDATE estoque_materiais
SET quantidade_total = quantidade_total + NEW.quantidade,
quantidade_disponivel = quantidade_disponivel + NEW.quantidade
WHERE id = NEW.material_id;
-- Para transferências
ELSIF NEW.tipo_movimentacao = 'transferencia' THEN
-- Apenas registra a movimentação, sem alterar quantidades
NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Remover trigger antigo e criar novo para AFTER INSERT
DROP TRIGGER IF EXISTS trigger_atualizar_estoque_movimentacao ON movimentacoes_estoque;
CREATE TRIGGER trigger_atualizar_estoque_movimentacao
AFTER INSERT ON movimentacoes_estoque
FOR EACH ROW
EXECUTE FUNCTION atualizar_estoque_movimentacao();
-- Recalcular automaticamente quantidade_empenhada baseado na tabela empenhos
UPDATE estoque_materiais
SET quantidade_empenhada = COALESCE(
(SELECT SUM(quantidade_empenhada - quantidade_utilizada)
FROM empenhos_material
WHERE material_id = estoque_materiais.id
AND status IN ('Empenhado')),
0
);
-- Corrigir quantidade_disponivel baseado no total menos empenhado
UPDATE estoque_materiais
SET quantidade_disponivel = quantidade_total - quantidade_empenhada
WHERE quantidade_total >= quantidade_empenhada;

View File

@@ -0,0 +1,36 @@
-- Verificar se existe política DELETE para estoque_materiais
CREATE OR REPLACE FUNCTION check_delete_permissions()
RETURNS TABLE(can_delete boolean, error_message text) AS $$
BEGIN
-- Tentar fazer um DELETE teste (sem fazer commit)
BEGIN
-- Verificar se existe ao menos um registro para testar
IF EXISTS (SELECT 1 FROM estoque_materiais LIMIT 1) THEN
-- Simular um DELETE para ver se RLS bloqueia
PERFORM * FROM estoque_materiais WHERE false; -- Não deleta nada
RETURN QUERY SELECT true::boolean, 'DELETE permitido'::text;
ELSE
RETURN QUERY SELECT true::boolean, 'Nenhum registro para testar'::text;
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN QUERY SELECT false::boolean, SQLERRM::text;
END;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Adicionar política DELETE se não existir
DO $$
BEGIN
-- Verificar se a política DELETE existe
IF NOT EXISTS (
SELECT 1 FROM pg_policy
WHERE polname = 'Usuários autenticados podem excluir estoque'
AND polrelid = (SELECT oid FROM pg_class WHERE relname = 'estoque_materiais')
) THEN
-- Criar política DELETE
EXECUTE 'CREATE POLICY "Usuários autenticados podem excluir estoque" ON estoque_materiais FOR DELETE USING (auth.uid() IS NOT NULL)';
RAISE NOTICE 'Política DELETE criada para estoque_materiais';
ELSE
RAISE NOTICE 'Política DELETE já existe para estoque_materiais';
END IF;
END $$;

View File

@@ -0,0 +1,18 @@
-- Atualizar os dados padrão da tabela localizacoes_estoque
-- Substituir os dados atuais pelos da figura 1
-- Primeiro, desativar todos os registros existentes
UPDATE localizacoes_estoque SET ativo = false;
-- Inserir as novas localizações baseadas na figura 1
INSERT INTO localizacoes_estoque (nome, codigo, descricao, ativo) VALUES
('Área de Expedição', 'AE', 'Área destinada para expedição de materiais', true),
('Área de Recebimento', 'AR', 'Área destinada para recebimento de materiais', true),
('Área Externa', 'AEX', 'Área externa para armazenamento', true),
('Depósito Pequeno', 'DP', 'Depósito de pequeno porte', true),
('Galpão Principal', 'GP', 'Galpão principal de armazenamento', true)
ON CONFLICT (nome) DO UPDATE SET
codigo = EXCLUDED.codigo,
descricao = EXCLUDED.descricao,
ativo = EXCLUDED.ativo;

View File

@@ -0,0 +1,39 @@
-- Criar tabela qualidades_aco se não existir
CREATE TABLE IF NOT EXISTS public.qualidades_aco (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
nome TEXT NOT NULL,
codigo TEXT,
descricao TEXT,
ativo BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar RLS
ALTER TABLE public.qualidades_aco ENABLE ROW LEVEL SECURITY;
-- Criar políticas RLS
CREATE POLICY "Todos podem visualizar qualidades_aco"
ON public.qualidades_aco
FOR SELECT
USING (true);
CREATE POLICY "Usuários autenticados podem gerenciar qualidades_aco"
ON public.qualidades_aco
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Criar trigger para atualizar updated_at
CREATE TRIGGER update_updated_at_qualidades_aco
BEFORE UPDATE ON public.qualidades_aco
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_qualidades_aco();
-- Inserir dados padrão das qualidades mostradas na imagem
INSERT INTO public.qualidades_aco (nome, codigo, descricao) VALUES
('ASTM A500', 'ASTM A500', 'Aço carbono para tubos'),
('SAE 1020', 'SAE 1020', 'Aço carbono baixo teor'),
('ASTM A572', 'ASTM A572', 'Aço carbono alta resistência'),
('SAE 1045', 'SAE 1045', 'Aço carbono médio teor'),
('ASTM A36', 'ASTM A36', 'Aço carbono estrutural')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,38 @@
-- Criar tabela qualidades_aco se não existir
CREATE TABLE IF NOT EXISTS public.qualidades_aco (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
nome TEXT NOT NULL,
descricao TEXT,
ativo BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Habilitar RLS
ALTER TABLE public.qualidades_aco ENABLE ROW LEVEL SECURITY;
-- Criar políticas RLS
CREATE POLICY "Todos podem visualizar qualidades_aco"
ON public.qualidades_aco
FOR SELECT
USING (true);
CREATE POLICY "Usuários autenticados podem gerenciar qualidades_aco"
ON public.qualidades_aco
FOR ALL
USING (auth.uid() IS NOT NULL);
-- Criar trigger para atualizar updated_at
CREATE TRIGGER update_updated_at_qualidades_aco
BEFORE UPDATE ON public.qualidades_aco
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_qualidades_aco();
-- Inserir dados padrão das qualidades mostradas na imagem
INSERT INTO public.qualidades_aco (nome, descricao) VALUES
('ASTM A500', 'Aço carbono para tubos'),
('SAE 1020', 'Aço carbono baixo teor'),
('ASTM A572', 'Aço carbono alta resistência'),
('SAE 1045', 'Aço carbono médio teor'),
('ASTM A36', 'Aço carbono estrutural')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,147 @@
-- 1) Tipos (enums) para os campos de seleção
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'atribuicao_frequencia') THEN
CREATE TYPE public.atribuicao_frequencia AS ENUM ('horaria','2xdia','diaria','2xsemanal','semanal','quinzenal','mensal');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'atribuicao_metodo') THEN
CREATE TYPE public.atribuicao_metodo AS ENUM ('impresso','sistema','sistema-impresso','email','verbal');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'atribuicao_cliente') THEN
CREATE TYPE public.atribuicao_cliente AS ENUM ('interno','processo','obra','contrato','geral');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'atribuicao_importancia') THEN
CREATE TYPE public.atribuicao_importancia AS ENUM ('essencial','estrategico','suporte','informativo');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'atribuicao_duracao') THEN
CREATE TYPE public.atribuicao_duracao AS ENUM ('<=1 hora','2 horas','4 horas','8 horas');
END IF;
END$$;
-- 2) Função para checar admin ou função Diretoria
CREATE OR REPLACE FUNCTION public.is_admin_or_diretoria(_user_id uuid)
RETURNS boolean
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path TO 'public'
AS $$
SELECT public.has_role(_user_id, 'admin'::app_role)
OR EXISTS (
SELECT 1
FROM public.profiles p
JOIN public.functions f ON f.id = p.function_id
WHERE p.id = _user_id
AND lower(f.name) LIKE 'diretoria%'
);
$$;
-- 3) Função para gerar abreviação (3 letras) do usuário
CREATE OR REPLACE FUNCTION public.generate_user_abbrev(_user_id uuid)
RETURNS text
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
name_text text;
abbrev text;
BEGIN
SELECT COALESCE(NULLIF(trim(p.full_name), ''), split_part(p.email, '@', 1))
INTO name_text
FROM public.profiles p
WHERE p.id = _user_id;
IF name_text IS NULL OR name_text = '' THEN
RETURN 'USR';
END IF;
abbrev := upper(substring(regexp_replace(name_text, '[^A-Za-z0-9]', '', 'g') from 1 for 3));
IF length(abbrev) < 3 THEN
abbrev := rpad(abbrev, 3, 'X');
END IF;
RETURN abbrev;
END;
$function$;
-- 4) Tabela de Atribuições
CREATE TABLE IF NOT EXISTS public.atribuicoes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
user_abbrev char(3) NOT NULL DEFAULT 'USR',
attribution varchar(300) NOT NULL,
frequency public.atribuicao_frequencia NOT NULL,
method public.atribuicao_metodo NOT NULL,
client public.atribuicao_cliente NOT NULL,
importance public.atribuicao_importancia NOT NULL,
duration public.atribuicao_duracao NOT NULL,
created_by uuid NOT NULL REFERENCES public.profiles(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Índices úteis
CREATE INDEX IF NOT EXISTS idx_atribuicoes_user_id ON public.atribuicoes(user_id);
CREATE INDEX IF NOT EXISTS idx_atribuicoes_created_by ON public.atribuicoes(created_by);
CREATE INDEX IF NOT EXISTS idx_atribuicoes_created_at ON public.atribuicoes(created_at);
-- 5) Trigger para defaults (abreviação + updated_at)
CREATE OR REPLACE FUNCTION public.set_atribuicoes_defaults()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Ajustar abreviação se vier vazia ou inválida
IF NEW.user_abbrev IS NULL OR length(btrim(NEW.user_abbrev)) <> 3 THEN
NEW.user_abbrev := upper(substring(public.generate_user_abbrev(NEW.user_id) from 1 for 3));
ELSE
NEW.user_abbrev := upper(NEW.user_abbrev);
END IF;
NEW.updated_at := now();
RETURN NEW;
END;
$function$;
DROP TRIGGER IF EXISTS trg_atribuicoes_defaults ON public.atribuicoes;
CREATE TRIGGER trg_atribuicoes_defaults
BEFORE INSERT OR UPDATE ON public.atribuicoes
FOR EACH ROW
EXECUTE FUNCTION public.set_atribuicoes_defaults();
-- 6) RLS
ALTER TABLE public.atribuicoes ENABLE ROW LEVEL SECURITY;
-- SELECT: Admin/Diretoria veem tudo; demais apenas suas próprias
DROP POLICY IF EXISTS "View own or admin/diretoria all" ON public.atribuicoes;
CREATE POLICY "View own or admin/diretoria all"
ON public.atribuicoes
FOR SELECT
USING ( public.is_admin_or_diretoria(auth.uid()) OR user_id = auth.uid() );
-- INSERT: apenas Admin/Diretoria e exige created_by = auth.uid()
DROP POLICY IF EXISTS "Only admin/diretoria can insert atribuições" ON public.atribuicoes;
CREATE POLICY "Only admin/diretoria can insert atribuições"
ON public.atribuicoes
FOR INSERT
WITH CHECK ( public.is_admin_or_diretoria(auth.uid()) AND created_by = auth.uid() );
-- UPDATE: apenas Admin/Diretoria
DROP POLICY IF EXISTS "Only admin/diretoria can update atribuições" ON public.atribuicoes;
CREATE POLICY "Only admin/diretoria can update atribuições"
ON public.atribuicoes
FOR UPDATE
USING ( public.is_admin_or_diretoria(auth.uid()) );
-- DELETE: apenas Admin/Diretoria
DROP POLICY IF EXISTS "Only admin/diretoria can delete atribuições" ON public.atribuicoes;
CREATE POLICY "Only admin/diretoria can delete atribuições"
ON public.atribuicoes
FOR DELETE
USING ( public.is_admin_or_diretoria(auth.uid()) );

Some files were not shown because too many files have changed in this diff Show More