first(); // $talla->update_date = Verta::instance(Carbon::parse($talla->updated_at))->format('Y/n/j-H:i'); $new_customer_major = Customer::where('type',1)->whereDate('created_at','<=',now())->whereDate('created_at','>=',now()->subMonths(3)) ->count(); $new_customer_single = Customer::where('type',2)->whereDate('created_at','<=',now())->whereDate('created_at','>=',now()->subMonths(3)) ->count(); $all_customer_major = Customer::where('type',1)->count(); $all_customer_single = Customer::where('type',2)->count(); $archive_customer_major = Customer::where('type',1)->where('is_archive',true)->count(); $archive_customer_single = Customer::where('type',2)->where('is_archive',true)->count(); $all_gold = Order::where('direction',2)->where('type',1)->sum('amount');// جمع طلاهایی که از مشتری خریدیم $all_gold_single = Order::where('direction',2)->where('type',2)->sum('amount');// جمع طلاهایی که از مشتری خریدیم $await_list = Order::where('direction',1)->where('sts',1)->get(); $best_customer_major=Order::with('customer')->where('type',1)->where('sts',2)->select('customer_id',DB::raw('sum(amount) as amount')) ->groupBy('customer_id')->limit(10)->get(); $best_customer_single=Wallet::with('customer')->whereNull('fund_id')->where('owner_type',2)->orderBy('gold','desc')->limit(10)->get(); // GT $GT_all_customers = Customer::where('type',2)->where('is_archive',false)->count(); $GT_no_login_customers = Customer::where('type',2)->where('is_archive',false)->where('last_seen_at',null)->count(); $GT_no_valid_customers = Customer::where('type',2)->where('is_archive',false)->where('is_legal',0)->count(); $GT_no_valid_customers_data = Customer::where('type',2)->where('is_archive',false)->where('is_legal',0)->get(['id','name','mobile']); $GT_active_customers = Customer::where('type', 2) ->where('is_archive', false) ->whereIn('id', function($query) { $query->select('owner') ->from('wallets'); })->count(); $GT_active_customers_data = Customer::where('type', 2) ->where('is_archive', false) ->whereIn('id', function($query) { $query->select('owner') ->from('wallets'); })->get(['id','name','mobile']); $transactions_admins = TransactionsAdmin::with(['withdraw','order',"user",'customerTo','customer'])->get(); $startDate = $request->startDateProduct ? Carbon::parse($request->startDateProduct)->startOfDay() : now()->subMonth()->startOfDay(); $endDate = $request->endDateProduct ? Carbon::parse($request->endDateProduct)->endOfDay() : now()->endOfDay(); $products_report = Product::with(['limitedTransactions' => function ($query) use ($startDate, $endDate) { $query->whereBetween('created_at', [$startDate, $endDate]); }]) ->whereHas('limitedTransactions', function ($query) use ($startDate, $endDate) { $query->whereBetween('created_at', [$startDate, $endDate]); }) ->get(); $products_report->each(function ($product) { $product->transaction_sum_gold_buy = $product->limitedTransactions ->where('gold', '>', 0) ->sum('gold'); $product->total_negative_gold_sell = $product->limitedTransactions ->where('gold', '<', 0) ->sum('gold'); $product->total_gold = $product->limitedTransactions->sum('gold'); }); $gramPrice = (int) ($tallaPrice->price ?? 0); $FPrices = []; $mainProductId = app(ConfigService::class)->mainProductId(); if ($mainProductId) { $mainProduct = Product::find($mainProductId); if ($mainProduct) { $gramPrice = (int) ($mainProduct->price + ($mainProduct->price_profit ?? 0)); $FPrices[] = [ 'id' => 'main_product', 'name' => $mainProduct->title ?? 'محصول اصلی', 'price' => $gramPrice, 'profit' => 0, ]; } } $FPrices[] = ['id' => '1', 'name' => 'افجو', 'price' => $gramPrice, 'profit' => 0]; $gatewayRow = Cnt::where('key', 'is_gateway_open')->first(); return response()->json([ 'GT_all_customers'=>$GT_all_customers, 'GT_no_login_customers'=>$GT_no_login_customers, 'GT_no_valid_customers'=>$GT_no_valid_customers, 'GT_no_valid_customers_data'=>$GT_no_valid_customers_data, 'GT_active_customers'=>$GT_active_customers, 'GT_active_customers_data'=>$GT_active_customers_data, 'GT_deActive_customers'=>$GT_all_customers-$GT_active_customers, 'GT_transactions_admins'=>$transactions_admins, 'GT_products_report' => $products_report, 'update_date' => now()->timezone('Asia/Tehran')->format('Y-m-d H:i:s'), 'pricem' => round($gramPrice * 4.331802 / 10000) * 10000, 'prices' => $FPrices, 'is_gateway_open' => $gatewayRow && (string) $gatewayRow->value === '1', 'price'=>$tallaPrice?->price ?? $gramPrice, 'new_customer_major'=>$new_customer_major, 'new_customer_single'=>$new_customer_single, 'all_customer_major'=>$all_customer_major, 'all_customer_single'=>$all_customer_single, 'archive_customer_major'=>$archive_customer_major, 'archive_customer_single'=>$archive_customer_single, 'all_gold'=>$all_gold, 'all_gold_single'=>$all_gold_single, 'await_order'=>$await_list, 'best_customer'=>$best_customer_major, 'best_customer_single'=>$best_customer_single, ]); } public function gateway() { $cnt = Cnt::where('key', 'is_gateway_open')->first(); if (!$cnt) { $cnt = Cnt::create([ 'key' => 'is_gateway_open', 'value' => '1', ]); } else { $cnt->value = ((string) $cnt->value === '0') ? '1' : '0'; $cnt->save(); } return $cnt; } public function getBalance(Request $request, BalanceReportService $balanceReportService) { $startDate = Carbon::parse($request->startDate)->startOfDay(); $endDate = Carbon::parse($request->endDate)->endOfDay(); $response=[]; $response["liman"]=[]; $response["kalhor"]=[]; $excludeCustomerId = (int) app(ConfigService::class)->getInt('balance_report_exclude_customer_id', 4); $catalogMetalIds = $balanceReportService->catalogMetalIds(); if ($catalogMetalIds === []) { $catalogMetalIds = [1]; } $limanMetalBuckets = $balanceReportService->initMetalBalanceBuckets($catalogMetalIds); $kalhorMetalBuckets = $balanceReportService->initMetalBalanceBuckets($catalogMetalIds); $response["pay_at_update"] = Order::whereNull('pay_at')->update(['pay_at' => DB::raw('created_at')]); //liman $response["liman"]["orders"] = Order::with(['transactions.product', 'customer']) ->where('customer_id', '!=', $excludeCustomerId) ->where('sts', 2) ->whereBetween('pay_at', [$startDate, $endDate]) ->get(); $response["liman"]["balance"]["gold"] = 0; $response["liman"]["balance"]["cash"] = 0; $response["liman"]["balance_abs"]["gold"] = 0; $response["liman"]["balance_abs"]["cash"] = 0; foreach ($response["liman"]["orders"] as $OrderGoldBalanceRow) { $balanceReportService->applyOrderToBuckets($limanMetalBuckets, $OrderGoldBalanceRow); if ($OrderGoldBalanceRow->direction == 1){ $response["liman"]["balance"]["gold"] += $OrderGoldBalanceRow->amount; $response["liman"]["balance"]["cash"] -= $OrderGoldBalanceRow->total_price; } if ($OrderGoldBalanceRow->direction == 2){ $response["liman"]["balance"]["gold"] -= $OrderGoldBalanceRow->amount; $response["liman"]["balance"]["cash"] += $OrderGoldBalanceRow->total_price; } $response["liman"]["balance_abs"]["gold"] += $OrderGoldBalanceRow->amount; $response["liman"]["balance_abs"]["cash"] += $OrderGoldBalanceRow->total_price; } $response['goldBalanceOfLiman']= -$response["liman"]["balance"]["gold"]; $response['cashBalanceOfLiman']= -$response["liman"]["balance"]["cash"]; //kalhor // $response["kalhor"]["orders"] = Cache::remember("kalhor_orders_" . md5($startDate . $endDate), 900, function () use ($startDate, $endDate) { // $body = [ // 'FromDate' => Jalalian::forge($startDate)->format('Y/m/d'), // 'ItemId' => 0, // 'OrderIndex' => 0, // 'ToDate' => Jalalian::forge($endDate)->format('Y/m/d'), // ]; // // return Http::withHeaders(config('kalhor.headers')) // ->withCookies(config('kalhor.cookies'), 'b750.ir') // ->post('https://b750.ir/Deal/DealsList', $body)->json(); // }); $response["kalhor"]["balance"]["gold"] = 0; $response["kalhor"]["balance"]["cash"] = 0; $response["kalhor"]["balance_abs"]["gold"] = 0; $response["kalhor"]["balance_abs"]["cash"] = 0; if (!empty($response["kalhor"]["orders"]['Data'])) { foreach ($response["kalhor"]["orders"]['Data'] as &$data) { $data['0_save_in_liman']= 0; if ($data['DeclineReason'] == 0 and $data['OrderStatusStr'] !="لغو شده") { //رد نشده بود $data['0_save_in_liman']= 1; if ($data['DealTypeStr'] == "خرید" or $data['DealTypeStr'] == "خرید _ دستی") { $response["kalhor"]["balance"]['gold'] += $data['Count']; $response["kalhor"]["balance"]['cash'] -= $data['TotalPrice']; $direction =2; $balanceReportService->applyKalhorSlice( $kalhorMetalBuckets, (float) $data['Count'], (float) $data['TotalPrice'], 2 ); } elseif ($data['DealTypeStr'] == "فروش" or $data['DealTypeStr'] == "فروش _ دستی") { $response["kalhor"]["balance"]['gold'] -= $data['Count']; $response["kalhor"]["balance"]['cash'] += $data['TotalPrice']; $direction =1; $balanceReportService->applyKalhorSlice( $kalhorMetalBuckets, (float) $data['Count'], (float) $data['TotalPrice'], 1 ); } $response["kalhor"]["balance_abs"]['gold'] += $data['Count']; $response["kalhor"]["balance_abs"]['cash'] += $data['TotalPrice']; if (Order::where('ref_id', $data['OrderCode'])->doesntExist()){ $request = new Request([ 'amount' => $data['Count'], 'customer_id' => 12, 'method' => "liman", 'direction' => $direction, 'product_id' => 2, "price"=>$data['Mazane'], "cash"=>$data['TotalPrice'], 'ref_id' => $data['OrderCode'], 'pay_at' =>Jalalian::fromFormat('Y/m/d', explode('_', $data['ModifiedDateStr'])[0])->toCarbon()->format('Y-m-d') . ' ' . explode('_', $data['ModifiedDateStr'])[1], 'description' => "FROM KALHOR" ]); // $AdminOrderController = new AdminOrderController(); // $response["kalhor"]["newOrders"][$data['OrderCode']] = $AdminOrderController->store($request); } } } } $response['goldBalanceOfKalhor']= $response["kalhor"]["balance"]["gold"]; $response['cashBalanceOfKalhor']= $response["kalhor"]["balance"]["cash"]; //main $response['cashBalance']= -$response["liman"]["balance"]["cash"]+$response["kalhor"]["balance"]["cash"]; $response['goldBalance']= -$response["liman"]["balance"]["gold"]+$response["kalhor"]["balance"]["gold"]; $metalBalances = $balanceReportService->buildMetalBalancesResponse($limanMetalBuckets, $kalhorMetalBuckets); $response['metalBalances'] = $metalBalances['items']; if (!empty($metalBalances['items'])) { $response['goldBalance'] = $metalBalances['totalGoldBalance']; } //mazaneh $changeMesghal = config('change_mesghal'); /* $response['mazanehBalanceOfKalhor'] = $response["kalhor"]["balance_abs"]["gold"] != 0 ? ( $response["kalhor"]["balance_abs"]["cash"] / $response["kalhor"]["balance_abs"]["gold"]) * $changeMesghal : 0; $response['mazanehBalanceOfLiman'] = $response["kalhor"]["balance_abs"]["gold"] != 0 ? ( $response["liman"]["balance_abs"]["cash"] / $response["liman"]["balance_abs"]["gold"]) * $changeMesghal : 0; $response['mazanehBalance'] = $response["kalhor"]["balance_abs"]["gold"] != 0 ? (($response["liman"]["balance_abs"]["cash"]+$response["kalhor"]["balance_abs"]["cash"]) / ($response["liman"]["balance_abs"]["gold"]+$response["kalhor"]["balance_abs"]["gold"])) * $changeMesghal : 0;*/ $response['mazanehBalanceOfKalhor'] = $response["kalhor"]["balance_abs"]["gold"] != 0 ? ($response["kalhor"]["balance_abs"]["cash"] / $response["kalhor"]["balance_abs"]["gold"]) * $changeMesghal : 0; $response['mazanehBalanceOfLiman'] = $response["liman"]["balance_abs"]["gold"] != 0 ? ($response["liman"]["balance_abs"]["cash"] / $response["liman"]["balance_abs"]["gold"]) * $changeMesghal : 0; $totalGold = $response["liman"]["balance_abs"]["gold"] + $response["kalhor"]["balance_abs"]["gold"]; $response['mazanehBalance'] = $totalGold != 0 ? (($response["liman"]["balance_abs"]["cash"] + $response["kalhor"]["balance_abs"]["cash"]) / $totalGold) * $changeMesghal : 0; $response['income_money'] = Withdraw::whereNotNull('zify_code')->where('sts',2)->whereBetween('updated_at', [$startDate, $endDate])->sum('cash'); return $response; } /** * محصول مقداری/آبشده‌ای که کارمزد مبلغ‌محور از کیف همان محصول کسر می‌شود. * (trade_by_quantity یا دسته آبشده/کاتد؛ مفتول مس هم اگر مقداری باشد.) */ private function isQuantityProfitProduct(?Product $product): bool { if (!$product) { return false; } $payload = self::productUnitPayload($product); if (self::usesQuantityTrade($payload)) { return true; } if (self::isMeltedTradeGroup($payload['group_type'] ?? null)) { return true; } // مفتول مس در لیست تعدادی قدیمی است؛ اگر واحد وزنی دارد در سود مقداری بیاید $group = trim((string) ($payload['group_type'] ?? '')); if ($group === 'مفتول مس' && self::isWeightBasedProductUnit($payload['unit'] ?? null)) { return true; } return !self::isCountBasedProduct($payload); } public function finance(Request $request) { $startDate = $request->startDate ? Carbon::parse($request->startDate)->startOfDay() : Carbon::now()->subDays(30)->startOfDay(); $endDate = $request->endDate ? Carbon::parse($request->endDate)->endOfDay() : Carbon::now()->endOfDay(); // Fetch orders and transactions within the date range $productSelect = ['id', 'title', 'group_type', 'unit', 'amount', 'trade_by_quantity', 'direct_price']; if (Product::hasStoredPriceColumns()) { $productSelect[] = 'price'; } $ords = Order::with(['product' => function ($q) use ($productSelect) { $q->withTrashed()->select($productSelect); }])->whereBetween('created_at', [$startDate, $endDate])->get(); // Initialize response data $response = [ "fullPriceM1" => 0, "fullPriceM2" => 0, "fullPriceSood" => 0, "fullPriceKarmozd" => 0, "orderCount" => $ords->count(), "Gifts" => 0, "GiftsPrise" => 0, "sumAll" => 0, // تفکیک سود بر اساس سمت کسر کارمزد "profitGoldGrams" => 0, // سود طلایی/فلزی: مجموع کارمزدهای کسرشده از فلز (گرم) "profitRial" => 0, // سود ریالی: مجموع کارمزدهای کسرشده از ریال (ریال) "profitGoldValueRial" => 0, /** @var array */ "profitQuantityProducts" => [], "daily" => [], ]; // هدیه = فقط پاداش معرفی/ثبت‌نام و سود زیرمجموعه. // معاملهٔ مشتری و شارژ دستی طلا توسط ادمین هدیه نیستند. $giftsGold = (float) Transaction::query() ->whereIn('type', self::GIFT_TRANSACTION_TYPES) ->whereBetween('created_at', [$startDate, $endDate]) ->sum('gold'); $response['Gifts'] = number_format($giftsGold, 3); $lastGoldPrice = (float) (Price::latest()->first()?->price ?? 0); $response['GiftsPrise'] = $lastGoldPrice > 0 ? (int) floor($giftsGold * $lastGoldPrice) : 0; /** @var array $quantityBuckets */ $quantityBuckets = []; // Calculate order-related sums $daily = []; foreach ($ords as $ord) { $response['fullPriceM1'] += abs($ord->total_price_original - $ord->payment_amount); $response['fullPriceM2'] += $ord->service_charge_price + $ord->total_fee; $response['fullPriceSood'] += $ord->total_fee; $response['fullPriceKarmozd'] += $ord->service_charge_price; // تفکیک کارمزد به فلز/ریال بر اساس سمت کسر // fee_asset === 'gold' → کارمزد از فلز/آبشده گرفته شده (گرم)؛ در غیر این صورت ریالی محسوب می‌شود. $feeFromGold = $ord->fee_asset === 'gold'; $goldFeeGrams = $feeFromGold ? (float) $ord->service_charge : 0.0; $rialFee = $feeFromGold ? 0.0 : (float) $ord->service_charge_price; $response['profitGoldGrams'] += $goldFeeGrams; $response['profitRial'] += $rialFee; $product = $ord->product; $productId = (int) ($ord->product_id ?? 0); if ($feeFromGold && $goldFeeGrams > 0 && $this->isQuantityProfitProduct($product)) { $payload = self::productUnitPayload($product); $bucketKey = (string) $productId; if (!isset($quantityBuckets[$bucketKey])) { $unitPrice = Product::hasStoredPriceColumns() ? (float) ($product->price ?? 0) : 0.0; if ($unitPrice <= 0) { $unitPrice = $lastGoldPrice; } $quantityBuckets[$bucketKey] = [ 'product_id' => $productId, 'title' => (string) ($product->title ?? ('محصول #' . $productId)), 'group_type' => $product->group_type ?? null, 'grams' => 0.0, 'unit_price' => $unitPrice, 'unit' => self::productQuantityUnit($payload), ]; } $quantityBuckets[$bucketKey]['grams'] += $goldFeeGrams; } $dateKey = $ord->created_at ? Carbon::parse($ord->created_at)->toDateString() : 'نامشخص'; if (!isset($daily[$dateKey])) { $daily[$dateKey] = [ 'date' => $dateKey, 'profit_gold' => 0.0, // گرم 'profit_rial' => 0.0, // ریال (کارمزد ریالی) 'spread_rial' => 0.0, // سود اختلاف قیمت (ریال) 'order_count' => 0, 'profit_by_product' => [], ]; } $daily[$dateKey]['profit_gold'] += $goldFeeGrams; $daily[$dateKey]['profit_rial'] += $rialFee; $daily[$dateKey]['spread_rial'] += (float) $ord->total_fee; $daily[$dateKey]['order_count']++; if ($feeFromGold && $goldFeeGrams > 0 && $this->isQuantityProfitProduct($product) && $productId > 0) { $pidKey = (string) $productId; if (!isset($daily[$dateKey]['profit_by_product'][$pidKey])) { $daily[$dateKey]['profit_by_product'][$pidKey] = [ 'product_id' => $productId, 'title' => (string) ($product->title ?? ('محصول #' . $productId)), 'grams' => 0.0, ]; } $daily[$dateKey]['profit_by_product'][$pidKey]['grams'] += $goldFeeGrams; } } $response['profitGoldGrams'] = round($response['profitGoldGrams'], 4); // سندهای تبدیل سود ریالی به طلا — فقط برای فلز اصلی طلا معنا دارد $mainMetal = trim((string) self::Config('main_metal')); $applyProfitConversions = $mainMetal === '' || $mainMetal === 'طلا'; $convertedRial = 0.0; $convertedGram = 0.0; if ($applyProfitConversions) { $conversions = ProfitConversion::whereBetween('converted_at', [$startDate, $endDate])->get(); foreach ($conversions as $conversion) { $rialAmount = (float) $conversion->rial_amount; $gramAmount = (float) $conversion->gram_amount; $convertedRial += $rialAmount; $convertedGram += $gramAmount; $dateKey = $conversion->converted_at ? Carbon::parse($conversion->converted_at)->toDateString() : null; if (!$dateKey) { continue; } if (!isset($daily[$dateKey])) { $daily[$dateKey] = [ 'date' => $dateKey, 'profit_gold' => 0.0, 'profit_rial' => 0.0, 'spread_rial' => 0.0, 'order_count' => 0, 'profit_by_product' => [], ]; } $daily[$dateKey]['profit_rial'] -= $rialAmount; $daily[$dateKey]['profit_gold'] += $gramAmount; } } $response['profitRialGross'] = $response['profitRial']; $response['profitGoldGramsGross'] = $response['profitGoldGrams']; $response['profitRialConverted'] = round($convertedRial, 0); $response['profitGoldConvertedGram'] = round($convertedGram, 4); $response['profitRial'] = round($response['profitRial'] - $convertedRial, 0); $response['profitGoldGrams'] = round($response['profitGoldGrams'] + $convertedGram, 4); $quantityProducts = []; $quantityValueRial = 0; foreach ($quantityBuckets as $bucket) { $grams = round($bucket['grams'], 4); $unitPrice = (float) $bucket['unit_price']; $valueRial = $unitPrice > 0 ? (int) floor($grams * $unitPrice) : 0; $quantityValueRial += $valueRial; $quantityProducts[] = [ 'product_id' => $bucket['product_id'], 'title' => $bucket['title'], 'group_type' => $bucket['group_type'], 'grams' => $grams, 'value_rial' => $valueRial, 'unit' => $bucket['unit'], ]; } usort($quantityProducts, function ($a, $b) { return $b['grams'] <=> $a['grams']; }); $response['profitQuantityProducts'] = $quantityProducts; // ارزش ریالی: اگر تفکیک مقداری داریم از جمع همان‌ها (+تبدیل)، وگرنه مظنهٔ کلی if (count($quantityProducts) > 0) { $convertedValue = $lastGoldPrice > 0 ? (int) floor($convertedGram * $lastGoldPrice) : 0; $response['profitGoldValueRial'] = $quantityValueRial + $convertedValue; } else { $response['profitGoldValueRial'] = $lastGoldPrice > 0 ? (int) floor($response['profitGoldGrams'] * $lastGoldPrice) : 0; } krsort($daily); $response['daily'] = array_map(function ($row) { $row['profit_gold'] = round($row['profit_gold'], 4); $row['profit_rial'] = round($row['profit_rial'], 0); $byProduct = array_values($row['profit_by_product'] ?? []); foreach ($byProduct as &$p) { $p['grams'] = round($p['grams'], 4); } unset($p); usort($byProduct, function ($a, $b) { return $b['grams'] <=> $a['grams']; }); $row['profit_by_product'] = $byProduct; return $row; }, array_values($daily)); $response['totalTradingProfit'] = $response['fullPriceSood'] + $response['fullPriceKarmozd']; $response['sumAll'] = $response['fullPriceM1'] - $response['GiftsPrise']; return $response; } public function sumSellToCustomerMajor(Request $request){ $data = $request->all(); $order = Order::where('direction',1) ->where('sts',2) ->whereDate('created_at','>=',Carbon::parse($data['start_at'])) ->whereDate('created_at','<=',Carbon::parse($data['end_at'])) ->where('type',1) ->sum('amount'); return response()->json($order); } public function sumSellToCustomerSingle(Request $request){ $data = $request->all(); $order = Order::where('direction',1) ->where('sts',2) ->whereDate('created_at','>=',Carbon::parse($data['start_at'])) ->whereDate('created_at','<=',Carbon::parse($data['end_at'])) ->where('type',2) ->sum('amount'); return response()->json($order); } public function flow(Request $request){ $data = $request->all(); if ($data['type'] == 'week'){ $query=Order::where('sts',2) ->whereDate('created_at','<=',now()) ->whereDate('created_at','>=',now()->subWeek()) ->select(['direction',DB::raw('Date(created_at) as date'),DB::raw('sum(amount) as total')]) ->groupBy(['date','direction']) ->get(); } elseif ($data['type'] == 'month'){ $query=Order::where('sts',2) ->whereDate('created_at','<=',now()) ->whereDate('created_at','>=',now()->subMonth()) ->select(['direction',DB::raw('Date(created_at) as date'),DB::raw('sum(amount) as total')]) ->groupBy(['date','direction']) ->get(); } if (in_array($data['type'],['week','month'])){ $send = []; foreach ($query as $i){ if(in_array($i['date'],array_keys($send))){ $send[$i['date']][$i['direction']] = $i['total']; }else{ $send[$i['date']]['date'] =$i['date']; $send[$i['date']][$i['direction']] = $i['total']; } } } else{ $query=Order::where('sts',2) ->whereDate('created_at','<=',now()) ->whereDate('created_at','>=',now()->subYear()) ->select(['direction',DB::raw('MONTH(created_at) as month'),DB::raw('sum(amount) as total')]) ->groupBy(['month','direction']) ->get(); for ($i=0;$iformat('%B'); } } return response()->json(array_values($send)); } public function orderPeakHours(Request $request) { $days = (int) $request->input('days', 30); $days = min(max($days, 7), 90); $start = now()->subDays($days)->startOfDay(); $rows = Order::query() ->where('sts', 2) ->where('type', 2) ->where('created_at', '>=', $start) ->select([ DB::raw('HOUR(created_at) as hour'), 'direction', DB::raw('COUNT(*) as count'), ]) ->groupBy('hour', 'direction') ->get(); $hours = []; for ($h = 0; $h < 24; $h++) { $hours[$h] = ['hour' => $h, 'buy' => 0, 'sell' => 0, 'total' => 0]; } foreach ($rows as $row) { $h = (int) $row->hour; if ($h < 0 || $h > 23) { continue; } $count = (int) $row->count; if ((int) $row->direction === 1) { $hours[$h]['buy'] += $count; } elseif ((int) $row->direction === 2) { $hours[$h]['sell'] += $count; } $hours[$h]['total'] += $count; } $peakHour = 0; $peakCount = 0; foreach ($hours as $slot) { if ($slot['total'] > $peakCount) { $peakCount = $slot['total']; $peakHour = $slot['hour']; } } return response()->json([ 'hours' => array_values($hours), 'peak_hour' => $peakCount > 0 ? $peakHour : null, 'peak_count' => $peakCount, 'days' => $days, 'total_orders' => array_sum(array_column($hours, 'total')), ]); } /** * لیست سندهای تبدیل سود ریالی به طلا در یک بازه + جمع کل. */ public function profitConversions(Request $request) { $query = ProfitConversion::query(); if ($request->filled('startDate')) { $query->where('converted_at', '>=', Carbon::parse($request->startDate)->startOfDay()); } if ($request->filled('endDate')) { $query->where('converted_at', '<=', Carbon::parse($request->endDate)->endOfDay()); } $items = $query->orderByDesc('converted_at')->orderByDesc('id')->get(); return response()->json([ 'items' => $items, 'totalRial' => (float) $items->sum('rial_amount'), 'totalGram' => round((float) $items->sum('gram_amount'), 4), ]); } /** * ثبت سند تبدیل سود ریالی به طلا. * اگر گرم ارسال نشود از روی مبلغ و قیمت واحد محاسبه می‌شود و بالعکس. */ public function storeProfitConversion(Request $request) { $data = $request->validate([ 'rial_amount' => 'nullable|numeric|min:0', 'gram_amount' => 'nullable|numeric|min:0', 'unit_price' => 'nullable|numeric|min:0', 'converted_at' => 'nullable|date', 'description' => 'nullable|string|max:500', ]); $unitPrice = (float) ($data['unit_price'] ?? 0); if ($unitPrice <= 0) { $unitPrice = (float) (Price::latest()->first()?->price ?? 0); } $rial = (float) ($data['rial_amount'] ?? 0); $gram = (float) ($data['gram_amount'] ?? 0); if ($gram <= 0 && $rial > 0 && $unitPrice > 0) { $gram = round($rial / $unitPrice, 4); } elseif ($rial <= 0 && $gram > 0 && $unitPrice > 0) { $rial = round($gram * $unitPrice); } if ($rial <= 0 && $gram <= 0) { throw new \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException('مبلغ ریالی یا وزن طلا را وارد کنید.'); } $conversion = ProfitConversion::create([ 'user_id' => optional($request->user())->id, 'rial_amount' => $rial, 'gram_amount' => $gram, 'unit_price' => $unitPrice, 'converted_at' => !empty($data['converted_at']) ? Carbon::parse($data['converted_at']) : now(), 'description' => $data['description'] ?? null, ]); return response()->json($conversion, 201); } public function deleteProfitConversion($id) { $conversion = ProfitConversion::findOrFail($id); $conversion->delete(); return response()->json(['deleted' => true]); } /** * تطبیق دفتر اکانت روی نود همکار (Montzar) با سفارش‌های مچ‌شده — نه ته‌حساب. */ public function montzarReconcile(Request $request) { $config = app(ConfigService::class); if (!$config->usesMontzarPricing()) { return response()->json([ 'message' => 'این گزارش فقط وقتی منبع قیمت montzar است در دسترس است.', ], 422); } $startDate = $request->input('startDate') ?: now()->subDays(30)->toDateString(); $endDate = $request->input('endDate') ?: now()->toDateString(); try { $payload = app(\App\Services\MontzarReconcileService::class) ->reconcile((string) $startDate, (string) $endDate); } catch (\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e) { throw $e; } catch (\Throwable $e) { report($e); return response()->json([ 'message' => 'خواندن دفتر همکار ناموفق بود: ' . $e->getMessage(), ], 422); } return response()->json($payload); } }