Files
boursecu_server/app/Services/BalanceReportService.php
T
2026-06-23 21:04:29 +03:30

252 lines
7.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Order;
use App\Models\Product;
class BalanceReportService
{
public const METAL_LABELS = [
1 => 'طلا',
2 => 'نقره',
3 => 'مس',
];
/** @return list<int> */
public function catalogMetalIds(): array
{
return Product::query()
->where('active', 1)
->whereNotNull('metal_id')
->where('metal_id', '>', 0)
->distinct()
->orderBy('metal_id')
->pluck('metal_id')
->map(static fn ($id) => (int) $id)
->values()
->all();
}
public function metalLabel(int $metalId): string
{
return self::METAL_LABELS[$metalId] ?? "فلز {$metalId}";
}
/** واحد نمایش موجودی/تراز هر فلز در ادمین — مس کیلوگرم، بقیه گرم */
public function metalDisplayUnit(int $metalId): string
{
return (int) $metalId === 3 ? 'کیلوگرم' : 'گرم';
}
public function gramsToMetalDisplay(float $grams, int $metalId): float
{
return (int) $metalId === 3 ? $grams / 1000.0 : $grams;
}
/**
* @return array<int, string>
*/
public function catalogMetalsMeta(): array
{
$items = [];
foreach ($this->catalogMetalIds() as $metalId) {
$metalId = (int) $metalId;
$items[] = [
'metal_id' => $metalId,
'label' => $this->metalLabel($metalId),
'display_unit' => $this->metalDisplayUnit($metalId),
];
}
return $items;
}
/**
* @return array<int, array{gold: float, cash: float}>
*/
public function resolveOrderMetalSlices(Order $order): array
{
$order->loadMissing(['transactions.product']);
$slices = [];
$txWithProduct = $order->transactions->filter(
static fn ($tx) => $tx->product_id && abs((float) ($tx->gold ?? 0)) > 0
);
if ($txWithProduct->isNotEmpty()) {
foreach ($txWithProduct as $tx) {
$metalId = (int) ($tx->product?->metal_id ?? 1);
if ($metalId <= 0) {
$metalId = 1;
}
$gold = abs((float) ($tx->gold ?? 0));
if ($gold <= 0) {
continue;
}
$slices[$metalId] = ($slices[$metalId] ?? 0.0) + $gold;
}
$totalGold = array_sum($slices);
$result = [];
foreach ($slices as $metalId => $gold) {
$cashShare = $totalGold > 0
? (float) $order->total_price * ($gold / $totalGold)
: 0.0;
$result[(int) $metalId] = ['gold' => $gold, 'cash' => $cashShare];
}
return $result;
}
$amount = (float) ($order->amount ?? 0);
$totalPrice = (float) ($order->total_price ?? 0);
if ($amount <= 0) {
return [];
}
$metalId = 1;
$tx = $order->transactions->first(static fn ($t) => $t->product_id);
if ($tx?->product) {
$metalId = max(1, (int) ($tx->product->metal_id ?? 1));
}
return [$metalId => ['gold' => $amount, 'cash' => $totalPrice]];
}
/**
* @param list<int> $metalIds
* @return array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}>
*/
public function initMetalBalanceBuckets(array $metalIds): array
{
$buckets = [];
foreach ($metalIds as $metalId) {
$buckets[(int) $metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
return $buckets;
}
/**
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $buckets
*/
public function applyOrderToBuckets(array &$buckets, Order $order): void
{
$slices = $this->resolveOrderMetalSlices($order);
foreach ($slices as $metalId => $slice) {
$metalId = (int) $metalId;
if (! isset($buckets[$metalId])) {
$buckets[$metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
$gold = (float) $slice['gold'];
$cash = (float) $slice['cash'];
if ((int) $order->direction === 1) {
$buckets[$metalId]['gold'] += $gold;
$buckets[$metalId]['cash'] -= $cash;
} elseif ((int) $order->direction === 2) {
$buckets[$metalId]['gold'] -= $gold;
$buckets[$metalId]['cash'] += $cash;
}
$buckets[$metalId]['gold_abs'] += $gold;
$buckets[$metalId]['cash_abs'] += $cash;
}
}
/**
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $limanBuckets
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $kalhorBuckets
*/
public function buildMetalBalancesResponse(array $limanBuckets, array $kalhorBuckets): array
{
$metalIds = array_values(array_unique(array_merge(
array_keys($limanBuckets),
array_keys($kalhorBuckets)
)));
sort($metalIds);
$items = [];
$totalGoldBalance = 0.0;
foreach ($metalIds as $metalId) {
$metalId = (int) $metalId;
$liman = $limanBuckets[$metalId] ?? [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
$kalhor = $kalhorBuckets[$metalId] ?? [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
$goldBalance = -$liman['gold'] + $kalhor['gold'];
$cashBalance = -$liman['cash'] + $kalhor['cash'];
$goldBalanceOfLiman = -$liman['gold'];
$goldBalanceOfKalhor = $kalhor['gold'];
$items[] = [
'metal_id' => $metalId,
'label' => $this->metalLabel($metalId),
'balance' => round($goldBalance, 4),
'goldBalance' => round($goldBalance, 4),
'cashBalance' => round($cashBalance, 2),
'goldBalanceOfLiman' => round($goldBalanceOfLiman, 4),
'goldBalanceOfKalhor' => round($goldBalanceOfKalhor, 4),
];
$totalGoldBalance += $goldBalance;
}
return [
'items' => $items,
'totalGoldBalance' => round($totalGoldBalance, 4),
];
}
/** @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $buckets */
public function applyKalhorSlice(
array &$buckets,
float $gold,
float $cash,
int $direction,
int $metalId = 1
): void {
if (! isset($buckets[$metalId])) {
$buckets[$metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
if ($direction === 2) {
$buckets[$metalId]['gold'] += $gold;
$buckets[$metalId]['cash'] -= $cash;
} elseif ($direction === 1) {
$buckets[$metalId]['gold'] -= $gold;
$buckets[$metalId]['cash'] += $cash;
}
$buckets[$metalId]['gold_abs'] += $gold;
$buckets[$metalId]['cash_abs'] += $cash;
}
}