From 9ab2b0505cc22b6e0bb7ff05a42dd9f8f73e428c Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 14 Aug 2026 21:37:03 +0700 Subject: [PATCH] feat: enhance notification system with new NotificationService methods and integrate notifications for various actions in services --- app/Jobs/CheckAttendancePenaltiesJob.php | 25 +++++++- app/Jobs/SendAttendanceReminderJob.php | 34 +++++----- .../Finance/Payroll/PayrollPeriodService.php | 4 +- app/Services/Admin/Manage/CuttingService.php | 39 ++++++++++-- app/Services/Admin/Manage/PurchaseService.php | 22 ++++++- app/Services/Admin/Manage/RestockService.php | 22 ++++++- .../Admin/Manage/TransactionService.php | 62 ++++++++++++++++++- .../Admin/Master/Product/ProductService.php | 24 ++++++- .../Master/RawMaterial/RawMaterialService.php | 44 ++++++++++++- app/Services/NotificationService.php | 51 ++++++++++----- 10 files changed, 272 insertions(+), 55 deletions(-) diff --git a/app/Jobs/CheckAttendancePenaltiesJob.php b/app/Jobs/CheckAttendancePenaltiesJob.php index 7fb9cba..ef089e2 100644 --- a/app/Jobs/CheckAttendancePenaltiesJob.php +++ b/app/Jobs/CheckAttendancePenaltiesJob.php @@ -10,6 +10,7 @@ use App\Models\PayrollAdjustment; use App\Models\PayrollPeriod; use App\Models\User; +use App\Services\NotificationService; use App\Settings\HRSettings; use Carbon\CarbonInterface; use Illuminate\Bus\Queueable; @@ -131,11 +132,21 @@ private function handleLate( 'attendance_id' => $attendance->id, 'type' => PayrollAdjustmentType::DEDUCTION, 'amount' => $hrSettings->late_penalty_amount, - 'description' => 'Denda keterlambatan '.$date->format('d/m/Y').' - '.$lateMinutes.' menit', + 'description' => 'Denda keterlambatan '.$date->translatedFormat('l, d F Y').' - '.$lateMinutes.' menit', ]); $this->recalculatePayroll($payroll); }); + + $employeeUser = $payroll->employee->user ?? null; + if ($employeeUser) { + NotificationService::notifyUsers( + [$employeeUser], + 'Denda Keterlambatan', + 'Anda terkena denda keterlambatan sebesar Rp '.number_format($hrSettings->late_penalty_amount, 0, ',', '.')." pada {$date->translatedFormat('l, d F Y')} ({$lateMinutes} menit).", + route('admin.finance.payroll-periods.index'), + ); + } } private function handleAbsent( @@ -149,7 +160,7 @@ private function handleAbsent( return; } - $description = 'Denda ketidakhadiran '.$date->format('d/m/Y'); + $description = 'Denda ketidakhadiran '.$date->translatedFormat('l, d F Y'); $existing = PayrollAdjustment::where('payroll_id', $payroll->id) ->where('type', PayrollAdjustmentType::DEDUCTION) @@ -172,6 +183,16 @@ private function handleAbsent( $this->recalculatePayroll($payroll); }); + + $employeeUser = $employee->user ?? null; + if ($employeeUser) { + NotificationService::notifyUsers( + [$employeeUser], + 'Denda Ketidakhadiran', + 'Anda terkena denda ketidakhadiran sebesar Rp '.number_format($hrSettings->absent_penalty_amount, 0, ',', '.')." pada {$date->translatedFormat('l, d F Y')}.", + route('admin.finance.payroll-periods.index'), + ); + } } private function recalculatePayroll(Payroll $payroll): void diff --git a/app/Jobs/SendAttendanceReminderJob.php b/app/Jobs/SendAttendanceReminderJob.php index 9db831f..b1016f8 100644 --- a/app/Jobs/SendAttendanceReminderJob.php +++ b/app/Jobs/SendAttendanceReminderJob.php @@ -6,13 +6,12 @@ use App\Models\Attendance; use App\Models\LeaveRequest; use App\Models\User; -use App\Notifications\WebPushNotification; +use App\Services\NotificationService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; class SendAttendanceReminderJob implements ShouldQueue { @@ -25,7 +24,7 @@ public function handle(): void $today = now()->toDateString(); $users = User::query() - ->select(['id', 'name']) + ->select(['id']) ->active() ->whereHas('roles', function ($q) { $q->whereHas('permissions', fn($pq) => $pq->where('name', Permission::ATTENDANCES_CREATE->value)); @@ -33,11 +32,11 @@ public function handle(): void ->whereHas('employee') ->get(); - foreach ($users as $user) { + $eligibleUsers = $users->filter(function (User $user) use ($today) { $employee = $user->employee; if (! $employee) { - continue; + return false; } $hasAttendedToday = Attendance::where('employee_id', $employee->id) @@ -45,7 +44,7 @@ public function handle(): void ->exists(); if ($hasAttendedToday) { - continue; + return false; } $isOnLeave = LeaveRequest::approved() @@ -54,19 +53,18 @@ public function handle(): void ->where('end_date', '>=', $today) ->exists(); - if ($isOnLeave) { - continue; - } + return ! $isOnLeave; + }); - try { - $user->notify(new WebPushNotification( - title: 'Reminder Presensi', - body: 'Selamat pagi! Jangan lupa untuk melakukan presensi masuk hari ini.', - url: route('admin.hr.attendances.index'), - )); - } catch (\Exception $e) { - Log::error("Gagal mengirim reminder presensi ke user {$user->id}: {$e->getMessage()}"); - } + if ($eligibleUsers->isEmpty()) { + return; } + + NotificationService::notifyUsers( + $eligibleUsers, + 'Reminder Presensi', + 'Selamat pagi! Jangan lupa untuk melakukan presensi masuk hari ini.', + route('admin.hr.attendances.index'), + ); } } diff --git a/app/Services/Admin/Finance/Payroll/PayrollPeriodService.php b/app/Services/Admin/Finance/Payroll/PayrollPeriodService.php index 09bfe2b..d2d8ce3 100644 --- a/app/Services/Admin/Finance/Payroll/PayrollPeriodService.php +++ b/app/Services/Admin/Finance/Payroll/PayrollPeriodService.php @@ -147,7 +147,7 @@ public function pay(Payroll $payroll): Payroll NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], title: 'Gaji Dibayar', - body: "Gaji karyawan {$payroll->employee->name} sebesar {$payroll->formatted_amount} telah dibayar".' oleh '.auth()->user()->full_name.'.', + body: "Gaji {$employeeUser->full_name} sebesar {$payroll->formatted_amount} telah dibayar oleh " . auth()->user()->full_name . '.', url: route('admin.finance.payroll-periods.index'), additionalUser: $employeeUser, ); @@ -178,7 +178,7 @@ public function cancel(Payroll $payroll): Payroll NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], title: 'Gaji Dibatalkan', - body: "Gaji karyawan {$payroll->employee->name} sebesar {$payroll->formatted_amount} telah dibatalkan".' oleh '.auth()->user()->full_name.'.', + body: "Gaji {$employeeUser->full_name} sebesar {$payroll->formatted_amount} telah dibatalkan".' oleh '.auth()->user()->full_name.'.', url: route('admin.finance.payroll-periods.index'), additionalUser: $employeeUser, ); diff --git a/app/Services/Admin/Manage/CuttingService.php b/app/Services/Admin/Manage/CuttingService.php index 7f2d047..cfb6f88 100644 --- a/app/Services/Admin/Manage/CuttingService.php +++ b/app/Services/Admin/Manage/CuttingService.php @@ -2,12 +2,14 @@ namespace App\Services\Admin\Manage; +use App\Enums\Role; use App\Models\Cutting; use App\Models\CuttingMaterial; use App\Models\CuttingMaterialCombination; use App\Models\CuttingResult; use App\Models\RawMaterialPrice; use App\Services\Concerns\RegistersMedia; +use App\Services\NotificationService; use App\Services\S3PresignedService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -61,7 +63,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = return $paginator; } - public function getMaterials(Cutting $cutting): \Illuminate\Support\Collection + public function getMaterials(Cutting $cutting): Collection { return $cutting->cuttingMaterials() ->select(['id', 'cutting_id', 'raw_material_price_id', 'material_usage', 'material_result', 'combination_id']) @@ -85,7 +87,7 @@ public function getMaterials(Cutting $cutting): \Illuminate\Support\Collection }); } - public function getCombinations(Cutting $cutting): \Illuminate\Support\Collection + public function getCombinations(Cutting $cutting): Collection { return $cutting->cuttingMaterialCombinations() ->select(['id', 'cutting_id', 'material_result']) @@ -248,7 +250,7 @@ public function getForEdit(Cutting $cutting): array public function store(array $data): Cutting { - return DB::transaction(function () use ($data) { + $cutting = DB::transaction(function () use ($data) { foreach ($data['materials'] as $materialData) { $usage = (int) ($materialData['material_usage'] ?? 0); if ($usage <= 0) { @@ -346,11 +348,20 @@ public function store(array $data): Cutting return $cutting; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Cutting Baru', + body: 'Cutting berhasil ditambahkan oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.cuttings.index'), + ); + + return $cutting; } public function update(Cutting $cutting, array $data): Cutting { - return DB::transaction(function () use ($cutting, $data) { + $cutting = DB::transaction(function () use ($cutting, $data) { $cutting->load(['cuttingMaterials.rawMaterialPrice']); foreach ($cutting->cuttingMaterials as $oldMaterial) { @@ -450,11 +461,20 @@ public function update(Cutting $cutting, array $data): Cutting return $cutting; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Cutting Diperbarui', + body: 'Cutting berhasil diperbarui oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.cuttings.index'), + ); + + return $cutting; } public function destroy(Cutting $cutting): bool { - return DB::transaction(function () use ($cutting) { + $result = DB::transaction(function () use ($cutting) { $cutting->load('cuttingMaterials.rawMaterialPrice'); foreach ($cutting->cuttingMaterials as $material) { @@ -470,5 +490,14 @@ public function destroy(Cutting $cutting): bool return true; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Cutting Dihapus', + body: 'Cutting berhasil dihapus oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.cuttings.index'), + ); + + return $result; } } diff --git a/app/Services/Admin/Manage/PurchaseService.php b/app/Services/Admin/Manage/PurchaseService.php index 0380aa1..1b2c8d9 100644 --- a/app/Services/Admin/Manage/PurchaseService.php +++ b/app/Services/Admin/Manage/PurchaseService.php @@ -340,7 +340,7 @@ private function storeNew(array $data): Purchase public function update(Purchase $purchase, array $data): Purchase { - return DB::transaction(function () use ($purchase, $data) { + $purchase = DB::transaction(function () use ($purchase, $data) { $purchase->load('purchaseItems.rawMaterialPrice.rawMaterial'); $oldItems = $purchase->purchaseItems; @@ -478,11 +478,20 @@ public function update(Purchase $purchase, array $data): Purchase return $purchase; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Belanja Diperbarui', + body: 'Belanja bahan baku sebesar '.$purchase->formatted_total.' berhasil diperbarui oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.purchases.index'), + ); + + return $purchase; } public function destroy(Purchase $purchase): bool { - return DB::transaction(function () use ($purchase) { + $result = DB::transaction(function () use ($purchase) { $purchase->load('purchaseItems.rawMaterialPrice'); // Remove the stock the purchase added, keep the variants. @@ -497,5 +506,14 @@ public function destroy(Purchase $purchase): bool return true; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU], + title: 'Belanja Dihapus', + body: 'Belanja bahan baku berhasil dihapus oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.purchases.index'), + ); + + return $result; } } diff --git a/app/Services/Admin/Manage/RestockService.php b/app/Services/Admin/Manage/RestockService.php index 432f0aa..394adaa 100644 --- a/app/Services/Admin/Manage/RestockService.php +++ b/app/Services/Admin/Manage/RestockService.php @@ -107,7 +107,7 @@ public function store(array $data): Restock public function update(Restock $restock, array $data): Restock { - return DB::transaction(function () use ($restock, $data) { + $restock = DB::transaction(function () use ($restock, $data) { $restock->load('restockItems'); $restock->restockItems->each(function (RestockItem $item) use ($restock) { @@ -138,11 +138,20 @@ public function update(Restock $restock, array $data): Restock return $restock; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], + title: 'Restock Diperbarui', + body: 'Restock berhasil diperbarui oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.restocks.index'), + ); + + return $restock; } public function destroy(Restock $restock): bool { - return DB::transaction(function () use ($restock) { + $result = DB::transaction(function () use ($restock) { $restock->load('restockItems'); $restock->restockItems->each(function (RestockItem $item) use ($restock) { @@ -154,6 +163,15 @@ public function destroy(Restock $restock): bool return true; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], + title: 'Restock Dihapus', + body: 'Restock berhasil dihapus oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.restocks.index'), + ); + + return $result; } private function buildItemRows(array $items, string $stockType, $now, int &$total): array diff --git a/app/Services/Admin/Manage/TransactionService.php b/app/Services/Admin/Manage/TransactionService.php index 06d9dbc..0876b1a 100644 --- a/app/Services/Admin/Manage/TransactionService.php +++ b/app/Services/Admin/Manage/TransactionService.php @@ -94,6 +94,44 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = return $paginator; } + public function getForEdit(Order $order): array + { + $order->load([ + 'orderItems:id,order_id,product_variant_id,stock_quality,quantity,unit_price', + ]); + + $stockType = $order->orderItems->first()?->stock_quality?->value ?? 'good'; + + $orderMedia = $order->getFirstMedia('photos'); + + return [ + 'id' => $order->id, + 'order_number' => $order->order_number, + 'stock_type' => $stockType, + 'channel' => $order->channel->value, + 'price_type' => $order->price_type->value, + 'payment_type' => $order->payment_type->value, + 'customer_id' => $order->customer_id, + 'marketing_id' => $order->marketing_id, + 'discount' => $order->discount, + 'nego_price' => $order->nego_price, + 'is_completed' => $order->status === OrderStatus::COMPLETED, + 'tiktok_order_id' => $order->tiktok_order_id, + 'shopee_order_id' => $order->shopee_order_id, + 'notes' => $order->notes, + 'photo_key' => $orderMedia?->getCustomProperty('s3_key') ?? $orderMedia?->file_name, + 'photo_url' => $orderMedia + ? $this->s3Service->getTemporaryUrl($orderMedia->getPath()) + : null, + 'items' => $order->orderItems->map(fn (OrderItem $item) => [ + 'id' => $item->id, + 'product_variant_id' => $item->product_variant_id, + 'quantity' => $item->quantity, + 'unit_price' => $item->unit_price, + ]), + ]; + } + public function getItems(Order $order): \Illuminate\Support\Collection { return $order->orderItems() @@ -224,7 +262,7 @@ public function store(array $data): Order NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Transaksi Baru', - body: 'Transaksi '.$order->order_number.' sebesar Rp '.number_format($totalAmount, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.', + body: 'Transaksi '.$order->order_number.' sebesar Rp '.$order->formatted_total_amount.' berhasil dicatat oleh '.auth()->user()->full_name.'.', url: route('admin.manage.transactions.index'), ); @@ -234,7 +272,7 @@ public function store(array $data): Order public function update(Order $order, array $data): Order { - return DB::transaction(function () use ($order, $data) { + $order = DB::transaction(function () use ($order, $data) { $order->load('orderItems'); $oldStockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value; @@ -296,11 +334,20 @@ public function update(Order $order, array $data): Order return $order; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], + title: 'Transaksi Diperbarui', + body: 'Transaksi '.$order->order_number.' sebesar '.$order->formatted_total_amount.' berhasil diperbarui oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.transactions.index'), + ); + + return $order; } public function destroy(Order $order): bool { - return DB::transaction(function () use ($order) { + $result = DB::transaction(function () use ($order) { $order->load('orderItems'); $stockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value; @@ -314,6 +361,15 @@ public function destroy(Order $order): bool return true; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], + title: 'Transaksi Dihapus', + body: 'Transaksi '.$order->order_number.' berhasil dihapus oleh '.auth()->user()->full_name.'.', + url: route('admin.manage.transactions.index'), + ); + + return $result; } public function updateStatus(Order $order, string $status): Order diff --git a/app/Services/Admin/Master/Product/ProductService.php b/app/Services/Admin/Master/Product/ProductService.php index 8506736..7f1b934 100644 --- a/app/Services/Admin/Master/Product/ProductService.php +++ b/app/Services/Admin/Master/Product/ProductService.php @@ -439,6 +439,15 @@ public function toggleStatus(Product $product): void $product->update([ 'status' => $product->status->value === 'active' ? 'inactive' : 'active', ]); + + $status = $product->status->value === 'active' ? 'diaktifkan' : 'dinonaktifkan'; + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Status Produk Diubah', + body: "Produk \"{$product->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.products.index'), + ); } public function toggleFeatured(Product $product): void @@ -448,6 +457,15 @@ public function toggleFeatured(Product $product): void $product->update([ 'is_featured' => ! $product->is_featured, ]); + + $status = $product->is_featured ? 'ditandai sebagai unggulan' : 'dihapus dari unggulan'; + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Status Unggulan Produk Diubah', + body: "Produk \"{$product->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.products.index'), + ); } public function approve(Product $product): void @@ -457,7 +475,7 @@ public function approve(Product $product): void ]); NotificationService::notify( - roles: [Role::OWNER, Role::DEVELOPER], + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Produk Disetujui', body: "Produk \"{$product->name}\" telah disetujui oleh ".auth()->user()->full_name.'.', url: route('admin.master.products.index'), @@ -473,7 +491,7 @@ public function reject(Product $product, string $reason = ''): void ]); NotificationService::notify( - roles: [Role::OWNER, Role::DEVELOPER], + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Produk Ditolak', body: "Produk \"{$product->name}\" telah ditolak oleh ".auth()->user()->full_name.'.', url: route('admin.master.products.index'), @@ -489,7 +507,7 @@ public function resubmit(Product $product): void ]); NotificationService::notify( - roles: [Role::OWNER, Role::DEVELOPER], + roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Produk Diajukan Ulang', body: "Produk \"{$product->name}\" telah diajukan ulang oleh ".auth()->user()->full_name.'.', url: route('admin.master.products.index'), diff --git a/app/Services/Admin/Master/RawMaterial/RawMaterialService.php b/app/Services/Admin/Master/RawMaterial/RawMaterialService.php index d05364d..543d568 100644 --- a/app/Services/Admin/Master/RawMaterial/RawMaterialService.php +++ b/app/Services/Admin/Master/RawMaterial/RawMaterialService.php @@ -2,9 +2,11 @@ namespace App\Services\Admin\Master\RawMaterial; +use App\Enums\Role; use App\Models\RawMaterial; use App\Models\RawMaterialPrice; use App\Services\Concerns\RegistersMedia; +use App\Services\NotificationService; use App\Services\S3PresignedService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -71,7 +73,7 @@ public function getVariants(RawMaterial $rawMaterial): Collection public function store(array $data): RawMaterial { - return DB::transaction(function () use ($data) { + $rawMaterial = DB::transaction(function () use ($data) { $rawMaterial = RawMaterial::create([ 'name' => $data['name'], 'unit' => $data['unit'], @@ -103,6 +105,15 @@ public function store(array $data): RawMaterial return $rawMaterial; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Bahan Baku Baru', + body: "Bahan baku \"{$rawMaterial->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.raw-materials.index'), + ); + + return $rawMaterial; } public function getForEdit(RawMaterial $rawMaterial): array @@ -137,7 +148,7 @@ public function getForEdit(RawMaterial $rawMaterial): array public function update(RawMaterial $rawMaterial, array $data): RawMaterial { - return DB::transaction(function () use ($rawMaterial, $data) { + $rawMaterial = DB::transaction(function () use ($rawMaterial, $data) { $rawMaterial->update([ 'name' => $data['name'], 'unit' => $data['unit'], @@ -214,17 +225,35 @@ public function update(RawMaterial $rawMaterial, array $data): RawMaterial return $rawMaterial; }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Bahan Baku Diperbarui', + body: "Bahan baku \"{$rawMaterial->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.raw-materials.index'), + ); + + return $rawMaterial; } public function destroy(RawMaterial $rawMaterial): bool { - return DB::transaction(function () use ($rawMaterial) { + $result = DB::transaction(function () use ($rawMaterial) { $rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) { $price->delete(); }); return $rawMaterial->delete(); }); + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Bahan Baku Dihapus', + body: "Bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.raw-materials.index'), + ); + + return $result; } public function toggleStatus(RawMaterial $rawMaterial): void @@ -232,5 +261,14 @@ public function toggleStatus(RawMaterial $rawMaterial): void $rawMaterial->update([ 'is_active' => ! $rawMaterial->is_active, ]); + + $status = $rawMaterial->is_active ? 'diaktifkan' : 'dinonaktifkan'; + + NotificationService::notify( + roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO], + title: 'Status Bahan Baku Diubah', + body: "Bahan baku \"{$rawMaterial->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.', + url: route('admin.master.raw-materials.index'), + ); } } diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index 53d4595..527ee6d 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -5,6 +5,7 @@ use App\Enums\Role; use App\Models\User; use App\Notifications\WebPushNotification; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; class NotificationService @@ -12,32 +13,52 @@ class NotificationService /** * @param array $roles */ - public static function notify(array $roles, string $title, string $body, string $url, ?User $additionalUser = null): void + public static function notify(array $roles, string $title, string $body, string $url, ?User $additionalUser = null, ?User $except = null): void { - $roleLabels = array_map(fn (Role $role) => $role->label(), $roles); + $except ??= auth()->user(); + + $roleValues = array_map(fn (Role $role) => $role->value, $roles); $users = User::query() ->select(['id']) ->where('is_active', true) - ->whereHas('roles', fn ($q) => $q->whereIn('name', $roleLabels)) + ->where('id', '!=', $except?->id) + ->whereHas('roles', fn ($q) => $q->whereIn('name', $roleValues)) ->get(); - if ($additionalUser && ! $users->contains('id', $additionalUser->id)) { + if ($additionalUser && $additionalUser->id !== $except?->id && ! $users->contains('id', $additionalUser->id)) { $users->push($additionalUser); } $users->each(function (User $user) use ($title, $body, $url) { - try { - $user->notifications()->create([ - 'title' => $title, - 'body' => $body, - 'url' => $url, - ]); - - $user->notify(new WebPushNotification($title, $body)); - } catch (\Exception $e) { - Log::error("Gagal mengirim notifikasi ke user {$user->id}: {$e->getMessage()}"); - } + self::sendNotification($user, $title, $body, $url); }); } + + /** + * @param Collection|array $users + */ + public static function notifyUsers(Collection|array $users, string $title, string $body, string $url): void + { + $users = $users instanceof Collection ? $users : collect($users); + + $users->each(function (User $user) use ($title, $body, $url) { + self::sendNotification($user, $title, $body, $url); + }); + } + + private static function sendNotification(User $user, string $title, string $body, string $url): void + { + try { + $user->notifications()->create([ + 'title' => $title, + 'body' => $body, + 'url' => $url, + ]); + + $user->notify(new WebPushNotification($title, $body)); + } catch (\Exception $e) { + Log::error("Gagal mengirim notifikasi ke user {$user->id}: {$e->getMessage()}"); + } + } }