option('rollback')) { return $this->rollback($deduplicator, $rollbackPath); } $file = $this->argument('file'); if (! $file || ! is_file($file)) { $this->error('Add meg a kitöltött döntési lapot. Ha még nincs: php artisan producers:dedupe-report'); return self::FAILURE; } $decisions = $this->readDecisions($file); if ($decisions === []) { $this->error('A lapon nem találtam feldolgozható sort. Megvan az "' . ProducersDedupeReport::COLUMN_ID . '" oszlop a fejlécben?'); return self::FAILURE; } ['merges' => $merges, 'errors' => $errors] = $deduplicator->buildPlan($decisions); foreach ($errors as $error) { $this->warn(' ! ' . $error); } if ($merges === []) { $this->error('Nincs végrehajtható összevonás.'); return self::FAILURE; } $this->summarize($merges); if (! $this->option('apply')) { $this->newLine(); $this->info('Ez csak kimutatás volt. A végrehajtáshoz: --apply'); return self::SUCCESS; } if ($errors !== [] && ! $this->confirmed('Voltak figyelmeztetések. Biztosan folytatod?')) { return self::FAILURE; } $database = DB::connection()->getDatabaseName(); if (! $this->confirmed("Végrehajtás a(z) '{$database}' adatbázison. Folytatod?")) { return self::FAILURE; } $report = $deduplicator->apply($merges); $reportPath = $this->writeReport($report); $this->newLine(); $this->info('Az összevonás megtörtént.'); foreach ($report['totals'] as $table => $count) { $this->line(' ' . str_pad($table, 24) . number_format($count) . ' sor átírva'); } $this->line(' Jelentés: ' . $reportPath); $this->newLine(); $this->comment('Visszagörgetés: php artisan producers:dedupe --rollback="' . $reportPath . '"'); return self::SUCCESS; } /** * Megerősítés, --force esetén kérdés nélkül. */ private function confirmed(string $question): bool { return $this->option('force') || $this->confirm($question, false); } /** * A lap fejlécnév alapján olvasódik, nem oszlopbetű szerint: a megrendelő * beszúrhat oszlopot vagy átrendezheti a lapot anélkül, hogy ez eltörne. * * @return array */ private function readDecisions(string $file): array { $sheet = IOFactory::load($file)->getActiveSheet(); $highestRow = $sheet->getHighestDataRow(); $highestColumn = $sheet->getHighestDataColumn(); $columnCount = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); $headerRow = null; $columns = []; for ($row = 1; $row <= min($highestRow, 30); $row++) { for ($column = 1; $column <= $columnCount; $column++) { $value = trim((string) $sheet->getCellByColumnAndRow($column, $row)->getValue()); if ($value === ProducersDedupeReport::COLUMN_ID) { $headerRow = $row; } } if ($headerRow !== null) { for ($column = 1; $column <= $columnCount; $column++) { $value = trim((string) $sheet->getCellByColumnAndRow($column, $headerRow)->getValue()); if ($value !== '') { $columns[$value] = $column; } } break; } } if ($headerRow === null) { return []; } $idColumn = $columns[ProducersDedupeReport::COLUMN_ID] ?? null; $groupColumn = $columns[ProducersDedupeReport::COLUMN_GROUP] ?? null; $nameColumn = $columns[ProducersDedupeReport::COLUMN_FINAL_NAME] ?? null; if (! $idColumn || ! $nameColumn) { return []; } $decisions = []; for ($row = $headerRow + 1; $row <= $highestRow; $row++) { $id = (int) $sheet->getCellByColumnAndRow($idColumn, $row)->getValue(); if ($id <= 0) { continue; } $decisions[] = [ 'group' => $groupColumn ? $sheet->getCellByColumnAndRow($groupColumn, $row)->getValue() : null, 'id' => $id, 'final_name' => trim((string) $sheet->getCellByColumnAndRow($nameColumn, $row)->getValue()), ]; } return $decisions; } private function summarize(array $merges): void { $rows = []; $totals = ['products' => 0, 'order_archives_items' => 0, 'pricelist_file_lines' => 0]; foreach ($merges as $merge) { foreach ($merge['from'] as $from) { foreach ($totals as $key => $value) { $totals[$key] += $from['counts'][$key]; } $rows[] = [ '"' . $from['name'] . '"', '→ #' . $merge['keeper_id'] . ' "' . $merge['final_name'] . '"' . ($merge['rename'] ? ' (átnevezés)' : ''), number_format($from['counts']['products']), number_format($from['counts']['order_archives_items']), ]; } } $this->table(['Beolvasztandó', 'Célrekord', 'Termék', 'Rendelési tétel'], $rows); $this->line('Csoport: ' . count($merges) . ' | beolvasztandó rekord: ' . array_sum(array_map(fn ($m) => count($m['from']), $merges))); $this->line('Átírandó sorok: products ' . number_format($totals['products']) . ' · order_archives_items ' . number_format($totals['order_archives_items']) . ' · pricelist_file_lines ' . number_format($totals['pricelist_file_lines'])); } private function writeReport(array $report): string { $path = $this->option('report') ?: storage_path( 'app/private/producers-dedupe-' . now()->format('Y-m-d_His') . '.json' ); $directory = dirname($path); if (! is_dir($directory)) { mkdir($directory, 0775, true); } file_put_contents($path, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); return $path; } private function rollback(ProducerDeduplicator $deduplicator, string $path): int { if (! is_file($path)) { $this->error('A jelentésfájl nem található: ' . $path); return self::FAILURE; } $report = json_decode((string) file_get_contents($path), true); if (! is_array($report) || ! isset($report['restore'])) { $this->error('A fájl nem érvényes producers:dedupe jelentés.'); return self::FAILURE; } $database = DB::connection()->getDatabaseName(); if (($report['database'] ?? null) !== $database) { $this->warn('A jelentés a(z) "' . ($report['database'] ?? '?') . '" adatbázisról készült, most viszont "' . $database . '" az aktív.'); if (! $this->confirmed('Biztosan folytatod?')) { return self::FAILURE; } } $this->line('Visszagörgetés: ' . count($report['merges'] ?? []) . ' összevonás (' . ($report['created_at'] ?? '?') . ')'); if (! $this->confirmed("Visszagörgetés a(z) '{$database}' adatbázison. Folytatod?")) { return self::FAILURE; } $restored = $deduplicator->rollback($report); $this->info('Visszagörgetve.'); foreach ($restored as $table => $count) { $this->line(' ' . str_pad($table, 24) . number_format($count) . ' sor visszaállítva'); } return self::SUCCESS; } }