2026-01-24 14:03:11 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\Cnt;
|
|
|
|
|
use App\Models\Customer;
|
|
|
|
|
use App\Models\Order;
|
|
|
|
|
use App\Models\Price;
|
2026-06-04 12:18:53 +03:30
|
|
|
use App\Models\Product;
|
2026-07-10 23:29:36 +03:30
|
|
|
use App\Models\ProfitConversion;
|
2026-01-24 14:03:11 +00:00
|
|
|
use App\Models\TransactionsAdmin;
|
|
|
|
|
use App\Models\Withdraw;
|
2026-06-23 20:35:13 +03:30
|
|
|
use App\Services\BalanceReportService;
|
2026-06-04 12:18:53 +03:30
|
|
|
use App\Services\ConfigService;
|
2026-01-24 14:03:11 +00:00
|
|
|
use Carbon\Carbon;
|
|
|
|
|
use Hekmatinasser\Verta\Verta;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2026-07-25 16:47:30 +03:30
|
|
|
use Rooyesh\Wallet\models\Transaction;
|
2026-01-24 14:03:11 +00:00
|
|
|
use Rooyesh\Wallet\models\Wallet;
|
|
|
|
|
|
|
|
|
|
class DashboardController extends Controller
|
|
|
|
|
{
|
2026-07-25 16:47:30 +03:30
|
|
|
/** نوع تراکنشهای کیف که «هدیه» شمرده میشوند: ۸ پاداش معرفی/ثبتنام، ۹ سود زیرمجموعه */
|
|
|
|
|
private const GIFT_TRANSACTION_TYPES = [8, 9];
|
|
|
|
|
|
2026-06-04 12:18:53 +03:30
|
|
|
public function dashboardMaster(Request $request)
|
|
|
|
|
{
|
2026-01-24 14:03:11 +00:00
|
|
|
$tallaPrice = Price::latest()->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();
|
|
|
|
|
|
2026-06-04 12:18:53 +03:30
|
|
|
$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 = [];
|
2026-06-04 14:19:54 +03:30
|
|
|
$mainProductId = app(ConfigService::class)->mainProductId();
|
|
|
|
|
if ($mainProductId) {
|
|
|
|
|
$mainProduct = Product::find($mainProductId);
|
|
|
|
|
if ($mainProduct) {
|
|
|
|
|
$gramPrice = (int) ($mainProduct->price + ($mainProduct->price_profit ?? 0));
|
2026-06-04 12:18:53 +03:30
|
|
|
$FPrices[] = [
|
2026-06-04 14:19:54 +03:30
|
|
|
'id' => 'main_product',
|
|
|
|
|
'name' => $mainProduct->title ?? 'محصول اصلی',
|
2026-06-04 12:18:53 +03:30
|
|
|
'price' => $gramPrice,
|
|
|
|
|
'profit' => 0,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$FPrices[] = ['id' => '1', 'name' => 'افجو', 'price' => $gramPrice, 'profit' => 0];
|
|
|
|
|
|
|
|
|
|
$gatewayRow = Cnt::where('key', 'is_gateway_open')->first();
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
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,
|
2026-06-04 12:18:53 +03:30
|
|
|
'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,
|
2026-01-24 14:03:11 +00:00
|
|
|
'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,
|
|
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-06-04 12:18:53 +03:30
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 20:35:13 +03:30
|
|
|
public function getBalance(Request $request, BalanceReportService $balanceReportService)
|
2026-01-24 14:03:11 +00:00
|
|
|
{
|
|
|
|
|
$startDate = Carbon::parse($request->startDate)->startOfDay();
|
|
|
|
|
$endDate = Carbon::parse($request->endDate)->endOfDay();
|
|
|
|
|
$response=[];
|
|
|
|
|
$response["liman"]=[];
|
|
|
|
|
$response["kalhor"]=[];
|
|
|
|
|
|
2026-06-04 12:18:53 +03:30
|
|
|
$excludeCustomerId = (int) app(ConfigService::class)->getInt('balance_report_exclude_customer_id', 4);
|
2026-06-23 20:35:13 +03:30
|
|
|
$catalogMetalIds = $balanceReportService->catalogMetalIds();
|
|
|
|
|
if ($catalogMetalIds === []) {
|
|
|
|
|
$catalogMetalIds = [1];
|
|
|
|
|
}
|
|
|
|
|
$limanMetalBuckets = $balanceReportService->initMetalBalanceBuckets($catalogMetalIds);
|
|
|
|
|
$kalhorMetalBuckets = $balanceReportService->initMetalBalanceBuckets($catalogMetalIds);
|
2026-06-04 12:18:53 +03:30
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
$response["pay_at_update"] = Order::whereNull('pay_at')->update(['pay_at' => DB::raw('created_at')]);
|
|
|
|
|
//liman
|
2026-06-04 12:18:53 +03:30
|
|
|
$response["liman"]["orders"] = Order::with(['transactions.product', 'customer'])
|
|
|
|
|
->where('customer_id', '!=', $excludeCustomerId)
|
|
|
|
|
->where('sts', 2)
|
|
|
|
|
->whereBetween('pay_at', [$startDate, $endDate])
|
|
|
|
|
->get();
|
2026-01-24 14:03:11 +00:00
|
|
|
$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) {
|
2026-06-23 20:35:13 +03:30
|
|
|
$balanceReportService->applyOrderToBuckets($limanMetalBuckets, $OrderGoldBalanceRow);
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
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;
|
2026-06-23 20:35:13 +03:30
|
|
|
$balanceReportService->applyKalhorSlice(
|
|
|
|
|
$kalhorMetalBuckets,
|
|
|
|
|
(float) $data['Count'],
|
|
|
|
|
(float) $data['TotalPrice'],
|
|
|
|
|
2
|
|
|
|
|
);
|
2026-01-24 14:03:11 +00:00
|
|
|
} elseif ($data['DealTypeStr'] == "فروش" or $data['DealTypeStr'] == "فروش _ دستی") {
|
|
|
|
|
$response["kalhor"]["balance"]['gold'] -= $data['Count'];
|
|
|
|
|
$response["kalhor"]["balance"]['cash'] += $data['TotalPrice'];
|
|
|
|
|
$direction =1;
|
2026-06-23 20:35:13 +03:30
|
|
|
$balanceReportService->applyKalhorSlice(
|
|
|
|
|
$kalhorMetalBuckets,
|
|
|
|
|
(float) $data['Count'],
|
|
|
|
|
(float) $data['TotalPrice'],
|
|
|
|
|
1
|
|
|
|
|
);
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
|
|
|
|
$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"];
|
2026-06-23 20:35:13 +03:30
|
|
|
|
|
|
|
|
$metalBalances = $balanceReportService->buildMetalBalancesResponse($limanMetalBuckets, $kalhorMetalBuckets);
|
|
|
|
|
$response['metalBalances'] = $metalBalances['items'];
|
|
|
|
|
if (!empty($metalBalances['items'])) {
|
|
|
|
|
$response['goldBalance'] = $metalBalances['totalGoldBalance'];
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
//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;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
/**
|
|
|
|
|
* محصول مقداری/آبشدهای که کارمزد مبلغمحور از کیف همان محصول کسر میشود.
|
|
|
|
|
* (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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
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
|
2026-07-23 11:09:31 +03:30
|
|
|
$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();
|
2026-01-24 14:03:11 +00:00
|
|
|
|
|
|
|
|
// Initialize response data
|
|
|
|
|
$response = [
|
|
|
|
|
"fullPriceM1" => 0,
|
|
|
|
|
"fullPriceM2" => 0,
|
|
|
|
|
"fullPriceSood" => 0,
|
|
|
|
|
"fullPriceKarmozd" => 0,
|
|
|
|
|
"orderCount" => $ords->count(),
|
|
|
|
|
"Gifts" => 0,
|
|
|
|
|
"GiftsPrise" => 0,
|
2026-07-10 23:29:36 +03:30
|
|
|
"sumAll" => 0,
|
|
|
|
|
// تفکیک سود بر اساس سمت کسر کارمزد
|
2026-07-23 11:09:31 +03:30
|
|
|
"profitGoldGrams" => 0, // سود طلایی/فلزی: مجموع کارمزدهای کسرشده از فلز (گرم)
|
2026-07-10 23:29:36 +03:30
|
|
|
"profitRial" => 0, // سود ریالی: مجموع کارمزدهای کسرشده از ریال (ریال)
|
|
|
|
|
"profitGoldValueRial" => 0,
|
2026-07-23 11:09:31 +03:30
|
|
|
/** @var array<int, array{product_id:int,title:string,group_type:?string,grams:float,value_rial:int,unit:string}> */
|
|
|
|
|
"profitQuantityProducts" => [],
|
2026-07-10 23:29:36 +03:30
|
|
|
"daily" => [],
|
2026-01-24 14:03:11 +00:00
|
|
|
];
|
|
|
|
|
|
2026-07-25 16:47:30 +03:30
|
|
|
// هدیه = فقط پاداش معرفی/ثبتنام و سود زیرمجموعه.
|
|
|
|
|
// معاملهٔ مشتری و شارژ دستی طلا توسط ادمین هدیه نیستند.
|
|
|
|
|
$giftsGold = (float) Transaction::query()
|
|
|
|
|
->whereIn('type', self::GIFT_TRANSACTION_TYPES)
|
|
|
|
|
->whereBetween('created_at', [$startDate, $endDate])
|
|
|
|
|
->sum('gold');
|
2026-06-11 16:41:28 +03:30
|
|
|
$response['Gifts'] = number_format($giftsGold, 3);
|
2026-01-24 14:03:11 +00:00
|
|
|
|
2026-06-11 16:41:28 +03:30
|
|
|
$lastGoldPrice = (float) (Price::latest()->first()?->price ?? 0);
|
|
|
|
|
$response['GiftsPrise'] = $lastGoldPrice > 0 ? (int) floor($giftsGold * $lastGoldPrice) : 0;
|
2026-07-23 11:09:31 +03:30
|
|
|
|
|
|
|
|
/** @var array<string, array{product_id:int,title:string,group_type:?string,grams:float,unit_price:float,unit:string}> $quantityBuckets */
|
|
|
|
|
$quantityBuckets = [];
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
// Calculate order-related sums
|
2026-07-10 23:29:36 +03:30
|
|
|
$daily = [];
|
2026-01-24 14:03:11 +00:00
|
|
|
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;
|
2026-07-10 23:29:36 +03:30
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
// تفکیک کارمزد به فلز/ریال بر اساس سمت کسر
|
|
|
|
|
// fee_asset === 'gold' → کارمزد از فلز/آبشده گرفته شده (گرم)؛ در غیر این صورت ریالی محسوب میشود.
|
2026-07-10 23:29:36 +03:30
|
|
|
$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;
|
|
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 23:29:36 +03:30
|
|
|
$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,
|
2026-07-23 11:09:31 +03:30
|
|
|
'profit_by_product' => [],
|
2026-07-10 23:29:36 +03:30
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
$daily[$dateKey]['profit_gold'] += $goldFeeGrams;
|
|
|
|
|
$daily[$dateKey]['profit_rial'] += $rialFee;
|
|
|
|
|
$daily[$dateKey]['spread_rial'] += (float) $ord->total_fee;
|
|
|
|
|
$daily[$dateKey]['order_count']++;
|
2026-07-23 11:09:31 +03:30
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-07-10 23:29:36 +03:30
|
|
|
|
|
|
|
|
$response['profitGoldGrams'] = round($response['profitGoldGrams'], 4);
|
2026-07-11 22:31:16 +03:30
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
// سندهای تبدیل سود ریالی به طلا — فقط برای فلز اصلی طلا معنا دارد
|
|
|
|
|
$mainMetal = trim((string) self::Config('main_metal'));
|
|
|
|
|
$applyProfitConversions = $mainMetal === '' || $mainMetal === 'طلا';
|
|
|
|
|
|
2026-07-11 22:31:16 +03:30
|
|
|
$convertedRial = 0.0;
|
|
|
|
|
$convertedGram = 0.0;
|
|
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
if ($applyProfitConversions) {
|
|
|
|
|
$conversions = ProfitConversion::whereBetween('converted_at', [$startDate, $endDate])->get();
|
2026-07-11 22:31:16 +03:30
|
|
|
|
2026-07-23 11:09:31 +03:30
|
|
|
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;
|
2026-07-11 22:31:16 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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);
|
2026-07-23 11:09:31 +03:30
|
|
|
|
|
|
|
|
$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;
|
|
|
|
|
}
|
2026-07-10 23:29:36 +03:30
|
|
|
|
2026-07-11 22:31:16 +03:30
|
|
|
krsort($daily);
|
|
|
|
|
$response['daily'] = array_map(function ($row) {
|
|
|
|
|
$row['profit_gold'] = round($row['profit_gold'], 4);
|
|
|
|
|
$row['profit_rial'] = round($row['profit_rial'], 0);
|
2026-07-23 11:09:31 +03:30
|
|
|
$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;
|
2026-07-11 22:31:16 +03:30
|
|
|
return $row;
|
|
|
|
|
}, array_values($daily));
|
|
|
|
|
|
2026-07-01 22:27:51 +03:30
|
|
|
$response['totalTradingProfit'] = $response['fullPriceSood'] + $response['fullPriceKarmozd'];
|
|
|
|
|
$response['sumAll'] = $response['fullPriceM1'] - $response['GiftsPrise'];
|
2026-01-24 14:03:11 +00:00
|
|
|
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;$i<count($query);$i++){
|
|
|
|
|
$query[$i]['month_fa'] = Verta::instance( $query[$i]['month'])->format('%B');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return response()->json(array_values($send));
|
|
|
|
|
}
|
2026-06-21 21:51:18 +03:30
|
|
|
|
|
|
|
|
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')),
|
|
|
|
|
]);
|
|
|
|
|
}
|
2026-07-10 23:29:36 +03:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* لیست سندهای تبدیل سود ریالی به طلا در یک بازه + جمع کل.
|
|
|
|
|
*/
|
|
|
|
|
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]);
|
|
|
|
|
}
|
2026-08-03 18:44:27 +03:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* تطبیق دفتر اکانت روی نود همکار (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);
|
|
|
|
|
}
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|