ClearYearData.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Models\Contract;
  4. use App\Models\File;
  5. use App\Models\MafOrder;
  6. use App\Models\Order;
  7. use App\Models\Product;
  8. use App\Models\ProductSKU;
  9. use App\Models\Reclamation;
  10. use App\Models\Schedule;
  11. use App\Models\Ttn;
  12. use Illuminate\Console\Command;
  13. use Illuminate\Support\Facades\DB;
  14. use Illuminate\Support\Facades\Storage;
  15. class ClearYearData extends Command
  16. {
  17. protected $signature = 'app:clear-year-data
  18. {year : Год для очистки данных}
  19. {--force : Выполнить без подтверждения}';
  20. protected $description = 'Очистка всех данных CRM за указанный год и связанных зависимостей';
  21. public function handle(): int
  22. {
  23. $year = (int) $this->argument('year');
  24. if ($year < 2020 || $year > 2100) {
  25. $this->error("Некорректный год: {$year}");
  26. return self::FAILURE;
  27. }
  28. $stats = $this->collectStats($year);
  29. $this->info("Данные для удаления за {$year} год:");
  30. $this->table(
  31. ['Сущность', 'Количество'],
  32. collect($stats)->map(fn($count, $name) => [$name, $count])->toArray()
  33. );
  34. if (!$this->option('force')) {
  35. if (!$this->confirm('Вы уверены, что хотите удалить все эти данные? Это действие необратимо!')) {
  36. $this->info('Операция отменена.');
  37. return self::SUCCESS;
  38. }
  39. }
  40. $this->info('Начинаю удаление данных...');
  41. DB::beginTransaction();
  42. try {
  43. $this->clearData($year);
  44. DB::commit();
  45. $this->info('Все данные за ' . $year . ' год успешно удалены.');
  46. return self::SUCCESS;
  47. } catch (\Exception $e) {
  48. DB::rollBack();
  49. $this->error('Ошибка при удалении данных: ' . $e->getMessage());
  50. return self::FAILURE;
  51. }
  52. }
  53. private function collectStats(int $year): array
  54. {
  55. $orderIds = Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  56. $productIds = Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  57. $mafOrderIds = MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  58. $productSkuIds = ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  59. $reclamationCount = Reclamation::whereIn('order_id', $orderIds)->count();
  60. // Собираем ID файлов для удаления
  61. $fileIds = $this->collectFileIds($year, $orderIds, $productIds, $productSkuIds);
  62. return [
  63. 'Заказы (Orders)' => $orderIds->count(),
  64. 'Заказы МАФ (MafOrders)' => $mafOrderIds->count(),
  65. 'Продукты (Products)' => $productIds->count(),
  66. 'SKU продуктов (ProductSKU)' => $productSkuIds->count(),
  67. 'Рекламации (Reclamations)' => $reclamationCount,
  68. 'Расписания (Schedules)' => Schedule::whereIn('order_id', $orderIds)->count(),
  69. 'ТТН (Ttns)' => Ttn::where('year', $year)->count(),
  70. 'Контракты (Contracts)' => Contract::where('year', $year)->count(),
  71. 'Файлы (Files)' => $fileIds->count(),
  72. ];
  73. }
  74. private function collectFileIds(int $year, $orderIds, $productIds, $productSkuIds): \Illuminate\Support\Collection
  75. {
  76. $fileIds = collect();
  77. // Файлы заказов (фото, документы, ведомости)
  78. $fileIds = $fileIds->merge(
  79. DB::table('order_photo')->whereIn('order_id', $orderIds)->pluck('file_id')
  80. );
  81. $fileIds = $fileIds->merge(
  82. DB::table('order_document')->whereIn('order_id', $orderIds)->pluck('file_id')
  83. );
  84. $fileIds = $fileIds->merge(
  85. DB::table('order_statement')->whereIn('order_id', $orderIds)->pluck('file_id')
  86. );
  87. // Рекламации связанных заказов
  88. $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
  89. $fileIds = $fileIds->merge(
  90. DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
  91. );
  92. $fileIds = $fileIds->merge(
  93. DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
  94. );
  95. $fileIds = $fileIds->merge(
  96. DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
  97. );
  98. $fileIds = $fileIds->merge(
  99. DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id')
  100. );
  101. // Сертификаты продуктов
  102. $fileIds = $fileIds->merge(
  103. Product::withoutGlobalScopes()->withTrashed()
  104. ->whereIn('id', $productIds)
  105. ->whereNotNull('certificate_id')
  106. ->pluck('certificate_id')
  107. );
  108. // Паспорта SKU
  109. $fileIds = $fileIds->merge(
  110. ProductSKU::withoutGlobalScopes()->withTrashed()
  111. ->whereIn('id', $productSkuIds)
  112. ->whereNotNull('passport_id')
  113. ->pluck('passport_id')
  114. );
  115. // Файлы ТТН
  116. $fileIds = $fileIds->merge(
  117. Ttn::where('year', $year)->whereNotNull('file_id')->pluck('file_id')
  118. );
  119. return $fileIds->unique();
  120. }
  121. private function clearData(int $year): void
  122. {
  123. $orderIds = Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  124. $productIds = Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  125. $mafOrderIds = MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  126. $productSkuIds = ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->pluck('id');
  127. $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
  128. // Собираем файлы до удаления связей
  129. $fileIds = $this->collectFileIds($year, $orderIds, $productIds, $productSkuIds);
  130. $this->info('Удаление рекламаций и связанных данных...');
  131. // Детали рекламаций (cascadeOnDelete, но удаляем явно для уверенности)
  132. DB::table('reclamation_details')->whereIn('reclamation_id', $reclamationIds)->delete();
  133. DB::table('reclamation_product_sku')->whereIn('reclamation_id', $reclamationIds)->delete();
  134. DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->delete();
  135. DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->delete();
  136. DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->delete();
  137. DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->delete();
  138. Reclamation::whereIn('id', $reclamationIds)->delete();
  139. $this->info('Удаление связей заказов с файлами...');
  140. DB::table('order_photo')->whereIn('order_id', $orderIds)->delete();
  141. DB::table('order_document')->whereIn('order_id', $orderIds)->delete();
  142. DB::table('order_statement')->whereIn('order_id', $orderIds)->delete();
  143. $this->info('Удаление расписаний...');
  144. Schedule::whereIn('order_id', $orderIds)->delete();
  145. $this->info('Удаление SKU продуктов...');
  146. ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
  147. $this->info('Удаление заказов...');
  148. Order::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
  149. $this->info('Удаление заказов МАФ...');
  150. MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
  151. $this->info('Удаление продуктов...');
  152. // Сначала обнуляем certificate_id чтобы избежать проблем с FK
  153. Product::withoutGlobalScopes()->withTrashed()
  154. ->whereIn('id', $productIds)
  155. ->update(['certificate_id' => null]);
  156. Product::withoutGlobalScopes()->withTrashed()->where('year', $year)->forceDelete();
  157. $this->info('Удаление ТТН...');
  158. // Обнуляем file_id перед удалением
  159. Ttn::where('year', $year)->update(['file_id' => null]);
  160. Ttn::where('year', $year)->delete();
  161. $this->info('Удаление контрактов...');
  162. Contract::where('year', $year)->delete();
  163. $this->info('Удаление файлов...');
  164. $this->deleteFiles($fileIds);
  165. }
  166. private function deleteFiles($fileIds): void
  167. {
  168. $files = File::whereIn('id', $fileIds)->get();
  169. foreach ($files as $file) {
  170. // Удаляем физический файл
  171. if ($file->path && Storage::disk('public')->exists($file->path)) {
  172. Storage::disk('public')->delete($file->path);
  173. }
  174. }
  175. // Удаляем записи из БД
  176. File::whereIn('id', $fileIds)->delete();
  177. }
  178. }