firstOrFail(); $zone = match (true) { $type === ShipmentType::Parcel && $direction === 'export' => $country->export_zone_parcel, $type === ShipmentType::Parcel && $direction === 'import' => $country->import_zone_parcel, $type !== ShipmentType::Parcel && $direction === 'export' => $country->export_zone_doc, $type !== ShipmentType::Parcel && $direction === 'import' => $country->import_zone_doc, }; // 3. یافتن نرخ پایه $baseRate = $this->lookupRate($direction, $type, $chargeableWeight, $zone); // 4. محاسبات مالی $financials = $this->calculateFinancials($baseRate); // 5. هزینه‌های جانبی $extraCosts = $this->calculateExtraCosts($data); // 6. قیمت قبل از تخفیف $subtotal = $financials['net_rial'] + array_sum($extraCosts); // 7. اعمال تخفیف // مهم: اینجا دیگر used_count تغییر نمی‌کند. $discountResult = $this->applyDiscount( $subtotal, $data['discount_code'] ?? null ); // 8. محاسبات مالی نهایی $vatRate = (float) SystemSetting::get('vat_rate', 0.09); $finalPrice = $discountResult['final_price']; $vatAmount = $finalPrice * $vatRate; $totalFee = $finalPrice + $vatAmount; // 9. خروجی return [ 'base_price' => $baseRate, 'net_dirham' => $financials['net_dirham'], 'net_rial' => $financials['net_rial'], 'extra_service' => $extraCosts['extra_service'], 'packing_cost' => $extraCosts['packing_cost'], 'domestic_pickup' => $extraCosts['domestic_pickup'], 'domestic_delivery' => $extraCosts['domestic_delivery'], 'warehousing_cost' => $extraCosts['warehousing_cost'], 'discount_applied' => $discountResult['success'], 'discount_code' => $discountResult['discount_code'] ?? null, 'discount_amount' => $discountResult['discount_amount'], 'discount_message' => $discountResult['message'], 'vat_amount' => $vatAmount, 'total_fee' => $totalFee, 'zone' => $zone, 'chargeable_weight' => $chargeableWeight, ]; } /** * ثبت مصرف واقعی یک کد تخفیف. * * این متد باید فقط بعد از محاسبه موفق و داخل transaction * ثبت سفارش فراخوانی شود. */ public function consumeDiscountCode(?string $code): void { if (empty($code)) { return; } $discount = DiscountCode::query() ->where('code', $code) ->lockForUpdate() ->first(); if (!$discount) { throw new InvalidArgumentException('کد تخفیف معتبر نیست.'); } if (!$discount->is_active) { throw new InvalidArgumentException('کد تخفیف غیرفعال است.'); } if ($discount->expires_at && $discount->expires_at->isPast()) { throw new InvalidArgumentException('کد تخفیف منقضی شده است.'); } if ( $discount->usage_limit !== null && $discount->used_count >= $discount->usage_limit ) { throw new InvalidArgumentException('ظرفیت استفاده از این کد تخفیف به پایان رسیده است.'); } $discount->increment('used_count'); } /** * محاسبه ارزش مالی نرخ پایه. */ private function calculateFinancials(float $baseRate): array { $profitMargin = (float) SystemSetting::get('profit_margin', 1.25); $aedToIrr = (float) SystemSetting::get('aed_to_irr', 455000); $netDirham = $baseRate * $profitMargin; $netRial = $netDirham * $aedToIrr; return [ 'net_dirham' => $netDirham, 'net_rial' => $netRial, ]; } /** * محاسبه مجموع هزینه‌های جانبی. */ private function calculateExtraCosts(array $data): array { $packingCostDefault = (float) SystemSetting::get( 'packing_cost_default', 100000 ); return [ 'extra_service' => (float) ($data['extra_service'] ?? 0), 'packing_cost' => (float) ($data['packing_cost'] ?? $packingCostDefault), 'domestic_pickup' => (float) ($data['domestic_pickup'] ?? 0), 'domestic_delivery' => (float) ($data['domestic_delivery'] ?? 0), 'warehousing_cost' => (float) ($data['warehousing_cost'] ?? 0), ]; } /** * اعمال کد تخفیف روی قیمت. * * این متد pure است و used_count را تغییر نمی‌دهد. */ public function applyDiscount(float $totalPrice, ?string $code): array { if (empty($code)) { return [ 'success' => false, 'message' => 'کد تخفیف وارد نشده است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } $discount = DiscountCode::where('code', $code)->first(); // 1. بررسی وجود if (!$discount) { return [ 'success' => false, 'message' => 'کد تخفیف نامعتبر است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } // 2. بررسی فعال بودن if (!$discount->is_active) { return [ 'success' => false, 'message' => 'این کد تخفیف غیرفعال شده است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } // 3. بررسی تاریخ انقضا if ($discount->expires_at && $discount->expires_at->isPast()) { return [ 'success' => false, 'message' => 'این کد تخفیف منقضی شده است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } // 4. حداقل مبلغ سفارش if ( $discount->min_order_amount > 0 && $totalPrice < $discount->min_order_amount ) { return [ 'success' => false, 'message' => 'حداقل مبلغ سفارش برای استفاده از این کد ' . number_format($discount->min_order_amount) . ' ریال است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } // 5. محدودیت تعداد استفاده if ( $discount->usage_limit && $discount->used_count >= $discount->usage_limit ) { return [ 'success' => false, 'message' => 'ظرفیت استفاده از این کد تخفیف به پایان رسیده است.', 'discount_amount' => 0, 'final_price' => $totalPrice, ]; } // محاسبه تخفیف $discountAmount = 0; if ($discount->type === 'percentage') { $discountAmount = ($totalPrice * $discount->value) / 100; } elseif ($discount->type === 'fixed') { $discountAmount = $discount->value; } // جلوگیری از تخفیف بیشتر از مبلغ سفارش $discountAmount = min($discountAmount, $totalPrice); $finalPrice = $totalPrice - $discountAmount; return [ 'success' => true, 'message' => 'کد تخفیف با موفقیت اعمال شد.', 'discount_amount' => $discountAmount, 'final_price' => $finalPrice, 'discount_code' => $discount->code, ]; } /** * جستجوی نرخ حمل و نقل در دیتابیس. */ private function lookupRate( string $direction, ShipmentType $type, float $weight, int $zone ): float { $zoneColumn = 'zone_' . $zone; $rate = \App\Models\ShippingRate::query() ->where('direction', $direction) ->where('type', $type->value) ->where('weight', '<=', $weight) ->where($zoneColumn, '>', 0) ->orderByDesc('weight') ->value($zoneColumn); if ($rate === null) { throw new InvalidArgumentException( 'No rate found for the given parameters.' ); } return (float) $rate; } }