2026-06-23 20:35:13 +03:30
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
2026-06-23 21:21:04 +03:30
|
|
|
use App\Http\Controllers\Controller;
|
2026-06-23 20:35:13 +03:30
|
|
|
use App\Models\Order;
|
|
|
|
|
use App\Models\Product;
|
|
|
|
|
|
|
|
|
|
class BalanceReportService
|
|
|
|
|
{
|
|
|
|
|
public const METAL_LABELS = [
|
|
|
|
|
1 => 'طلا',
|
|
|
|
|
2 => 'نقره',
|
|
|
|
|
3 => 'مس',
|
|
|
|
|
];
|
|
|
|
|
|
2026-06-24 01:22:56 +03:30
|
|
|
public function __construct(
|
|
|
|
|
private readonly ConfigService $configService
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 20:35:13 +03:30
|
|
|
/** @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}";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 21:04:29 +03:30
|
|
|
/** واحد نمایش موجودی/تراز هر فلز در ادمین — مس کیلوگرم، بقیه گرم */
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 20:35:13 +03:30
|
|
|
/**
|
|
|
|
|
* @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;
|
|
|
|
|
}
|
2026-06-23 21:21:04 +03:30
|
|
|
|
|
|
|
|
public function resolveTransactionMetalId(mixed $transaction): int
|
|
|
|
|
{
|
|
|
|
|
$product = $this->transactionProduct($transaction);
|
|
|
|
|
if ($product !== null) {
|
|
|
|
|
$metalId = (int) ($product->metal_id ?? 0);
|
|
|
|
|
|
|
|
|
|
return $metalId > 0 ? $metalId : 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:22:56 +03:30
|
|
|
$walletProduct = $this->transactionWalletProduct($transaction);
|
|
|
|
|
if ($walletProduct !== null) {
|
|
|
|
|
$metalId = (int) ($walletProduct->metal_id ?? 0);
|
|
|
|
|
|
|
|
|
|
return $metalId > 0 ? $metalId : 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->isMainWalletMetalTransaction($transaction)) {
|
|
|
|
|
return $this->configService->mainWalletMetalId();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 21:21:04 +03:30
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:22:56 +03:30
|
|
|
public function resolveTransactionDisplayTypeStr(mixed $transaction, ?int $metalId = null): ?string
|
|
|
|
|
{
|
2026-06-24 01:32:32 +03:30
|
|
|
$type = $this->resolveTransactionType($transaction);
|
|
|
|
|
if (in_array($type, [3, 4, 8, 9], true)) {
|
2026-06-24 01:22:56 +03:30
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:32:32 +03:30
|
|
|
$currentLabel = trim((string) ($this->lineValue($transaction, 'type_str') ?? ''));
|
|
|
|
|
$prefix = $this->resolveTransactionActionPrefix($transaction);
|
|
|
|
|
$productLabel = $this->resolveTransactionProductLabel($transaction);
|
2026-06-24 01:22:56 +03:30
|
|
|
|
2026-06-24 01:32:32 +03:30
|
|
|
if ($prefix !== null && $productLabel !== null) {
|
|
|
|
|
return trim($prefix . ' ' . $productLabel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($prefix !== null) {
|
|
|
|
|
$metalId ??= $this->resolveTransactionMetalId($transaction);
|
|
|
|
|
|
|
|
|
|
return trim($prefix . ' ' . $this->metalLabel($metalId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($currentLabel === 'تعریف نشده' || $currentLabel === '') {
|
|
|
|
|
return $this->resolveFallbackMetalTransactionLabel($transaction, $metalId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->shouldReplaceGenericTransactionLabel($currentLabel) && $productLabel !== null) {
|
|
|
|
|
$fallbackPrefix = $this->resolveTransactionActionPrefix($transaction)
|
|
|
|
|
?? $this->inferActionPrefixFromLabel($currentLabel)
|
|
|
|
|
?? $this->inferActionPrefixFromGold($transaction);
|
|
|
|
|
|
|
|
|
|
if ($fallbackPrefix !== null) {
|
|
|
|
|
return trim($fallbackPrefix . ' ' . $productLabel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveFallbackMetalTransactionLabel(mixed $transaction, ?int $metalId = null): ?string
|
|
|
|
|
{
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
|
|
|
|
if (abs($gold) < 0.0000001) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$prefix = $this->resolveTransactionActionPrefix($transaction)
|
|
|
|
|
?? $this->inferActionPrefixFromGold($transaction);
|
|
|
|
|
if ($prefix === null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$productLabel = $this->resolveTransactionProductLabel($transaction);
|
|
|
|
|
if ($productLabel !== null) {
|
|
|
|
|
return trim($prefix . ' ' . $productLabel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$metalId ??= $this->resolveTransactionMetalId($transaction);
|
|
|
|
|
|
|
|
|
|
return trim($prefix . ' ' . $this->metalLabel($metalId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveTransactionType(mixed $transaction): int
|
|
|
|
|
{
|
|
|
|
|
$type = $this->lineValue($transaction, 'type');
|
|
|
|
|
if ($type !== null && $type !== '' && is_numeric($type)) {
|
|
|
|
|
return (int) $type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveTransactionActionPrefix(mixed $transaction): ?string
|
|
|
|
|
{
|
|
|
|
|
$type = $this->resolveTransactionType($transaction);
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
|
|
|
|
|
|
|
|
|
return match (true) {
|
|
|
|
|
$type === 1 => 'خرید',
|
|
|
|
|
$type === 2 => 'فروش',
|
|
|
|
|
$type === 51 => 'اصلاح',
|
|
|
|
|
in_array($type, [6, 10], true) => 'برداشت',
|
|
|
|
|
$type === 5 => 'واریز',
|
|
|
|
|
$type === 7 => $gold >= 0 ? 'واریز' : 'برداشت',
|
|
|
|
|
$type === 12 => 'برداشت وجه',
|
|
|
|
|
abs($gold) > 0 => $gold > 0 ? 'واریز' : 'برداشت',
|
2026-06-24 01:22:56 +03:30
|
|
|
default => null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:32:32 +03:30
|
|
|
private function inferActionPrefixFromGold(mixed $transaction): ?string
|
|
|
|
|
{
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
|
|
|
|
if (abs($gold) < 0.0000001) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $gold > 0 ? 'واریز' : 'برداشت';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function inferActionPrefixFromLabel(string $label): ?string
|
|
|
|
|
{
|
|
|
|
|
if (preg_match('/^خرید/u', $label)) {
|
|
|
|
|
return 'خرید';
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^فروش/u', $label)) {
|
|
|
|
|
return 'فروش';
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^برداشت/u', $label)) {
|
|
|
|
|
return 'برداشت';
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^(واریز|موجودی)/u', $label)) {
|
|
|
|
|
return 'واریز';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function shouldReplaceGenericTransactionLabel(string $label): bool
|
|
|
|
|
{
|
|
|
|
|
if ($label === 'تعریف نشده') {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$generic = [
|
|
|
|
|
'واریز',
|
|
|
|
|
'برداشت',
|
|
|
|
|
'واریز طلایی',
|
|
|
|
|
'برداشت طلایی',
|
|
|
|
|
'خرید طلا',
|
|
|
|
|
'فروش طلا',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return in_array($label, $generic, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function resolveTransactionProductLabel(mixed $transaction): ?string
|
|
|
|
|
{
|
|
|
|
|
$product = $this->transactionProduct($transaction);
|
|
|
|
|
if ($product !== null && !empty($product->title)) {
|
|
|
|
|
return trim((string) $product->title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$walletProduct = $this->transactionWalletProduct($transaction);
|
|
|
|
|
if ($walletProduct !== null && !empty($walletProduct->title)) {
|
|
|
|
|
return trim((string) $walletProduct->title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($this->isMainWalletMetalTransaction($transaction)) {
|
|
|
|
|
$labels = $this->configService->mainWalletProductLabels();
|
|
|
|
|
|
|
|
|
|
return $labels !== '' ? $labels : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 21:21:04 +03:30
|
|
|
public function transactionGoldGrams(mixed $transaction): float
|
|
|
|
|
{
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
|
|
|
|
if (abs($gold) < 0.0000001) {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product = $this->transactionProduct($transaction);
|
|
|
|
|
if ($product !== null) {
|
|
|
|
|
return Controller::productLineWeightToGrams($product, $gold);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Controller::weightToGrams($gold);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param list<int> $catalogMetalIds
|
|
|
|
|
* @return array<string, float>
|
|
|
|
|
*/
|
|
|
|
|
public function buildTransactionMetalDisplayMap(mixed $transaction, array $catalogMetalIds): array
|
|
|
|
|
{
|
|
|
|
|
$map = [];
|
|
|
|
|
foreach ($catalogMetalIds as $metalId) {
|
|
|
|
|
$map[(string) (int) $metalId] = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$type = (int) ($this->lineValue($transaction, 'type') ?? 0);
|
|
|
|
|
if (in_array($type, [3, 4], true)) {
|
|
|
|
|
return $map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
|
|
|
|
if (abs($gold) < 0.0000001) {
|
|
|
|
|
return $map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$metalId = $this->resolveTransactionMetalId($transaction);
|
|
|
|
|
$grams = abs($this->transactionGoldGrams($transaction));
|
|
|
|
|
$display = $this->gramsToMetalDisplay($grams, $metalId);
|
|
|
|
|
if ($gold < 0) {
|
|
|
|
|
$display *= -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$map[(string) $metalId] = round($display, 3);
|
|
|
|
|
|
|
|
|
|
return $map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param list<int> $catalogMetalIds
|
|
|
|
|
*/
|
|
|
|
|
public function enrichTransactionTableRow(mixed &$row, array $catalogMetalIds): void
|
|
|
|
|
{
|
|
|
|
|
$metalMap = $this->buildTransactionMetalDisplayMap($row, $catalogMetalIds);
|
|
|
|
|
$metalId = $this->resolveTransactionMetalId($row);
|
|
|
|
|
$metalLabel = $this->metalLabel($metalId);
|
2026-06-24 01:22:56 +03:30
|
|
|
$displayType = $this->resolveTransactionDisplayTypeStr($row, $metalId);
|
2026-06-24 01:32:32 +03:30
|
|
|
$currentTypeStr = trim((string) ($this->lineValue($row, 'type_str') ?? ''));
|
|
|
|
|
|
|
|
|
|
if ($displayType === null && ($currentTypeStr === 'تعریف نشده' || $currentTypeStr === '')) {
|
|
|
|
|
$displayType = $this->resolveFallbackMetalTransactionLabel($row, $metalId);
|
|
|
|
|
}
|
2026-06-23 21:21:04 +03:30
|
|
|
|
|
|
|
|
if (is_array($row)) {
|
|
|
|
|
$row['metal_gold'] = $metalMap;
|
|
|
|
|
$row['transaction_metal_id'] = $metalId;
|
|
|
|
|
$row['transaction_metal_label'] = $metalLabel;
|
2026-06-24 01:22:56 +03:30
|
|
|
if ($displayType !== null) {
|
|
|
|
|
$row['type_str'] = $displayType;
|
|
|
|
|
}
|
2026-06-23 21:21:04 +03:30
|
|
|
foreach ($catalogMetalIds as $mid) {
|
|
|
|
|
$row['metal_gold_' . $mid] = $metalMap[(string) $mid] ?? 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($row)) {
|
|
|
|
|
$row->metal_gold = $metalMap;
|
|
|
|
|
$row->transaction_metal_id = $metalId;
|
|
|
|
|
$row->transaction_metal_label = $metalLabel;
|
2026-06-24 01:22:56 +03:30
|
|
|
if ($displayType !== null) {
|
|
|
|
|
$row->type_str = $displayType;
|
|
|
|
|
}
|
2026-06-23 21:21:04 +03:30
|
|
|
foreach ($catalogMetalIds as $mid) {
|
|
|
|
|
$row->{'metal_gold_' . $mid} = $metalMap[(string) $mid] ?? 0.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function transactionProduct(mixed $transaction): ?object
|
|
|
|
|
{
|
|
|
|
|
if (is_object($transaction) && isset($transaction->product)) {
|
|
|
|
|
return $transaction->product;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_array($transaction) && isset($transaction['product'])) {
|
|
|
|
|
$product = $transaction['product'];
|
|
|
|
|
|
|
|
|
|
return is_object($product) ? $product : (object) $product;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:22:56 +03:30
|
|
|
private function transactionWalletProduct(mixed $transaction): ?object
|
|
|
|
|
{
|
|
|
|
|
$wallet = $this->lineValue($transaction, 'wallet');
|
|
|
|
|
if ($wallet === null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($wallet)) {
|
|
|
|
|
return isset($wallet->product) ? $wallet->product : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_array($wallet) && isset($wallet['product'])) {
|
|
|
|
|
$product = $wallet['product'];
|
|
|
|
|
|
|
|
|
|
return is_object($product) ? $product : (object) $product;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function isMainWalletMetalTransaction(mixed $transaction): bool
|
|
|
|
|
{
|
|
|
|
|
$wallet = $this->lineValue($transaction, 'wallet');
|
|
|
|
|
if ($wallet === null) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($wallet)) {
|
2026-06-24 01:32:32 +03:30
|
|
|
if ($wallet->product_id !== null || $wallet->fund_id !== null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} elseif (is_array($wallet)) {
|
|
|
|
|
if (($wallet['product_id'] ?? null) !== null || ($wallet['fund_id'] ?? null) !== null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return true;
|
2026-06-24 01:22:56 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-24 01:32:32 +03:30
|
|
|
$type = $this->resolveTransactionType($transaction);
|
|
|
|
|
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
|
2026-06-24 01:22:56 +03:30
|
|
|
|
2026-06-24 01:32:32 +03:30
|
|
|
return in_array($type, [1, 2, 5, 6, 7, 10, 51], true) || abs($gold) > 0;
|
2026-06-24 01:22:56 +03:30
|
|
|
}
|
|
|
|
|
|
2026-06-23 21:40:33 +03:30
|
|
|
public function resolveOrderMetalId(mixed $order): int
|
|
|
|
|
{
|
|
|
|
|
$product = $this->resolveOrderProduct($order);
|
|
|
|
|
if ($product !== null) {
|
|
|
|
|
$metalId = (int) ($product->metal_id ?? 0);
|
|
|
|
|
|
|
|
|
|
return $metalId > 0 ? $metalId : 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param list<int> $catalogMetalIds
|
|
|
|
|
*/
|
|
|
|
|
public function enrichOrderTableRow(mixed &$row, array $catalogMetalIds = []): void
|
2026-06-23 21:28:49 +03:30
|
|
|
{
|
|
|
|
|
$product = $this->resolveOrderProduct($row);
|
|
|
|
|
$unit = $this->resolveOrderAmountUnit($product);
|
|
|
|
|
$amount = (float) ($this->lineValue($row, 'amount') ?? 0);
|
|
|
|
|
$displayAmount = $this->resolveOrderDisplayAmount($amount, $product);
|
2026-06-23 21:40:33 +03:30
|
|
|
$metalId = $this->resolveOrderMetalId($row);
|
|
|
|
|
$metalLabel = $this->metalLabel($metalId);
|
|
|
|
|
$price = (float) ($this->lineValue($row, 'price') ?? 0);
|
|
|
|
|
|
|
|
|
|
$metalPrices = [];
|
|
|
|
|
foreach ($catalogMetalIds as $mid) {
|
|
|
|
|
$mid = (int) $mid;
|
|
|
|
|
$metalPrices[(string) $mid] = $mid === $metalId ? $price : 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$payload = [
|
|
|
|
|
'amount_unit' => $unit,
|
|
|
|
|
'amount_display' => $displayAmount,
|
|
|
|
|
'order_metal_id' => $metalId,
|
|
|
|
|
'order_metal_label' => $metalLabel,
|
|
|
|
|
'metal_prices' => $metalPrices,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($catalogMetalIds as $mid) {
|
|
|
|
|
$payload['metal_price_' . (int) $mid] = $metalPrices[(string) (int) $mid] ?? 0.0;
|
|
|
|
|
}
|
2026-06-23 21:28:49 +03:30
|
|
|
|
|
|
|
|
if (is_array($row)) {
|
2026-06-23 21:40:33 +03:30
|
|
|
foreach ($payload as $key => $value) {
|
|
|
|
|
$row[$key] = $value;
|
|
|
|
|
}
|
2026-06-23 21:28:49 +03:30
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($row)) {
|
2026-06-23 21:40:33 +03:30
|
|
|
foreach ($payload as $key => $value) {
|
|
|
|
|
$row->{$key} = $value;
|
|
|
|
|
}
|
2026-06-23 21:28:49 +03:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function resolveOrderAmountUnit(?object $product): string
|
|
|
|
|
{
|
|
|
|
|
if ($product === null) {
|
|
|
|
|
return Controller::tradeWeightUnit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Controller::productQuantityUnit(Controller::productUnitPayload($product));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveOrderDisplayAmount(float $amount, ?object $product): float
|
|
|
|
|
{
|
|
|
|
|
if ($product === null || abs($amount) < 0.0000001) {
|
|
|
|
|
return round($amount, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$payload = Controller::productUnitPayload($product);
|
|
|
|
|
$productUnit = trim((string) ($payload['unit'] ?? ''));
|
|
|
|
|
if (!Controller::isWeightBasedProductUnit($productUnit)) {
|
|
|
|
|
return round($amount, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tradeUnit = Controller::tradeWeightUnit();
|
|
|
|
|
if ($productUnit !== $tradeUnit) {
|
|
|
|
|
return round(Controller::convertWeight($amount, $tradeUnit, $productUnit), 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return round($amount, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function resolveOrderProduct(mixed $order): ?object
|
|
|
|
|
{
|
|
|
|
|
$transactions = $this->lineValue($order, 'transactions');
|
|
|
|
|
if ($transactions instanceof \Illuminate\Support\Collection) {
|
|
|
|
|
$transactions = $transactions->all();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!is_array($transactions)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($transactions as $tx) {
|
|
|
|
|
$product = is_object($tx) ? ($tx->product ?? null) : ($tx['product'] ?? null);
|
|
|
|
|
if ($product) {
|
|
|
|
|
return is_object($product) ? $product : (object) $product;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 21:21:04 +03:30
|
|
|
private function lineValue(mixed $line, string $key): mixed
|
|
|
|
|
{
|
|
|
|
|
if (is_array($line)) {
|
|
|
|
|
return $line[$key] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_object($line)) {
|
|
|
|
|
return $line->{$key} ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-06-23 20:35:13 +03:30
|
|
|
}
|