diff --git a/.gitignore b/.gitignore
index 14cf3aa..7e5b437 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,7 @@ Thumbs.db
# هسته لاراول (پکیجهای دانلودی و تنظیمات)
04_Laravel/vendor/
04_Laravel/node_modules/
-04_Laravel/.env
\ No newline at end of file
+04_Laravel/.env
+
+# سیملینک storage:link — با artisan ساخته میشود، نباید کامیت شود
+04_Laravel/public/storage/
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 f27a35a..ecbd770 100644
--- a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php
+++ b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php
@@ -497,7 +497,104 @@ function ifnex_orders_list_shortcode($atts) {
}
});
});
+
+ // 🔹 بارگذاری تعهدنامههای سفارش از طریق AJAX
+ var $cf = $('#ifnex-commitment-forms');
+ var cfOrderId = $cf.data('order-id');
+ if (cfOrderId) {
+ $.post(ifnex_ajax.ajax_url, {
+ action: 'ifnex_get_commitment_forms',
+ order_id: cfOrderId,
+ nonce: ifnex_ajax.nonce
+ }).done(function(res) {
+ if (!res || !res.success) {
+ $cf.html('
تعهدنامهای برای این سفارش ثبت نشده است.
');
+ return;
+ }
+ var html = '';
+ for (var i = 0; i < forms.length; i++) {
+ var f = forms[i];
+ var status = f.upload_status || 'pending';
+ var isUploaded = (status === 'uploaded' || status === 'approved');
+ var statusLabel = isUploaded ? '✅ آپلود شده' : '⏳ در انتظار آپلود';
+ var statusClass = isUploaded ? 'ifnex-badge-success' : 'ifnex-badge-warning';
+ var dlHref = '?order_id=' + cfOrderId + '&download=commitment-template&form_id=' + f.id;
+ var signedHref = '?order_id=' + cfOrderId + '&download=commitment-signed&form_id=' + f.id;
+ html += 'خطا در ارتباط با سرور.
');
+ });
+ }
});
+
+ // تعریف روی window چون از onclick صدا زده میشود
+ window.uploadCommitmentForm = function(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);
+
+ // جستجوی نزدیکترین container با .ifnex-form-actions (والد دکمه)
+ var container = input.closest('.ifnex-form-actions') || input.parentElement;
+ var originalHtml = container.innerHTML;
+ container.innerHTML = '';
+ var reasonHtml = (form.upload_status === 'rejected' && form.notes)
+ ? '
⚠️ دلیل رد: ' + form.notes + '
'
+ : '';
+ // دانلود از طریق پروکسی وردپرس (توکن لاراول سمت سرور ارسال میشود؛ لینک مستقیم به API بدون توکن 401/500 میدهد)
+ var dlHref = '?order_id=&download=commitment-template&form_id=' + form.id;
+ var signedHref = '?order_id=&download=commitment-signed&form_id=' + form.id;
+ html += '
';
});
@@ -1270,17 +1390,17 @@ jQuery(document).ready(function($) {
$('#ifnex-commitment-forms').html('
خطا در بارگذاری تعهدنامهها.
');
}
});
- });
});
function uploadCommitmentForm(orderId, formId, input) {
+ // بدون $ — وردپرس jQuery را در حالت noConflict لود میکند و $ سراسری وجود ندارد
var file = input.files[0]; if (!file) return;
var fd = new FormData();
fd.append('action', 'ifnex_upload_commitment_form');
fd.append('order_id', orderId); fd.append('form_id', formId);
fd.append('file', file); fd.append('nonce', ifnex_ajax.nonce);
- var btn = $(input).closest('.ifnex-form-actions'); var orig = btn.html();
+ var btn = jQuery(input).closest('.ifnex-form-actions'); var orig = btn.html();
btn.html('
در حال آپلود...');
- $.ajax({
+ jQuery.ajax({
url: ifnex_ajax.ajax_url, method: 'POST', data: fd, processData: false, contentType: false,
success: function(r) { if (r.success) { alert('آپلود شد.'); location.reload(); } else { alert('خطا: ' + (r.data||'')); btn.html(orig); } },
error: function() { alert('خطا در ارتباط با سرور.'); btn.html(orig); }
@@ -1333,6 +1453,72 @@ function ifnex_handle_pdf_download($order_id, $download_type) {
exit;
}
+// ══════════════════════════════════════════════════════════════
+// پروکسی دانلود تعهدنامه (قالب خام یا فایل امضاشده) با توکن لاراول
+// ══════════════════════════════════════════════════════════════
+function ifnex_handle_commitment_download($order_id, $form_id, $type = 'template') {
+ $user_id = get_current_user_id();
+ if (!$user_id) wp_die('برای دانلود باید وارد شوید.');
+
+ $form_id = intval($form_id);
+ if (!$form_id) wp_die('شناسه فرم نامعتبر است.');
+
+ $bridge = new IFNEX_User_Bridge();
+ $order = $bridge->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');
+ $endpoint = ($type === 'signed') ? 'download' : 'template';
+ $download_url = rtrim($api_url, '/') . "/customer/orders/{$order_id}/commitment-forms/{$form_id}/{$endpoint}";
+
+ $token = get_user_meta($user_id, 'ifnex_laravel_token', true);
+ if (!$token) wp_die('توکن احراز هویت یافت نشد.');
+
+ $response = wp_remote_get($download_url, array(
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $token,
+ 'Accept' => '*/*',
+ ),
+ 'timeout' => 60,
+ ));
+
+ if (is_wp_error($response)) wp_die('خطا در ارتباط با سرور.');
+
+ $status_code = wp_remote_retrieve_response_code($response);
+ if ($status_code !== 200) {
+ $body = wp_remote_retrieve_body($response);
+ $err = json_decode($body, true);
+ $msg = $err['message'] ?? ('خطا در دانلود فایل (کد: ' . $status_code . ')');
+ wp_die(esc_html($msg));
+ }
+
+ $file_content = wp_remote_retrieve_body($response);
+ $awb = $order['data']['awb_no'] ?? ('order-' . $order_id);
+
+ // تشخیص نوع محتوا و نام فایل از روی هدر Laravel
+ $ctype = wp_remote_retrieve_header($response, 'content-type');
+ if (empty($ctype)) $ctype = 'application/octet-stream';
+
+ $disp = wp_remote_retrieve_header($response, 'content-disposition');
+ $filename = ($type === 'signed' ? 'signed-' : 'template-') . $form_id . '-' . $awb;
+ if ($disp && preg_match('/filename="?([^";]+)"?/i', $disp, $m)) {
+ $filename = trim($m[1]);
+ } else {
+ // fallback بر اساس content-type
+ $ext = (stripos($ctype, 'pdf') !== false) ? 'pdf'
+ : ((stripos($ctype, 'png') !== false) ? 'png'
+ : ((stripos($ctype, 'jpeg') !== false) ? 'jpg' : 'bin'));
+ $filename .= '.' . $ext;
+ }
+
+ nocache_headers();
+ header('Content-Type: ' . $ctype);
+ 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/04_Laravel/app/Filament/Resources/ShipmentResource/RelationManagers/CommitmentFormsRelationManager.php b/04_Laravel/app/Filament/Resources/ShipmentResource/RelationManagers/CommitmentFormsRelationManager.php
index bf472f7..1f53917 100644
--- a/04_Laravel/app/Filament/Resources/ShipmentResource/RelationManagers/CommitmentFormsRelationManager.php
+++ b/04_Laravel/app/Filament/Resources/ShipmentResource/RelationManagers/CommitmentFormsRelationManager.php
@@ -2,6 +2,9 @@
namespace App\Filament\Resources\ShipmentResource\RelationManagers;
+use App\Models\ShipmentCommitmentForm;
+use App\Notifications\CommitmentFormRejectedNotification;
+use App\Notifications\CommitmentFormVerifiedNotification;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
@@ -18,23 +21,13 @@ class CommitmentFormsRelationManager extends RelationManager
public function form(Form $form): Form
{
+ // رکوردها فقط از آپلود مشتری یا مقداردهی خودکار ساخته میشوند؛
+ // از این فرم فقط یادداشت قابل ویرایش است (وضعیت با اکشنهای تأیید/رد تغییر میکند)
return $form->schema([
- Forms\Components\Select::make('status')
- ->options([
- 'pending' => 'در انتظار',
- 'uploaded' => 'بارگذاری شده',
- 'verified' => 'تأیید شده',
- 'rejected' => 'رد شده',
- ])
- ->label('وضعیت'),
Forms\Components\Textarea::make('notes')
->rows(3)
- ->label('یادداشت'),
- Forms\Components\Select::make('verified_by')
- ->relationship('verifier', 'name')
- ->label('تأییدکننده'),
- Forms\Components\DateTimePicker::make('verified_at')
- ->label('تاریخ تأیید'),
+ ->label('یادداشت')
+ ->maxLength(1000),
]);
}
@@ -42,7 +35,21 @@ class CommitmentFormsRelationManager extends RelationManager
{
return $table
->columns([
- Tables\Columns\TextColumn::make('form.title')->label('فرم تعهدنامه')->limit(40),
+ Tables\Columns\TextColumn::make('form.title')
+ ->label('فرم تعهدنامه')
+ ->description(fn ($record) => $record->form?->description)
+ ->limit(40),
+ Tables\Columns\TextColumn::make('form.direction')
+ ->label('جهت')
+ ->badge()
+ ->color('gray')
+ ->formatStateUsing(fn ($state): string => match ($state) {
+ 'export' => 'صادرات',
+ 'import' => 'واردات',
+ 'both' => 'هردو',
+ default => (string) $state,
+ })
+ ->toggleable(),
Tables\Columns\TextColumn::make('status')
->label('وضعیت')
->badge()
@@ -54,29 +61,112 @@ class CommitmentFormsRelationManager extends RelationManager
default => 'gray',
})
->formatStateUsing(fn (string $state): string => match ($state) {
- 'pending' => 'در انتظار',
- 'uploaded' => 'بارگذاری شده',
+ 'pending' => 'در انتظار آپلود',
+ 'uploaded' => 'آپلود شده',
'verified' => 'تأیید شده',
'rejected' => 'رد شده',
default => $state,
}),
- Tables\Columns\TextColumn::make('uploader.name')->label('بارگذاری توسط')->toggleable(),
- Tables\Columns\TextColumn::make('verifier.name')->label('تأییدکننده')->toggleable(),
- Tables\Columns\TextColumn::make('verified_at')->label('تاریخ تأیید')->dateTime('Y/m/d H:i')->toggleable(),
- Tables\Columns\TextColumn::make('notes')->label('یادداشت')->limit(40)->toggleable(),
+ Tables\Columns\TextColumn::make('uploaded_file_type')
+ ->label('فایل مشتری')
+ ->formatStateUsing(function ($state, $record) {
+ if (!$state) return '—';
+ $size = $record->uploaded_file_size ? ' · ' . number_format($record->uploaded_file_size / 1024, 0) . ' KB' : '';
+ return strtoupper($state) . $size;
+ }),
+ Tables\Columns\TextColumn::make('created_at')
+ ->label('تاریخ آپلود')
+ ->dateTime('Y/m/d H:i')
+ ->toggleable(),
+ Tables\Columns\TextColumn::make('uploader.name')
+ ->label('بارگذاری توسط')
+ ->toggleable(isToggledHiddenByDefault: true),
+ Tables\Columns\TextColumn::make('verifier.name')
+ ->label('تأییدکننده')
+ ->toggleable(),
+ Tables\Columns\TextColumn::make('verified_at')
+ ->label('تاریخ تأیید')
+ ->dateTime('Y/m/d H:i')
+ ->toggleable(),
+ Tables\Columns\TextColumn::make('notes')
+ ->label('یادداشت / دلیل')
+ ->limit(40)
+ ->toggleable(),
])
->defaultSort('created_at', 'desc')
// ساخت دستی مجاز نیست — این رکوردها از آپلود تعهدنامه توسط مشتری ایجاد میشوند
->headerActions([])
->actions([
- Tables\Actions\EditAction::make(),
- Tables\Actions\Action::make('download')
- ->label('دانلود فایل')
+ Tables\Actions\Action::make('downloadTemplate')
+ ->label('قالب')
->icon('heroicon-o-arrow-down-tray')
- ->url(fn ($record) => $record->uploaded_file_path ? asset('storage/' . $record->uploaded_file_path) : null)
+ ->color('gray')
+ ->url(fn ($record) => $record->form?->file_path
+ ? route('admin.commitment-forms.template', ['shipment' => $record->shipment_id, 'form' => $record->commitment_form_id])
+ : null)
->openUrlInNewTab()
- ->visible(fn ($record) => !empty($record->uploaded_file_path)),
+ ->visible(fn ($record) => !empty($record->form?->file_path))
+ ->tooltip('دانلود قالب خام تعهدنامه'),
+ Tables\Actions\Action::make('downloadSigned')
+ ->label('فایل مشتری')
+ ->icon('heroicon-o-document-arrow-down')
+ ->color('info')
+ ->url(fn ($record) => $record->uploaded_file_path
+ ? route('admin.commitment-forms.signed', ['shipment' => $record->shipment_id, 'record' => $record->id])
+ : null)
+ ->openUrlInNewTab()
+ ->visible(fn ($record) => !empty($record->uploaded_file_path))
+ ->tooltip('دانلود فایل امضاشدهٔ آپلودشده توسط مشتری'),
+ Tables\Actions\Action::make('verify')
+ ->label('تأیید')
+ ->icon('heroicon-o-check-circle')
+ ->color('success')
+ ->requiresConfirmation()
+ ->modalDescription('با تأیید، به مشتری اعلان ارسال میشود.')
+ ->form([
+ Forms\Components\Textarea::make('notes')
+ ->label('یادداشت برای مشتری (اختیاری)')
+ ->rows(2)
+ ->maxLength(1000),
+ ])
+ ->action(function (ShipmentCommitmentForm $record, array $data): void {
+ $record->update([
+ 'status' => 'verified',
+ 'notes' => $data['notes'] ?: $record->notes,
+ 'verified_by' => auth()->id(),
+ 'verified_at' => now(),
+ ]);
+
+ $record->shipment->user?->notify(new CommitmentFormVerifiedNotification($record));
+ })
+ ->visible(fn ($record) => in_array($record->status, ['uploaded', 'rejected'])),
+ Tables\Actions\Action::make('reject')
+ ->label('رد')
+ ->icon('heroicon-o-x-circle')
+ ->color('danger')
+ ->form([
+ Forms\Components\Textarea::make('notes')
+ ->label('دلیل رد (برای مشتری نمایش داده میشود)')
+ ->rows(2)
+ ->required()
+ ->maxLength(1000),
+ ])
+ ->action(function (ShipmentCommitmentForm $record, array $data): void {
+ $record->update([
+ 'status' => 'rejected',
+ 'notes' => $data['notes'],
+ 'verified_by' => auth()->id(),
+ 'verified_at' => now(),
+ ]);
+
+ $record->shipment->user?->notify(new CommitmentFormRejectedNotification($record));
+ })
+ ->visible(fn ($record) => in_array($record->status, ['uploaded', 'verified'])),
+ Tables\Actions\EditAction::make()
+ ->label('یادداشت')
+ ->icon('heroicon-o-pencil-square')
+ ->tooltip('ویرایش یادداشت'),
])
->bulkActions([]);
}
-}
\ No newline at end of file
+}
diff --git a/04_Laravel/app/Http/Controllers/Admin/CommitmentFileController.php b/04_Laravel/app/Http/Controllers/Admin/CommitmentFileController.php
new file mode 100644
index 0000000..ef646f9
--- /dev/null
+++ b/04_Laravel/app/Http/Controllers/Admin/CommitmentFileController.php
@@ -0,0 +1,69 @@
+authorizePanel();
+
+ if (!$form->file_path || !Storage::disk('public')->exists($form->file_path)) {
+ abort(404, 'فایل قالب یافت نشد.');
+ }
+
+ $ext = pathinfo($form->file_path, PATHINFO_EXTENSION);
+
+ return Storage::disk('public')->download(
+ $form->file_path,
+ 'template-' . $form->id . '-' . $shipment->awb_no . '.' . $ext
+ );
+ }
+
+ /**
+ * دانلود فایل امضاشدهای که مشتری آپلود کرده است.
+ * GET /admin/shipments/{shipment}/commitment-forms/{record}/signed
+ */
+ public function signed(Shipment $shipment, ShipmentCommitmentForm $record)
+ {
+ $this->authorizePanel();
+
+ if ($record->shipment_id !== $shipment->id) {
+ abort(404);
+ }
+
+ if (!$record->uploaded_file_path || !Storage::disk('secure')->exists($record->uploaded_file_path)) {
+ abort(404, 'فایل امضاشده یافت نشد.');
+ }
+
+ return Storage::disk('secure')->download(
+ $record->uploaded_file_path,
+ 'commitment-' . $record->commitment_form_id . '-' . $shipment->awb_no . '.' . $record->uploaded_file_type
+ );
+ }
+
+ /**
+ * فقط کاربران پنل ادمین (super_admin/admin/staff) مجاز به دانلود هستند.
+ * همان منطق User::canAccessPanel — بدون نیاز به نمونه پنل (بیرون از کانتکست Filament)
+ */
+ private function authorizePanel(): void
+ {
+ abort_unless(auth()->user()?->hasAnyRole(['super_admin', 'admin', 'staff']), 403);
+ }
+}
diff --git a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php
index c7dda72..a19fca1 100644
--- a/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php
+++ b/04_Laravel/app/Http/Controllers/Api/CommitmentFormController.php
@@ -92,34 +92,82 @@ class CommitmentFormController extends Controller
})
->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();
+ ->get();
- 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
- ? route('customer.commitment-forms.download', ['shipment' => $shipment->id, 'form' => $form->id])
- : null,
- 'uploaded_at' => $upload ? $upload->created_at : null,
- 'notes' => $upload ? $upload->notes : null,
- ];
- });
+ $rows = ShipmentCommitmentForm::where('shipment_id', $shipment->id)
+ ->whereIn('commitment_form_id', $forms->pluck('id'))
+ ->get()
+ ->keyBy('commitment_form_id');
+
+ // ساخت ردیف pending برای فرمهایی که هنوز رکورد ندارند —
+ // تا بخش تعهدنامهها در پنل ادمین همه فرمها را همراه وضعیت نمایش دهد
+ foreach ($forms as $form) {
+ if (!isset($rows[$form->id])) {
+ $rows[$form->id] = ShipmentCommitmentForm::create([
+ 'shipment_id' => $shipment->id,
+ 'commitment_form_id' => $form->id,
+ 'status' => 'pending',
+ ]);
+ }
+ }
+
+ $data = $forms->map(function ($form) use ($shipment, $rows) {
+ $upload = $rows[$form->id];
+
+ return [
+ 'id' => $form->id,
+ 'title' => $form->title,
+ 'description' => $form->description,
+ // 🛡️ URL دانلود قالب از طریق route محافظتشده (نه asset عمومی)
+ 'file_url' => route('customer.commitment-forms.template', [
+ 'shipment' => $shipment->id,
+ 'form' => $form->id,
+ ]),
+ 'file_type' => strtoupper(pathinfo($form->file_path, PATHINFO_EXTENSION)),
+ 'direction' => $form->direction,
+ 'upload_status' => $upload->status,
+ 'uploaded_file_url' => $upload->uploaded_file_path
+ ? route('customer.commitment-forms.download', ['shipment' => $shipment->id, 'form' => $form->id])
+ : null,
+ 'uploaded_at' => $upload->created_at,
+ 'notes' => $upload->notes,
+ ];
+ });
return response()->json([
'success' => true,
- 'data' => $forms,
+ 'data' => $data,
]);
}
+ /**
+ * دانلود قالب خام تعهدنامه (محافظتشده با بررسی مالکیت سفارش).
+ * GET /api/v1/customer/orders/{shipment}/commitment-forms/{form}/template
+ */
+ public function downloadTemplate(Shipment $shipment, CommitmentForm $form)
+ {
+ $user = auth()->user();
+
+ if ($shipment->user_id !== $user->id) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'شما به این سفارش دسترسی ندارید.',
+ ], 403);
+ }
+
+ if (!$form->file_path || !Storage::disk('public')->exists($form->file_path)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'فایل قالب یافت نشد.',
+ ], 404);
+ }
+
+ $ext = pathinfo($form->file_path, PATHINFO_EXTENSION);
+ $filename = 'template-' . $form->id . '-' . $shipment->awb_no . '.' . $ext;
+
+ return Storage::disk('public')->download($form->file_path, $filename);
+ }
+
/**
* آپلود فرم تعهدنامه امضاشده
* POST /api/v1/customer/orders/{shipment}/commitment-forms/{form}/upload
@@ -214,6 +262,4 @@ class CommitmentFormController extends Controller
'commitment-' . $form->id . '-' . $shipment->awb_no . '.' . $upload->uploaded_file_type
);
}
-}
-}
-
+}
\ No newline at end of file
diff --git a/04_Laravel/app/Notifications/CommitmentFormRejectedNotification.php b/04_Laravel/app/Notifications/CommitmentFormRejectedNotification.php
new file mode 100644
index 0000000..785bf6b
--- /dev/null
+++ b/04_Laravel/app/Notifications/CommitmentFormRejectedNotification.php
@@ -0,0 +1,42 @@
+record->loadMissing(['shipment', 'form']);
+
+ $awb = $this->record->shipment->awb_no;
+ $title = $this->record->form?->title ?? ('فرم #' . $this->record->commitment_form_id);
+ $reason = $this->record->notes ? " دلیل: {$this->record->notes}" : '';
+
+ return [
+ 'type' => 'commitment_rejected',
+ 'shipment_id' => $this->record->shipment_id,
+ 'awb_no' => $awb,
+ 'message' => "تعهدنامه «{$title}» سفارش {$awb} رد شد. لطفاً فایل اصلاحشده را دوباره آپلود کنید.{$reason}",
+ 'action_url' => '/order-detail/?order_id=' . $this->record->shipment_id,
+ ];
+ }
+}
diff --git a/04_Laravel/app/Notifications/CommitmentFormVerifiedNotification.php b/04_Laravel/app/Notifications/CommitmentFormVerifiedNotification.php
new file mode 100644
index 0000000..d2f2dcf
--- /dev/null
+++ b/04_Laravel/app/Notifications/CommitmentFormVerifiedNotification.php
@@ -0,0 +1,41 @@
+record->loadMissing(['shipment', 'form']);
+
+ $awb = $this->record->shipment->awb_no;
+ $title = $this->record->form?->title ?? ('فرم #' . $this->record->commitment_form_id);
+
+ return [
+ 'type' => 'commitment_verified',
+ 'shipment_id' => $this->record->shipment_id,
+ 'awb_no' => $awb,
+ 'message' => "تعهدنامه «{$title}» سفارش {$awb} تأیید شد. ممنون از همکاری شما.",
+ 'action_url' => '/order-detail/?order_id=' . $this->record->shipment_id,
+ ];
+ }
+}
diff --git a/04_Laravel/bootstrap/app.php b/04_Laravel/bootstrap/app.php
index f5f89b1..b214cef 100644
--- a/04_Laravel/bootstrap/app.php
+++ b/04_Laravel/bootstrap/app.php
@@ -26,6 +26,11 @@ return Application::configure(basePath: dirname(__DIR__))
'api_key' => ApiKeyAuth::class,
]);
+ // پیشفرض لاراول ۱۱ مهمانها را به route('login') میفرستد که در این پروژه وجود ندارد
+ // و باعث 500 میشود؛ با null، AuthenticationException به هندلر exceptions میرسد
+ // و برای api/* پاسخ JSON 401 برگردانده میشود.
+ $middleware->redirectGuestsTo(fn () => null);
+
// Stateful API برای Sanctum
$middleware->statefulApi();
@@ -52,5 +57,9 @@ return Application::configure(basePath: dirname(__DIR__))
'error' => 'توکن نامعتبر است یا منقضی شده است. لطفاً دوباره وارد شوید.',
], 401);
}
+
+ // مهمانهای وب (مثلاً بازکردن مستقیم روتهای دانلود ادمین) به ورود پنل هدایت شوند —
+ // هندلر پیشفرض لاراول به route('login') میرود که در این پروژه وجود ندارد
+ return redirect()->guest(route('filament.admin.auth.login'));
});
})->create();
\ No newline at end of file
diff --git a/04_Laravel/routes/api.php b/04_Laravel/routes/api.php
index d6c3f4c..61262df 100644
--- a/04_Laravel/routes/api.php
+++ b/04_Laravel/routes/api.php
@@ -97,7 +97,10 @@ Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
Route::get('/orders/{shipment}/commitment-forms', [CommitmentFormController::class, 'shipmentForms']);
Route::post('/orders/{shipment}/commitment-forms/{form}/upload', [CommitmentFormController::class, 'uploadSigned']);
Route::get('/orders/{shipment}/commitment-forms/{form}/download', [CommitmentFormController::class, 'downloadSigned'])
- ->name('customer.commitment-forms.download');
+ ->name('customer.commitment-forms.download');
+ // دانلود قالب خام (محافظتشده با auth به جای symlink عمومی)
+ Route::get('/orders/{shipment}/commitment-forms/{form}/template', [CommitmentFormController::class, 'downloadTemplate'])
+ ->name('customer.commitment-forms.template');
// نوتیفیکیشنها
Route::get('/notifications', [CustomerOrderController::class, 'notifications']);
Route::post('/notifications/{notification}/read', [CustomerOrderController::class, 'markNotificationRead']);
diff --git a/04_Laravel/routes/web.php b/04_Laravel/routes/web.php
index b5e6930..1d7b0da 100644
--- a/04_Laravel/routes/web.php
+++ b/04_Laravel/routes/web.php
@@ -1,5 +1,6 @@
group(function () {
Route::get('/shipments/{shipment}/pdf/invoice', [ShipmentPdfController::class, 'invoice'])->name('shipments.pdf.invoice');
Route::get('/shipments/{shipment}/pdf/label', [ShipmentPdfController::class, 'label'])->name('shipments.pdf.label');
Route::get('/shipments/{shipment}/pdf/import-invoice', [ShipmentPdfController::class, 'importInvoice'])->name('shipments.pdf.import-invoice');
+
+ // دانلود امن فایلهای تعهدنامه برای پنل ادمین (کنترل دسترسی داخل کنترلر با canAccessPanel)
+ Route::get('/admin/shipments/{shipment}/commitment-forms/{form}/template', [CommitmentFileController::class, 'template'])
+ ->name('admin.commitment-forms.template');
+ Route::get('/admin/shipments/{shipment}/commitment-forms/{record}/signed', [CommitmentFileController::class, 'signed'])
+ ->name('admin.commitment-forms.signed');
});
// صفحات نتیجه پرداخت