ifnex/04_Laravel/app/Http/Controllers/Api/StaffOrderController.php
Kazem Alghasi 02c29db696 feat(core): implement order approval flow, credit system, and import invoicing
Introduce a comprehensive set of commercial features including a multi-step
order approval workflow, customer credit management, and specialized
import service invoicing.

Key changes:
- Implement `pending_approval` and `approved` shipment statuses to allow
  staff verification before customer payment.
- Add a credit system to `User` model with `credit_limit` and `credit_used`
  to manage customer balances and debts.
- Develop a new `importInvoice` PDF generation service following the
  "Sheet ENG Invoice" specification for import services.
- Add Filament resources for managing Audit Logs, Commitment Forms,
  Customer Credits, and Shipment Checklists.
- Implement staff-specific APIs for order approval/rejection and
  customer financial status monitoring.
- Integrate Kavenegar SMS service for mobile verification and notifications.
- Add bulk tracking import functionality via CSV/Excel.
- Update WordPress bridge assets (CSS/JS) to support the new multi-step
  order form UI and updated redirection logic.
- Update deployment configurations and documentation to reflect new
  production domains and feature sets.
2026-09-03 06:04:20 +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,
'old_status' => $oldStatus->value,
'new_status' => ShipmentStatus::Approved->value,
'changed_by' => $user->id,
'notes' => $validated['notes'] ?? 'تأیید توسط کارمند',
]);
});
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,
'old_status' => $oldStatus->value,
'new_status' => ShipmentStatus::Cancelled->value,
'changed_by' => $user->id,
'notes' => 'رد شده: ' . $validated['reason'],
]);
});
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');
}
}
}