commit melhorias sgf
This commit is contained in:
@@ -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');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user