From 1f41f04199db9c0bf4c8a3c5eafaf3141b30c896 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 13 Aug 2026 00:39:46 +0700 Subject: [PATCH] feat: remove unused media collection clearing in destroy methods and add CleanupOrphanedMediaJob for orphaned media management --- app/Jobs/CleanupOrphanedMediaJob.php | 96 +++++++++++++++++++ .../Finance/Cash/CashTransactionService.php | 2 - app/Services/Admin/Finance/ExpenseService.php | 2 - app/Services/Admin/Manage/CuttingService.php | 1 - app/Services/Admin/Manage/PurchaseService.php | 1 - app/Services/Admin/Manage/RestockService.php | 1 - .../Admin/Manage/TransactionService.php | 1 - .../Admin/Master/Product/ProductService.php | 1 - .../Master/Product/ProductVariantService.php | 1 - .../Master/RawMaterial/RawMaterialService.php | 1 - .../RawMaterial/RawMaterialVariantService.php | 2 - routes/console.php | 2 + 12 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 app/Jobs/CleanupOrphanedMediaJob.php diff --git a/app/Jobs/CleanupOrphanedMediaJob.php b/app/Jobs/CleanupOrphanedMediaJob.php new file mode 100644 index 0000000..179e1ac --- /dev/null +++ b/app/Jobs/CleanupOrphanedMediaJob.php @@ -0,0 +1,96 @@ +getOrphanedMedia(); + + if ($orphanedMedia->isEmpty()) { + Log::info('CleanupOrphanedMediaJob: Tidak ada media orphaned.'); + + return; + } + + $totalDeleted = 0; + + $orphanedMedia->each(function (Media $media) use (&$totalDeleted) { + $this->deleteMedia($media); + $totalDeleted++; + }); + + Log::info("CleanupOrphanedMediaJob: Berhasil hapus {$totalDeleted} media orphaned."); + } + + private function getOrphanedMedia() + { + $allMedia = Media::select(['id', 'model_type', 'model_id', 'file_name', 'custom_properties', 'disk']) + ->get() + ->groupBy(fn (Media $m) => $m->model_type.'|'.$m->model_id); + + $orphanedIds = []; + + foreach ($allMedia as $key => $mediaItems) { + [$modelType, $modelId] = explode('|', $key); + + if ($this->isOrphaned($modelType, (int) $modelId)) { + foreach ($mediaItems as $media) { + $orphanedIds[] = $media->id; + } + } + } + + if (empty($orphanedIds)) { + return collect(); + } + + return Media::whereIn('id', $orphanedIds)->get(); + } + + private function isOrphaned(string $modelType, int $modelId): bool + { + if (! class_exists($modelType)) { + return true; + } + + $model = $modelType::withTrashed()->find($modelId); + + if (! $model) { + return true; + } + + if (method_exists($model, 'trashed') && $model->trashed()) { + return $model->deleted_at->lt(now()->subMonth()); + } + + return false; + } + + private function deleteMedia(Media $media): void + { + $s3Key = $media->getCustomProperty('s3_key'); + + if ($s3Key && Storage::disk($media->disk)->exists($s3Key)) { + Storage::disk($media->disk)->delete($s3Key); + } + + $media->forceDelete(); + } +} diff --git a/app/Services/Admin/Finance/Cash/CashTransactionService.php b/app/Services/Admin/Finance/Cash/CashTransactionService.php index a757761..35c2f93 100644 --- a/app/Services/Admin/Finance/Cash/CashTransactionService.php +++ b/app/Services/Admin/Finance/Cash/CashTransactionService.php @@ -168,8 +168,6 @@ public function destroy(CashTransaction $transaction): bool Cache::forget("cash_transaction_receipt_{$media->id}"); } - $transaction->clearMediaCollection('photos'); - $deleted = $transaction->delete(); if ($deleted) { diff --git a/app/Services/Admin/Finance/ExpenseService.php b/app/Services/Admin/Finance/ExpenseService.php index e35db0b..40a75cb 100644 --- a/app/Services/Admin/Finance/ExpenseService.php +++ b/app/Services/Admin/Finance/ExpenseService.php @@ -130,8 +130,6 @@ public function destroy(Expense $expense): bool Cache::forget("expense_receipt_{$media->id}"); } - $expense->clearMediaCollection('photos'); - $deleted = $expense->delete(); if ($deleted) { diff --git a/app/Services/Admin/Manage/CuttingService.php b/app/Services/Admin/Manage/CuttingService.php index 3f5783c..d94ac21 100644 --- a/app/Services/Admin/Manage/CuttingService.php +++ b/app/Services/Admin/Manage/CuttingService.php @@ -357,7 +357,6 @@ public function destroy(Cutting $cutting): bool } } - $cutting->clearMediaCollection('images'); $cutting->cuttingResults()->delete(); $cutting->cuttingMaterials()->delete(); $cutting->cuttingMaterialCombinations()->delete(); diff --git a/app/Services/Admin/Manage/PurchaseService.php b/app/Services/Admin/Manage/PurchaseService.php index ed3cdfc..183e7fa 100644 --- a/app/Services/Admin/Manage/PurchaseService.php +++ b/app/Services/Admin/Manage/PurchaseService.php @@ -480,7 +480,6 @@ public function destroy(Purchase $purchase): bool } }); - $purchase->clearMediaCollection('photos'); $purchase->purchaseItems()->delete(); $purchase->delete(); diff --git a/app/Services/Admin/Manage/RestockService.php b/app/Services/Admin/Manage/RestockService.php index 18e9fb2..754eec9 100644 --- a/app/Services/Admin/Manage/RestockService.php +++ b/app/Services/Admin/Manage/RestockService.php @@ -144,7 +144,6 @@ public function destroy(Restock $restock): bool }); $restock->restockItems()->delete(); - $restock->clearMediaCollection('photos'); $restock->delete(); return true; diff --git a/app/Services/Admin/Manage/TransactionService.php b/app/Services/Admin/Manage/TransactionService.php index 6e76841..1d18922 100644 --- a/app/Services/Admin/Manage/TransactionService.php +++ b/app/Services/Admin/Manage/TransactionService.php @@ -299,7 +299,6 @@ public function destroy(Order $order): bool }); $order->orderItems()->delete(); - $order->clearMediaCollection('photos'); $order->delete(); return true; diff --git a/app/Services/Admin/Master/Product/ProductService.php b/app/Services/Admin/Master/Product/ProductService.php index 112b75f..44983f5 100644 --- a/app/Services/Admin/Master/Product/ProductService.php +++ b/app/Services/Admin/Master/Product/ProductService.php @@ -404,7 +404,6 @@ public function destroy(Product $product): bool $result = DB::transaction(function () use ($product) { $product->productVariants->each(function (ProductVariant $variant) { $variant->productPrices()->delete(); - $variant->clearMediaCollection('images'); $variant->delete(); }); diff --git a/app/Services/Admin/Master/Product/ProductVariantService.php b/app/Services/Admin/Master/Product/ProductVariantService.php index dafe278..77c5bee 100644 --- a/app/Services/Admin/Master/Product/ProductVariantService.php +++ b/app/Services/Admin/Master/Product/ProductVariantService.php @@ -156,7 +156,6 @@ public function destroy(Product $product, ProductVariant $variant): bool $result = DB::transaction(function () use ($variant) { $variant->productPrices()->delete(); - $variant->clearMediaCollection('images'); return $variant->delete(); }); diff --git a/app/Services/Admin/Master/RawMaterial/RawMaterialService.php b/app/Services/Admin/Master/RawMaterial/RawMaterialService.php index 308b775..40f3601 100644 --- a/app/Services/Admin/Master/RawMaterial/RawMaterialService.php +++ b/app/Services/Admin/Master/RawMaterial/RawMaterialService.php @@ -213,7 +213,6 @@ public function destroy(RawMaterial $rawMaterial): bool { return DB::transaction(function () use ($rawMaterial) { $rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) { - $price->clearMediaCollection('images'); $price->delete(); }); diff --git a/app/Services/Admin/Master/RawMaterial/RawMaterialVariantService.php b/app/Services/Admin/Master/RawMaterial/RawMaterialVariantService.php index c4d528d..7268f97 100644 --- a/app/Services/Admin/Master/RawMaterial/RawMaterialVariantService.php +++ b/app/Services/Admin/Master/RawMaterial/RawMaterialVariantService.php @@ -93,8 +93,6 @@ public function update(RawMaterialPrice $variant, array $data): RawMaterialPrice public function destroy(RawMaterial $rawMaterial, RawMaterialPrice $variant): bool { $result = DB::transaction(function () use ($variant) { - $variant->clearMediaCollection('images'); - return $variant->delete(); }); diff --git a/routes/console.php b/routes/console.php index cafc9f8..26bbe3b 100644 --- a/routes/console.php +++ b/routes/console.php @@ -2,7 +2,9 @@ use App\Console\Commands\GeneratePayrollCommand; use App\Jobs\CheckAttendancePenaltiesJob; +use App\Jobs\CleanupOrphanedMediaJob; use Illuminate\Support\Facades\Schedule; Schedule::command(GeneratePayrollCommand::class)->monthlyOn(1, '00:00'); Schedule::job(new CheckAttendancePenaltiesJob)->dailyAt('00:00'); +Schedule::job(new CleanupOrphanedMediaJob)->monthlyOn(1, '01:00');