diff --git a/01_Documents/Sheet ENG invoice.pdf b/01_Documents/Sheet ENG invoice.pdf new file mode 100644 index 0000000..283ed68 Binary files /dev/null and b/01_Documents/Sheet ENG invoice.pdf differ diff --git a/01_Documents/Sheet ENG invoice.xlsx b/01_Documents/Sheet ENG invoice.xlsx new file mode 100644 index 0000000..99b3979 Binary files /dev/null and b/01_Documents/Sheet ENG invoice.xlsx differ diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css index 915b764..1e52f79 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-bridge.css @@ -448,3 +448,164 @@ gap: 0.5rem !important; } } + +/* ============================================ + ORDER FORM STEPS + ============================================ */ +.ifnex-form-step { + display: none !important; +} + +.ifnex-form-step.active { + display: block !important; +} + +.ifnex-steps { + display: flex !important; + justify-content: space-between !important; + margin-bottom: 2rem !important; + padding: 1rem !important; + background: var(--ifnex-gray-50) !important; + border-radius: var(--ifnex-radius-lg) !important; + border: 1px solid var(--ifnex-gray-200) !important; +} + +.ifnex-step { + flex: 1 !important; + text-align: center !important; + padding: 0.5rem !important; + position: relative !important; + color: var(--ifnex-gray-400) !important; + font-size: 0.75rem !important; + transition: all 0.3s ease !important; +} + +.ifnex-step span { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + width: 28px !important; + height: 28px !important; + border-radius: 50% !important; + background: var(--ifnex-gray-200) !important; + color: var(--ifnex-gray-500) !important; + font-weight: 600 !important; + font-size: 0.875rem !important; + margin-bottom: 0.25rem !important; +} + +.ifnex-step.active { + color: var(--ifnex-primary) !important; + font-weight: 600 !important; +} + +.ifnex-step.active span { + background: var(--ifnex-primary) !important; + color: var(--ifnex-white) !important; +} + +.ifnex-step.done { + color: var(--ifnex-success) !important; +} + +.ifnex-step.done span { + background: var(--ifnex-success) !important; + color: var(--ifnex-white) !important; +} + +.ifnex-form-nav { + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + margin-top: 1.5rem !important; + padding-top: 1rem !important; + border-top: 1px solid var(--ifnex-gray-200) !important; +} + +.ifnex-form-card { + background: var(--ifnex-white) !important; + border: 1px solid var(--ifnex-gray-200) !important; + border-radius: var(--ifnex-radius-lg) !important; + padding: 1.5rem !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important; +} + +.ifnex-form-card h3 { + font-size: 1rem !important; + font-weight: 600 !important; + color: var(--ifnex-gray-900) !important; + margin-bottom: 1rem !important; + padding-bottom: 0.5rem !important; + border-bottom: 2px solid var(--ifnex-primary) !important; +} + +.ifnex-form-grid { + display: grid !important; + grid-template-columns: repeat(2, 1fr) !important; + gap: 1rem !important; +} + +.ifnex-form-grid .full { + grid-column: span 2 !important; +} + +.ifnex-field { + display: flex !important; + flex-direction: column !important; +} + +.ifnex-field label { + font-size: 0.875rem !important; + font-weight: 500 !important; + color: var(--ifnex-gray-700) !important; + margin-bottom: 0.375rem !important; +} + +.ifnex-field input, +.ifnex-field select, +.ifnex-field textarea { + padding: 0.625rem 0.875rem !important; + border: 1px solid var(--ifnex-gray-300) !important; + border-radius: var(--ifnex-radius-md) !important; + font-size: 0.875rem !important; + font-family: var(--ifnex-font-family) !important; + transition: all 0.2s ease !important; +} + +.ifnex-field input:focus, +.ifnex-field select:focus, +.ifnex-field textarea:focus { + outline: none !important; + border-color: var(--ifnex-primary) !important; + box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1) !important; +} + +.ifnex-error-box { + padding: 0.75rem 1rem !important; + background: var(--ifnex-danger-light) !important; + border: 1px solid var(--ifnex-danger) !important; + border-radius: var(--ifnex-radius-md) !important; + color: var(--ifnex-danger) !important; + font-size: 0.875rem !important; + margin-bottom: 1rem !important; +} + +.ifnex-success-box { + padding: 0.75rem 1rem !important; + background: var(--ifnex-success-light) !important; + border: 1px solid var(--ifnex-success) !important; + border-radius: var(--ifnex-radius-md) !important; + color: var(--ifnex-success) !important; + font-size: 0.875rem !important; + margin-bottom: 1rem !important; +} + +.ifnex-warning-box { + padding: 0.75rem 1rem !important; + background: var(--ifnex-warning-light) !important; + border: 1px solid var(--ifnex-warning) !important; + border-radius: var(--ifnex-radius-md) !important; + color: #92400e !important; + font-size: 0.875rem !important; + margin-bottom: 1rem !important; +} diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/js/ifnex-order-form.js b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/js/ifnex-order-form.js index 774b70f..b7fcca3 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/js/ifnex-order-form.js +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/js/ifnex-order-form.js @@ -731,7 +731,9 @@ jQuery(document).ready(function($) { btn.prop('disabled', false).text('✅ تأیید و ثبت سفارش'); if (res.success) { var orderId = res.data.data.id || res.data.id; - window.location.href = ifnex_ajax.orders_url + 'order-payment/?order_id=' + orderId; + // با فلوی جدید، کاربر به صفحه جزئیات سفارش هدایت میشود + // (چون پرداخت فقط بعد از تأیید کارمند امکانپذیر است) + window.location.href = ifnex_ajax.orders_url + 'order-detail/?order_id=' + orderId; } else { $('#ifnex-submit-error').text(res.data || 'خطا در ثبت سفارش').show(); } 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 bb1239b..3b13d7c 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php @@ -231,18 +231,41 @@ function ifnex_user_profile_shortcode($atts) { -
وضعیت: ' . esc_html($data['status']['label']) . '
+ // بررسی وضعیت (فقط سفارشات تأیید شده قابل پرداخت هستند) + if ($data['status']['value'] !== 'approved') { + $message = 'این سفارش هنوز تأیید نشده است و قابل پرداخت نیست.'; + if ($data['status']['value'] === 'pending_approval') { + $message = 'این سفارش در انتظار تأیید کارمند است. پس از تأیید، گزینه پرداخت فعال خواهد شد.'; + } elseif ($data['status']['is_paid']) { + $message = 'این سفارش قبلاً پرداخت شده است.'; + } + + return ''; } @@ -938,7 +972,7 @@ function ifnex_order_detail_shortcode($atts) { ← بازگشت به سفارشات - + 💳 پرداخت diff --git a/04_Laravel/app/Enums/ShipmentStatus.php b/04_Laravel/app/Enums/ShipmentStatus.php index e4b9355..2149b32 100644 --- a/04_Laravel/app/Enums/ShipmentStatus.php +++ b/04_Laravel/app/Enums/ShipmentStatus.php @@ -4,6 +4,8 @@ namespace App\Enums; enum ShipmentStatus: string { + case PendingApproval = 'pending_approval'; + case Approved = 'approved'; case PendingPayment = 'pending_payment'; case Cancelled = 'cancelled'; case Processed = 'processed'; @@ -18,15 +20,17 @@ enum ShipmentStatus: string public function label(): string { return match ($this) { + self::PendingApproval => 'در انتظار تأیید', + self::Approved => 'تأیید شده - در انتظار پرداخت', self::PendingPayment => 'در انتظار پرداخت', self::Cancelled => 'لغو شده', - self::Processed => 'Processed', - self::PickedUp => 'Picked Up', - self::InTransit => 'In Transit', - self::OutForDelivery => 'Out For Delivery', - self::Failed => 'Failed', - self::Delivered => 'Delivered', - self::Returned => 'Returned', + self::Processed => 'در حال پردازش', + self::PickedUp => 'تحویل به حملکننده', + self::InTransit => 'در حال حمل', + self::OutForDelivery => 'آماده تحویل', + self::Failed => 'تحویل ناموفق', + self::Delivered => 'تحویل شده', + self::Returned => 'برگشتی', self::Archived => 'آرشیو', }; } @@ -34,6 +38,8 @@ enum ShipmentStatus: string public function color(): string { return match ($this) { + self::PendingApproval => 'warning', + self::Approved => 'info', self::PendingPayment => 'warning', self::Cancelled => 'danger', self::Processed => 'gray', @@ -49,11 +55,37 @@ enum ShipmentStatus: string public function isPaid(): bool { - return $this !== self::PendingPayment; + return !in_array($this, [ + self::PendingApproval, + self::PendingPayment, + ]); } public function canBeCancelledByCustomer(): bool { - return $this === self::PendingPayment; + return in_array($this, [ + self::PendingApproval, + self::PendingPayment, + ]); } -} \ No newline at end of file + + public function isApproved(): bool + { + return $this === self::Approved; + } + + public function isPendingApproval(): bool + { + return $this === self::PendingApproval; + } + + public function canBeApprovedByStaff(): bool + { + return $this === self::PendingApproval; + } + + public function canBePaid(): bool + { + return $this === self::Approved; + } +} diff --git a/04_Laravel/app/Filament/Pages/BulkTrackingImport.php b/04_Laravel/app/Filament/Pages/BulkTrackingImport.php new file mode 100644 index 0000000..d0ae96d --- /dev/null +++ b/04_Laravel/app/Filament/Pages/BulkTrackingImport.php @@ -0,0 +1,198 @@ +form->fill([ + 'csv_file' => null, + 'status' => 'in_transit', + 'description' => '', + ]); + } + + public function form(Form $form): Form + { + return $form + ->schema([ + Forms\Components\Section::make('فایل CSV') + ->schema([ + Forms\Components\FileUpload::make('csv_file') + ->label('فایل CSV') + ->acceptedFileTypes(['text/csv', 'text/plain']) + ->maxSize(10240) // 10MB + ->required() + ->helperText('فرمت: awb_no,status,description (هر سطر یک سفارش)'), + ]), + + Forms\Components\Section::make('تنظیمات') + ->schema([ + Forms\Components\Select::make('status') + ->label('وضعیت جدید') + ->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($status) => [$status->value => $status->label()])->toArray()) + ->default('in_transit') + ->required(), + Forms\Components\TextInput::make('description') + ->label('توضیحات پیشفرض') + ->placeholder('مثال: بهروزرسانی گروهی وضعیت ترکینگ') + ->default('ایمپورت گروهی وضعیت'), + ]), + + Forms\Components\Section::make('پیشنمایش') + ->schema([ + Forms\Components\Placeholder::make('preview') + ->label('پیشنمایش فایل') + ->content(fn () => $this->getPreviewContent()), + ]) + ->collapsible(), + ]) + ->statePath('data'); + } + + private function getPreviewContent(): string + { + $file = $this->data['csv_file'] ?? null; + + if (!$file) { + return 'فایلی انتخاب نشده است. لطفاً فایل CSV را آپلود کنید.'; + } + + try { + $path = is_array($file) ? ($file['path'] ?? reset($file)) : $file; + $content = file_get_contents($path); + $lines = explode("\n", trim($content)); + + $preview = "فرمت فایل CSV:
+وضعیتهای مجاز:
+نمونه فایل:
+.awb_no,status,description +123456789,in_transit,ارسال شد از تهران +987654321,delivered,تحویل داده شد+
{{ $receiver['name'] }}
+{{ $receiver['company'] ?: '' }}
+{{ $receiver['address'] }}
+{{ $receiver['city'] }}, {{ $receiver['country'] }}
+| Invoice No. | +{{ $invoice_no }} | +
| Date | +{{ $invoice_date }} | +
| Origin | +{{ $shipment->fromCountry?->name }} | +
| Transport | +Air Freight | +
| Customs | +{{ $customs_office ?: 'Imam Khomeini Airport' }} | +
| No. | +Item Description | +Unit | +Qty | +Unit Price | +Total (Currency) | +Total (Rial) | +Notes | +
|---|---|---|---|---|---|---|---|
| {{ $index + 1 }} | +{{ $item['description'] }} | +{{ $item['unit'] ?: 'service' }} | +{{ number_format($item['quantity'], 2) }} | +{{ $item['unit_price'] > 0 ? number_format($item['unit_price'], 2) : '0.00' }} | +{{ $item['total_currency'] > 0 ? number_format($item['total_currency'], 2) : '0.00' }} | +{{ $item['total_rial'] > 0 ? number_format($item['total_rial']) : '0' }} | +{{ $item['notes'] ?: '' }} | +
| Freight Subtotal | +{{ number_format($freight_subtotal_currency, 2) }} | +{{ number_format($freight_subtotal_rial) }} | ++ | ||||
| Clearance Subtotal | +{{ number_format($clearance_subtotal_currency, 2) }} | +{{ number_format($clearance_subtotal_rial) }} | ++ | ||||
| Currency Rate: | +{{ number_format($exchange_rate) }} | ++ | |||||
| TOTAL | +{{ number_format($total_currency, 2) }} | +{{ number_format($total_rial) }} | ++ | ||||
{{ $terms_and_conditions }}
+