|
@@ -0,0 +1,217 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Console\Commands;
|
|
|
|
|
+
|
|
|
|
|
+use App\Models\Contract;
|
|
|
|
|
+use App\Models\File;
|
|
|
|
|
+use App\Models\MafOrder;
|
|
|
|
|
+use App\Models\Order;
|
|
|
|
|
+use App\Models\Product;
|
|
|
|
|
+use App\Models\ProductSKU;
|
|
|
|
|
+use App\Models\Reclamation;
|
|
|
|
|
+use App\Models\Schedule;
|
|
|
|
|
+use App\Models\Ttn;
|
|
|
|
|
+use Illuminate\Console\Command;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
|
|
+use Illuminate\Support\Facades\Storage;
|
|
|
|
|
+
|
|
|
|
|
+class ClearYearData extends Command
|
|
|
|
|
+{
|
|
|
|
|
+ protected $signature = 'app:clear-year-data
|
|
|
|
|
+ {year : Год для очистки данных}
|
|
|
|
|
+ {--force : Выполнить без подтверждения}';
|
|
|
|
|
+
|
|
|
|
|
+ protected $description = 'Очистка всех данных CRM за указанный год и связанных зависимостей';
|
|
|
|
|
+
|
|
|
|
|
+ public function handle(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $year = (int) $this->argument('year');
|
|
|
|
|
+
|
|
|
|
|
+ if ($year < 2020 || $year > 2100) {
|
|
|
|
|
+ $this->error("Некорректный год: {$year}");
|
|
|
|
|
+ return self::FAILURE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $stats = $this->collectStats($year);
|
|
|
|
|
+
|
|
|
|
|
+ $this->info("Данные для удаления за {$year} год:");
|
|
|
|
|
+ $this->table(
|
|
|
|
|
+ ['Сущность', 'Количество'],
|
|
|
|
|
+ collect($stats)->map(fn($count, $name) => [$name, $count])->toArray()
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (!$this->option('force')) {
|
|
|
|
|
+ if (!$this->confirm('Вы уверены, что хотите удалить все эти данные? Это действие необратимо!')) {
|
|
|
|
|
+ $this->info('Операция отменена.');
|
|
|
|
|
+ return self::SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Начинаю удаление данных...');
|
|
|
|
|
+
|
|
|
|
|
+ DB::beginTransaction();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ $this->clearData($year);
|
|
|
|
|
+ DB::commit();
|
|
|
|
|
+ $this->info('Все данные за ' . $year . ' год успешно удалены.');
|
|
|
|
|
+ return self::SUCCESS;
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ $this->error('Ошибка при удалении данных: ' . $e->getMessage());
|
|
|
|
|
+ return self::FAILURE;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function collectStats(int $year): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $orderIds = Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $productIds = Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $mafOrderIds = MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $productSkuIds = ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+
|
|
|
|
|
+ $reclamationCount = Reclamation::whereIn('order_id', $orderIds)->count();
|
|
|
|
|
+
|
|
|
|
|
+ // Собираем ID файлов для удаления
|
|
|
|
|
+ $fileIds = $this->collectFileIds($year, $orderIds, $productIds, $productSkuIds);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'Заказы (Orders)' => $orderIds->count(),
|
|
|
|
|
+ 'Заказы МАФ (MafOrders)' => $mafOrderIds->count(),
|
|
|
|
|
+ 'Продукты (Products)' => $productIds->count(),
|
|
|
|
|
+ 'SKU продуктов (ProductSKU)' => $productSkuIds->count(),
|
|
|
|
|
+ 'Рекламации (Reclamations)' => $reclamationCount,
|
|
|
|
|
+ 'Расписания (Schedules)' => Schedule::whereIn('order_id', $orderIds)->count(),
|
|
|
|
|
+ 'ТТН (Ttns)' => Ttn::where('year', $year)->count(),
|
|
|
|
|
+ 'Контракты (Contracts)' => Contract::where('year', $year)->count(),
|
|
|
|
|
+ 'Файлы (Files)' => $fileIds->count(),
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function collectFileIds(int $year, $orderIds, $productIds, $productSkuIds): \Illuminate\Support\Collection
|
|
|
|
|
+ {
|
|
|
|
|
+ $fileIds = collect();
|
|
|
|
|
+
|
|
|
|
|
+ // Файлы заказов (фото, документы, ведомости)
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('order_photo')->whereIn('order_id', $orderIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('order_document')->whereIn('order_id', $orderIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('order_statement')->whereIn('order_id', $orderIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Рекламации связанных заказов
|
|
|
|
|
+ $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
|
|
|
|
|
+
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Сертификаты продуктов
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ Product::withoutGlobalScopes()->withTrashed()
|
|
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
|
|
+ ->whereNotNull('certificate_id')
|
|
|
|
|
+ ->pluck('certificate_id')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Паспорта SKU
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ ProductSKU::withoutGlobalScopes()->withTrashed()
|
|
|
|
|
+ ->whereIn('id', $productSkuIds)
|
|
|
|
|
+ ->whereNotNull('passport_id')
|
|
|
|
|
+ ->pluck('passport_id')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // Файлы ТТН
|
|
|
|
|
+ $fileIds = $fileIds->merge(
|
|
|
|
|
+ Ttn::where('year', $year)->whereNotNull('file_id')->pluck('file_id')
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return $fileIds->unique();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function clearData(int $year): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $orderIds = Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $productIds = Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $mafOrderIds = MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $productSkuIds = ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
|
|
|
|
|
+ $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
|
|
|
|
|
+
|
|
|
|
|
+ // Собираем файлы до удаления связей
|
|
|
|
|
+ $fileIds = $this->collectFileIds($year, $orderIds, $productIds, $productSkuIds);
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление рекламаций и связанных данных...');
|
|
|
|
|
+ // Детали рекламаций (cascadeOnDelete, но удаляем явно для уверенности)
|
|
|
|
|
+ DB::table('reclamation_details')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ DB::table('reclamation_product_sku')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->delete();
|
|
|
|
|
+ Reclamation::whereIn('id', $reclamationIds)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление связей заказов с файлами...');
|
|
|
|
|
+ DB::table('order_photo')->whereIn('order_id', $orderIds)->delete();
|
|
|
|
|
+ DB::table('order_document')->whereIn('order_id', $orderIds)->delete();
|
|
|
|
|
+ DB::table('order_statement')->whereIn('order_id', $orderIds)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление расписаний...');
|
|
|
|
|
+ Schedule::whereIn('order_id', $orderIds)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление SKU продуктов...');
|
|
|
|
|
+ ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление заказов...');
|
|
|
|
|
+ Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление заказов МАФ...');
|
|
|
|
|
+ MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление продуктов...');
|
|
|
|
|
+ // Сначала обнуляем certificate_id чтобы избежать проблем с FK
|
|
|
|
|
+ Product::withoutGlobalScopes()->withTrashed()
|
|
|
|
|
+ ->whereIn('id', $productIds)
|
|
|
|
|
+ ->update(['certificate_id' => null]);
|
|
|
|
|
+ Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление ТТН...');
|
|
|
|
|
+ // Обнуляем file_id перед удалением
|
|
|
|
|
+ Ttn::where('year', $year)->update(['file_id' => null]);
|
|
|
|
|
+ Ttn::where('year', $year)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление контрактов...');
|
|
|
|
|
+ Contract::where('year', $year)->delete();
|
|
|
|
|
+
|
|
|
|
|
+ $this->info('Удаление файлов...');
|
|
|
|
|
+ $this->deleteFiles($fileIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function deleteFiles($fileIds): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $files = File::whereIn('id', $fileIds)->get();
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($files as $file) {
|
|
|
|
|
+ // Удаляем физический файл
|
|
|
|
|
+ if ($file->path && Storage::disk('public')->exists($file->path)) {
|
|
|
|
|
+ Storage::disk('public')->delete($file->path);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Удаляем записи из БД
|
|
|
|
|
+ File::whereIn('id', $fileIds)->delete();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|