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 bb87e08..d1fa1dc 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 @@ -940,6 +940,12 @@ jQuery(document).ready(function($) { receiver_phone: $('#ifnex-receiver-phone').val(), receiver_city: $('#ifnex-receiver-city').val(), receiver_address: $('#ifnex-receiver-address').val(), + sender_state: $('#ifnex-sender-state').val(), + sender_postal_code: $('#ifnex-sender-postal-code').val(), + sender_national_id: $('#ifnex-sender-national-id').val(), + receiver_state: $('#ifnex-receiver-state').val(), + receiver_postal_code: $('#ifnex-receiver-postal-code').val(), + receiver_national_id: $('#ifnex-receiver-national-id').val(), }; } 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 7f3f71a..bf5f617 100644 --- a/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php +++ b/03_WordPress/wp-content/plugins/ifnex-bridge/includes/shortcodes.php @@ -749,14 +749,20 @@ function ifnex_order_form_shortcode($atts) {
-
+
+
+
+

📮 اطلاعات گیرنده

-
+
+
+
+
@@ -1287,12 +1293,18 @@ function ifnex_order_detail_shortcode($atts) {

📞

📍

+

🏙️

+

📮

+

🪪

📮 گیرنده

📞

📍

+

🏙️

+

📮

+

🪪

diff --git a/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php b/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php index c2a4ea4..1b07a8f 100644 --- a/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php +++ b/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php @@ -95,20 +95,22 @@ class CustomerOrderController extends Controller 'sender_phone' => ['required', 'string', 'max:50'], 'sender_email' => ['nullable', 'email', 'max:255'], 'sender_address' => ['required', 'string', 'max:1000'], + 'sender_state' => ['nullable', 'string', 'max:255'], 'sender_city' => ['nullable', 'string', 'max:255'], - 'sender_zip' => ['nullable', 'string', 'max:20'], - 'sender_id_number' => ['nullable', 'string', 'max:50'], - + 'sender_postal_code' => ['nullable', 'string', 'max:20'], + 'sender_national_id' => ['nullable', 'string', 'max:50'], + // اطلاعات گیرنده 'receiver_name' => ['required', 'string', 'max:255'], 'receiver_company' => ['nullable', 'string', 'max:255'], 'receiver_phone' => ['required', 'string', 'max:50'], 'receiver_email' => ['nullable', 'email', 'max:255'], 'receiver_address' => ['required', 'string', 'max:1000'], + 'receiver_state' => ['nullable', 'string', 'max:255'], 'receiver_city' => ['nullable', 'string', 'max:255'], - 'receiver_zip' => ['nullable', 'string', 'max:20'], - 'receiver_id_number' => ['nullable', 'string', 'max:50'], - + 'receiver_postal_code' => ['nullable', 'string', 'max:20'], + 'receiver_national_id' => ['nullable', 'string', 'max:50'], + // خدمات اضافی 'extra_service' => ['nullable', 'numeric', 'min:0'], 'packing_cost' => ['nullable', 'numeric', 'min:0'], @@ -245,19 +247,21 @@ class CustomerOrderController extends Controller 'sender_phone' => $validated['sender_phone'], 'sender_email' => $validated['sender_email'] ?? null, 'sender_address' => $validated['sender_address'], + 'sender_state' => $validated['sender_state'] ?? null, 'sender_city' => $validated['sender_city'] ?? null, - 'sender_zip' => $validated['sender_zip'] ?? null, - 'sender_id_number' => $validated['sender_id_number'] ?? null, - + 'sender_postal_code' => $validated['sender_postal_code'] ?? null, + 'sender_national_id' => $validated['sender_national_id'] ?? null, + // گیرنده 'receiver_name' => $validated['receiver_name'], 'receiver_company' => $validated['receiver_company'] ?? null, 'receiver_phone' => $validated['receiver_phone'], 'receiver_email' => $validated['receiver_email'] ?? null, 'receiver_address' => $validated['receiver_address'], + 'receiver_state' => $validated['receiver_state'] ?? null, 'receiver_city' => $validated['receiver_city'] ?? null, - 'receiver_zip' => $validated['receiver_zip'] ?? null, - 'receiver_id_number' => $validated['receiver_id_number'] ?? null, + 'receiver_postal_code' => $validated['receiver_postal_code'] ?? null, + 'receiver_national_id' => $validated['receiver_national_id'] ?? null, ]); // ذخیره بسته‌ها (Multi-Package) @@ -406,9 +410,10 @@ class CustomerOrderController extends Controller 'sender_phone' => ['required', 'string', 'max:50'], 'sender_email' => ['nullable', 'email', 'max:255'], 'sender_address' => ['required', 'string', 'max:1000'], + 'sender_state' => ['nullable', 'string', 'max:255'], 'sender_city' => ['nullable', 'string', 'max:255'], - 'sender_zip' => ['nullable', 'string', 'max:20'], - 'sender_id_number' => ['nullable', 'string', 'max:50'], + 'sender_postal_code' => ['nullable', 'string', 'max:20'], + 'sender_national_id' => ['nullable', 'string', 'max:50'], // گیرنده 'receiver_name' => ['required', 'string', 'max:255'], @@ -416,9 +421,10 @@ class CustomerOrderController extends Controller 'receiver_phone' => ['required', 'string', 'max:50'], 'receiver_email' => ['nullable', 'email', 'max:255'], 'receiver_address' => ['required', 'string', 'max:1000'], + 'receiver_state' => ['nullable', 'string', 'max:255'], 'receiver_city' => ['nullable', 'string', 'max:255'], - 'receiver_zip' => ['nullable', 'string', 'max:20'], - 'receiver_id_number' => ['nullable', 'string', 'max:50'], + 'receiver_postal_code' => ['nullable', 'string', 'max:20'], + 'receiver_national_id' => ['nullable', 'string', 'max:50'], // خدمات 'extra_service' => ['nullable', 'numeric', 'min:0'], @@ -563,18 +569,20 @@ class CustomerOrderController extends Controller 'sender_phone' => $validated['sender_phone'], 'sender_email' => $validated['sender_email'] ?? null, 'sender_address' => $validated['sender_address'], + 'sender_state' => $validated['sender_state'] ?? null, 'sender_city' => $validated['sender_city'] ?? null, - 'sender_zip' => $validated['sender_zip'] ?? null, - 'sender_id_number' => $validated['sender_id_number'] ?? null, + 'sender_postal_code' => $validated['sender_postal_code'] ?? null, + 'sender_national_id' => $validated['sender_national_id'] ?? null, 'receiver_name' => $validated['receiver_name'], 'receiver_company' => $validated['receiver_company'] ?? null, 'receiver_phone' => $validated['receiver_phone'], 'receiver_email' => $validated['receiver_email'] ?? null, 'receiver_address' => $validated['receiver_address'], + 'receiver_state' => $validated['receiver_state'] ?? null, 'receiver_city' => $validated['receiver_city'] ?? null, - 'receiver_zip' => $validated['receiver_zip'] ?? null, - 'receiver_id_number' => $validated['receiver_id_number'] ?? null, + 'receiver_postal_code' => $validated['receiver_postal_code'] ?? null, + 'receiver_national_id' => $validated['receiver_national_id'] ?? null, ]); $shipment->packages()->delete(); @@ -720,12 +728,6 @@ class CustomerOrderController extends Controller $account_balance = $wallet_balance - $pending_payments_total; // بدهی‌های چندارزی (EUR/USD/AED/CNY) — به همان ارز خارجی، نه معادل ریالی - $foreign_credits = app(\App\Services\CustomerCreditService::class) - ->getCustomerDebtsByCurrency($user); - - $account_balance = $wallet_balance - $pending_payments_total; - - // بدهی‌های چندارزی — به همان ارز خارجی $foreign_credits = app(\App\Services\CustomerCreditService::class) ->getCustomerDebtsByCurrency($user); @@ -777,22 +779,22 @@ class CustomerOrderController extends Controller ]); } - /** - * لیست کشورها برای فرم - * GET /api/v1/customer/countries - */ - public function countries(): JsonResponse - { - $countries = Country::select('id', 'name', 'iso_code', 'export_zone_parcel', 'import_zone_parcel') - ->where('is_active', true) - ->orderBy('name') - ->get(); + /** + * لیست کشورها برای فرم + * GET /api/v1/customer/countries + */ + public function countries(): JsonResponse + { + $countries = Country::select('id', 'name', 'iso_code', 'export_zone_parcel', 'import_zone_parcel') + ->where('is_active', true) + ->orderBy('name') + ->get(); - return response()->json([ - 'success' => true, - 'data' => $countries, - ]); - } + return response()->json([ + 'success' => true, + 'data' => $countries, + ]); + } /** * دریافت نوتیفیکیشن‌های کاربر @@ -938,7 +940,10 @@ class CustomerOrderController extends Controller 'phone' => $shipment->sender_phone, 'email' => $shipment->sender_email, 'address' => $shipment->sender_address, + 'state' => $shipment->sender_state, 'city' => $shipment->sender_city, + 'postal_code' => $shipment->sender_postal_code, + 'national_id' => $shipment->sender_national_id, ]; $data['receiver'] = [ @@ -947,7 +952,10 @@ class CustomerOrderController extends Controller 'phone' => $shipment->receiver_phone, 'email' => $shipment->receiver_email, 'address' => $shipment->receiver_address, + 'state' => $shipment->receiver_state, 'city' => $shipment->receiver_city, + 'postal_code' => $shipment->receiver_postal_code, + 'national_id' => $shipment->receiver_national_id, ]; $data['packages'] = $shipment->packages->map(fn($package) => [ @@ -1120,4 +1128,4 @@ class CustomerOrderController extends Controller return response()->json($result, 400); } -} \ No newline at end of file +} diff --git a/04_Laravel/app/Http/Controllers/Api/TrackController.php b/04_Laravel/app/Http/Controllers/Api/TrackController.php index ae48b18..7a94bfe 100644 --- a/04_Laravel/app/Http/Controllers/Api/TrackController.php +++ b/04_Laravel/app/Http/Controllers/Api/TrackController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Shipment; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; /** * API ترکینگ مرسولات — فاز ۰ @@ -12,13 +13,18 @@ use Illuminate\Http\JsonResponse; * GET /api/v1/track/{awb_no} * * خروجی: تایم‌لاین کامل رویدادهای ترکینگ + اطلاعات پایه مرسوله + نگاشت‌های شرکت حمل + * + * امنیت: نمایش لایه‌بندی‌شده (Tiered Display) + * - کاربر ناشناس: فقط وضعیت فعلی و موقعیت + * - کاربر لاگین‌شده (مالک): اطلاعات کامل فرستنده/گیرنده (مخفف‌شده) + * - ادمین: اطلاعات کامل */ class TrackController extends Controller { /** * دریافت تایم‌لاین ترکینگ یک مرسوله */ - public function show(string $awb_no): JsonResponse + public function show(string $awb_no, Request $request): JsonResponse { $shipment = Shipment::where('awb_no', $awb_no) ->with([ @@ -37,38 +43,89 @@ class TrackController extends Controller ], 404); } + // بررسی سطح دسترسی کاربر + $user = $request->user(); + $isOwner = $user && $shipment->user_id === $user->id; + $isAdmin = $user && $user->hasRole('super_admin'); + + // لایه ۱: اطلاعات عمومی (همیشه نمایش داده می‌شود) + $publicData = [ + 'shipment' => [ + 'awb_no' => $shipment->awb_no, + 'direction' => $shipment->direction?->value, + 'type' => $shipment->type?->value, + 'status' => $shipment->status?->value, + 'status_label' => $shipment->status?->label(), + 'from_country' => $shipment->fromCountry?->name, + 'to_country' => $shipment->toCountry?->name, + 'weight' => $shipment->weight, + 'created_at' => $shipment->created_at?->toIso8601String(), + ], + 'carrier_mappings' => $shipment->carrierMappings->map(fn ($mapping) => [ + 'carrier_code' => $mapping->carrier_code, + 'carrier_tracking_number' => $mapping->carrier_tracking_number, + 'booking_number' => $mapping->booking_number, + 'delivery_status' => $mapping->delivery_status, + 'notes' => $mapping->notes, + ]), + 'tracking_events' => $shipment->trackingEvents->map(fn ($event) => [ + 'date' => $event->event_date?->toDateString(), + 'time' => $event->event_time ? ($event->event_time instanceof \Carbon\Carbon ? $event->event_time->format('H:i') : $event->event_time) : null, + 'location' => $event->location, + 'description' => $event->event_description, + 'status' => $event->delivery_status, + 'source' => $event->source, + ]), + ]; + + // لایه ۲: اطلاعات مالک (فقط اگه کاربر لاگین کرده و مالک مرسوله باشه) + if ($isOwner || $isAdmin) { + $publicData['shipment']['sender_name'] = $shipment->sender_name; + $publicData['shipment']['receiver_name'] = $shipment->receiver_name; + $publicData['shipment']['sender_phone'] = $shipment->sender_phone; + $publicData['shipment']['receiver_phone'] = $shipment->receiver_phone; + $publicData['shipment']['sender_address'] = $shipment->sender_address; + $publicData['shipment']['receiver_address'] = $shipment->receiver_address; + } else { + // برای کاربر ناشناس، فقط نام مخفف‌شده رو نشون بده + $publicData['shipment']['sender_name'] = $this->maskName($shipment->sender_name); + $publicData['shipment']['receiver_name'] = $this->maskName($shipment->receiver_name); + $publicData['shipment']['sender_phone'] = null; + $publicData['shipment']['receiver_phone'] = null; + $publicData['shipment']['sender_address'] = null; + $publicData['shipment']['receiver_address'] = null; + $publicData['shipment']['is_anonymous'] = true; + } + return response()->json([ 'success' => true, - 'data' => [ - 'shipment' => [ - 'awb_no' => $shipment->awb_no, - 'direction' => $shipment->direction?->value, - 'type' => $shipment->type?->value, - 'status' => $shipment->status?->value, - 'status_label' => $shipment->status?->label(), - 'from_country' => $shipment->fromCountry?->name, - 'to_country' => $shipment->toCountry?->name, - 'sender_name' => $shipment->sender_name, - 'receiver_name' => $shipment->receiver_name, - 'weight' => $shipment->weight, - 'created_at' => $shipment->created_at?->toIso8601String(), - ], - 'carrier_mappings' => $shipment->carrierMappings->map(fn ($mapping) => [ - 'carrier_code' => $mapping->carrier_code, - 'carrier_tracking_number' => $mapping->carrier_tracking_number, - 'booking_number' => $mapping->booking_number, - 'delivery_status' => $mapping->delivery_status, - 'notes' => $mapping->notes, - ]), - 'tracking_events' => $shipment->trackingEvents->map(fn ($event) => [ - 'date' => $event->event_date?->toDateString(), - 'time' => $event->event_time ? ($event->event_time instanceof \Carbon\Carbon ? $event->event_time->format('H:i') : $event->event_time) : null, - 'location' => $event->location, - 'description' => $event->event_description, - 'status' => $event->delivery_status, - 'source' => $event->source, - ]), - ], + 'data' => $publicData, ]); } -} + + /** + * مخفی‌سازی بخشی از نام برای کاربران ناشناس + * مثال: "Akbar Salmanizadeh" → "A. Sal***zadeh" + */ + private function maskName(?string $name): ?string + { + if (!$name) { + return null; + } + + $parts = explode(' ', $name); + $first = substr($parts[0], 0, 1) . '.'; + + if (count($parts) > 1) { + $last = $parts[1]; + if (strlen($last) > 4) { + $last = substr($last, 0, 3) . '***' . substr($last, -3); + } else { + $last = substr($last, 0, 1) . '***'; + } + return $first . ' ' . $last; + } + + return $first; + } +} \ No newline at end of file diff --git a/04_Laravel/app/Models/Shipment.php b/04_Laravel/app/Models/Shipment.php index 50e635e..3b686b5 100644 --- a/04_Laravel/app/Models/Shipment.php +++ b/04_Laravel/app/Models/Shipment.php @@ -57,6 +57,9 @@ class Shipment extends Model 'sender_city', 'sender_zip', 'sender_id_number', + 'sender_state', + 'sender_postal_code', + 'sender_national_id', // Receiver 'receiver_name', 'receiver_company', @@ -66,6 +69,9 @@ class Shipment extends Model 'receiver_city', 'receiver_zip', 'receiver_id_number', + 'receiver_state', + 'receiver_postal_code', + 'receiver_national_id', // Customs 'reason_for_export', 'content_description', diff --git a/04_Laravel/app/Services/PriceCalculatorService.php b/04_Laravel/app/Services/PriceCalculatorService.php index 4e9a3a4..d4246ae 100644 --- a/04_Laravel/app/Services/PriceCalculatorService.php +++ b/04_Laravel/app/Services/PriceCalculatorService.php @@ -263,17 +263,31 @@ class PriceCalculatorService ): float { $zoneColumn = 'zone_' . $zone; + // نرخی را پیدا کن که وزنش بزرگتر مساوی وزن واردشده باشد + // مثلاً اگه وزن 0.3 است، نرخ 0.5 را برمی‌گرداند $rate = \App\Models\ShippingRate::query() ->where('direction', $direction) ->where('type', $type->value) - ->where('weight', '<=', $weight) + ->where('weight', '>=', $weight) ->where($zoneColumn, '>', 0) - ->orderByDesc('weight') + ->orderBy('weight') // صعودی - اولین نرخ بزرگتر ->value($zoneColumn); + // اگه نرخی با وزن بزرگتر پیدا نشد، + // آخرین نرخ موجود (بزرگترین وزن) رو بگیر + // این برای مواقعی هست که وزن واردشده از بزرگترین وزن در جدول بیشتره + if ($rate === null) { + $rate = \App\Models\ShippingRate::query() + ->where('direction', $direction) + ->where('type', $type->value) + ->where($zoneColumn, '>', 0) + ->orderByDesc('weight') // نزولی - بزرگترین وزن + ->value($zoneColumn); + } + if ($rate === null) { throw new InvalidArgumentException( - 'No rate found for the given parameters.' + 'برای این مسیر و وزن، نرخی تعریف نشده است. لطفاً با پشتیبانی تماس بگیرید.' ); } diff --git a/04_Laravel/database/migrations/2026_10_27_033451_add_postal_and_national_id_to_shipments_table.php b/04_Laravel/database/migrations/2026_10_27_033451_add_postal_and_national_id_to_shipments_table.php new file mode 100644 index 0000000..53b39e8 --- /dev/null +++ b/04_Laravel/database/migrations/2026_10_27_033451_add_postal_and_national_id_to_shipments_table.php @@ -0,0 +1,39 @@ +string('sender_postal_code', 20)->nullable()->after('sender_state'); + $table->string('sender_national_id', 50)->nullable()->after('sender_postal_code'); + + // گیرنده + $table->string('receiver_postal_code', 20)->nullable()->after('receiver_state'); + $table->string('receiver_national_id', 50)->nullable()->after('receiver_postal_code'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('shipments', function (Blueprint $table) { + $table->dropColumn([ + 'sender_postal_code', + 'sender_national_id', + 'receiver_postal_code', + 'receiver_national_id', + ]); + }); + } +}; \ No newline at end of file