Testing Alpha
This commit is contained in:
289
src/Application/Seeding/SeederOrchestrator.php
Normal file
289
src/Application/Seeding/SeederOrchestrator.php
Normal file
@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Application\Seeding;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Koneko\VuexyAdmin\Application\Seeding\SeederReportBuilder;
|
||||
use Koneko\VuexyAdmin\Application\Traits\Seeders\Main\HasSeederLogger;
|
||||
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarImageService;
|
||||
use Koneko\VuexyAdmin\Application\UI\Avatar\AvatarInitialsService;
|
||||
use Koneko\VuexyAdmin\Support\Seeders\AbstractDataSeeder;
|
||||
|
||||
class SeederOrchestrator
|
||||
{
|
||||
use HasSeederLogger;
|
||||
|
||||
protected ?Command $command = null;
|
||||
protected SeederReportBuilder $reporter;
|
||||
|
||||
public function __construct(?Command $command = null)
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->reporter = new SeederReportBuilder();
|
||||
}
|
||||
|
||||
public function run(array|string|null $only = null, array $options = []): void
|
||||
{
|
||||
// Configuración del entorno
|
||||
if (isset($options['env'])) {
|
||||
config(['seeder.env' => $options['env']]);
|
||||
}
|
||||
|
||||
// Determinar si se debe generar reporte
|
||||
$shouldGenerateReport = $options['report'] ?? config('seeder.defaults.report', false);
|
||||
|
||||
// Ejecutar limpieza si está activada
|
||||
$this->clearAssetsIfNeeded($options);
|
||||
|
||||
// Filtrar módulos y resolver dependencias
|
||||
$modules = $this->filterModules(config('seeder.modules', []), $only);
|
||||
$modules = $this->resolveDependencies($modules);
|
||||
|
||||
foreach ($modules as $key => $config) {
|
||||
$this->log("Iniciando módulo: <info>{$key}</info>");
|
||||
|
||||
$mergedConfig = array_merge(config('seeder.defaults', []), $config, $options);
|
||||
$this->processModule($key, $mergedConfig);
|
||||
}
|
||||
|
||||
// Guardar reporte solo si está habilitado
|
||||
if ($shouldGenerateReport) {
|
||||
$this->reporter->saveReports();
|
||||
}
|
||||
}
|
||||
|
||||
protected function processModule(string $key, array $config): void
|
||||
{
|
||||
try {
|
||||
$seederClass = $config['seeder'];
|
||||
$seeder = app($seederClass);
|
||||
|
||||
// Inyectar comando si es posible
|
||||
if (method_exists($seeder, 'setCommand')) {
|
||||
$seeder->setCommand($this->command);
|
||||
|
||||
} elseif (property_exists($seeder, 'command')) {
|
||||
$seeder->command = $this->command;
|
||||
}
|
||||
|
||||
// Configuración general para seeders avanzados
|
||||
if ($seeder instanceof AbstractDataSeeder) {
|
||||
if ($this->command) {
|
||||
$seeder->setCommand($this->command);
|
||||
}
|
||||
|
||||
if (isset($config['file'])) {
|
||||
$seeder->setTargetFile($config['file']);
|
||||
}
|
||||
|
||||
if ($config['truncate'] ?? false) {
|
||||
$this->log("🗑️ Truncando tabla para {$key}...");
|
||||
$seeder->truncate();
|
||||
}
|
||||
}
|
||||
|
||||
// Modo simulación
|
||||
if ($config['dry-run'] ?? false) {
|
||||
$this->logDryRun($key, $config);
|
||||
|
||||
$this->reporter->addModule([
|
||||
'name' => $key,
|
||||
'status' => 'skipped',
|
||||
'file' => $config['file'] ?? null,
|
||||
'records' => null,
|
||||
'time' => null,
|
||||
'truncated' => $config['truncate'] ?? false,
|
||||
'faker' => false,
|
||||
'details' => 'Dry run',
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ejecución real
|
||||
$this->executeSeeder($seeder, $key, $config);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->log("❌ Error en módulo {$key}: {$e->getMessage()}");
|
||||
|
||||
$this->reporter->addModule([
|
||||
'name' => $key,
|
||||
'status' => 'failed',
|
||||
'file' => $config['file'] ?? null,
|
||||
'records' => null,
|
||||
'time' => null,
|
||||
'truncated' => $config['truncate'] ?? false,
|
||||
'faker' => false,
|
||||
'details' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function executeSeeder($seeder, string $key, array $config): void
|
||||
{
|
||||
$records = null;
|
||||
|
||||
if ($config['transactional'] ?? false) {
|
||||
$connection = $seeder->getModel()->getConnection();
|
||||
$connection->transaction(function () use ($seeder, $key, $config, &$records) {
|
||||
$records = $this->runSeederCore($seeder, $key, $config);
|
||||
});
|
||||
} else {
|
||||
$records = $this->runSeederCore($seeder, $key, $config);
|
||||
}
|
||||
|
||||
$this->reporter->addModule([
|
||||
'name' => $key,
|
||||
'status' => 'completed',
|
||||
'records' => $records,
|
||||
'time' => round(microtime(true) - LARAVEL_START, 2) . 's',
|
||||
'truncated' => $config['truncate'] ?? false,
|
||||
'faker' => isset($config['fake']),
|
||||
'file' => $config['file'] ?? 'N/A',
|
||||
'note' => 'OK',
|
||||
]);
|
||||
|
||||
$this->log("✅ Módulo completado: <info>{$key}</info>\n");
|
||||
}
|
||||
|
||||
protected function runSeederCore($seeder, string $key, array $config): int
|
||||
{
|
||||
$records = 0;
|
||||
|
||||
$runFake = $config['fake'] ?? false;
|
||||
$runNormal = !$runFake || !($config['faker_only'] ?? false); // si no está en modo faker_only, corre run()
|
||||
|
||||
// Ejecutar inserción desde archivo solo si se permite
|
||||
if ($runNormal && method_exists($seeder, 'run')) {
|
||||
$seeder->run($config);
|
||||
$records = method_exists($seeder, 'getProcessedCount') ? $seeder->getProcessedCount() : 0;
|
||||
}
|
||||
|
||||
// Ejecutar faker si se permite
|
||||
if ($runFake && method_exists($seeder, 'runFake')) {
|
||||
$min = (int) ($config['fake-count'] ?? $config['fake']['min'] ?? 1);
|
||||
$max = (int) ($config['fake-count'] ?? $config['fake']['max'] ?? $min);
|
||||
$total = $min === $max ? $min : rand($min, $max);
|
||||
|
||||
$seeder->runFake($total, $config['fake']);
|
||||
$records += method_exists($seeder, 'getProcessedCount') ? $seeder->getProcessedCount() : $total;
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
protected function resolveDependencies(array $modules): array
|
||||
{
|
||||
$resolved = [];
|
||||
$unresolved = $modules;
|
||||
|
||||
while (!empty($unresolved)) {
|
||||
$resolvedCount = count($resolved);
|
||||
|
||||
foreach ($unresolved as $key => $module) {
|
||||
$dependencies = $module['depends_on'] ?? [];
|
||||
|
||||
if (collect($dependencies)->every(fn($dep) => isset($resolved[$dep]))) {
|
||||
$resolved[$key] = $module;
|
||||
unset($unresolved[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolvedCount === count($resolved)) {
|
||||
$keys = implode(', ', array_keys($unresolved));
|
||||
throw new \RuntimeException("Dependencias circulares o no resueltas detectadas en: {$keys}");
|
||||
}
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
protected function filterModules(array $modules, $only): array
|
||||
{
|
||||
$filtered = [];
|
||||
|
||||
foreach ($modules as $key => $module) {
|
||||
// 1. Si está deshabilitado, lo ignoramos completamente
|
||||
if (!($module['enabled'] ?? true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Si hay filtro por "only", respetarlo
|
||||
if ($only !== null) {
|
||||
$onlyList = is_array($only) ? $only : [$only];
|
||||
if (!in_array($key, $onlyList)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$filtered[$key] = $module;
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
protected function clearAssetsIfNeeded(array $options = []): void
|
||||
{
|
||||
if ($options['dry-run'] ?? false) {
|
||||
$this->log("🔹 [DRY RUN] Simularía limpieza de assets");
|
||||
return;
|
||||
}
|
||||
|
||||
$config = config('seeder.clear_assets', []);
|
||||
|
||||
foreach ($config as $assetType => $enabled) {
|
||||
if (!$enabled) continue;
|
||||
|
||||
match ($assetType) {
|
||||
'avatars' => app(AvatarImageService::class)->clearAllProfilePhotos(),
|
||||
'initials' => app(AvatarInitialsService::class)->clearAllInitials(),
|
||||
'attachments' => Storage::disk('attachments')->deleteDirectory('all'),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected function logDryRun(string $key, array $config): void
|
||||
{
|
||||
$this->log("🔹 [DRY RUN] Simulación para módulo: <info>{$key}</info>");
|
||||
$this->log("├─ Seeder: <comment>{$config['seeder']}</comment>");
|
||||
if (isset($config['file'])) {
|
||||
$this->log("├─ Archivo: <comment>{$config['file']}</comment>");
|
||||
}
|
||||
if (isset($config['fake'])) {
|
||||
$count = $config['fake-count'] ?? ($config['fake']['max'] ?? 'aleatorio');
|
||||
$this->log("├─ Datos falsos: <comment>{$count} registros</comment>");
|
||||
}
|
||||
if ($config['truncate'] ?? false) {
|
||||
$this->log("└─ <fg=red>TRUNCATE activado</>");
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableModules(): array
|
||||
{
|
||||
return config('seeder.modules', []);
|
||||
}
|
||||
|
||||
public function getSeederClass(string $module): ?string
|
||||
{
|
||||
return config("seeder.modules.$module.seeder");
|
||||
}
|
||||
|
||||
public function getReportPath(): string
|
||||
{
|
||||
return $this->reporter->getReportPathMarkdown();
|
||||
}
|
||||
|
||||
public function getReport(): array
|
||||
{
|
||||
return $this->reporter->getReportData();
|
||||
}
|
||||
|
||||
public function setCommand(Command $command): static
|
||||
{
|
||||
$this->command = $command;
|
||||
return $this;
|
||||
}
|
||||
}
|
163
src/Application/Seeding/SeederReportBuilder.php
Normal file
163
src/Application/Seeding/SeederReportBuilder.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Koneko\VuexyAdmin\Application\Seeding;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class SeederReportBuilder
|
||||
{
|
||||
protected array $modules = [];
|
||||
protected array $metadata = [];
|
||||
|
||||
/**
|
||||
* Setea los metadatos globales del reporte.
|
||||
*/
|
||||
public function setMetadata(array $data): static
|
||||
{
|
||||
$this->metadata = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrega información de un módulo procesado.
|
||||
*/
|
||||
public function addModule(array $data): static
|
||||
{
|
||||
$this->modules[] = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finaliza la metadata antes de guardar.
|
||||
*/
|
||||
public function finalize(): void
|
||||
{
|
||||
$this->metadata['executed_at'] = now()->toDateTimeString();
|
||||
$this->metadata['duration'] = round(microtime(true) - LARAVEL_START, 2) . 's';
|
||||
$this->metadata['status'] = collect($this->modules)->contains(fn($mod) => ($mod['status'] ?? '') === 'failed')
|
||||
? '❌ With errors'
|
||||
: '✅ Success';
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda el reporte Markdown.
|
||||
*/
|
||||
public function saveReports(): void
|
||||
{
|
||||
$this->finalize();
|
||||
$this->saveMarkdown();
|
||||
$this->saveJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera y guarda el reporte en Markdown.
|
||||
*/
|
||||
public function saveMarkdown(?string $path = null): string
|
||||
{
|
||||
$env = $this->metadata['env'] ?? 'local';
|
||||
$timestamp = now()->format('Y-m-d-H-i-s');
|
||||
$dir = database_path("seeders/reports/{$env}");
|
||||
|
||||
File::ensureDirectoryExists($dir);
|
||||
$filePath = $path ?? "{$dir}/seed-report-{$env}-{$timestamp}.md";
|
||||
|
||||
File::put($filePath, $this->generateMarkdown());
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera y guarda el reporte en JSON.
|
||||
*/
|
||||
public function saveJson(?string $path = null): string
|
||||
{
|
||||
$env = $this->metadata['env'] ?? 'local';
|
||||
$timestamp = now()->format('Y-m-d-H-i-s');
|
||||
$dir = database_path("seeders/reports/{$env}");
|
||||
|
||||
File::ensureDirectoryExists($dir);
|
||||
$filePath = $path ?? "{$dir}/seed-report-{$env}-{$timestamp}.json";
|
||||
|
||||
File::put($filePath, json_encode([
|
||||
'metadata' => $this->metadata,
|
||||
'modules' => $this->modules,
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera la tabla Markdown completa.
|
||||
*/
|
||||
public function generateMarkdown(): string
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
$lines[] = '# 📊 Koneko ERP - Seeders Report';
|
||||
$lines[] = '';
|
||||
$lines[] = '## 🛠️ Metadata';
|
||||
$lines[] = '| Metadata | Value |';
|
||||
$lines[] = '|-----------------|---------------------------|';
|
||||
$lines[] = '| **Project** | ' . ($this->metadata['project'] ?? 'N/A') . ' |';
|
||||
$lines[] = '| **Version** | ' . ($this->metadata['version'] ?? 'N/A') . ' |';
|
||||
$lines[] = '| **Environment** | `' . ($this->metadata['env'] ?? 'N/A') . '` |';
|
||||
$lines[] = '| **Executed at** | ' . ($this->metadata['executed_at'] ?? now()) . ' |';
|
||||
$lines[] = '| **Duration** | ' . ($this->metadata['duration'] ?? 'N/A') . ' |';
|
||||
$lines[] = '| **Status** | ' . ($this->metadata['status'] ?? 'N/A') . ' |';
|
||||
$lines[] = '';
|
||||
$lines[] = '## 📦 Modules Summary';
|
||||
$lines[] = '';
|
||||
$lines[] = '| Module | Status | Records | Time | Truncated | Faker | File | Note |';
|
||||
$lines[] = '|-----------------|--------|---------|--------|-----------|-------|-------------|------|';
|
||||
|
||||
foreach ($this->modules as $mod) {
|
||||
$lines[] = sprintf(
|
||||
'| %-15s | %-6s | %-7s | %-6s | %-9s | %-5s | %-11s | %-4s |',
|
||||
$mod['name'] ?? '-',
|
||||
$mod['status'] ?? '-',
|
||||
(string) ($mod['records'] ?? '-'),
|
||||
(string) ($mod['time'] ?? '-'),
|
||||
($mod['truncated'] ?? false) ? '✔️' : '✖️',
|
||||
($mod['faker'] ?? false) ? '✔️' : '✖️',
|
||||
$mod['file'] ?? 'N/A',
|
||||
$mod['note'] ?? '-'
|
||||
);
|
||||
}
|
||||
|
||||
$lines[] = '';
|
||||
$lines[] = '## 📂 Archivos utilizados';
|
||||
$lines[] = '';
|
||||
|
||||
$files = collect($this->modules)->pluck('file')->filter()->unique();
|
||||
|
||||
foreach ($files as $file) {
|
||||
$lines[] = '- `' . $file . '`';
|
||||
}
|
||||
|
||||
$lines[] = '';
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta del reporte Markdown generado (opcional en runtime).
|
||||
*/
|
||||
public function getReportPathMarkdown(): string
|
||||
{
|
||||
$env = $this->metadata['env'] ?? 'local';
|
||||
return database_path("seeders/reports/{$env}/seed-report-{$env}.md");
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene los datos del reporte como array.
|
||||
*/
|
||||
public function getReportData(): array
|
||||
{
|
||||
return [
|
||||
'metadata' => $this->metadata,
|
||||
'modules' => $this->modules,
|
||||
];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user