configService->mainWalletProductIds(); } /** @param Collection|array|null $wallets */ public function mainWalletCash(Collection|array|null $wallets, ?int $ownerType = null): float { $line = $this->mainWalletLine($wallets, $ownerType); return (float) ($this->lineValue($line, 'cash') ?? 0); } /** @param Collection|array|null $wallets */ public function mainWalletGold(Collection|array|null $wallets, ?int $ownerType = null): float { $line = $this->mainWalletLine($wallets, $ownerType); return (float) ($this->lineValue($line, 'gold') ?? 0); } /** * موجودی مؤثر کیف اصلی برای نمایش آبشده = * gold کیف اصلی + gold کیف‌های legacy محصولات main_wallet_products. * * @param int|null $ownerType اگر داده شود فقط همان owner_type جمع می‌شود (هم‌تراز wallet/me). */ public function effectiveMainWalletGold(int $ownerId, ?int $ownerType = null): float { $mainQuery = Wallet::query() ->where('owner', $ownerId) ->whereNull('fund_id') ->whereNull('product_id'); if ($ownerType !== null) { $mainQuery->where('owner_type', $ownerType); } $mainGold = (float) $mainQuery->sum('gold'); $productIds = $this->mainWalletProductIds(); if ($productIds === []) { return round($mainGold, 3); } $orphanedQuery = Wallet::query() ->where('owner', $ownerId) ->whereNull('fund_id') ->whereIn('product_id', $productIds); if ($ownerType !== null) { $orphanedQuery->where('owner_type', $ownerType); } $orphanedGold = (float) $orphanedQuery->sum('gold'); return round($mainGold + $orphanedGold, 3); } /** * جمع طلای قابل‌نمایش: کیف اصلی + کیف محصولات + قلک‌ها. * کیف‌های legacy محصولات main_wallet_products شمرده نمی‌شوند. * * @param Collection|array|null $wallets */ public function totalGoldWeight( Collection|array|null $wallets, ?array $mainWalletProductIds = null ): float { $mainWalletProductIds ??= $this->mainWalletProductIds(); $sum = 0.0; foreach ($this->walletLines($wallets) as $line) { $fundId = $this->lineValue($line, 'fund_id'); $productId = (int) ($this->lineValue($line, 'product_id') ?? 0); $gold = (float) ($this->lineValue($line, 'gold') ?? 0); if ($gold <= 0) { continue; } if ($fundId !== null) { $sum += $gold; continue; } if ($productId === 0) { $sum += $gold; continue; } if ($mainWalletProductIds !== [] && in_array($productId, $mainWalletProductIds, true)) { continue; } $sum += $gold; } return $sum; } /** @return list */ public function catalogMetalIds(): array { $ids = $this->balanceReportService->catalogMetalIds(); return $ids !== [] ? $ids : [1]; } public function resolveMainWalletMetalId(): int { $mainWalletProductIds = $this->mainWalletProductIds(); if ($mainWalletProductIds !== []) { $product = Product::query() ->whereIn('id', $mainWalletProductIds) ->orderBy('id') ->first(); return max(1, (int) ($product?->metal_id ?? 1)); } $metalIds = $this->catalogMetalIds(); return count($metalIds) === 1 ? (int) $metalIds[0] : 1; } /** * فلز مرتبط با یک ردیف کیف در جدول ادمین. * * @param Collection|array|null $wallets */ public function resolveWalletLineMetalId(mixed $line, ?int $mainWalletMetalId = null): int { $fundId = $this->lineValue($line, 'fund_id'); $productId = $this->lineValue($line, 'product_id'); if ($fundId !== null) { return 1; } if ($productId !== null) { $product = $this->lineProduct($line); $metalId = (int) ($product?->metal_id ?? 0); return $metalId > 0 ? $metalId : 1; } return $mainWalletMetalId ?? $this->resolveMainWalletMetalId(); } /** * @param list $catalogMetalIds * @return array */ public function walletLineMetalGoldMap( mixed $line, array $catalogMetalIds, ?int $mainWalletMetalId = null ): array { $map = []; foreach ($catalogMetalIds as $metalId) { $map[(string) $metalId] = 0.0; } $goldGrams = $this->walletLineGoldGrams($line); if ($goldGrams <= 0) { return $map; } $metalId = $this->resolveWalletLineMetalId($line, $mainWalletMetalId); $map[(string) $metalId] = round( $this->balanceReportService->gramsToMetalDisplay($goldGrams, $metalId), 3 ); return $map; } /** * @param Collection|array $walletLines * @param list $catalogMetalIds * @return array جمع به گرم */ public function sumWalletMetalGrams(Collection|array $walletLines, array $catalogMetalIds): array { $buckets = []; foreach ($catalogMetalIds as $metalId) { $buckets[(string) (int) $metalId] = 0.0; } $mainWalletMetalId = $this->resolveMainWalletMetalId(); foreach ($this->walletLines($walletLines) as $line) { $goldGrams = $this->walletLineGoldGrams($line); if ($goldGrams <= 0) { continue; } $metalId = (string) $this->resolveWalletLineMetalId($line, $mainWalletMetalId); $buckets[$metalId] = ($buckets[$metalId] ?? 0.0) + $goldGrams; } return $buckets; } /** * @param Collection|array $walletLines * @param list $catalogMetalIds * @return list */ public function buildWalletMetalSums(Collection|array $walletLines, array $catalogMetalIds): array { $gramBuckets = $this->sumWalletMetalGrams($walletLines, $catalogMetalIds); $items = []; foreach ($catalogMetalIds as $metalId) { $metalId = (int) $metalId; $goldGrams = (float) ($gramBuckets[(string) $metalId] ?? 0.0); $displayGold = round($this->balanceReportService->gramsToMetalDisplay($goldGrams, $metalId), 3); $price = $this->livePricePerGram($metalId); $items[] = [ 'metal_id' => $metalId, 'label' => $this->balanceReportService->metalLabel($metalId), 'gold' => $displayGold, 'display_unit' => $this->balanceReportService->metalDisplayUnit($metalId), 'goldToCash' => round($goldGrams * $price, 2), ]; } return $items; } /** * @param Collection|array $walletLines * @param list $catalogMetalIds * @return array مقادیر نمایشی */ public function buildCustomerWalletMetalDisplayMap(Collection|array $walletLines, array $catalogMetalIds): array { $gramBuckets = $this->sumWalletMetalGrams($walletLines, $catalogMetalIds); $map = []; foreach ($catalogMetalIds as $metalId) { $metalId = (int) $metalId; $goldGrams = (float) ($gramBuckets[(string) $metalId] ?? 0.0); $map[(string) $metalId] = round( $this->balanceReportService->gramsToMetalDisplay($goldGrams, $metalId), 3 ); } return $map; } /** * @param list $catalogMetalIds */ public function enrichWalletTableRow(mixed &$row, array $catalogMetalIds, int $mainWalletMetalId): void { $metalMap = $this->walletLineMetalGoldMap($row, $catalogMetalIds, $mainWalletMetalId); $meta = $this->buildWalletTableRowMeta($row, $mainWalletMetalId); if (is_array($row)) { $row['metal_gold'] = $metalMap; foreach ($catalogMetalIds as $metalId) { $row['metal_gold_' . $metalId] = $metalMap[(string) $metalId] ?? 0.0; } foreach ($meta as $key => $value) { $row[$key] = $value; } return; } if (is_object($row)) { $row->metal_gold = $metalMap; foreach ($catalogMetalIds as $metalId) { $row->{'metal_gold_' . $metalId} = $metalMap[(string) $metalId] ?? 0.0; } foreach ($meta as $key => $value) { $row->{$key} = $value; } } } /** * @return array */ private function buildWalletTableRowMeta(mixed $row, int $mainWalletMetalId): array { $fundId = $this->lineValue($row, 'fund_id'); $productId = $this->lineValue($row, 'product_id'); $product = $this->lineProduct($row); $fund = $this->lineFund($row); $cash = (float) ($this->lineValue($row, 'cash') ?? 0); if ($fundId !== null) { $kind = 'fund'; $kindLabel = 'قلک (صندوق)'; $fundTitle = trim((string) ($fund?->title ?? '')); $subject = $fundTitle !== '' ? "قلک «{$fundTitle}»" : 'قلک بدون عنوان'; } elseif ($productId === null) { $kind = 'main'; $kindLabel = 'کیف اصلی'; $subject = 'کیف اصلی — ریال و طلای آبشده'; } else { $kind = 'product'; $kindLabel = 'کیف محصول'; $title = trim((string) ($product?->title ?? '')); $subject = $title !== '' ? "محصول «{$title}»" : "محصول کد {$productId}"; } $metalId = $this->resolveWalletLineMetalId($row, $mainWalletMetalId); $goldGrams = $this->walletLineGoldGrams($row); $goldToCash = $goldGrams > 0 ? (int) round($goldGrams * $this->livePricePerGram($metalId)) : 0; return [ 'wallet_kind' => $kind, 'wallet_kind_label' => $kindLabel, 'wallet_subject' => $subject, 'product_title' => $product?->title ?? null, 'product_group_type' => $product?->group_type ?? null, 'product_unit' => $product?->unit ?? null, 'product_amount' => $product?->amount ?? null, 'fund_title' => $fund?->title ?? null, 'wallet_metal_id' => $metalId, 'wallet_metal_label' => $this->balanceReportService->metalLabel($metalId), 'gold_to_cash' => $goldToCash, 'gold_grams' => round($goldGrams, 3), 'total_rial_value' => (int) round($cash + $goldToCash), ]; } private function livePricePerGram(int $metalId): float { if ($metalId === 1) { $live = (int) app(ProductDisplayPriceService::class)->resolveAppBasePrice(); if ($live > 0) { return (float) $live; } return (float) (Price::latest('id')->value('price') ?? 0); } $product = Product::query() ->where('active', 1) ->where('metal_id', $metalId) ->where('price', '>', 0) ->orderBy('id') ->first(); if (!$product) { return 0.0; } $unitPrice = (float) $product->price + (float) ($product->price_profit ?? 0); $amount = (float) ($product->amount ?? 1); return $amount > 0 ? $unitPrice * $amount : $unitPrice; } private function lineProduct(mixed $line): ?object { if (is_object($line) && isset($line->product)) { return $line->product; } if (is_array($line) && isset($line['product'])) { $product = $line['product']; return is_object($product) ? $product : (object) $product; } return null; } private function lineFund(mixed $line): ?object { if (is_object($line) && isset($line->fund)) { return $line->fund; } if (is_array($line) && isset($line['fund'])) { $fund = $line['fund']; return is_object($fund) ? $fund : (object) $fund; } return null; } private function walletLineGoldGrams(mixed $line): float { $gold = (float) ($this->lineValue($line, 'gold') ?? 0); if ($gold <= 0) { return 0.0; } $product = $this->lineProduct($line); if ($product !== null) { return Controller::productLineWeightToGrams($product, $gold); } return Controller::weightToGrams($gold); } /** @param Collection|array|null $wallets */ private function mainWalletLine(Collection|array|null $wallets, ?int $ownerType = null): mixed { foreach ($this->walletLines($wallets) as $line) { if ($this->lineValue($line, 'fund_id') !== null || $this->lineValue($line, 'product_id') !== null) { continue; } if ($ownerType !== null) { $lineOwnerType = $this->lineValue($line, 'owner_type'); if ($lineOwnerType !== null && (int) $lineOwnerType !== $ownerType) { continue; } } return $line; } return null; } /** @param Collection|array|null $wallets */ private function walletLines(Collection|array|null $wallets): array { if ($wallets === null) { return []; } if ($wallets instanceof Collection) { return $wallets->all(); } return is_array($wallets) ? $wallets : []; } 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; } }