diff --git a/app/Http/Controllers/Admin/CustomerController.php b/app/Http/Controllers/Admin/CustomerController.php index 4007671..6a70fca 100644 --- a/app/Http/Controllers/Admin/CustomerController.php +++ b/app/Http/Controllers/Admin/CustomerController.php @@ -16,6 +16,7 @@ use Rooyesh\Wallet\interfaces\WalletInterface; use Rooyesh\Wallet\models\Transaction; use Rooyesh\Wallet\models\Wallet; use App\Models\Setting; +use App\Services\BalanceReportService; use App\Services\ConfigService; use App\Services\WalletBalanceService; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -133,19 +134,27 @@ class CustomerController extends SuperController return response()->json(true); } public function tableWallets(Request $request){ - $query_table = Customer::with(['wallet','referral'])->where("is_archive",false)/*->where('type',1)*/; + $query_table = Customer::with(['wallet.product:id,metal_id', 'referral'])->where("is_archive",false)/*->where('type',1)*/; TableEngine::create($query_table, $request) ->initSearch($this->search) ->select(['*',Customer::$SELECT_TYPE_STR]) ->export($query_table); $balanceService = app(WalletBalanceService::class); - $mainWalletProductIds = app(ConfigService::class)->mainWalletProductIds(); + $balanceReport = app(BalanceReportService::class); + $catalogMetalIds = $balanceService->catalogMetalIds(); + $query_table['catalogMetals'] = $balanceReport->catalogMetalsMeta(); + if (isset($query_table['rows'])) { - $query_table['rows'] = collect($query_table['rows'])->map(function ($row) use ($balanceService, $mainWalletProductIds) { + $query_table['rows'] = collect($query_table['rows'])->map(function ($row) use ($balanceService, $catalogMetalIds) { $wallets = collect($row['wallet'] ?? []); $row['wallet_cash'] = $balanceService->mainWalletCash($wallets); - $row['wallet_gold'] = $balanceService->totalGoldWeight($wallets, $mainWalletProductIds); + $metalMap = $balanceService->buildCustomerWalletMetalDisplayMap($wallets, $catalogMetalIds); + $row['wallet_metal_gold'] = $metalMap; + foreach ($catalogMetalIds as $metalId) { + $row['wallet_metal_gold_' . $metalId] = $metalMap[(string) $metalId] ?? 0.0; + } + $row['wallet_gold'] = array_sum($metalMap); return $row; })->values(); diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index 42bfbb3..1f2049b 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use App\Http\Controllers\Admin\TahesabController; use App\Models\Setting; use App\Models\User; +use App\Services\WalletBalanceService; use Hekmatinasser\Verta\Verta; @@ -301,28 +302,42 @@ class WalletController extends Controller public function indexWallet(Request $request) { $lastPrice = (float) (Price::latest()->first()?->price ?? 0); + $balanceService = app(WalletBalanceService::class); + $catalogMetalIds = $balanceService->catalogMetalIds(); + $mainWalletMetalId = $balanceService->resolveMainWalletMetalId(); // همان فیلتر/جستجوی جدول — بدون صفحه‌بندی — برای مجموع ردیف‌های قابل مشاهده $sumQuery = Wallet::query(); TableEngine::create($sumQuery, $request->all()) ->initSearch(['customer.name']); - $totalGold = (float) (clone $sumQuery)->sum('gold'); + $sumRows = (clone $sumQuery)->with('product:id,metal_id')->get(); + $metalBalances = $balanceService->buildWalletMetalSums($sumRows, $catalogMetalIds); + $totalGold = array_sum(array_column($metalBalances, 'gold')); + $totalGoldToCash = array_sum(array_column($metalBalances, 'goldToCash')); $totalCash = (float) (clone $sumQuery)->sum('cash'); $sums = [ 'gold' => round($totalGold, 2), - 'goldToCash' => round($totalGold * $lastPrice, 2), + 'goldToCash' => round($totalGoldToCash, 2), 'cash' => round($totalCash, 2), 'cashToGold' => $lastPrice > 0 ? round($totalCash / $lastPrice, 2) : 0, + 'metalBalances' => $metalBalances, ]; - $records = Wallet::with('customer'); + $records = Wallet::with(['customer', 'product:id,metal_id']); TableEngine::create($records, $request->all()) ->initSearch(['customer.name']) ->select(['*']) ->export($records); + if (isset($records['rows'])) { + foreach ($records['rows'] as &$row) { + $balanceService->enrichWalletTableRow($row, $catalogMetalIds, $mainWalletMetalId); + } + unset($row); + } + $records['sums'] = $sums; return response()->json($records); diff --git a/app/Services/BalanceReportService.php b/app/Services/BalanceReportService.php index 7cc25a6..99c16b4 100644 --- a/app/Services/BalanceReportService.php +++ b/app/Services/BalanceReportService.php @@ -33,6 +33,35 @@ class BalanceReportService 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 */ diff --git a/app/Services/WalletBalanceService.php b/app/Services/WalletBalanceService.php index d61db65..202a91e 100644 --- a/app/Services/WalletBalanceService.php +++ b/app/Services/WalletBalanceService.php @@ -2,6 +2,9 @@ namespace App\Services; +use App\Http\Controllers\Controller; +use App\Models\Price; +use App\Models\Product; use Illuminate\Support\Collection; /** @@ -12,8 +15,10 @@ use Illuminate\Support\Collection; */ class WalletBalanceService { - public function __construct(private ConfigService $configService) - { + public function __construct( + private ConfigService $configService, + private BalanceReportService $balanceReportService + ) { } public function mainWalletProductIds(): array @@ -79,6 +84,240 @@ class WalletBalanceService 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); + + if (is_array($row)) { + $row['metal_gold'] = $metalMap; + foreach ($catalogMetalIds as $metalId) { + $row['metal_gold_' . $metalId] = $metalMap[(string) $metalId] ?? 0.0; + } + + return; + } + + if (is_object($row)) { + $row->metal_gold = $metalMap; + foreach ($catalogMetalIds as $metalId) { + $row->{'metal_gold_' . $metalId} = $metalMap[(string) $metalId] ?? 0.0; + } + } + } + + 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 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): mixed {