validate([ 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], 'status' => ['nullable', 'string'], 'page' => ['nullable', 'integer', 'min:1'], ]); $user = Auth::user(); $query = Shipment::query() ->where('user_id', $user->id) ->with(['fromCountry', 'toCountry', 'items']) ->orderBy('created_at', 'desc'); if (!empty($validated['status'])) { $query->where('status', $validated['status']); } $shipments = $query->paginate($validated['per_page'] ?? 15); return response()->json([ 'success' => true, 'data' => $shipments->map(function ($shipment) { return $this->formatShipment($shipment); }), 'pagination' => [ 'current_page' => $shipments->currentPage(), 'last_page' => $shipments->lastPage(), 'per_page' => $shipments->perPage(), 'total' => $shipments->total(), ], ]); } /** * ثبت سفارش جدید * POST /api/v1/customer/orders */ public function store(Request $request): JsonResponse { try { $validated = $request->validate([ // مسیر و نوع 'direction' => ['required', 'in:export,import'], 'type' => ['required', 'in:DOC_NORMAL,DOC_ECONOMY,PARCEL'], 'from_country_id' => ['required', 'exists:countries,id'], 'to_country_id' => ['required', 'exists:countries,id'], // وزن و ابعاد 'weight' => ['required', 'numeric', 'min:0.1'], 'volumetric_weight' => ['nullable', 'numeric', 'min:0'], 'dimensions' => ['nullable', 'string', 'max:100'], // اطلاعات فرستنده 'sender_name' => ['required', 'string', 'max:255'], 'sender_company' => ['nullable', 'string', 'max:255'], 'sender_phone' => ['required', 'string', 'max:50'], 'sender_email' => ['nullable', 'email', 'max:255'], 'sender_address' => ['required', 'string', 'max:1000'], 'sender_city' => ['nullable', 'string', 'max:255'], 'sender_zip' => ['nullable', 'string', 'max:20'], 'sender_id_number' => ['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_city' => ['nullable', 'string', 'max:255'], 'receiver_zip' => ['nullable', 'string', 'max:20'], 'receiver_id_number' => ['nullable', 'string', 'max:50'], // خدمات اضافی 'extra_service' => ['nullable', 'numeric', 'min:0'], 'packing_cost' => ['nullable', 'numeric', 'min:0'], 'domestic_pickup' => ['nullable', 'numeric', 'min:0'], 'domestic_delivery' => ['nullable', 'numeric', 'min:0'], 'warehousing_cost' => ['nullable', 'numeric', 'min:0'], // تخفیف 'discount_code' => ['nullable', 'string', 'max:50'], // اقلام گمرکی 'items' => ['nullable', 'array', 'max:9'], 'items.*.description' => ['required_with:items', 'string', 'max:500'], 'items.*.hs_code' => ['required_with:items', 'string', 'max:20'], 'items.*.quantity' => ['required_with:items', 'integer', 'min:1'], 'items.*.unit_price' => ['required_with:items', 'numeric', 'min:0'], ]); } catch (ValidationException $e) { return response()->json([ 'success' => false, 'message' => 'خطا در اعتبارسنجی اطلاعات', 'errors' => $e->errors(), ], 422); } $user = Auth::user(); // بررسی اینکه کاربر کیف پول فعال دارد if (!$user->wallet || $user->wallet->is_frozen) { return response()->json([ 'success' => false, 'message' => 'کیف پول شما غیرفعال است. لطفاً با پشتیبانی تماس بگیرید.', ], 403); } // دریافت کشور مقصد برای محاسبه قیمت $destinationCountry = Country::findOrFail( $validated['direction'] === 'export' ? $validated['to_country_id'] : $validated['from_country_id'] ); // محاسبه قیمت try { $pricingData = [ 'direction' => $validated['direction'] === 'export' ? 'Outbound' : 'Inbound', 'type' => $validated['type'], 'country_iso' => $destinationCountry->iso_code, 'weight' => (float) $validated['weight'], 'volumetric_weight' => (float) ($validated['volumetric_weight'] ?? 0), 'extra_service' => (float) ($validated['extra_service'] ?? 0), 'packing_cost' => (float) ($validated['packing_cost'] ?? 0), 'domestic_pickup' => (float) ($validated['domestic_pickup'] ?? 0), 'domestic_delivery' => (float) ($validated['domestic_delivery'] ?? 0), 'warehousing_cost' => (float) ($validated['warehousing_cost'] ?? 0), 'discount_code' => $validated['discount_code'] ?? null, ]; $priceResult = $this->calculator->calculate($pricingData); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'خطا در محاسبه قیمت: ' . $e->getMessage(), ], 400); } // ساخت شماره AWB (فرمت: IFN-YYYY-XXXXX) $awbNo = $this->generateAwbNumber(); // ذخیره سفارش try { $shipment = DB::transaction(function () use ($validated, $user, $priceResult, $awbNo) { $chargeableWeight = max( (float) $validated['weight'], (float) ($validated['volumetric_weight'] ?? 0) ); $shipment = Shipment::create([ 'user_id' => $user->id, 'awb_no' => $awbNo, 'direction' => $validated['direction'], 'type' => $validated['type'], 'status' => ShipmentStatus::PendingPayment, // وزن 'weight' => $validated['weight'], 'volumetric_weight' => $validated['volumetric_weight'] ?? null, 'chargeable_weight' => $chargeableWeight, 'dimensions' => $validated['dimensions'] ?? null, // کشورها 'from_country_id' => $validated['from_country_id'], 'to_country_id' => $validated['to_country_id'], // اطلاعات مالی (از محاسبه) 'shipping_price' => $priceResult['net_dirham'], 'extra_service' => $priceResult['extra_service'], 'packing_cost' => $priceResult['packing_cost'], 'domestic_pickup' => $priceResult['domestic_pickup'], 'domestic_delivery' => $priceResult['domestic_delivery'], 'warehousing_cost' => $priceResult['warehousing_cost'], 'vat_amount' => $priceResult['vat_amount'], 'discount' => $priceResult['discount_amount'], 'total_fee' => $priceResult['total_fee'], 'net_dirham' => $priceResult['net_dirham'], 'net_rial' => $priceResult['net_rial'], // فرستنده 'sender_name' => $validated['sender_name'], 'sender_company' => $validated['sender_company'] ?? null, 'sender_phone' => $validated['sender_phone'], 'sender_email' => $validated['sender_email'] ?? null, 'sender_address' => $validated['sender_address'], 'sender_city' => $validated['sender_city'] ?? null, 'sender_zip' => $validated['sender_zip'] ?? null, 'sender_id_number' => $validated['sender_id_number'] ?? 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_city' => $validated['receiver_city'] ?? null, 'receiver_zip' => $validated['receiver_zip'] ?? null, 'receiver_id_number' => $validated['receiver_id_number'] ?? null, ]); // ذخیره اقلام گمرکی if (!empty($validated['items'])) { foreach ($validated['items'] as $index => $item) { ShipmentItem::create([ 'shipment_id' => $shipment->id, 'row_number' => $index + 1, 'description' => $item['description'], 'hs_code' => $item['hs_code'], 'quantity' => $item['quantity'], 'unit_price' => $item['unit_price'], 'total_usd' => $item['quantity'] * $item['unit_price'], ]); } } return $shipment; }); return response()->json([ 'success' => true, 'message' => 'سفارش شما با موفقیت ثبت شد. لطفاً برای تکمیل، پرداخت را انجام دهید.', 'data' => $this->formatShipment($shipment->load(['fromCountry', 'toCountry', 'items'])), 'pricing' => $priceResult, ], 201); } catch (\Exception $e) { return response()->json([ 'success' => false, 'message' => 'خطا در ثبت سفارش: ' . $e->getMessage(), ], 500); } } /** * جزئیات یک سفارش * GET /api/v1/customer/orders/{shipment} */ public function show(Shipment $shipment): JsonResponse { $user = Auth::user(); // بررسی مالکیت if ($shipment->user_id !== $user->id) { return response()->json([ 'success' => false, 'message' => 'شما به این سفارش دسترسی ندارید.', ], 403); } $shipment->load(['fromCountry', 'toCountry', 'items', 'trackingEvents']); return response()->json([ 'success' => true, 'data' => $this->formatShipment($shipment, detailed: true), ]); } /** * لغو سفارش (فقط در وضعیت pending_payment) * POST /api/v1/customer/orders/{shipment}/cancel */ public function cancel(Shipment $shipment): JsonResponse { $user = Auth::user(); if ($shipment->user_id !== $user->id) { return response()->json([ 'success' => false, 'message' => 'شما به این سفارش دسترسی ندارید.', ], 403); } if (!$shipment->status->canBeCancelledByCustomer()) { return response()->json([ 'success' => false, 'message' => 'این سفارش در وضعیتی نیست که بتوان آن را لغو کرد.', ], 400); } $shipment->update(['status' => ShipmentStatus::Cancelled]); return response()->json([ 'success' => true, 'message' => 'سفارش با موفقیت لغو شد.', 'data' => $this->formatShipment($shipment->fresh()), ]); } /** * پروفایل و آمار کاربر * GET /api/v1/customer/profile */ public function profile(Request $request): JsonResponse { $user = Auth::user(); $user->load(['wallet']); // آمار سفارشات $stats = [ 'total_orders' => Shipment::where('user_id', $user->id)->count(), 'pending_orders' => Shipment::where('user_id', $user->id) ->where('status', ShipmentStatus::PendingPayment) ->count(), 'active_orders' => Shipment::where('user_id', $user->id) ->whereIn('status', [ ShipmentStatus::Processed, ShipmentStatus::PickedUp, ShipmentStatus::InTransit, ShipmentStatus::OutForDelivery, ]) ->count(), 'delivered_orders' => Shipment::where('user_id', $user->id) ->where('status', ShipmentStatus::Delivered) ->count(), 'total_spent' => Shipment::where('user_id', $user->id) ->where('status', '!=', ShipmentStatus::PendingPayment) ->where('status', '!=', ShipmentStatus::Cancelled) ->sum('total_fee'), ]; return response()->json([ 'success' => true, 'user' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'member_since' => $user->created_at->format('Y-m-d'), ], 'wallet' => $user->wallet ? [ 'balance' => $user->wallet->balance, 'is_frozen' => $user->wallet->is_frozen, ] : null, 'stats' => $stats, ]); } /** * لیست کشورها برای فرم * 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, ]); } /** * فرمت کردن Shipment برای خروجی API */ private function formatShipment(Shipment $shipment, bool $detailed = false): array { $data = [ 'id' => $shipment->id, 'awb_no' => $shipment->awb_no, 'direction' => $shipment->direction?->value, 'type' => $shipment->type?->value, 'status' => [ 'value' => $shipment->status?->value, 'label' => $shipment->status?->label(), 'color' => $shipment->status?->color(), 'is_paid' => $shipment->status?->isPaid(), 'can_cancel' => $shipment->status?->canBeCancelledByCustomer(), ], 'from_country' => $shipment->fromCountry ? [ 'id' => $shipment->fromCountry->id, 'name' => $shipment->fromCountry->name, 'iso_code' => $shipment->fromCountry->iso_code, ] : null, 'to_country' => $shipment->toCountry ? [ 'id' => $shipment->toCountry->id, 'name' => $shipment->toCountry->name, 'iso_code' => $shipment->toCountry->iso_code, ] : null, 'weight' => $shipment->weight, 'chargeable_weight' => $shipment->chargeable_weight, 'total_fee' => $shipment->total_fee, 'net_rial' => $shipment->net_rial, 'net_dirham' => $shipment->net_dirham, 'created_at' => $shipment->created_at->toIso8601String(), 'created_at_jalali' => $this->toJalali($shipment->created_at), ]; if ($detailed) { $data['sender'] = [ 'name' => $shipment->sender_name, 'company' => $shipment->sender_company, 'phone' => $shipment->sender_phone, 'email' => $shipment->sender_email, 'address' => $shipment->sender_address, 'city' => $shipment->sender_city, ]; $data['receiver'] = [ 'name' => $shipment->receiver_name, 'company' => $shipment->receiver_company, 'phone' => $shipment->receiver_phone, 'email' => $shipment->receiver_email, 'address' => $shipment->receiver_address, 'city' => $shipment->receiver_city, ]; $data['financial'] = [ 'shipping_price' => $shipment->shipping_price, 'extra_service' => $shipment->extra_service, 'packing_cost' => $shipment->packing_cost, 'domestic_pickup' => $shipment->domestic_pickup, 'domestic_delivery' => $shipment->domestic_delivery, 'warehousing_cost' => $shipment->warehousing_cost, 'vat_amount' => $shipment->vat_amount, 'discount' => $shipment->discount, 'total_fee' => $shipment->total_fee, ]; $data['items'] = $shipment->items->map(fn($item) => [ 'row' => $item->row_number, 'description' => $item->description, 'hs_code' => $item->hs_code, 'quantity' => $item->quantity, 'unit_price' => $item->unit_price, 'total' => $item->total_usd, ]); $data['tracking_events'] = $shipment->trackingEvents->map(fn($event) => [ 'date' => $event->event_date, 'description' => $event->event_description, 'location' => $event->location, ]); } return $data; } /** * تولید شماره AWB */ private function generateAwbNumber(): string { do { $number = 'IFN-' . date('Y') . '-' . str_pad(random_int(10000, 99999), 5, '0', STR_PAD_LEFT); } while (Shipment::where('awb_no', $number)->exists()); return $number; } /** * تبدیل تاریخ به شمسی */ private function toJalali($date): string { if (!$date) return '—'; try { return \Morilog\Jalali\Jalalian::fromCarbon($date)->format('Y/m/d H:i'); } catch (\Exception $e) { return $date->format('Y-m-d H:i'); } } }