feat(notification): 實作通知輪詢與優化顯示名稱
- 新增通知輪詢 API 與前端自動更新機制 - 修正生產工單單號格式為 PRO-YYYYMMDD-XX - 確保通知顯示實際建立者名稱而非系統
This commit is contained in:
@@ -90,6 +90,12 @@ class HandleInertiaRequests extends Middleware
|
||||
|
||||
return $brandingData;
|
||||
},
|
||||
'notifications' => function () use ($request) {
|
||||
return $request->user() ? [
|
||||
'latest' => $request->user()->notifications()->latest()->limit(10)->get(),
|
||||
'unread_count' => $request->user()->unreadNotifications()->count(),
|
||||
] : null;
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
41
app/Modules/Core/Controllers/NotificationController.php
Normal file
41
app/Modules/Core/Controllers/NotificationController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark a specific notification as read.
|
||||
*/
|
||||
public function markAsRead(Request $request, string $id)
|
||||
{
|
||||
$notification = $request->user()->notifications()->findOrFail($id);
|
||||
$notification->markAsRead();
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read.
|
||||
*/
|
||||
public function markAllAsRead(Request $request)
|
||||
{
|
||||
$request->user()->unreadNotifications->markAsRead();
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for new notifications.
|
||||
*/
|
||||
public function check(Request $request)
|
||||
{
|
||||
return response()->json([
|
||||
'unread_count' => $request->user()->unreadNotifications()->count(),
|
||||
'latest' => $request->user()->notifications()->latest()->limit(10)->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,11 @@ Route::post('/login', [LoginController::class, 'store']);
|
||||
Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 通知
|
||||
Route::post('/notifications/read-all', [\App\Modules\Core\Controllers\NotificationController::class, 'markAllAsRead'])->name('notifications.read-all');
|
||||
Route::post('/notifications/{id}/read', [\App\Modules\Core\Controllers\NotificationController::class, 'markAsRead'])->name('notifications.read');
|
||||
Route::get('/notifications/check', [\App\Modules\Core\Controllers\NotificationController::class, 'check'])->name('notifications.check');
|
||||
|
||||
// 儀表板 - 所有登入使用者皆可存取
|
||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ class PurchaseOrder extends Model
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Modules\Core\Models\User::class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
54
app/Modules/Procurement/Notifications/NewPurchaseOrder.php
Normal file
54
app/Modules/Procurement/Notifications/NewPurchaseOrder.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Procurement\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
|
||||
class NewPurchaseOrder extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
protected $purchaseOrder;
|
||||
protected $creatorName;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct(PurchaseOrder $purchaseOrder, string $creatorName)
|
||||
{
|
||||
$this->purchaseOrder = $purchaseOrder;
|
||||
$this->creatorName = $creatorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'type' => 'purchase_order',
|
||||
'action' => 'created',
|
||||
'purchase_order_id' => $this->purchaseOrder->id,
|
||||
'code' => $this->purchaseOrder->code,
|
||||
'creator_name' => $this->creatorName,
|
||||
'message' => "{$this->creatorName} 建立了新的採購單:{$this->purchaseOrder->code}",
|
||||
'link' => route('purchase-orders.index', ['search' => $this->purchaseOrder->code]), // 暫時導向列表並搜尋,若有詳情頁可改
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Modules/Procurement/Observers/PurchaseOrderObserver.php
Normal file
31
app/Modules/Procurement/Observers/PurchaseOrderObserver.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Procurement\Observers;
|
||||
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
use App\Modules\Procurement\Notifications\NewPurchaseOrder;
|
||||
use App\Modules\Core\Models\User;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class PurchaseOrderObserver
|
||||
{
|
||||
/**
|
||||
* Handle the PurchaseOrder "created" event.
|
||||
*/
|
||||
public function created(PurchaseOrder $purchaseOrder): void
|
||||
{
|
||||
// 找出有檢視採購單權限的使用者
|
||||
$users = User::permission('purchase_orders.view')->get();
|
||||
|
||||
// 排除建立者自己(避免自己收到自己的通知)
|
||||
// $users = $users->reject(function ($user) use ($purchaseOrder) {
|
||||
// return $user->id === $purchaseOrder->user_id;
|
||||
// });
|
||||
|
||||
$creatorName = $purchaseOrder->user ? $purchaseOrder->user->name : '系統';
|
||||
|
||||
if ($users->isNotEmpty()) {
|
||||
Notification::send($users, new NewPurchaseOrder($purchaseOrder, $creatorName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@ use Illuminate\Support\ServiceProvider;
|
||||
use App\Modules\Procurement\Contracts\ProcurementServiceInterface;
|
||||
use App\Modules\Procurement\Services\ProcurementService;
|
||||
|
||||
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
use App\Modules\Procurement\Observers\PurchaseOrderObserver;
|
||||
|
||||
class ProcurementServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
@@ -15,6 +19,6 @@ class ProcurementServiceProvider extends ServiceProvider
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
PurchaseOrder::observe(PurchaseOrderObserver::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +112,17 @@ class ProductionOrder extends Model
|
||||
|
||||
public static function generateCode()
|
||||
{
|
||||
$prefix = 'PO' . now()->format('Ymd');
|
||||
$lastOrder = self::where('code', 'like', $prefix . '%')->latest()->first();
|
||||
$prefix = 'PRO-' . now()->format('Ymd') . '-';
|
||||
$lastOrder = self::where('code', 'like', $prefix . '%')
|
||||
->lockForUpdate()
|
||||
->orderBy('code', 'desc')
|
||||
->first();
|
||||
|
||||
if ($lastOrder) {
|
||||
$lastSequence = intval(substr($lastOrder->code, -3));
|
||||
$sequence = str_pad($lastSequence + 1, 3, '0', STR_PAD_LEFT);
|
||||
$lastSequence = intval(substr($lastOrder->code, -2));
|
||||
$sequence = str_pad($lastSequence + 1, 2, '0', STR_PAD_LEFT);
|
||||
} else {
|
||||
$sequence = '001';
|
||||
$sequence = '01';
|
||||
}
|
||||
return $prefix . $sequence;
|
||||
}
|
||||
@@ -127,4 +131,9 @@ class ProductionOrder extends Model
|
||||
{
|
||||
return $this->hasMany(ProductionOrderItem::class);
|
||||
}
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Modules\Core\Models\User::class);
|
||||
}
|
||||
}
|
||||
|
||||
54
app/Modules/Production/Notifications/NewProductionOrder.php
Normal file
54
app/Modules/Production/Notifications/NewProductionOrder.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Production\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use App\Modules\Production\Models\ProductionOrder;
|
||||
|
||||
class NewProductionOrder extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
protected $productionOrder;
|
||||
protected $creatorName;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct(ProductionOrder $productionOrder, string $creatorName)
|
||||
{
|
||||
$this->productionOrder = $productionOrder;
|
||||
$this->creatorName = $creatorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return ['database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'type' => 'production_order',
|
||||
'action' => 'created',
|
||||
'production_order_id' => $this->productionOrder->id,
|
||||
'code' => $this->productionOrder->code,
|
||||
'creator_name' => $this->creatorName,
|
||||
'message' => "{$this->creatorName} 建立了新的生產工單:{$this->productionOrder->code}",
|
||||
'link' => route('production-orders.index', ['search' => $this->productionOrder->code]),
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Modules/Production/Observers/ProductionOrderObserver.php
Normal file
26
app/Modules/Production/Observers/ProductionOrderObserver.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Production\Observers;
|
||||
|
||||
use App\Modules\Production\Models\ProductionOrder;
|
||||
use App\Modules\Production\Notifications\NewProductionOrder;
|
||||
use App\Modules\Core\Models\User;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class ProductionOrderObserver
|
||||
{
|
||||
/**
|
||||
* Handle the ProductionOrder "created" event.
|
||||
*/
|
||||
public function created(ProductionOrder $productionOrder): void
|
||||
{
|
||||
// 找出有檢視生產工單權限的使用者
|
||||
$users = User::permission('production_orders.view')->get();
|
||||
|
||||
$creatorName = $productionOrder->user ? $productionOrder->user->name : '系統';
|
||||
|
||||
if ($users->isNotEmpty()) {
|
||||
Notification::send($users, new NewProductionOrder($productionOrder, $creatorName));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
app/Modules/Production/ProductionServiceProvider.php
Normal file
20
app/Modules/Production/ProductionServiceProvider.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Production;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Modules\Production\Models\ProductionOrder;
|
||||
use App\Modules\Production\Observers\ProductionOrderObserver;
|
||||
|
||||
class ProductionServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
ProductionOrder::observe(ProductionOrderObserver::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user