feat(api): expand shipment data fields and enhance tracking security
Implement additional address and identification fields for both sender and receiver across the WordPress plugin and Laravel API. This includes state, postal code, and national ID/passport information. Key changes: - Added database migration to include postal and national ID fields in the shipments table. - Updated WordPress order form shortcodes and JavaScript to capture new address fields. - Refactored Laravel `CustomerOrderController` validation and storage to support expanded shipment data. - Implemented tiered data visibility in `TrackController` to protect PII, providing masked data to anonymous users and full data to owners/admins. - Fixed weight-based rate lookup logic in `PriceCalculatorService` to correctly select the appropriate shipping tier.
This commit is contained in:
parent
35fde95f5e
commit
75fd65be2b
@ -940,6 +940,12 @@ jQuery(document).ready(function($) {
|
|||||||
receiver_phone: $('#ifnex-receiver-phone').val(),
|
receiver_phone: $('#ifnex-receiver-phone').val(),
|
||||||
receiver_city: $('#ifnex-receiver-city').val(),
|
receiver_city: $('#ifnex-receiver-city').val(),
|
||||||
receiver_address: $('#ifnex-receiver-address').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(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -749,14 +749,20 @@ function ifnex_order_form_shortcode($atts) {
|
|||||||
<div class="ifnex-form-grid">
|
<div class="ifnex-form-grid">
|
||||||
<div class="ifnex-field"><label for="ifnex-sender-name">نام و نام خانوادگی *</label><input type="text" id="ifnex-sender-name" name="ifnex_sender_name"></div>
|
<div class="ifnex-field"><label for="ifnex-sender-name">نام و نام خانوادگی *</label><input type="text" id="ifnex-sender-name" name="ifnex_sender_name"></div>
|
||||||
<div class="ifnex-field"><label for="ifnex-sender-phone">شماره تماس *</label><input type="tel" id="ifnex-sender-phone" name="ifnex_sender_phone"></div>
|
<div class="ifnex-field"><label for="ifnex-sender-phone">شماره تماس *</label><input type="tel" id="ifnex-sender-phone" name="ifnex_sender_phone"></div>
|
||||||
<div class="ifnex-field"><label for="ifnex-sender-city">شهر</label><input type="text" id="ifnex-sender-city" name="ifnex_sender_city"></div>
|
<div class="ifnex-field"><label for="ifnex-sender-state">استان / ایالت *</label><input type="text" id="ifnex-sender-state" name="ifnex_sender_state"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-sender-city">شهر *</label><input type="text" id="ifnex-sender-city" name="ifnex_sender_city"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-sender-postal-code">کد پستی *</label><input type="text" id="ifnex-sender-postal-code" name="ifnex_sender_postal_code" pattern="[0-9]{5,15}" maxlength="15"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-sender-national-id">کد ملی / شماره گذرنامه *</label><input type="text" id="ifnex-sender-national-id" name="ifnex_sender_national_id"></div>
|
||||||
<div class="ifnex-field full"><label for="ifnex-sender-address">آدرس کامل *</label><textarea id="ifnex-sender-address" name="ifnex_sender_address" rows="2"></textarea></div>
|
<div class="ifnex-field full"><label for="ifnex-sender-address">آدرس کامل *</label><textarea id="ifnex-sender-address" name="ifnex_sender_address" rows="2"></textarea></div>
|
||||||
</div>
|
</div>
|
||||||
<h3 style="margin-top:24px;">📮 اطلاعات گیرنده</h3>
|
<h3 style="margin-top:24px;">📮 اطلاعات گیرنده</h3>
|
||||||
<div class="ifnex-form-grid">
|
<div class="ifnex-form-grid">
|
||||||
<div class="ifnex-field"><label for="ifnex-receiver-name">نام و نام خانوادگی *</label><input type="text" id="ifnex-receiver-name" name="ifnex_receiver_name"></div>
|
<div class="ifnex-field"><label for="ifnex-receiver-name">نام و نام خانوادگی *</label><input type="text" id="ifnex-receiver-name" name="ifnex_receiver_name"></div>
|
||||||
<div class="ifnex-field"><label for="ifnex-receiver-phone">شماره تماس *</label><input type="tel" id="ifnex-receiver-phone" name="ifnex_receiver_phone"></div>
|
<div class="ifnex-field"><label for="ifnex-receiver-phone">شماره تماس *</label><input type="tel" id="ifnex-receiver-phone" name="ifnex_receiver_phone"></div>
|
||||||
<div class="ifnex-field"><label for="ifnex-receiver-city">شهر</label><input type="text" id="ifnex-receiver-city" name="ifnex_receiver_city"></div>
|
<div class="ifnex-field"><label for="ifnex-receiver-state">استان / ایالت *</label><input type="text" id="ifnex-receiver-state" name="ifnex_receiver_state"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-receiver-city">شهر *</label><input type="text" id="ifnex-receiver-city" name="ifnex_receiver_city"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-receiver-postal-code">کد پستی *</label><input type="text" id="ifnex-receiver-postal-code" name="ifnex_receiver_postal_code" pattern="[0-9]{5,15}" maxlength="15"></div>
|
||||||
|
<div class="ifnex-field"><label for="ifnex-receiver-national-id">کد ملی / شماره گذرنامه *</label><input type="text" id="ifnex-receiver-national-id" name="ifnex_receiver_national_id"></div>
|
||||||
<div class="ifnex-field full"><label for="ifnex-receiver-address">آدرس کامل *</label><textarea id="ifnex-receiver-address" name="ifnex_receiver_address" rows="2"></textarea></div>
|
<div class="ifnex-field full"><label for="ifnex-receiver-address">آدرس کامل *</label><textarea id="ifnex-receiver-address" name="ifnex_receiver_address" rows="2"></textarea></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ifnex-form-nav">
|
<div class="ifnex-form-nav">
|
||||||
@ -1287,12 +1293,18 @@ function ifnex_order_detail_shortcode($atts) {
|
|||||||
<p><?php echo esc_html($data['sender']['name'] ?? '—'); ?></p>
|
<p><?php echo esc_html($data['sender']['name'] ?? '—'); ?></p>
|
||||||
<p>📞 <?php echo esc_html($data['sender']['phone'] ?? '—'); ?></p>
|
<p>📞 <?php echo esc_html($data['sender']['phone'] ?? '—'); ?></p>
|
||||||
<p>📍 <?php echo esc_html($data['sender']['address'] ?? '—'); ?></p>
|
<p>📍 <?php echo esc_html($data['sender']['address'] ?? '—'); ?></p>
|
||||||
|
<p>🏙️ <?php echo esc_html(($data['sender']['state'] ?? '') . ' / ' . ($data['sender']['city'] ?? '')); ?></p>
|
||||||
|
<p>📮 <?php echo esc_html($data['sender']['postal_code'] ?? '—'); ?></p>
|
||||||
|
<p>🪪 <?php echo esc_html($data['sender']['national_id'] ?? '—'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ifnex-od-person">
|
<div class="ifnex-od-person">
|
||||||
<h4>📮 گیرنده</h4>
|
<h4>📮 گیرنده</h4>
|
||||||
<p><?php echo esc_html($data['receiver']['name'] ?? '—'); ?></p>
|
<p><?php echo esc_html($data['receiver']['name'] ?? '—'); ?></p>
|
||||||
<p>📞 <?php echo esc_html($data['receiver']['phone'] ?? '—'); ?></p>
|
<p>📞 <?php echo esc_html($data['receiver']['phone'] ?? '—'); ?></p>
|
||||||
<p>📍 <?php echo esc_html($data['receiver']['address'] ?? '—'); ?></p>
|
<p>📍 <?php echo esc_html($data['receiver']['address'] ?? '—'); ?></p>
|
||||||
|
<p>🏙️ <?php echo esc_html(($data['receiver']['state'] ?? '') . ' / ' . ($data['receiver']['city'] ?? '')); ?></p>
|
||||||
|
<p>📮 <?php echo esc_html($data['receiver']['postal_code'] ?? '—'); ?></p>
|
||||||
|
<p>🪪 <?php echo esc_html($data['receiver']['national_id'] ?? '—'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -95,20 +95,22 @@ class CustomerOrderController extends Controller
|
|||||||
'sender_phone' => ['required', 'string', 'max:50'],
|
'sender_phone' => ['required', 'string', 'max:50'],
|
||||||
'sender_email' => ['nullable', 'email', 'max:255'],
|
'sender_email' => ['nullable', 'email', 'max:255'],
|
||||||
'sender_address' => ['required', 'string', 'max:1000'],
|
'sender_address' => ['required', 'string', 'max:1000'],
|
||||||
|
'sender_state' => ['nullable', 'string', 'max:255'],
|
||||||
'sender_city' => ['nullable', 'string', 'max:255'],
|
'sender_city' => ['nullable', 'string', 'max:255'],
|
||||||
'sender_zip' => ['nullable', 'string', 'max:20'],
|
'sender_postal_code' => ['nullable', 'string', 'max:20'],
|
||||||
'sender_id_number' => ['nullable', 'string', 'max:50'],
|
'sender_national_id' => ['nullable', 'string', 'max:50'],
|
||||||
|
|
||||||
// اطلاعات گیرنده
|
// اطلاعات گیرنده
|
||||||
'receiver_name' => ['required', 'string', 'max:255'],
|
'receiver_name' => ['required', 'string', 'max:255'],
|
||||||
'receiver_company' => ['nullable', 'string', 'max:255'],
|
'receiver_company' => ['nullable', 'string', 'max:255'],
|
||||||
'receiver_phone' => ['required', 'string', 'max:50'],
|
'receiver_phone' => ['required', 'string', 'max:50'],
|
||||||
'receiver_email' => ['nullable', 'email', 'max:255'],
|
'receiver_email' => ['nullable', 'email', 'max:255'],
|
||||||
'receiver_address' => ['required', 'string', 'max:1000'],
|
'receiver_address' => ['required', 'string', 'max:1000'],
|
||||||
|
'receiver_state' => ['nullable', 'string', 'max:255'],
|
||||||
'receiver_city' => ['nullable', 'string', 'max:255'],
|
'receiver_city' => ['nullable', 'string', 'max:255'],
|
||||||
'receiver_zip' => ['nullable', 'string', 'max:20'],
|
'receiver_postal_code' => ['nullable', 'string', 'max:20'],
|
||||||
'receiver_id_number' => ['nullable', 'string', 'max:50'],
|
'receiver_national_id' => ['nullable', 'string', 'max:50'],
|
||||||
|
|
||||||
// خدمات اضافی
|
// خدمات اضافی
|
||||||
'extra_service' => ['nullable', 'numeric', 'min:0'],
|
'extra_service' => ['nullable', 'numeric', 'min:0'],
|
||||||
'packing_cost' => ['nullable', 'numeric', 'min:0'],
|
'packing_cost' => ['nullable', 'numeric', 'min:0'],
|
||||||
@ -245,19 +247,21 @@ class CustomerOrderController extends Controller
|
|||||||
'sender_phone' => $validated['sender_phone'],
|
'sender_phone' => $validated['sender_phone'],
|
||||||
'sender_email' => $validated['sender_email'] ?? null,
|
'sender_email' => $validated['sender_email'] ?? null,
|
||||||
'sender_address' => $validated['sender_address'],
|
'sender_address' => $validated['sender_address'],
|
||||||
|
'sender_state' => $validated['sender_state'] ?? null,
|
||||||
'sender_city' => $validated['sender_city'] ?? null,
|
'sender_city' => $validated['sender_city'] ?? null,
|
||||||
'sender_zip' => $validated['sender_zip'] ?? null,
|
'sender_postal_code' => $validated['sender_postal_code'] ?? null,
|
||||||
'sender_id_number' => $validated['sender_id_number'] ?? null,
|
'sender_national_id' => $validated['sender_national_id'] ?? null,
|
||||||
|
|
||||||
// گیرنده
|
// گیرنده
|
||||||
'receiver_name' => $validated['receiver_name'],
|
'receiver_name' => $validated['receiver_name'],
|
||||||
'receiver_company' => $validated['receiver_company'] ?? null,
|
'receiver_company' => $validated['receiver_company'] ?? null,
|
||||||
'receiver_phone' => $validated['receiver_phone'],
|
'receiver_phone' => $validated['receiver_phone'],
|
||||||
'receiver_email' => $validated['receiver_email'] ?? null,
|
'receiver_email' => $validated['receiver_email'] ?? null,
|
||||||
'receiver_address' => $validated['receiver_address'],
|
'receiver_address' => $validated['receiver_address'],
|
||||||
|
'receiver_state' => $validated['receiver_state'] ?? null,
|
||||||
'receiver_city' => $validated['receiver_city'] ?? null,
|
'receiver_city' => $validated['receiver_city'] ?? null,
|
||||||
'receiver_zip' => $validated['receiver_zip'] ?? null,
|
'receiver_postal_code' => $validated['receiver_postal_code'] ?? null,
|
||||||
'receiver_id_number' => $validated['receiver_id_number'] ?? null,
|
'receiver_national_id' => $validated['receiver_national_id'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ذخیره بستهها (Multi-Package)
|
// ذخیره بستهها (Multi-Package)
|
||||||
@ -406,9 +410,10 @@ class CustomerOrderController extends Controller
|
|||||||
'sender_phone' => ['required', 'string', 'max:50'],
|
'sender_phone' => ['required', 'string', 'max:50'],
|
||||||
'sender_email' => ['nullable', 'email', 'max:255'],
|
'sender_email' => ['nullable', 'email', 'max:255'],
|
||||||
'sender_address' => ['required', 'string', 'max:1000'],
|
'sender_address' => ['required', 'string', 'max:1000'],
|
||||||
|
'sender_state' => ['nullable', 'string', 'max:255'],
|
||||||
'sender_city' => ['nullable', 'string', 'max:255'],
|
'sender_city' => ['nullable', 'string', 'max:255'],
|
||||||
'sender_zip' => ['nullable', 'string', 'max:20'],
|
'sender_postal_code' => ['nullable', 'string', 'max:20'],
|
||||||
'sender_id_number' => ['nullable', 'string', 'max:50'],
|
'sender_national_id' => ['nullable', 'string', 'max:50'],
|
||||||
|
|
||||||
// گیرنده
|
// گیرنده
|
||||||
'receiver_name' => ['required', 'string', 'max:255'],
|
'receiver_name' => ['required', 'string', 'max:255'],
|
||||||
@ -416,9 +421,10 @@ class CustomerOrderController extends Controller
|
|||||||
'receiver_phone' => ['required', 'string', 'max:50'],
|
'receiver_phone' => ['required', 'string', 'max:50'],
|
||||||
'receiver_email' => ['nullable', 'email', 'max:255'],
|
'receiver_email' => ['nullable', 'email', 'max:255'],
|
||||||
'receiver_address' => ['required', 'string', 'max:1000'],
|
'receiver_address' => ['required', 'string', 'max:1000'],
|
||||||
|
'receiver_state' => ['nullable', 'string', 'max:255'],
|
||||||
'receiver_city' => ['nullable', 'string', 'max:255'],
|
'receiver_city' => ['nullable', 'string', 'max:255'],
|
||||||
'receiver_zip' => ['nullable', 'string', 'max:20'],
|
'receiver_postal_code' => ['nullable', 'string', 'max:20'],
|
||||||
'receiver_id_number' => ['nullable', 'string', 'max:50'],
|
'receiver_national_id' => ['nullable', 'string', 'max:50'],
|
||||||
|
|
||||||
// خدمات
|
// خدمات
|
||||||
'extra_service' => ['nullable', 'numeric', 'min:0'],
|
'extra_service' => ['nullable', 'numeric', 'min:0'],
|
||||||
@ -563,18 +569,20 @@ class CustomerOrderController extends Controller
|
|||||||
'sender_phone' => $validated['sender_phone'],
|
'sender_phone' => $validated['sender_phone'],
|
||||||
'sender_email' => $validated['sender_email'] ?? null,
|
'sender_email' => $validated['sender_email'] ?? null,
|
||||||
'sender_address' => $validated['sender_address'],
|
'sender_address' => $validated['sender_address'],
|
||||||
|
'sender_state' => $validated['sender_state'] ?? null,
|
||||||
'sender_city' => $validated['sender_city'] ?? null,
|
'sender_city' => $validated['sender_city'] ?? null,
|
||||||
'sender_zip' => $validated['sender_zip'] ?? null,
|
'sender_postal_code' => $validated['sender_postal_code'] ?? null,
|
||||||
'sender_id_number' => $validated['sender_id_number'] ?? null,
|
'sender_national_id' => $validated['sender_national_id'] ?? null,
|
||||||
|
|
||||||
'receiver_name' => $validated['receiver_name'],
|
'receiver_name' => $validated['receiver_name'],
|
||||||
'receiver_company' => $validated['receiver_company'] ?? null,
|
'receiver_company' => $validated['receiver_company'] ?? null,
|
||||||
'receiver_phone' => $validated['receiver_phone'],
|
'receiver_phone' => $validated['receiver_phone'],
|
||||||
'receiver_email' => $validated['receiver_email'] ?? null,
|
'receiver_email' => $validated['receiver_email'] ?? null,
|
||||||
'receiver_address' => $validated['receiver_address'],
|
'receiver_address' => $validated['receiver_address'],
|
||||||
|
'receiver_state' => $validated['receiver_state'] ?? null,
|
||||||
'receiver_city' => $validated['receiver_city'] ?? null,
|
'receiver_city' => $validated['receiver_city'] ?? null,
|
||||||
'receiver_zip' => $validated['receiver_zip'] ?? null,
|
'receiver_postal_code' => $validated['receiver_postal_code'] ?? null,
|
||||||
'receiver_id_number' => $validated['receiver_id_number'] ?? null,
|
'receiver_national_id' => $validated['receiver_national_id'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$shipment->packages()->delete();
|
$shipment->packages()->delete();
|
||||||
@ -720,12 +728,6 @@ class CustomerOrderController extends Controller
|
|||||||
$account_balance = $wallet_balance - $pending_payments_total;
|
$account_balance = $wallet_balance - $pending_payments_total;
|
||||||
|
|
||||||
// بدهیهای چندارزی (EUR/USD/AED/CNY) — به همان ارز خارجی، نه معادل ریالی
|
// بدهیهای چندارزی (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)
|
$foreign_credits = app(\App\Services\CustomerCreditService::class)
|
||||||
->getCustomerDebtsByCurrency($user);
|
->getCustomerDebtsByCurrency($user);
|
||||||
|
|
||||||
@ -777,22 +779,22 @@ class CustomerOrderController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* لیست کشورها برای فرم
|
* لیست کشورها برای فرم
|
||||||
* GET /api/v1/customer/countries
|
* GET /api/v1/customer/countries
|
||||||
*/
|
*/
|
||||||
public function countries(): JsonResponse
|
public function countries(): JsonResponse
|
||||||
{
|
{
|
||||||
$countries = Country::select('id', 'name', 'iso_code', 'export_zone_parcel', 'import_zone_parcel')
|
$countries = Country::select('id', 'name', 'iso_code', 'export_zone_parcel', 'import_zone_parcel')
|
||||||
->where('is_active', true)
|
->where('is_active', true)
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => $countries,
|
'data' => $countries,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* دریافت نوتیفیکیشنهای کاربر
|
* دریافت نوتیفیکیشنهای کاربر
|
||||||
@ -938,7 +940,10 @@ class CustomerOrderController extends Controller
|
|||||||
'phone' => $shipment->sender_phone,
|
'phone' => $shipment->sender_phone,
|
||||||
'email' => $shipment->sender_email,
|
'email' => $shipment->sender_email,
|
||||||
'address' => $shipment->sender_address,
|
'address' => $shipment->sender_address,
|
||||||
|
'state' => $shipment->sender_state,
|
||||||
'city' => $shipment->sender_city,
|
'city' => $shipment->sender_city,
|
||||||
|
'postal_code' => $shipment->sender_postal_code,
|
||||||
|
'national_id' => $shipment->sender_national_id,
|
||||||
];
|
];
|
||||||
|
|
||||||
$data['receiver'] = [
|
$data['receiver'] = [
|
||||||
@ -947,7 +952,10 @@ class CustomerOrderController extends Controller
|
|||||||
'phone' => $shipment->receiver_phone,
|
'phone' => $shipment->receiver_phone,
|
||||||
'email' => $shipment->receiver_email,
|
'email' => $shipment->receiver_email,
|
||||||
'address' => $shipment->receiver_address,
|
'address' => $shipment->receiver_address,
|
||||||
|
'state' => $shipment->receiver_state,
|
||||||
'city' => $shipment->receiver_city,
|
'city' => $shipment->receiver_city,
|
||||||
|
'postal_code' => $shipment->receiver_postal_code,
|
||||||
|
'national_id' => $shipment->receiver_national_id,
|
||||||
];
|
];
|
||||||
|
|
||||||
$data['packages'] = $shipment->packages->map(fn($package) => [
|
$data['packages'] = $shipment->packages->map(fn($package) => [
|
||||||
@ -1120,4 +1128,4 @@ class CustomerOrderController extends Controller
|
|||||||
|
|
||||||
return response()->json($result, 400);
|
return response()->json($result, 400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Shipment;
|
use App\Models\Shipment;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API ترکینگ مرسولات — فاز ۰
|
* API ترکینگ مرسولات — فاز ۰
|
||||||
@ -12,13 +13,18 @@ use Illuminate\Http\JsonResponse;
|
|||||||
* GET /api/v1/track/{awb_no}
|
* GET /api/v1/track/{awb_no}
|
||||||
*
|
*
|
||||||
* خروجی: تایملاین کامل رویدادهای ترکینگ + اطلاعات پایه مرسوله + نگاشتهای شرکت حمل
|
* خروجی: تایملاین کامل رویدادهای ترکینگ + اطلاعات پایه مرسوله + نگاشتهای شرکت حمل
|
||||||
|
*
|
||||||
|
* امنیت: نمایش لایهبندیشده (Tiered Display)
|
||||||
|
* - کاربر ناشناس: فقط وضعیت فعلی و موقعیت
|
||||||
|
* - کاربر لاگینشده (مالک): اطلاعات کامل فرستنده/گیرنده (مخففشده)
|
||||||
|
* - ادمین: اطلاعات کامل
|
||||||
*/
|
*/
|
||||||
class TrackController extends Controller
|
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)
|
$shipment = Shipment::where('awb_no', $awb_no)
|
||||||
->with([
|
->with([
|
||||||
@ -37,38 +43,89 @@ class TrackController extends Controller
|
|||||||
], 404);
|
], 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([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'data' => [
|
'data' => $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,
|
|
||||||
'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,
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* مخفیسازی بخشی از نام برای کاربران ناشناس
|
||||||
|
* مثال: "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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,6 +57,9 @@ class Shipment extends Model
|
|||||||
'sender_city',
|
'sender_city',
|
||||||
'sender_zip',
|
'sender_zip',
|
||||||
'sender_id_number',
|
'sender_id_number',
|
||||||
|
'sender_state',
|
||||||
|
'sender_postal_code',
|
||||||
|
'sender_national_id',
|
||||||
// Receiver
|
// Receiver
|
||||||
'receiver_name',
|
'receiver_name',
|
||||||
'receiver_company',
|
'receiver_company',
|
||||||
@ -66,6 +69,9 @@ class Shipment extends Model
|
|||||||
'receiver_city',
|
'receiver_city',
|
||||||
'receiver_zip',
|
'receiver_zip',
|
||||||
'receiver_id_number',
|
'receiver_id_number',
|
||||||
|
'receiver_state',
|
||||||
|
'receiver_postal_code',
|
||||||
|
'receiver_national_id',
|
||||||
// Customs
|
// Customs
|
||||||
'reason_for_export',
|
'reason_for_export',
|
||||||
'content_description',
|
'content_description',
|
||||||
|
|||||||
@ -263,17 +263,31 @@ class PriceCalculatorService
|
|||||||
): float {
|
): float {
|
||||||
$zoneColumn = 'zone_' . $zone;
|
$zoneColumn = 'zone_' . $zone;
|
||||||
|
|
||||||
|
// نرخی را پیدا کن که وزنش بزرگتر مساوی وزن واردشده باشد
|
||||||
|
// مثلاً اگه وزن 0.3 است، نرخ 0.5 را برمیگرداند
|
||||||
$rate = \App\Models\ShippingRate::query()
|
$rate = \App\Models\ShippingRate::query()
|
||||||
->where('direction', $direction)
|
->where('direction', $direction)
|
||||||
->where('type', $type->value)
|
->where('type', $type->value)
|
||||||
->where('weight', '<=', $weight)
|
->where('weight', '>=', $weight)
|
||||||
->where($zoneColumn, '>', 0)
|
->where($zoneColumn, '>', 0)
|
||||||
->orderByDesc('weight')
|
->orderBy('weight') // صعودی - اولین نرخ بزرگتر
|
||||||
->value($zoneColumn);
|
->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) {
|
if ($rate === null) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
'No rate found for the given parameters.'
|
'برای این مسیر و وزن، نرخی تعریف نشده است. لطفاً با پشتیبانی تماس بگیرید.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('shipments', function (Blueprint $table) {
|
||||||
|
// فرستنده
|
||||||
|
$table->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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user