🚀 Initial commit: Versão atual do TrackSteel APP
This commit is contained in:
40
supabase/config.toml
Normal file
40
supabase/config.toml
Normal 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
|
||||
282
supabase/functions/backup-database/index.ts
Normal file
282
supabase/functions/backup-database/index.ts
Normal 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' }
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
304
supabase/functions/cleanup-duplicates/index.ts
Normal file
304
supabase/functions/cleanup-duplicates/index.ts
Normal 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
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
139
supabase/functions/restore-database/index.ts
Normal file
139
supabase/functions/restore-database/index.ts
Normal 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' }
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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();
|
||||
@@ -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')
|
||||
);
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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')
|
||||
);
|
||||
@@ -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');
|
||||
$$;
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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';
|
||||
$$;
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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[];
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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 $$;
|
||||
@@ -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);
|
||||
@@ -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%';
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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$
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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$;
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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));
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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', 'M²', '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);
|
||||
@@ -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');
|
||||
@@ -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;
|
||||
@@ -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)';
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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)
|
||||
@@ -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$;
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
);
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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())
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
-- Adicionar nova coluna sem_componentes na tabela pecas
|
||||
ALTER TABLE pecas ADD COLUMN sem_componentes boolean DEFAULT false;
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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 $$;
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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;
|
||||
$$;
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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$;
|
||||
@@ -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$;
|
||||
@@ -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$;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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 $$;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
Reference in New Issue
Block a user