commit melhorias sgf

This commit is contained in:
2026-11-11 09:00:17 +02:00
parent 0b05a1192c
commit 1d11a5e57b
736 changed files with 40746 additions and 210 deletions
@@ -0,0 +1,184 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Tabela de mapeamento SGC ↔ SGF
Schema::create('sgc_mapping', function (Blueprint $table) {
$table->id();
$table->string('sgc_entidade_tipo', 30); // Fornecedor, CentroCusto, Categoria
$table->string('sgc_entidade_id', 60);
$table->string('sgf_codigo_mestre', 60);
$table->string('sgf_descricao', 200)->nullable();
$table->timestamp('data_sincronizacao')->useCurrent();
$table->string('status', 20)->default('Ativo');
$table->unique(['sgc_entidade_tipo', 'sgc_entidade_id']);
$table->index('sgc_entidade_tipo');
});
// Fornecedores (espelhado do SGC)
Schema::create('sgc_fornecedores', function (Blueprint $table) {
$table->id();
$table->string('sgf_fornecedor_id', 40)->unique();
$table->string('nuit', 20);
$table->string('nome_fornecedor', 200);
$table->string('nome_comercial', 200)->nullable();
$table->string('categoria_fornecedor', 80);
$table->string('subcategoria', 80)->nullable();
$table->string('pais', 3)->default('MOZ');
$table->string('moeda_preferencial', 3)->default('MZN');
$table->string('status_risco', 20)->default('Ativo');
$table->boolean('tem_contrato_mestre')->default(false);
$table->date('data_cadastro')->useCurrent();
$table->date('data_ultima_revisao')->nullable();
$table->timestamps();
$table->index('categoria_fornecedor');
$table->index('nuit');
});
// Centros de Custo
Schema::create('sgc_centros_custo', function (Blueprint $table) {
$table->id();
$table->string('sgf_centro_custo_id', 40)->unique();
$table->string('codigo', 20)->unique();
$table->string('nome_departamento', 150);
$table->string('director_responsavel', 150)->nullable();
$table->string('regiao', 80)->nullable();
$table->string('empresa', 100)->default('Tempo');
$table->string('status', 20)->default('Ativo');
$table->timestamps();
});
// Ordens de Compra
Schema::create('sgc_ordens_compra', function (Blueprint $table) {
$table->id();
$table->string('oc_id', 50)->unique();
$table->string('requisicao_id', 50)->nullable();
$table->date('data_oc');
$table->foreignId('fornecedor_id')->constrained('sgc_fornecedores');
$table->foreignId('centro_custo_id')->constrained('sgc_centros_custo');
$table->string('categoria', 80);
$table->decimal('valor_comprometido', 18, 2);
$table->string('moeda', 3)->default('MZN');
$table->decimal('taxa_cambio', 12, 6)->default(1.0);
$table->decimal('valor_comprometido_mzn', 18, 2)
->storedAs('valor_comprometido * taxa_cambio');
$table->integer('prazo_pagamento_dias')->default(30);
$table->date('data_entrega_prevista')->nullable();
$table->string('status', 20)->default('Emitida');
$table->boolean('cabimentado')->default(false);
$table->string('sgf_compromisso_id', 50)->nullable();
$table->timestamps();
$table->index('fornecedor_id');
$table->index('data_oc');
$table->index('status');
});
// Faturas
Schema::create('sgc_faturas', function (Blueprint $table) {
$table->id();
$table->string('fatura_id', 50)->unique();
$table->foreignId('ordem_compra_id')->constrained('sgc_ordens_compra');
$table->foreignId('fornecedor_id')->constrained('sgc_fornecedores');
$table->date('data_fatura');
$table->date('data_vencimento')->nullable();
$table->date('data_pagamento')->nullable();
$table->decimal('valor_fatura', 18, 2);
$table->decimal('valor_pago', 18, 2)->nullable();
$table->decimal('desconto_obtido', 18, 2)->default(0);
$table->decimal('iva_valor', 18, 2)->default(0);
$table->string('moeda', 3)->default('MZN');
$table->decimal('taxa_cambio', 12, 6)->default(1.0);
$table->decimal('valor_fatura_mzn', 18, 2)
->storedAs('valor_fatura * taxa_cambio');
$table->string('status_3_way_match', 20)->default('Pendente');
$table->string('status_pagamento', 20)->default('Aguardando');
$table->string('numero_comprovativo', 40)->nullable();
$table->text('motivo_excecao')->nullable();
$table->string('sgf_doc_number', 40)->unique();
$table->timestamp('sgf_sync_timestamp')->useCurrent();
$table->timestamps();
$table->index('ordem_compra_id');
$table->index('fornecedor_id');
$table->index('data_fatura');
$table->index('data_pagamento');
$table->index('status_pagamento');
});
// Recebimentos (Goods Receipt)
Schema::create('sgc_recebimentos', function (Blueprint $table) {
$table->id();
$table->string('recebimento_id', 50)->unique();
$table->foreignId('ordem_compra_id')->constrained('sgc_ordens_compra');
$table->date('data_recebimento');
$table->decimal('quantidade_recebida', 18, 4);
$table->decimal('valor_recebido', 18, 2);
$table->string('moeda', 3)->default('MZN');
$table->boolean('divergencia_valor')->default(false);
$table->boolean('divergencia_qtd')->default(false);
$table->text('observacoes')->nullable();
$table->timestamps();
$table->index('ordem_compra_id');
$table->index('data_recebimento');
});
// Log de integração (idempotência e auditoria)
Schema::create('sgc_integration_log', function (Blueprint $table) {
$table->id();
$table->uuid('idempotency_key')->unique();
$table->string('endpoint', 100);
$table->string('event_type', 50);
$table->json('payload');
$table->string('status', 20); // ACCEPTED, REJECTED, DUPLICATE
$table->json('response')->nullable();
$table->string('error_code', 40)->nullable();
$table->text('error_message')->nullable();
$table->integer('attempts')->default(1);
$table->timestamps();
$table->index('event_type');
$table->index('status');
$table->index('created_at');
});
// Dead Letter Queue
Schema::create('sgc_integration_dead_letter', function (Blueprint $table) {
$table->id();
$table->uuid('idempotency_key');
$table->string('endpoint', 100);
$table->json('payload');
$table->string('error_code', 40);
$table->text('error_message')->nullable();
$table->smallInteger('attempts')->default(0);
$table->timestamp('next_retry_at')->nullable();
$table->timestamp('resolved_at')->nullable();
$table->string('resolution', 40)->nullable();
$table->timestamps();
$table->index('error_code');
$table->index('next_retry_at');
});
}
public function down(): void
{
Schema::dropIfExists('sgc_integration_dead_letter');
Schema::dropIfExists('sgc_integration_log');
Schema::dropIfExists('sgc_recebimentos');
Schema::dropIfExists('sgc_faturas');
Schema::dropIfExists('sgc_ordens_compra');
Schema::dropIfExists('sgc_centros_custo');
Schema::dropIfExists('sgc_fornecedores');
Schema::dropIfExists('sgc_mapping');
}
};
@@ -0,0 +1,212 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// Dimensão Tempo
Schema::create('analytics_dim_tempo', function (Blueprint $table) {
$table->integer('data_id')->primary(); // YYYYMMDD
$table->date('data_completa')->unique();
$table->smallInteger('dia_semana');
$table->string('nome_dia_semana', 12);
$table->smallInteger('dia_mes');
$table->smallInteger('mes');
$table->string('nome_mes', 12);
$table->smallInteger('trimestre');
$table->smallInteger('ano');
$table->string('ano_mes', 7); // '2026-11'
$table->boolean('eh_dia_util')->default(true);
$table->boolean('eh_feriado')->default(false);
$table->smallInteger('semestre');
$table->index(['ano', 'mes']);
$table->index(['ano', 'trimestre']);
});
// Dimensão Fornecedor
Schema::create('analytics_dim_fornecedor', function (Blueprint $table) {
$table->id();
$table->string('sgf_fornecedor_id', 40)->unique();
$table->string('nuit', 20);
$table->string('nome_fornecedor', 200);
$table->string('nome_comercial', 200)->nullable();
$table->string('categoria_fornecedor', 80);
$table->string('subcategoria', 80)->nullable();
$table->string('pais', 3)->default('MOZ');
$table->string('moeda_preferencial', 3)->default('MZN');
$table->string('status_risco', 20)->default('Ativo');
$table->boolean('tem_contrato_mestre')->default(false);
$table->date('data_cadastro');
$table->date('data_ultima_revisao')->nullable();
$table->timestamps();
$table->index('categoria_fornecedor');
$table->index('nuit');
});
// Dimensão Organização
Schema::create('analytics_dim_organizacao', function (Blueprint $table) {
$table->id();
$table->string('sgf_centro_custo_id', 40)->unique();
$table->string('codigo', 20)->unique();
$table->string('nome_departamento', 150);
$table->string('director_responsavel', 150)->nullable();
$table->string('regiao', 80)->nullable();
$table->string('empresa', 100)->default('Tempo');
$table->string('status', 20)->default('Ativo');
$table->timestamps();
});
// Dimensão Categoria de Despesa
Schema::create('analytics_dim_categoria_despesa', function (Blueprint $table) {
$table->id();
$table->string('codigo', 20)->unique();
$table->string('nivel_1', 80); // ex: 'Despesas Operacionais'
$table->string('nivel_2', 80); // ex: 'Tecnologia de Informação'
$table->string('nivel_3', 80)->nullable(); // ex: 'Licenças de Software'
$table->string('tipo', 20); // Bem, Servico, Misto
$table->boolean('eh_capex')->default(false);
$table->timestamps();
$table->index(['nivel_1', 'nivel_2']);
});
// Dimensão Usuário
Schema::create('analytics_dim_usuario', function (Blueprint $table) {
$table->id();
$table->string('codigo_colaborador', 20)->unique();
$table->string('nome_completo', 150);
$table->string('email', 150)->nullable();
$table->string('cargo', 80)->nullable();
$table->foreignId('departamento_id')
->constrained('analytics_dim_organizacao');
$table->date('data_admissao')->nullable();
$table->string('status', 20)->default('Ativo');
$table->timestamps();
$table->index('departamento_id');
});
// Facto Ordem de Compra
Schema::create('analytics_fact_ordem_compra', function (Blueprint $table) {
$table->bigInteger('oc_id')->primary();
$table->bigInteger('requisicao_id')->nullable();
$table->integer('data_oc_id');
$table->foreignId('fornecedor_id')
->constrained('analytics_dim_fornecedor');
$table->foreignId('departamento_id')
->constrained('analytics_dim_organizacao');
$table->foreignId('categoria_id')
->constrained('analytics_dim_categoria_despesa');
$table->decimal('valor_comprometido', 18, 2);
$table->string('moeda', 3)->default('MZN');
$table->decimal('taxa_cambio', 12, 6)->default(1.0);
$table->decimal('valor_comprometido_mzn', 18, 2)
->storedAs('valor_comprometido * taxa_cambio');
$table->smallInteger('prazo_pagamento_dias')->default(30);
$table->date('data_entrega_prevista')->nullable();
$table->string('status', 20);
$table->timestamps();
$table->index('fornecedor_id');
$table->index('data_oc_id');
$table->index('status');
});
// Facto Fatura e Pagamento
Schema::create('analytics_fact_fatura_pagamento', function (Blueprint $table) {
$table->bigInteger('fatura_id')->primary();
$table->bigInteger('oc_id');
$table->foreignId('fornecedor_id')
->constrained('analytics_dim_fornecedor');
$table->integer('data_fatura_id');
$table->integer('data_vencimento_id')->nullable();
$table->integer('data_pagamento_id')->nullable();
$table->decimal('valor_fatura', 18, 2);
$table->decimal('valor_pago', 18, 2)->nullable();
$table->decimal('desconto_obtido', 18, 2)->default(0);
$table->decimal('iva_valor', 18, 2)->default(0);
$table->string('moeda', 3)->default('MZN');
$table->decimal('taxa_cambio', 12, 6)->default(1.0);
$table->decimal('valor_fatura_mzn', 18, 2)
->storedAs('valor_fatura * taxa_cambio');
$table->string('status_3_way_match', 20);
$table->string('status_pagamento', 20);
$table->string('numero_comprovativo', 40)->nullable();
$table->text('motivo_excecao')->nullable();
$table->string('sgf_doc_number', 40)->unique();
$table->timestamp('sgf_sync_timestamp')->useCurrent();
$table->timestamps();
$table->index('oc_id');
$table->index('fornecedor_id');
$table->index('data_fatura_id');
$table->index('data_pagamento_id');
$table->index('status_pagamento');
});
// View Materializada (agregado mensal)
DB::statement('
CREATE MATERIALIZED VIEW analytics_mv_spend_mensal AS
SELECT
t.ano,
t.mes,
t.ano_mes,
fp.fornecedor_id,
df.nome_fornecedor,
df.categoria_fornecedor,
org.id AS departamento_id,
org.nome_departamento,
dc.nivel_1,
dc.nivel_2,
COUNT(*) AS total_faturas,
SUM(fp.valor_fatura_mzn) AS valor_total_mzn,
SUM(fp.desconto_obtido * fp.taxa_cambio) AS desconto_total_mzn,
AVG(fp.valor_fatura_mzn) AS ticket_medio_mzn,
COUNT(*) FILTER (WHERE fp.status_3_way_match = \'Conforme\') AS faturas_conformes,
ROUND(
COUNT(*) FILTER (WHERE fp.status_3_way_match = \'Conforme\')::NUMERIC /
NULLIF(COUNT(*), 0) * 100, 2
) AS taxa_sucesso_3way_match
FROM analytics_fact_fatura_pagamento fp
JOIN analytics_dim_tempo t ON fp.data_fatura_id = t.data_id
JOIN analytics_dim_fornecedor df ON fp.fornecedor_id = df.id
JOIN analytics_dim_organizacao org ON org.id = (
SELECT oc.departamento_id
FROM analytics_fact_ordem_compra oc
WHERE oc.oc_id = fp.oc_id
)
JOIN analytics_dim_categoria_despesa dc ON dc.id = (
SELECT oc.categoria_id
FROM analytics_fact_ordem_compra oc
WHERE oc.oc_id = fp.oc_id
)
GROUP BY t.ano, t.mes, t.ano_mes, fp.fornecedor_id, df.nome_fornecedor,
df.categoria_fornecedor, org.id, org.nome_departamento,
dc.nivel_1, dc.nivel_2
');
DB::statement('
CREATE UNIQUE INDEX idx_mv_spend_mensal_pk
ON analytics_mv_spend_mensal (ano, mes, fornecedor_id, departamento_id, nivel_1, nivel_2)
');
}
public function down(): void
{
DB::statement('DROP MATERIALIZED VIEW IF EXISTS analytics_mv_spend_mensal');
Schema::dropIfExists('analytics_fact_fatura_pagamento');
Schema::dropIfExists('analytics_fact_ordem_compra');
Schema::dropIfExists('analytics_dim_usuario');
Schema::dropIfExists('analytics_dim_categoria_despesa');
Schema::dropIfExists('analytics_dim_organizacao');
Schema::dropIfExists('analytics_dim_fornecedor');
Schema::dropIfExists('analytics_dim_tempo');
}
};