ifnex/04_Laravel/app/Http/Controllers/Api/StaffOrderController.php
Kazem Alghasi d6e04d53fa feat(core): integrate Kavenegar SMS notifications and audit logging
Implement a comprehensive notification system using Kavenegar SMS
gateway and enhance system traceability through audit logging and
detailed shipment status history.

- SMS Integration:
  - Add Kavenegar SMS service with configurable API keys and sender
    numbers via system settings.
  - Implement automated SMS notifications for shipment approval,
    rejection, successful payments, and tracking updates.
  - Add administrative UI in Filament to manage SMS gateway settings
    and toggle specific notification types.
- Audit & Tracking:
  - Apply `Auditable` trait to core models (User, Shipment, Wallet,
    etc.) to track changes.
  - Refactor `ShipmentStatusHistory` to include status transitions
    (`from_status` to `to_status`) and specific reasons for changes.
  - Implement `ShipmentObserver` to automate notification triggers
    on status changes.
- Database & Config:
  - Add migrations for enhanced shipment status history tracking.
  - Update `.env.example` and `config/ifnex.php` with Kavenegar
    configuration parameters.
2026-09-10 00:52:18 +03:30

202 lines
7.0 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Shipment;
use App\Models\ShipmentStatusHistory;
use App\Enums\ShipmentStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class StaffOrderController extends Controller
{
/**
* لیست سفارشات در انتظار تأیید
* GET /api/v1/staff/orders/pending-approval
*/
public function pendingApproval(Request $request): JsonResponse
{
$validated = $request->validate([
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
]);
$shipments = Shipment::query()
->where('status', ShipmentStatus::PendingApproval)
->with(['fromCountry', 'toCountry', 'user'])
->orderBy('created_at', 'asc')
->paginate($validated['per_page'] ?? 15);
return response()->json([
'success' => true,
'data' => $shipments->map(function ($shipment) {
return $this->formatShipment($shipment);
}),
'pagination' => [
'current_page' => $shipments->currentPage(),
'last_page' => $shipments->lastPage(),
'per_page' => $shipments->perPage(),
'total' => $shipments->total(),
],
]);
}
/**
* تأیید سفارش توسط کارمند
* POST /api/v1/staff/orders/{shipment}/approve
*/
public function approve(Shipment $shipment, Request $request): JsonResponse
{
$user = Auth::user();
// بررسی وضعیت
if ($shipment->status !== ShipmentStatus::PendingApproval) {
return response()->json([
'success' => false,
'message' => 'این سفارش در وضعیت قابل تأیید نیست.',
], 400);
}
$validated = $request->validate([
'notes' => ['nullable', 'string', 'max:1000'],
]);
try {
DB::transaction(function () use ($shipment, $user, $validated) {
// تغییر وضعیت به Approved
$oldStatus = $shipment->status;
$shipment->update([
'status' => ShipmentStatus::Approved,
]);
// ثبت در تاریخچه تغییرات
ShipmentStatusHistory::create([
'shipment_id' => $shipment->id,
'from_status' => $oldStatus->value,
'to_status' => ShipmentStatus::Approved->value,
'reason' => $validated['notes'] ?? 'تأیید توسط کارمند',
'changed_by' => $user->id,
]);
});
return response()->json([
'success' => true,
'message' => 'سفارش با موفقیت تأیید شد. مشتری می‌تواند پرداخت را انجام دهد.',
'data' => $this->formatShipment($shipment->fresh()->load(['fromCountry', 'toCountry'])),
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'خطا در تأیید سفارش: ' . $e->getMessage(),
], 500);
}
}
/**
* رد سفارش توسط کارمند
* POST /api/v1/staff/orders/{shipment}/reject
*/
public function reject(Shipment $shipment, Request $request): JsonResponse
{
$user = Auth::user();
// بررسی وضعیت
if ($shipment->status !== ShipmentStatus::PendingApproval) {
return response()->json([
'success' => false,
'message' => 'این سفارش در وضعیت قابل رد نیست.',
], 400);
}
$validated = $request->validate([
'reason' => ['required', 'string', 'max:1000'],
]);
try {
DB::transaction(function () use ($shipment, $user, $validated) {
// تغییر وضعیت به Cancelled
$oldStatus = $shipment->status;
$shipment->update([
'status' => ShipmentStatus::Cancelled,
]);
// ثبت در تاریخچه تغییرات
ShipmentStatusHistory::create([
'shipment_id' => $shipment->id,
'from_status' => $oldStatus->value,
'to_status' => ShipmentStatus::Cancelled->value,
'reason' => 'رد شده: ' . $validated['reason'],
'changed_by' => $user->id,
]);
});
return response()->json([
'success' => true,
'message' => 'سفارش رد شد.',
'data' => $this->formatShipment($shipment->fresh()->load(['fromCountry', 'toCountry'])),
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'خطا در رد سفارش: ' . $e->getMessage(),
], 500);
}
}
/**
* فرمت کردن Shipment برای خروجی API
*/
private function formatShipment(Shipment $shipment): array
{
return [
'id' => $shipment->id,
'awb_no' => $shipment->awb_no,
'direction' => $shipment->direction?->value,
'type' => $shipment->type?->value,
'status' => [
'value' => $shipment->status?->value,
'label' => $shipment->status?->label(),
'color' => $shipment->status?->color(),
],
'from_country' => $shipment->fromCountry ? [
'id' => $shipment->fromCountry->id,
'name' => $shipment->fromCountry->name,
'iso_code' => $shipment->fromCountry->iso_code,
] : null,
'to_country' => $shipment->toCountry ? [
'id' => $shipment->toCountry->id,
'name' => $shipment->toCountry->name,
'iso_code' => $shipment->toCountry->iso_code,
] : null,
'user' => $shipment->user ? [
'id' => $shipment->user->id,
'name' => $shipment->user->name,
'email' => $shipment->user->email,
] : null,
'weight' => $shipment->weight,
'chargeable_weight' => $shipment->chargeable_weight,
'total_fee' => $shipment->total_fee,
'created_at' => $shipment->created_at->toIso8601String(),
'created_at_jalali' => $this->toJalali($shipment->created_at),
];
}
/**
* تبدیل تاریخ به شمسی
*/
private function toJalali($date): string
{
if (!$date) return '—';
try {
return \Morilog\Jalali\Jalalian::fromCarbon($date)->format('Y/m/d H:i');
} catch (\Exception $e) {
return $date->format('Y-m-d H:i');
}
}
}