357 lines
11 KiB
PL/PgSQL
357 lines
11 KiB
PL/PgSQL
-- Função RPC otimizada para buscar itens disponíveis para apontamento
|
|
-- Esta função substitui múltiplas queries por uma única consulta otimizada
|
|
|
|
CREATE OR REPLACE FUNCTION get_itens_disponiveis(
|
|
p_of_number TEXT DEFAULT NULL,
|
|
p_processo_id UUID DEFAULT NULL,
|
|
p_include_componentes BOOLEAN DEFAULT TRUE,
|
|
p_limit INTEGER DEFAULT 100
|
|
)
|
|
RETURNS TABLE (
|
|
item_id UUID,
|
|
item_type TEXT, -- 'peca' ou 'componente'
|
|
marca TEXT,
|
|
descricao TEXT,
|
|
peso_unitario NUMERIC,
|
|
quantidade_total NUMERIC,
|
|
quantidade_processada NUMERIC,
|
|
quantidade_disponivel NUMERIC,
|
|
etapa_fase TEXT,
|
|
processo_atual_id UUID,
|
|
processo_atual_nome TEXT,
|
|
processo_atual_ordem INTEGER,
|
|
proximos_processos JSONB,
|
|
peca_pai_id UUID,
|
|
peca_pai_marca TEXT
|
|
)
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
WITH
|
|
-- CTE para buscar peças da OF
|
|
pecas_of AS (
|
|
SELECT
|
|
p.id,
|
|
p.marca,
|
|
p.descricao,
|
|
p.peso_unitario,
|
|
p.quantidade,
|
|
p.etapa_fase,
|
|
p.of_number
|
|
FROM pecas p
|
|
WHERE (p_of_number IS NULL OR p.of_number = p_of_number)
|
|
),
|
|
|
|
-- CTE para buscar componentes das peças (se solicitado)
|
|
componentes_of AS (
|
|
SELECT
|
|
c.id,
|
|
c.marca_componente as marca,
|
|
c.descricao,
|
|
c.peso_unitario,
|
|
(c.quantidade_por_peca * p.quantidade) as quantidade,
|
|
c.peca_id,
|
|
p.marca as peca_pai_marca,
|
|
p.of_number
|
|
FROM componentes_peca c
|
|
INNER JOIN pecas_of p ON c.peca_id = p.id
|
|
WHERE p_include_componentes = true
|
|
),
|
|
|
|
-- CTE para calcular quantidades já processadas de peças
|
|
pecas_processadas AS (
|
|
SELECT
|
|
ap.peca_id,
|
|
ap.processo_id,
|
|
SUM(ap.quantidade_produzida) as quantidade_processada
|
|
FROM apontamentos_producao ap
|
|
INNER JOIN pecas_of p ON ap.peca_id = p.id
|
|
WHERE ap.tipo_apontamento = 'peca'
|
|
AND (p_of_number IS NULL OR ap.of_number = p_of_number)
|
|
AND (p_processo_id IS NULL OR ap.processo_id = p_processo_id)
|
|
GROUP BY ap.peca_id, ap.processo_id
|
|
),
|
|
|
|
-- CTE para calcular quantidades já processadas de componentes
|
|
componentes_processados AS (
|
|
SELECT
|
|
ap.componente_id,
|
|
ap.processo_id,
|
|
SUM(ap.quantidade_produzida) as quantidade_processada
|
|
FROM apontamentos_producao ap
|
|
INNER JOIN componentes_of c ON ap.componente_id = c.id
|
|
WHERE ap.tipo_apontamento = 'componente'
|
|
AND (p_of_number IS NULL OR ap.of_number = p_of_number)
|
|
AND (p_processo_id IS NULL OR ap.processo_id = p_processo_id)
|
|
GROUP BY ap.componente_id, ap.processo_id
|
|
),
|
|
|
|
-- CTE para buscar processos disponíveis
|
|
processos_disponiveis AS (
|
|
SELECT
|
|
pf.id,
|
|
pf.nome,
|
|
pf.ordem,
|
|
pf.ativo
|
|
FROM processos_fabricacao pf
|
|
WHERE pf.ativo = true
|
|
AND (p_processo_id IS NULL OR pf.id = p_processo_id)
|
|
ORDER BY pf.ordem
|
|
),
|
|
|
|
-- CTE para determinar próximos processos para cada item
|
|
proximos_processos_cte AS (
|
|
SELECT
|
|
pd.id as processo_id,
|
|
COALESCE(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', pd_next.id,
|
|
'nome', pd_next.nome,
|
|
'ordem', pd_next.ordem
|
|
) ORDER BY pd_next.ordem
|
|
) FILTER (WHERE pd_next.id IS NOT NULL),
|
|
'[]'::jsonb
|
|
) as proximos_processos
|
|
FROM processos_disponiveis pd
|
|
LEFT JOIN processos_disponiveis pd_next ON pd_next.ordem > pd.ordem
|
|
GROUP BY pd.id
|
|
)
|
|
|
|
-- Query principal: UNION de peças e componentes
|
|
SELECT
|
|
-- Peças
|
|
p.id as item_id,
|
|
'peca'::TEXT as item_type,
|
|
p.marca,
|
|
p.descricao,
|
|
p.peso_unitario,
|
|
p.quantidade as quantidade_total,
|
|
COALESCE(pp.quantidade_processada, 0) as quantidade_processada,
|
|
GREATEST(p.quantidade - COALESCE(pp.quantidade_processada, 0), 0) as quantidade_disponivel,
|
|
p.etapa_fase,
|
|
pd.id as processo_atual_id,
|
|
pd.nome as processo_atual_nome,
|
|
pd.ordem as processo_atual_ordem,
|
|
COALESCE(ppc.proximos_processos, '[]'::jsonb) as proximos_processos,
|
|
NULL::UUID as peca_pai_id,
|
|
NULL::TEXT as peca_pai_marca
|
|
FROM pecas_of p
|
|
CROSS JOIN processos_disponiveis pd
|
|
LEFT JOIN pecas_processadas pp ON p.id = pp.peca_id AND pd.id = pp.processo_id
|
|
LEFT JOIN proximos_processos_cte ppc ON pd.id = ppc.processo_id
|
|
WHERE GREATEST(p.quantidade - COALESCE(pp.quantidade_processada, 0), 0) > 0
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
-- Componentes
|
|
c.id as item_id,
|
|
'componente'::TEXT as item_type,
|
|
c.marca,
|
|
c.descricao,
|
|
c.peso_unitario,
|
|
c.quantidade as quantidade_total,
|
|
COALESCE(cp.quantidade_processada, 0) as quantidade_processada,
|
|
GREATEST(c.quantidade - COALESCE(cp.quantidade_processada, 0), 0) as quantidade_disponivel,
|
|
NULL::TEXT as etapa_fase, -- Componentes não têm etapa_fase
|
|
pd.id as processo_atual_id,
|
|
pd.nome as processo_atual_nome,
|
|
pd.ordem as processo_atual_ordem,
|
|
COALESCE(ppc.proximos_processos, '[]'::jsonb) as proximos_processos,
|
|
c.peca_id as peca_pai_id,
|
|
c.peca_pai_marca
|
|
FROM componentes_of c
|
|
CROSS JOIN processos_disponiveis pd
|
|
LEFT JOIN componentes_processados cp ON c.id = cp.componente_id AND pd.id = cp.processo_id
|
|
LEFT JOIN proximos_processos_cte ppc ON pd.id = ppc.processo_id
|
|
WHERE p_include_componentes = true
|
|
AND GREATEST(c.quantidade - COALESCE(cp.quantidade_processada, 0), 0) > 0
|
|
|
|
ORDER BY
|
|
item_type DESC, -- Peças primeiro, depois componentes
|
|
processo_atual_ordem ASC,
|
|
marca ASC
|
|
LIMIT p_limit;
|
|
END;
|
|
$$;
|
|
|
|
-- Comentários sobre a função
|
|
COMMENT ON FUNCTION get_itens_disponiveis IS 'Função otimizada para buscar itens (peças e componentes) disponíveis para apontamento de produção';
|
|
|
|
-- Criar índices para otimizar a performance da função RPC
|
|
CREATE INDEX IF NOT EXISTS idx_apontamentos_producao_of_tipo_processo
|
|
ON apontamentos_producao(of_number, tipo_apontamento, processo_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_apontamentos_producao_peca_processo
|
|
ON apontamentos_producao(peca_id, processo_id)
|
|
WHERE tipo_apontamento = 'peca';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_apontamentos_producao_componente_processo
|
|
ON apontamentos_producao(componente_id, processo_id)
|
|
WHERE tipo_apontamento = 'componente';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_pecas_of_number
|
|
ON pecas(of_number);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_componentes_peca_peca_id
|
|
ON componentes_peca(peca_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_processos_fabricacao_ativo_ordem
|
|
ON processos_fabricacao(ativo, ordem)
|
|
WHERE ativo = true;
|
|
|
|
-- Função auxiliar para buscar estatísticas de performance
|
|
CREATE OR REPLACE FUNCTION get_apontamentos_stats(
|
|
p_of_number TEXT DEFAULT NULL,
|
|
p_data_inicio DATE DEFAULT NULL,
|
|
p_data_fim DATE DEFAULT NULL
|
|
)
|
|
RETURNS TABLE (
|
|
total_apontamentos BIGINT,
|
|
total_pecas BIGINT,
|
|
total_componentes BIGINT,
|
|
processos_utilizados BIGINT,
|
|
quantidade_total_produzida NUMERIC,
|
|
periodo_inicio DATE,
|
|
periodo_fim DATE
|
|
)
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
COUNT(*) as total_apontamentos,
|
|
COUNT(*) FILTER (WHERE ap.tipo_apontamento = 'peca') as total_pecas,
|
|
COUNT(*) FILTER (WHERE ap.tipo_apontamento = 'componente') as total_componentes,
|
|
COUNT(DISTINCT ap.processo_id) as processos_utilizados,
|
|
SUM(ap.quantidade_produzida) as quantidade_total_produzida,
|
|
MIN(ap.data_apontamento::DATE) as periodo_inicio,
|
|
MAX(ap.data_apontamento::DATE) as periodo_fim
|
|
FROM apontamentos_producao ap
|
|
WHERE (p_of_number IS NULL OR ap.of_number = p_of_number)
|
|
AND (p_data_inicio IS NULL OR ap.data_apontamento::DATE >= p_data_inicio)
|
|
AND (p_data_fim IS NULL OR ap.data_apontamento::DATE <= p_data_fim);
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION get_apontamentos_stats IS 'Função para obter estatísticas de apontamentos de produção';
|
|
|
|
-- Função para validação sequencial otimizada
|
|
CREATE OR REPLACE FUNCTION validate_sequencia_processos(
|
|
p_of_number TEXT,
|
|
p_peca_id UUID DEFAULT NULL,
|
|
p_componente_id UUID DEFAULT NULL
|
|
)
|
|
RETURNS TABLE (
|
|
item_id UUID,
|
|
item_type TEXT,
|
|
marca TEXT,
|
|
processo_atual_id UUID,
|
|
processo_atual_nome TEXT,
|
|
processo_atual_ordem INTEGER,
|
|
pode_apontar BOOLEAN,
|
|
motivo_bloqueio TEXT,
|
|
processos_pendentes JSONB
|
|
)
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
WITH
|
|
-- Buscar todos os processos ordenados
|
|
processos_ordenados AS (
|
|
SELECT id, nome, ordem
|
|
FROM processos_fabricacao
|
|
WHERE ativo = true
|
|
ORDER BY ordem
|
|
),
|
|
|
|
-- Buscar apontamentos existentes
|
|
apontamentos_existentes AS (
|
|
SELECT
|
|
ap.peca_id,
|
|
ap.componente_id,
|
|
ap.processo_id,
|
|
pf.ordem as processo_ordem,
|
|
SUM(ap.quantidade_produzida) as quantidade_apontada
|
|
FROM apontamentos_producao ap
|
|
INNER JOIN processos_fabricacao pf ON ap.processo_id = pf.id
|
|
WHERE ap.of_number = p_of_number
|
|
AND (p_peca_id IS NULL OR ap.peca_id = p_peca_id)
|
|
AND (p_componente_id IS NULL OR ap.componente_id = p_componente_id)
|
|
GROUP BY ap.peca_id, ap.componente_id, ap.processo_id, pf.ordem
|
|
),
|
|
|
|
-- Determinar próximo processo válido para cada item
|
|
proximos_processos_validos AS (
|
|
SELECT
|
|
COALESCE(ae.peca_id, p_peca_id) as peca_id,
|
|
COALESCE(ae.componente_id, p_componente_id) as componente_id,
|
|
CASE
|
|
WHEN ae.processo_id IS NULL THEN (
|
|
SELECT id FROM processos_ordenados ORDER BY ordem LIMIT 1
|
|
)
|
|
ELSE (
|
|
SELECT po.id
|
|
FROM processos_ordenados po
|
|
WHERE po.ordem > ae.processo_ordem
|
|
ORDER BY po.ordem
|
|
LIMIT 1
|
|
)
|
|
END as proximo_processo_id
|
|
FROM (
|
|
SELECT DISTINCT
|
|
peca_id,
|
|
componente_id,
|
|
MAX(processo_ordem) as processo_ordem,
|
|
MAX(processo_id) as processo_id
|
|
FROM apontamentos_existentes
|
|
GROUP BY peca_id, componente_id
|
|
|
|
UNION ALL
|
|
|
|
-- Incluir itens que ainda não têm apontamentos
|
|
SELECT p_peca_id, p_componente_id, NULL, NULL
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM apontamentos_existentes
|
|
WHERE peca_id = p_peca_id OR componente_id = p_componente_id
|
|
)
|
|
) ae
|
|
)
|
|
|
|
SELECT
|
|
COALESCE(ppv.peca_id, ppv.componente_id) as item_id,
|
|
CASE WHEN ppv.peca_id IS NOT NULL THEN 'peca' ELSE 'componente' END as item_type,
|
|
COALESCE(p.marca, c.marca_componente) as marca,
|
|
po.id as processo_atual_id,
|
|
po.nome as processo_atual_nome,
|
|
po.ordem as processo_atual_ordem,
|
|
(ppv.proximo_processo_id IS NOT NULL) as pode_apontar,
|
|
CASE
|
|
WHEN ppv.proximo_processo_id IS NULL THEN 'Todos os processos já foram concluídos'
|
|
ELSE NULL
|
|
END as motivo_bloqueio,
|
|
COALESCE(
|
|
jsonb_agg(
|
|
jsonb_build_object(
|
|
'id', po_pendente.id,
|
|
'nome', po_pendente.nome,
|
|
'ordem', po_pendente.ordem
|
|
) ORDER BY po_pendente.ordem
|
|
) FILTER (WHERE po_pendente.id IS NOT NULL),
|
|
'[]'::jsonb
|
|
) as processos_pendentes
|
|
FROM proximos_processos_validos ppv
|
|
LEFT JOIN pecas p ON ppv.peca_id = p.id
|
|
LEFT JOIN componentes_peca c ON ppv.componente_id = c.id
|
|
LEFT JOIN processos_ordenados po ON ppv.proximo_processo_id = po.id
|
|
LEFT JOIN processos_ordenados po_pendente ON po_pendente.ordem >= po.ordem
|
|
GROUP BY
|
|
ppv.peca_id, ppv.componente_id, p.marca, c.marca_componente,
|
|
po.id, po.nome, po.ordem, ppv.proximo_processo_id;
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION validate_sequencia_processos IS 'Função para validar sequência de processos e determinar próximos passos válidos'; |