ifnex/04_Laravel/app/Http/Controllers/Api/Customer/CustomerOrderController.php

596 lines
24 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Customer;
use App\Http\Controllers\Controller;
use App\Models\Shipment;
use App\Models\ShipmentItem;
use App\Models\Country;
use App\Enums\ShipmentStatus;
use App\Enums\ShipmentDirection;
use App\Enums\ShipmentType;
use App\Services\PriceCalculatorService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class CustomerOrderController extends Controller
{
public function __construct(
protected PriceCalculatorService $calculator
) {}
/**
* لیست سفارشات کاربر
* GET /api/v1/customer/orders
*/
public function index(Request $request): JsonResponse
{
$validated = $request->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');
}
}
/**
* پرداخت سفارش از کیف پول
* POST /api/v1/customer/orders/{shipment}/pay-wallet
*/
public function payFromWallet(Shipment $shipment): JsonResponse
{
$user = Auth::user();
// بررسی مالکیت
if ($shipment->user_id !== $user->id) {
return response()->json([
'success' => false,
'message' => 'شما به این سفارش دسترسی ندارید.',
], 403);
}
// بررسی وضعیت
if ($shipment->status !== ShipmentStatus::PendingPayment) {
return response()->json([
'success' => false,
'message' => 'این سفارش در وضعیت قابل پرداخت نیست.',
], 400);
}
$wallet = $user->wallet;
if (!$wallet) {
return response()->json([
'success' => false,
'message' => 'کیف پول شما وجود ندارد.',
], 400);
}
$paymentService = app(\App\Services\OrderPaymentService::class);
$result = $paymentService->payFromWallet($shipment, $wallet);
if ($result['success']) {
return response()->json([
'success' => true,
'message' => $result['message'],
'data' => $this->formatShipment($shipment->fresh()->load(['fromCountry', 'toCountry'])),
]);
}
return response()->json($result, 400);
}
/**
* شروع پرداخت از درگاه
* POST /api/v1/customer/orders/{shipment}/pay-gateway
*/
public function payViaGateway(Request $request, Shipment $shipment): JsonResponse
{
$user = Auth::user();
// بررسی مالکیت
if ($shipment->user_id !== $user->id) {
return response()->json([
'success' => false,
'message' => 'شما به این سفارش دسترسی ندارید.',
], 403);
}
// بررسی وضعیت
if ($shipment->status !== ShipmentStatus::PendingPayment) {
return response()->json([
'success' => false,
'message' => 'این سفارش در وضعیت قابل پرداخت نیست.',
], 400);
}
$validated = $request->validate([
'frontend_callback' => ['required', 'url', 'max:500'],
]);
$wallet = $user->wallet;
if (!$wallet) {
return response()->json([
'success' => false,
'message' => 'کیف پول شما وجود ندارد.',
], 400);
}
$paymentService = app(\App\Services\OrderPaymentService::class);
$result = $paymentService->initiateGatewayPayment($shipment, $wallet);
if ($result['success']) {
// ذخیره callback URL در metadata تراکنش
$transaction = WalletTransaction::find($result['transaction_id']);
if ($transaction) {
$metadata = $transaction->metadata ?? [];
$metadata['frontend_callback'] = $validated['frontend_callback'];
$transaction->update(['metadata' => $metadata]);
}
return response()->json([
'success' => true,
'payment_url' => $result['payment_url'],
'transaction_id' => $result['transaction_id'],
]);
}
return response()->json($result, 400);
}
}