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.
202 lines
7.0 KiB
PHP
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');
|
|
}
|
|
}
|
|
}
|