'طلا', 2 => 'نقره', 3 => 'مس', ]; /** @return list */ 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 */ 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 */ 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 $metalIds * @return array */ 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 $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 $limanBuckets * @param array $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 $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; } public function resolveTransactionMetalId(mixed $transaction): int { $product = $this->transactionProduct($transaction); if ($product !== null) { $metalId = (int) ($product->metal_id ?? 0); return $metalId > 0 ? $metalId : 1; } return 1; } 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 $catalogMetalIds * @return array */ 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 $catalogMetalIds */ public function enrichTransactionTableRow(mixed &$row, array $catalogMetalIds): void { $metalMap = $this->buildTransactionMetalDisplayMap($row, $catalogMetalIds); $metalId = $this->resolveTransactionMetalId($row); $metalLabel = $this->metalLabel($metalId); if (is_array($row)) { $row['metal_gold'] = $metalMap; $row['transaction_metal_id'] = $metalId; $row['transaction_metal_label'] = $metalLabel; 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; 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; } public function enrichOrderTableRow(mixed &$row): void { $product = $this->resolveOrderProduct($row); $unit = $this->resolveOrderAmountUnit($product); $amount = (float) ($this->lineValue($row, 'amount') ?? 0); $displayAmount = $this->resolveOrderDisplayAmount($amount, $product); if (is_array($row)) { $row['amount_unit'] = $unit; $row['amount_display'] = $displayAmount; return; } if (is_object($row)) { $row->amount_unit = $unit; $row->amount_display = $displayAmount; } } 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; } 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; } }