diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css index 81b86fd..a344573 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css @@ -760,4 +760,120 @@ @media (max-width: 640px) { .ifnx-notif-item { padding: 12px 14px; } +} + +/* ═══════════════════════════════════════════ + Order Detail - Documents & Commitment Forms + ═══════════════════════════════════════════ */ + +.ifnex-docs-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; + margin-top: 12px; +} + +.ifnex-doc-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px 12px; + background: #fff; + border: 1px solid var(--ifnex-border); + border-radius: var(--ifnex-radius); + text-decoration: none !important; + transition: all 0.2s; +} + +.ifnex-doc-btn:hover { + border-color: var(--ifnex-primary); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transform: translateY(-2px); +} + +.ifnex-doc-icon { + font-size: 28px; + line-height: 1; +} + +.ifnex-doc-label { + font-size: 13px; + font-weight: 600; + color: var(--ifnex-dark); +} + +.ifnex-forms-list { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 12px; +} + +.ifnex-form-item { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + padding: 14px; + background: #fff; + border: 1px solid var(--ifnex-border); + border-radius: var(--ifnex-radius); +} + +.ifnex-form-info { + flex: 1; + min-width: 0; +} + +.ifnex-form-info strong { + display: block; + font-size: 14px; + color: var(--ifnex-dark); + margin-bottom: 4px; +} + +.ifnex-form-info p { + margin: 0; + font-size: 12px; + color: var(--ifnex-gray); +} + +.ifnex-form-actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.ifnex-form-actions .ifnex-badge { + margin-right: 4px; +} + +.ifnex-empty-state { + color: var(--ifnex-gray); + font-size: 14px; + margin: 8px 0; +} + +.ifnex-loading { + font-size: 12px; + color: var(--ifnex-primary); + font-weight: 600; +} + +.ifnex-text-warning { + color: var(--ifnex-warning); + font-size: 12px; + margin-top: 4px; +} + +@media (max-width: 640px) { + .ifnex-form-item { + flex-direction: column; + } + + .ifnex-form-actions { + width: 100%; + } } \ No newline at end of file diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php index a4ad058..b77ef0c 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php @@ -972,6 +972,13 @@ function ifnex_order_detail_shortcode($atts) { return '

شناسه سفارش مشخص نشده است.

'; } + // Handle PDF download + $download_type = sanitize_text_field($_GET['download'] ?? ''); + if ($download_type && in_array($download_type, ['awb', 'invoice', 'label', 'import-invoice'])) { + ifnex_handle_pdf_download($order_id, $download_type); + return ''; + } + $bridge = new IFNEX_User_Bridge(); $user_id = get_current_user_id(); @@ -1147,6 +1154,41 @@ function ifnex_order_detail_shortcode($atts) { + +
+

📄 دانلود مدارک

+
+ + 📦 + AWB + + + 🏷️ + Label + + + + 🧾 + Invoice + + + + + 📋 + Service Invoice + + +
+
+ + +
+

📝 تعهدنامه‌ها

+
+

در حال بارگذاری...

+
+
+
@@ -1217,6 +1259,43 @@ function ifnex_order_detail_shortcode($atts) { }); }); }); + + function uploadCommitmentForm(orderId, formId, input) { + var file = input.files[0]; + if (!file) return; + + var formData = new FormData(); + formData.append('action', 'ifnex_upload_commitment_form'); + formData.append('order_id', orderId); + formData.append('form_id', formId); + formData.append('file', file); + formData.append('nonce', ifnex_ajax.nonce); + + var btn = $(input).closest('.ifnex-form-actions'); + var originalHtml = btn.html(); + btn.html('در حال آپلود...'); + + $.ajax({ + url: ifnex_ajax.ajax_url, + method: 'POST', + data: formData, + processData: false, + contentType: false, + success: function(res) { + if (res.success) { + alert('فایل با موفقیت آپلود شد.'); + location.reload(); + } else { + alert('خطا: ' + (res.data || 'نامشخص')); + btn.html(originalHtml); + } + }, + error: function() { + alert('خطا در ارتباط با سرور.'); + btn.html(originalHtml); + } + }); + } get_customer_order($user_id, $order_id); + if (is_wp_error($order)) wp_die('خطا: ' . $order->get_error_message()); + + $api_url = get_option('ifnex_api_url', 'http://localhost:8000/api/v1'); + $download_url = rtrim($api_url, '/') . "/customer/orders/{$order_id}/pdf/{$download_type}"; + + $token = get_user_meta($user_id, 'ifnex_api_token', true); + if (!$token) wp_die('توکن احراز هویت یافت نشد.'); + + $response = wp_remote_get($download_url, array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/pdf', + ), + 'timeout' => 30, + )); + + if (is_wp_error($response)) wp_die('خطا در ارتباط با سرور.'); + + $status_code = wp_remote_retrieve_response_code($response); + if ($status_code !== 200) wp_die('خطا در دانلود فایل.'); + + $file_content = wp_remote_retrieve_body($response); + $filename = strtoupper($download_type) . '-' . $order['data']['awb_no'] . '.pdf'; + + nocache_headers(); + header('Content-Type: application/pdf'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($file_content)); + echo $file_content; + exit; +} + function ifnex_icon($name, $size = 20) { $icons = [ 'dashboard' => '', diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/user-bridge.php b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/user-bridge.php index 57c94dd..be7c0a6 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/user-bridge.php +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/user-bridge.php @@ -659,4 +659,183 @@ function ifnex_enqueue_global_assets() { 'orders_url' => home_url('/my-account/?tab=orders'), 'home_url' => home_url('/'), )) . ';', 'before'); +} + +// ══════════════════════════════════════════════════════════════ +// AJAX Handler برای دانلود مدارک PDF سفارش +// ══════════════════════════════════════════════════════════════ + +add_action('wp_ajax_ifnex_download_order_pdf', 'ifnex_download_order_pdf_ajax'); + +function ifnex_download_order_pdf_ajax() { + check_ajax_referer('ifnex_ajax_nonce', 'nonce'); + if (!is_user_logged_in()) { + wp_send_json_error('باید وارد شوید.'); + } + + $order_id = intval($_POST['order_id'] ?? 0); + $download_type = sanitize_text_field($_POST['download_type'] ?? ''); + + if (!$order_id) wp_send_json_error('شناسه سفارش نامعتبر است.'); + if (!in_array($download_type, ['awb', 'invoice', 'label', 'import-invoice'])) wp_send_json_error('نوع فایل نامعتبر است.'); + + $bridge = new IFNEX_User_Bridge(); + $user_id = get_current_user_id(); + + // بررسی مالکیت سفارش + $order = $bridge->get_customer_order($user_id, $order_id); + if (is_wp_error($order)) wp_send_json_error($order->get_error_message()); + + // ساخت URL دانلود از API لاراول + $api_url = get_option('ifnex_api_url', 'http://localhost:8000/api/v1'); + $download_url = rtrim($api_url, '/') . "/customer/orders/{$order_id}/pdf/{$download_type}"; + + // ارسال درخواست به لاراول با توکن احراز هویت + $token = get_user_meta($user_id, 'ifnex_api_token', true); + if (!$token) wp_send_json_error('توکن احراز هویت یافت نشد.'); + + $response = wp_remote_get($download_url, array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $token, + 'Accept' => 'application/pdf', + ), + 'timeout' => 30, + )); + + if (is_wp_error($response)) wp_send_json_error('خطا در ارتباط با سرور.'); + + $status_code = wp_remote_retrieve_response_code($response); + if ($status_code !== 200) wp_send_json_error('خطا در دانلود فایل.'); + + $file_content = wp_remote_retrieve_body($response); + $filename = strtoupper($download_type) . '-' . $order['data']['awb_no'] . '.pdf'; + + // ارسال فایل به مرورگر + nocache_headers(); + header('Content-Type: application/pdf'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Content-Length: ' . strlen($file_content)); + echo $file_content; + exit; +} + +// ══════════════════════════════════════════════════════════════ +// AJAX Handler برای دریافت لیست تعهدنامه‌های سفارش +// ══════════════════════════════════════════════════════════════ + +add_action('wp_ajax_ifnex_get_commitment_forms', 'ifnex_get_commitment_forms_ajax'); + +function ifnex_get_commitment_forms_ajax() { + check_ajax_referer('ifnex_ajax_nonce', 'nonce'); + if (!is_user_logged_in()) { + wp_send_json_error('باید وارد شوید.'); + } + + $order_id = intval($_POST['order_id'] ?? 0); + if (!$order_id) wp_send_json_error('شناسه سفارش نامعتبر است.'); + + $bridge = new IFNEX_User_Bridge(); + $user_id = get_current_user_id(); + + $order = $bridge->get_customer_order($user_id, $order_id); + if (is_wp_error($order)) wp_send_json_error($order->get_error_message()); + + $direction = strtolower($order['data']['direction'] ?? 'export'); + $api_url = get_option('ifnex_api_url', 'http://localhost:8000/api/v1'); + $url = rtrim($api_url, '/') . "/customer/orders/{$order_id}/commitment-forms"; + + $token = get_user_meta($user_id, 'ifnex_api_token', true); + if (!$token) wp_send_json_error('توکن احراز هویت یافت نشد.'); + + $response = wp_remote_get($url, array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $token, + ), + 'timeout' => 30, + )); + + if (is_wp_error($response)) wp_send_json_error('خطا در ارتباط با سرور.'); + + $body = json_decode(wp_remote_retrieve_body($response), true); + if (!$body || !isset($body['success'])) wp_send_json_error('خطا در دریافت اطلاعات.'); + + wp_send_json_success($body['data']); +} + +// ══════════════════════════════════════════════════════════════ +// AJAX Handler برای آپلود فرم تعهدنامه امضاشده +// ══════════════════════════════════════════════════════════════ + +add_action('wp_ajax_ifnex_upload_commitment_form', 'ifnex_upload_commitment_form_ajax'); + +function ifnex_upload_commitment_form_ajax() { + check_ajax_referer('ifnex_ajax_nonce', 'nonce'); + if (!is_user_logged_in()) { + wp_send_json_error('باید وارد شوید.'); + } + + $order_id = intval($_POST['order_id'] ?? 0); + $form_id = intval($_POST['form_id'] ?? 0); + + if (!$order_id) wp_send_json_error('شناسه سفارش نامعتبر است.'); + if (!$form_id) wp_send_json_error('شناسه فرم نامعتبر است.'); + if (!isset($_FILES['file'])) wp_send_json_error('فایل ارسال نشده است.'); + + $bridge = new IFNEX_User_Bridge(); + $user_id = get_current_user_id(); + + $order = $bridge->get_customer_order($user_id, $order_id); + if (is_wp_error($order)) wp_send_json_error($order->get_error_message()); + + $file = $_FILES['file']; + $allowed_types = array('application/pdf', 'image/jpeg', 'image/jpg', 'image/png'); + $max_size = 5 * 1024 * 1024; // 5MB + + if (!in_array($file['type'], $allowed_types)) wp_send_json_error('فرمت فایل مجاز نیست. فقط PDF, JPG, PNG.'); + if ($file['size'] > $max_size) wp_send_json_error('حجم فایل نباید بیشتر از 5MB باشد.'); + + // آپلود فایل به وردپرس + require_once(ABSPATH . 'wp-admin/includes/file.php'); + require_once(ABSPATH . 'wp-admin/includes/media.php'); + require_once(ABSPATH . 'wp-admin/includes/image.php'); + + $upload = wp_handle_upload($file, array('test_form' => false)); + if (isset($upload['error'])) wp_send_json_error('خطا در آپلود: ' . $upload['error']); + + $file_path = str_replace(wp_get_upload_dir()['basedir'] . '/', '', $upload['file']); + $file_url = $upload['url']; + + // ارسال به API لاراول + $api_url = get_option('ifnex_api_url', 'http://localhost:8000/api/v1'); + $url = rtrim($api_url, '/') . "/customer/orders/{$order_id}/commitment-forms/{$form_id}/upload"; + + $token = get_user_meta($user_id, 'ifnex_api_token', true); + if (!$token) wp_send_json_error('توکن احراز هویت یافت نشد.'); + + $multipart = new CURLFile($upload['file'], $file['type'], $file['name']); + $post_fields = array( + 'file' => $multipart, + 'notes' => sanitize_textarea_field($_POST['notes'] ?? ''), + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Authorization: Bearer ' . $token, + )); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code !== 200 && $http_code !== 201) wp_send_json_error('خطا در ارسال فایل به سرور.'); + + $result = json_decode($response, true); + if (!$result || !isset($result['success'])) wp_send_json_error('خطا در پردازش پاسخ سرور.'); + + wp_send_json_success($result['data']); } \ No newline at end of file diff --git a/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php b/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php new file mode 100644 index 0000000..eb2f79a --- /dev/null +++ b/04_Laravel/app/Filament/Widgets/FinanceOverviewWidget.php @@ -0,0 +1,55 @@ +startOfDay(); + + $totalRevenue = WalletTransaction::where('type', 'order_payment') + ->where('status', 'completed') + ->where('amount', '>', 0) + ->sum('amount'); + + $todayRevenue = WalletTransaction::where('type', 'order_payment') + ->where('status', 'completed') + ->where('amount', '>', 0) + ->where('created_at', '>=', $today) + ->sum('amount'); + + $pendingPayments = Shipment::where('status', 'approved')->count(); + $pendingAmount = Shipment::where('status', 'approved')->sum('total_fee'); + + $totalWalletBalance = Wallet::sum('balance'); + + return [ + Stat::make('درآمد کل (تکمیل شده)', number_format($totalRevenue) . ' ریال') + ->icon('heroicon-o-currency-dollar') + ->color('success'), + + Stat::make('درآمد امروز', number_format($todayRevenue) . ' ریال') + ->icon('heroicon-o-chart-bar') + ->color('info'), + + Stat::make('پرداخت‌های در انتظار', $pendingPayments . ' سفارش') + ->description(number_format($pendingAmount) . ' ریال') + ->icon('heroicon-o-clock') + ->color('warning'), + + Stat::make('مجموع موجودی کیف‌پول‌ها', number_format($totalWalletBalance) . ' ریال') + ->icon('heroicon-o-wallet') + ->color('gray'), + ]; + } +} diff --git a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php index aa33d99..ccf1e46 100644 --- a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php +++ b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php @@ -4,7 +4,11 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\CommitmentForm; +use App\Models\Shipment; +use App\Models\ShipmentCommitmentForm; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; class CommitmentFormController extends Controller { @@ -63,4 +67,114 @@ class CommitmentFormController extends Controller 'data' => $forms, ]); } + + /** + * دریافت لیست تعهدنامه‌های مربوط به سفارش خاص + * GET /api/v1/customer/orders/{shipment}/commitment-forms + */ + public function shipmentForms(Shipment $shipment): JsonResponse + { + $user = auth()->user(); + + // بررسی مالکیت + if ($shipment->user_id !== $user->id) { + return response()->json([ + 'success' => false, + 'message' => 'شما به این سفارش دسترسی ندارید.', + ], 403); + } + + $forms = CommitmentForm::query() + ->where('is_active', true) + ->where(function ($query) use ($shipment) { + $query->where('direction', 'both') + ->orWhere('direction', $shipment->direction); + }) + ->orderBy('sort_order') + ->orderBy('created_at', 'desc') + ->get() + ->map(function ($form) use ($shipment) { + $upload = ShipmentCommitmentForm::where('shipment_id', $shipment->id) + ->where('commitment_form_id', $form->id) + ->first(); + + return [ + 'id' => $form->id, + 'title' => $form->title, + 'description' => $form->description, + 'file_url' => $form->file_url, + 'file_type' => strtoupper(pathinfo($form->file_path, PATHINFO_EXTENSION)), + 'direction' => $form->direction, + 'upload_status' => $upload ? $upload->status : 'pending', + 'uploaded_file_url' => $upload && $upload->uploaded_file_path ? asset('storage/' . $upload->uploaded_file_path) : null, + 'uploaded_at' => $upload ? $upload->created_at : null, + 'notes' => $upload ? $upload->notes : null, + ]; + }); + + return response()->json([ + 'success' => true, + 'data' => $forms, + ]); + } + + /** + * آپلود فرم تعهدنامه امضاشده + * POST /api/v1/customer/orders/{shipment}/commitment-forms/{form}/upload + */ + public function uploadSigned(Request $request, Shipment $shipment, CommitmentForm $form): JsonResponse + { + $user = auth()->user(); + + // بررسی مالکیت + if ($shipment->user_id !== $user->id) { + return response()->json([ + 'success' => false, + 'message' => 'شما به این سفارش دسترسی ندارید.', + ], 403); + } + + $request->validate([ + 'file' => 'required|file|mimes:pdf,jpg,jpeg,png|max:5120', // حداکثر 5MB + 'notes' => 'nullable|string|max:1000', + ]); + + try { + $file = $request->file('file'); + $path = $file->store("commitment-forms/{$shipment->id}", 'public'); + + $upload = ShipmentCommitmentForm::updateOrCreate( + [ + 'shipment_id' => $shipment->id, + 'commitment_form_id' => $form->id, + ], + [ + 'uploaded_file_path' => $path, + 'uploaded_file_type' => $file->getClientOriginalExtension(), + 'uploaded_file_size' => $file->getSize(), + 'status' => 'uploaded', + 'notes' => $request->input('notes'), + 'uploaded_by' => $user->id, + ] + ); + + return response()->json([ + 'success' => true, + 'message' => 'فایل با موفقیت آپلود شد.', + 'data' => [ + 'id' => $upload->id, + 'file_url' => asset('storage/' . $path), + 'file_type' => $upload->uploaded_file_type, + 'status' => $upload->status, + 'uploaded_at' => $upload->created_at, + ], + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'خطا در آپلود فایل: ' . $e->getMessage(), + ], 500); + } + } } + diff --git a/04_Laravel/app/Models/ShipmentCommitmentForm.php b/04_Laravel/app/Models/ShipmentCommitmentForm.php new file mode 100644 index 0000000..58e40bf --- /dev/null +++ b/04_Laravel/app/Models/ShipmentCommitmentForm.php @@ -0,0 +1,47 @@ + 'integer', + 'verified_at' => 'datetime', + ]; + + public function shipment(): BelongsTo + { + return $this->belongsTo(Shipment::class); + } + + public function form(): BelongsTo + { + return $this->belongsTo(CommitmentForm::class, 'commitment_form_id'); + } + + public function uploader(): BelongsTo + { + return $this->belongsTo(User::class, 'uploaded_by'); + } + + public function verifier(): BelongsTo + { + return $this->belongsTo(User::class, 'verified_by'); + } +} diff --git a/04_Laravel/app/Models/User.php b/04_Laravel/app/Models/User.php index 93ca6fd..f5b25a2 100644 --- a/04_Laravel/app/Models/User.php +++ b/04_Laravel/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; +use Illuminate\Notifications\DatabaseNotification; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; use Spatie\Permission\Traits\HasRoles; @@ -44,6 +45,11 @@ class User extends Authenticatable return $this->hasOne(Wallet::class); } + public function notifications() + { + return $this->morphMany(DatabaseNotification::class, 'notifiable')->orderByDesc('created_at'); + } + // ─── Helper Methods (Role Checks) ──────────────── public function isSuperAdmin(): bool @@ -66,6 +72,16 @@ class User extends Authenticatable return $this->hasRole('customer'); } + public function markNotificationRead(string $notificationId): bool + { + $notification = $this->notifications()->where('id', $notificationId)->first(); + if ($notification) { + $notification->markAsRead(); + return true; + } + return false; + } + // ─── Credit Methods ────────────────────────────── public function getAvailableCreditAttribute(): float diff --git a/04_Laravel/database/migrations/2026_09_09_000001_create_shipment_commitment_forms_table.php b/04_Laravel/database/migrations/2026_09_09_000001_create_shipment_commitment_forms_table.php new file mode 100644 index 0000000..9c0e8fd --- /dev/null +++ b/04_Laravel/database/migrations/2026_09_09_000001_create_shipment_commitment_forms_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('shipment_id')->constrained()->cascadeOnDelete(); + $table->foreignId('commitment_form_id')->constrained()->cascadeOnDelete(); + $table->string('uploaded_file_path')->nullable(); + $table->string('uploaded_file_type')->nullable(); + $table->unsignedBigInteger('uploaded_file_size')->nullable(); + $table->enum('status', ['pending', 'uploaded', 'verified', 'rejected'])->default('pending'); + $table->text('notes')->nullable(); + $table->unsignedBigInteger('uploaded_by')->nullable(); + $table->unsignedBigInteger('verified_by')->nullable(); + $table->timestamp('verified_at')->nullable(); + $table->timestamps(); + + $table->foreign('uploaded_by')->references('id')->on('users')->nullOnDelete(); + $table->foreign('verified_by')->references('id')->on('users')->nullOnDelete(); + + $table->unique(['shipment_id', 'commitment_form_id']); + }); + } + + public function down(): void + { + Schema::dropIfExists('shipment_commitment_forms'); + } +}; diff --git a/04_Laravel/routes/api.php b/04_Laravel/routes/api.php index 1c8d28f..1c0e1b4 100644 --- a/04_Laravel/routes/api.php +++ b/04_Laravel/routes/api.php @@ -76,11 +76,19 @@ Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () { Route::get('/orders/{shipment}', [CustomerOrderController::class, 'show']); Route::post('/orders/{shipment}/cancel', [CustomerOrderController::class, 'cancel']); + // دانلود PDFهای سفارش + Route::get('/orders/{shipment}/pdf/awb', [ShipmentPdfController::class, 'awb']); + Route::get('/orders/{shipment}/pdf/invoice', [ShipmentPdfController::class, 'invoice']); + Route::get('/orders/{shipment}/pdf/label', [ShipmentPdfController::class, 'label']); + Route::get('/orders/{shipment}/pdf/import-invoice', [ShipmentPdfController::class, 'importInvoice']); + // تعهدنامه‌ها + Route::get('/orders/{shipment}/commitment-forms', [CommitmentFormController::class, 'shipmentForms']); + Route::post('/orders/{shipment}/commitment-forms/{form}/upload', [CommitmentFormController::class, 'uploadSigned']); + // نوتیفیکیشن‌ها Route::get('/notifications', [CustomerOrderController::class, 'notifications']); Route::post('/notifications/{notification}/read', [CustomerOrderController::class, 'markNotificationRead']); - // پرداخت سفارش Route::post('/orders/{shipment}/pay-wallet', [CustomerOrderController::class, 'payFromWallet']);