netgescon-day0/docs/06-sviluppo-moduli/SPECIFICA-SVILUPPO-MODULI.md

23 KiB

📋 GUIDA COMPLETA SVILUPPO MODULI NETGESCON

Versione: 1.0 - Data: 31 Luglio 2025


🎯 INTRODUZIONE

NetGescon utilizza un sistema modulare avanzato che permette di sviluppare, installare e monetizzare moduli aggiuntivi. Ogni modulo è completamente autonomo e può essere distribuito come prodotto commerciale.

🏗️ ARCHITETTURA MODULARE

NetGescon/
├── app/
│   ├── Services/
│   │   ├── ModuleManager.php    # Gestione moduli
│   │   └── HookManager.php      # Sistema eventi
│   └── Modules/
│       └── [NomeModulo]/
│           ├── module.json      # Configurazione modulo
│           ├── Controllers/     # Controller Laravel
│           ├── Routes/          # File route
│           ├── Migrations/      # Migrazioni database
│           ├── Seeders/         # Dati iniziali
│           ├── Views/           # Template Blade
│           └── Assets/          # CSS/JS specifici

🔧 STRUTTURA MODULO STANDARD

1. Configurazione Base (module.json)

{
    "name": "NomeModulo",
    "display_name": "Nome Visualizzato",
    "version": "1.0.0",
    "description": "Descrizione del modulo",
    "author": "Nome Sviluppatore",
    "email": "email@sviluppatore.com",
    "license": "commercial",
    "price": 99.00,
    "currency": "EUR",
    "billing": "yearly",
    
    "requirements": {
        "laravel": ">=10.0",
        "php": ">=8.1",
        "netgescon": ">=1.0"
    },
    
    "dependencies": [],
    
    "permissions": {
        "modulo_view": "Visualizzare il modulo",
        "modulo_create": "Creare elementi",
        "modulo_edit": "Modificare elementi", 
        "modulo_delete": "Eliminare elementi",
        "modulo_admin": "Amministrazione completa"
    },
    
    "menu_items": [
        {
            "title": "Dashboard Modulo",
            "route": "modulo.index",
            "icon": "fa-dashboard",
            "permission": "modulo_view",
            "order": 1
        }
    ],
    
    "hooks": [
        "modulo.before_create",
        "modulo.after_create",
        "modulo.before_update",
        "modulo.after_update",
        "modulo.before_delete",
        "modulo.after_delete"
    ],
    
    "config": {
        "auto_install_routes": true,
        "auto_install_migrations": true,
        "cache_enabled": true,
        "api_enabled": true
    }
}

2. Controller Base

<?php

namespace App\Modules\NomeModulo\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Services\HookManager;

class NomeModuloController extends Controller
{
    protected $hookManager;
    
    public function __construct()
    {
        // Middleware di autenticazione
        $this->middleware('auth');
        
        // Middleware permessi specifici del modulo
        $this->middleware('permission:modulo_view')->only(['index', 'show']);
        $this->middleware('permission:modulo_create')->only(['create', 'store']);
        $this->middleware('permission:modulo_edit')->only(['edit', 'update']);
        $this->middleware('permission:modulo_delete')->only(['destroy']);
        
        $this->hookManager = app(HookManager::class);
    }
    
    public function index(Request $request)
    {
        try {
            // Hook pre-azione
            $this->hookManager->execute('modulo.before_index', [$request]);
            
            // Logica del controller
            $data = DB::table('modulo_table')->paginate(20);
            
            // Hook post-azione
            $this->hookManager->execute('modulo.after_index', [$data]);
            
            return view('modules.nome-modulo.index', compact('data'));
            
        } catch (\Exception $e) {
            Log::error("Errore modulo: " . $e->getMessage());
            return back()->with('error', 'Errore: ' . $e->getMessage());
        }
    }
    
    // Altri metodi CRUD...
}

3. Routes (web.php)

<?php

use Illuminate\Support\Facades\Route;
use App\Modules\NomeModulo\Controllers\NomeModuloController;

// Gruppo route con middleware
Route::middleware(['auth', 'verified'])->group(function () {
    
    // Route principali
    Route::prefix('nome-modulo')->name('modulo.')->group(function () {
        Route::get('/', [NomeModuloController::class, 'index'])->name('index');
        Route::get('/create', [NomeModuloController::class, 'create'])->name('create');
        Route::post('/', [NomeModuloController::class, 'store'])->name('store');
        Route::get('/{id}', [NomeModuloController::class, 'show'])->name('show');
        Route::get('/{id}/edit', [NomeModuloController::class, 'edit'])->name('edit');
        Route::put('/{id}', [NomeModuloController::class, 'update'])->name('update');
        Route::delete('/{id}', [NomeModuloController::class, 'destroy'])->name('destroy');
    });
    
    // Route API per AJAX
    Route::prefix('api/nome-modulo')->name('api.modulo.')->group(function () {
        Route::get('/data', [NomeModuloController::class, 'getData'])->name('data');
        Route::post('/action', [NomeModuloController::class, 'action'])->name('action');
    });
});

4. Migration Standard

<?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
    {
        // Tabella principale del modulo
        Schema::create('modulo_table', function (Blueprint $table) {
            $table->id();
            $table->string('nome');
            $table->text('descrizione')->nullable();
            $table->enum('status', ['attivo', 'inattivo'])->default('attivo');
            $table->json('config')->nullable();
            
            // Audit fields standard
            $table->unsignedBigInteger('created_by')->nullable();
            $table->unsignedBigInteger('updated_by')->nullable();
            $table->timestamps();
            $table->softDeletes();
            
            // Indici per performance
            $table->index(['status', 'created_at']);
            $table->index('nome');
            
            // Foreign keys
            $table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
            $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null');
        });
        
        // Tabella log/cronologia
        Schema::create('modulo_history', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('record_id');
            $table->string('action');
            $table->json('old_data')->nullable();
            $table->json('new_data')->nullable();
            $table->unsignedBigInteger('user_id')->nullable();
            $table->timestamp('created_at');
            
            $table->index(['record_id', 'created_at']);
            $table->foreign('record_id')->references('id')->on('modulo_table')->onDelete('cascade');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
        });
    }
    
    public function down(): void
    {
        Schema::dropIfExists('modulo_history');
        Schema::dropIfExists('modulo_table');
    }
};

5. Seeder con Dati Base

<?php

namespace App\Modules\NomeModulo\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class NomeModuloSeeder extends Seeder
{
    public function run(): void
    {
        // Configurazioni base
        $configs = [
            [
                'chiave' => 'modulo.default_setting',
                'valore' => 'valore_default',
                'descrizione' => 'Impostazione di default',
                'created_at' => now(),
                'updated_at' => now()
            ]
        ];
        
        foreach ($configs as $config) {
            DB::table('modulo_config')->insert($config);
        }
        
        // Dati di esempio (solo in development)
        if (app()->environment('local', 'development')) {
            $esempi = [
                [
                    'nome' => 'Esempio 1',
                    'descrizione' => 'Dato di esempio',
                    'status' => 'attivo',
                    'created_by' => 1,
                    'created_at' => now(),
                    'updated_at' => now()
                ]
            ];
            
            foreach ($esempi as $esempio) {
                DB::table('modulo_table')->insert($esempio);
            }
        }
        
        echo "✅ Seeder NomeModulo completato\n";
    }
}

🔌 SISTEMA HOOK E EVENTI

Hook Disponibili nel Sistema

// Hook di sistema core
'app.boot'                    // All'avvio dell'applicazione
'app.before_request'          // Prima di ogni richiesta
'app.after_request'           // Dopo ogni richiesta

// Hook moduli
'module.before_install'       // Prima installazione modulo
'module.after_install'        // Dopo installazione modulo
'module.before_uninstall'     // Prima disinstallazione
'module.after_uninstall'      // Dopo disinstallazione

// Hook utenti
'user.before_login'           // Prima del login
'user.after_login'            // Dopo il login
'user.before_logout'          // Prima del logout

// Hook del tuo modulo
'modulo.before_create'        // Prima della creazione
'modulo.after_create'         // Dopo la creazione
'modulo.before_update'        // Prima dell'aggiornamento
'modulo.after_update'         // Dopo l'aggiornamento
'modulo.before_delete'        // Prima dell'eliminazione
'modulo.after_delete'         // Dopo l'eliminazione

Utilizzo Hook nel Modulo

// Nel controller
$this->hookManager->execute('modulo.before_create', [$data]);

// Registrazione hook listener (nel boot del modulo)
$this->hookManager->register('modulo.after_create', function($data) {
    // Logica eseguita dopo la creazione
    Log::info('Nuovo elemento creato: ' . $data['nome']);
});

💾 DATABASE E PERFORMANCE

Best Practices Database

  1. Nomi Tabelle: Sempre prefisso modulo (modulo_tabella)
  2. Indici: Su campi più interrogati (status, created_at, ecc.)
  3. Foreign Keys: Sempre con onDelete('set null') o cascade
  4. Soft Deletes: Per dati importanti
  5. JSON Fields: Per configurazioni flessibili
  6. Audit Trail: Campi created_by, updated_by, deleted_by

Ottimizzazioni Consigliate

// Uso di scope nei Model
public function scopeAttivo($query)
{
    return $query->where('status', 'attivo');
}

// Cache per query pesanti
$data = Cache::tags(['modulo_data'])->remember('modulo_list', 3600, function() {
    return DB::table('modulo_table')->where('status', 'attivo')->get();
});

// Query Builder ottimizzate
$risultati = DB::table('modulo_table')
    ->select(['id', 'nome', 'status'])  // Solo campi necessari
    ->where('status', 'attivo')
    ->orderBy('created_at', 'desc')
    ->limit(20)
    ->get();

🔐 SICUREZZA E PERMESSI

Sistema Permessi

// Definizione permessi nel module.json
"permissions": {
    "modulo_view": "Visualizzare elementi del modulo",
    "modulo_create": "Creare nuovi elementi",
    "modulo_edit": "Modificare elementi esistenti",
    "modulo_delete": "Eliminare elementi",
    "modulo_export": "Esportare dati",
    "modulo_admin": "Amministrazione completa modulo"
}

// Uso nei controller
$this->middleware('permission:modulo_view')->only(['index', 'show']);
$this->middleware('permission:modulo_create')->only(['create', 'store']);

// Controllo nei template Blade
@can('modulo_create')
    <a href="{{ route('modulo.create') }}" class="btn btn-primary">Nuovo</a>
@endcan

Validazione Input

// Nel controller
public function store(Request $request)
{
    $validated = $request->validate([
        'nome' => 'required|string|max:255|unique:modulo_table',
        'email' => 'required|email',
        'importo' => 'required|numeric|min:0',
        'data' => 'required|date|after:today'
    ]);
    
    // Sanitizzazione aggiuntiva
    $validated['nome'] = strip_tags($validated['nome']);
    
    // Creazione record...
}

📦 INSTALLAZIONE E DISTRIBUZIONE

Processo di Installazione

  1. Preparazione ZIP:

    NomeModulo.zip
    ├── module.json
    ├── Controllers/
    ├── Routes/
    ├── Migrations/
    ├── Seeders/
    └── Views/
    
  2. Installazione via ModuleManager:

    $moduleManager = app(ModuleManager::class);
    $result = $moduleManager->installModule('NomeModulo', $zipPath);
    
  3. Registrazione Automatica:

    • Route caricate automaticamente
    • Migration eseguite
    • Permessi registrati
    • Menu integrato

Command Artisan per Installazione

// app/Console/Commands/InstallModule.php
class InstallModule extends Command
{
    protected $signature = 'module:install {name} {--zip=}';
    
    public function handle()
    {
        $name = $this->argument('name');
        $zipPath = $this->option('zip');
        
        $moduleManager = app(ModuleManager::class);
        
        try {
            $result = $moduleManager->installModule($name, $zipPath);
            $this->info("✅ Modulo {$name} installato con successo!");
        } catch (\Exception $e) {
            $this->error("❌ Errore installazione: " . $e->getMessage());
        }
    }
}

🎨 FRONTEND E UX

Template Blade Base

{{-- resources/views/modules/nome-modulo/index.blade.php --}}
@extends('layouts.app')

@section('title', 'Nome Modulo')

@section('content')
<div class="container-fluid">
    <div class="row">
        <div class="col-12">
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <h3 class="card-title">Nome Modulo</h3>
                    @can('modulo_create')
                        <a href="{{ route('modulo.create') }}" class="btn btn-primary">
                            <i class="fas fa-plus"></i> Nuovo
                        </a>
                    @endcan
                </div>
                
                <div class="card-body">
                    {{-- Filtri di ricerca --}}
                    <form method="GET" class="mb-3">
                        <div class="row">
                            <div class="col-md-4">
                                <input type="text" name="search" class="form-control" 
                                       placeholder="Cerca..." value="{{ request('search') }}">
                            </div>
                            <div class="col-md-2">
                                <button type="submit" class="btn btn-outline-primary">
                                    <i class="fas fa-search"></i>
                                </button>
                            </div>
                        </div>
                    </form>
                    
                    {{-- Tabella dati --}}
                    <div class="table-responsive">
                        <table class="table table-striped">
                            <thead>
                                <tr>
                                    <th>Nome</th>
                                    <th>Status</th>
                                    <th>Data Creazione</th>
                                    <th>Azioni</th>
                                </tr>
                            </thead>
                            <tbody>
                                @forelse($data as $item)
                                    <tr>
                                        <td>{{ $item->nome }}</td>
                                        <td>
                                            <span class="badge badge-{{ $item->status == 'attivo' ? 'success' : 'secondary' }}">
                                                {{ ucfirst($item->status) }}
                                            </span>
                                        </td>
                                        <td>{{ $item->created_at->format('d/m/Y H:i') }}</td>
                                        <td>
                                            @can('modulo_view')
                                                <a href="{{ route('modulo.show', $item->id) }}" class="btn btn-sm btn-info">
                                                    <i class="fas fa-eye"></i>
                                                </a>
                                            @endcan
                                            @can('modulo_edit')
                                                <a href="{{ route('modulo.edit', $item->id) }}" class="btn btn-sm btn-warning">
                                                    <i class="fas fa-edit"></i>
                                                </a>
                                            @endcan
                                            @can('modulo_delete')
                                                <form method="POST" action="{{ route('modulo.destroy', $item->id) }}" 
                                                      style="display: inline-block;">
                                                    @csrf
                                                    @method('DELETE')
                                                    <button type="submit" class="btn btn-sm btn-danger" 
                                                            onclick="return confirm('Sei sicuro?')">
                                                        <i class="fas fa-trash"></i>
                                                    </button>
                                                </form>
                                            @endcan
                                        </td>
                                    </tr>
                                @empty
                                    <tr>
                                        <td colspan="4" class="text-center">Nessun elemento trovato</td>
                                    </tr>
                                @endforelse
                            </tbody>
                        </table>
                    </div>
                    
                    {{-- Paginazione --}}
                    {{ $data->links() }}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

CSS/JS Specifici del Modulo

/* public/css/modules/nome-modulo.css */
.modulo-card {
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.modulo-stats {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border-radius: 8px;
    padding: 20px;
}

🧪 TESTING E DEBUG

Unit Test Base

<?php

namespace Tests\Modules\NomeModulo;

use Tests\TestCase;
use App\Modules\NomeModulo\Controllers\NomeModuloController;

class NomeModuloTest extends TestCase
{
    public function test_index_displays_data()
    {
        $user = User::factory()->create();
        $user->givePermissionTo('modulo_view');
        
        $response = $this->actingAs($user)->get(route('modulo.index'));
        
        $response->assertStatus(200);
        $response->assertViewIs('modules.nome-modulo.index');
    }
    
    public function test_create_requires_permission()
    {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)->get(route('modulo.create'));
        
        $response->assertStatus(403);
    }
    
    public function test_store_validates_input()
    {
        $user = User::factory()->create();
        $user->givePermissionTo('modulo_create');
        
        $response = $this->actingAs($user)->post(route('modulo.store'), []);
        
        $response->assertSessionHasErrors(['nome', 'email']);
    }
}

Logging e Debug

// Nel controller
use Illuminate\Support\Facades\Log;

public function action()
{
    Log::info('Modulo: Inizio azione', ['user' => auth()->id()]);
    
    try {
        // Logica
        Log::debug('Modulo: Dati elaborati', ['data' => $processedData]);
        
    } catch (\Exception $e) {
        Log::error('Modulo: Errore azione', [
            'error' => $e->getMessage(),
            'trace' => $e->getTraceAsString(),
            'user' => auth()->id()
        ]);
    }
}

💰 MODELLO BUSINESS E PRICING

Categorie di Moduli

  1. Gratuiti (€0):

    • Moduli base/community
    • Funzionalità essenziali
  2. Standard (€49-99/anno):

    • Moduli specializzati
    • Funzionalità avanzate
  3. Premium (€199-299/anno):

    • Moduli complessi
    • Integrazione esterna
    • Support prioritario
  4. Enterprise (€499+/anno):

    • Moduli mission-critical
    • Customizzazione
    • SLA garantiti

Licenze e Distribuzione

{
    "license": "commercial",
    "price": 99.00,
    "currency": "EUR", 
    "billing": "yearly",
    "trial_days": 30,
    "support_included": true,
    "updates_included": true,
    "installation_limit": 1
}

🚀 DEPLOYMENT E MANUTENZIONE

Checklist Pre-Release

  • Test su ambiente locale
  • Validazione sicurezza
  • Performance test
  • Documentazione completa
  • Screenshot/demo
  • Pricing definito
  • Support plan

Processo di Aggiornamento

// Migration di aggiornamento
public function up(): void
{
    // Verifica versione precedente
    $currentVersion = DB::table('modules')
        ->where('name', 'NomeModulo')
        ->value('version');
    
    if (version_compare($currentVersion, '1.1.0', '<')) {
        // Modifiche per v1.1.0
        Schema::table('modulo_table', function (Blueprint $table) {
            $table->string('nuovo_campo')->nullable();
        });
    }
}

📚 RISORSE E SUPPORTO

Documentazione Obbligatoria

  1. README.md - Panoramica e installazione
  2. CHANGELOG.md - Log delle modifiche
  3. API.md - Documentazione API se presente
  4. HELP.md - Guida utente

Support e Community

  • Forum sviluppatori NetGescon
  • Documentation wiki
  • Code repository template
  • Issue tracking system

CHECKLIST MODULO COMPLETO

Files Essenziali

  • module.json configurato
  • Controller con CRUD base
  • Route definitions
  • Migration database
  • Seeder con dati base
  • Template Blade base
  • Middleware permessi
  • Hook integration
  • Unit tests
  • Documentazione

Funzionalità Base

  • Autenticazione/Autorizzazione
  • CRUD operations
  • Ricerca e filtri
  • Paginazione
  • Export dati
  • Logging attività
  • Cache optimization
  • Error handling
  • Input validation
  • API endpoints

Qualità Codice

  • PSR-4 compliance
  • Commenti e documentation
  • Error handling robusto
  • Security best practices
  • Performance optimization
  • Mobile responsive UI
  • Cross-browser compatibility
  • Accessibility standards

🎯 CONCLUSIONE

Questa guida fornisce tutto il necessario per sviluppare moduli professionali per NetGescon. Il sistema modulare è progettato per essere:

  • Scalabile: Supporta crescita dell'applicazione
  • Sicuro: Permessi granulari e validazione
  • Performante: Cache e ottimizzazioni database
  • Manutenibile: Codice pulito e documentato
  • Commerciale: Modello business integrato

Utilizzando questa specifica, puoi sviluppare moduli che si integrano perfettamente con NetGescon e possono essere distribuiti come prodotti commerciali.


📞 CONTATTI SVILUPPO