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) {
📮 اطلاعات گیرنده
📮 گیرنده
📞
📍
+
🏙️
+
📮
+
🪪
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