From 3f8c47b1fc4b238e4ce892edd7095eac1046b6b6 Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 21 Jul 2026 22:23:09 +0330 Subject: [PATCH] fix some bugs 5 --- .../Controllers/Admin/CustomerController.php | 39 +++++- .../Controllers/Admin/OrderController.php | 16 ++- .../Controllers/Admin/WalletController.php | 8 +- .../Pargar/PargarInboundController.php | 14 +- app/Services/BalanceReportService.php | 126 +++++++++++++++--- app/Services/PargarInboundWalletService.php | 48 ++++++- 6 files changed, 215 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/Admin/CustomerController.php b/app/Http/Controllers/Admin/CustomerController.php index 550108f..acf2418 100644 --- a/app/Http/Controllers/Admin/CustomerController.php +++ b/app/Http/Controllers/Admin/CustomerController.php @@ -129,9 +129,17 @@ class CustomerController extends SuperController public function funds(Request $request,$customer_id) { $mainWalletProductIds = app(\App\Services\ConfigService::class)->mainWalletProductIds(); + $balanceService = app(WalletBalanceService::class); + $catalogMetalIds = $balanceService->catalogMetalIds(); + $mainWalletMetalId = $balanceService->resolveMainWalletMetalId(); + $productCols = implode(',', self::WALLET_PRODUCT_COLUMNS); // فقط کیف محصول‌ها (نه کیف اصلی) — هر ردیف = موجودی واقعی یک محصول/فلز - $query_table = Wallet::with(['product:id,title,metal_id,unit']) + $query_table = Wallet::with([ + 'product' => static function ($q) use ($productCols) { + $q->withTrashed()->select(explode(',', $productCols)); + }, + ]) ->where('owner', $customer_id) ->whereNotNull('product_id'); if ($mainWalletProductIds !== []) { @@ -147,7 +155,9 @@ class CustomerController extends SuperController if ($mainWalletProductIds !== []) { $representative = null; foreach ($mainWalletProductIds as $pid) { - $representative = \App\Models\Product::select(['id', 'title', 'metal_id', 'unit'])->find($pid); + $representative = \App\Models\Product::withTrashed() + ->select(self::WALLET_PRODUCT_COLUMNS) + ->find($pid); if ($representative) { break; } @@ -176,6 +186,31 @@ class CustomerController extends SuperController } } + if (isset($query_table['rows'])) { + foreach ($query_table['rows'] as &$row) { + $balanceService->enrichWalletTableRow($row, $catalogMetalIds, $mainWalletMetalId); + // اگر eager-load محصول نیامده بود، از lookup پر کن + $productId = (int) (is_array($row) ? ($row['product_id'] ?? 0) : ($row->product_id ?? 0)); + $hasProduct = is_array($row) + ? ! empty($row['product']) + : ! empty($row->product); + if ($productId > 0 && ! $hasProduct) { + $product = \App\Models\Product::withTrashed() + ->select(self::WALLET_PRODUCT_COLUMNS) + ->find($productId); + if ($product) { + if (is_array($row)) { + $row['product'] = $product; + } else { + $row->setRelation('product', $product); + } + $balanceService->enrichWalletTableRow($row, $catalogMetalIds, $mainWalletMetalId); + } + } + } + unset($row); + } + return response()->json($query_table); } public function archive($id){ diff --git a/app/Http/Controllers/Admin/OrderController.php b/app/Http/Controllers/Admin/OrderController.php index dbaa456..3ca77f6 100644 --- a/app/Http/Controllers/Admin/OrderController.php +++ b/app/Http/Controllers/Admin/OrderController.php @@ -17,8 +17,12 @@ class OrderController extends Controller $records = Order::with([ 'customer', - 'product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', - 'transactions.product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', + 'product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']); + }, + 'transactions.product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']); + }, ]); TableEngine::create($records, $request->all()) ->initSearch(['customer.name']) @@ -44,8 +48,12 @@ class OrderController extends Controller $records = Order::with([ 'customer', - 'product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', - 'transactions.product:id,title,unit,metal_id,group_type,amount,trade_by_quantity,direct_price', + 'product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']); + }, + 'transactions.product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']); + }, ])->where('type',2); TableEngine::create($records, $request->all()) ->initSearch(['customer.name']) diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index b71b9b4..a32405b 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -476,8 +476,12 @@ class WalletController extends Controller $records = Transaction::with([ 'customer', - 'wallet.product:id,title,metal_id', - 'product:id,title,metal_id,group_type,unit,amount,trade_by_quantity,direct_price', + 'wallet.product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'metal_id', 'group_type', 'unit', 'amount', 'trade_by_quantity', 'direct_price']); + }, + 'product' => static function ($q) { + $q->withTrashed()->select(['id', 'title', 'metal_id', 'group_type', 'unit', 'amount', 'trade_by_quantity', 'direct_price']); + }, ])->whereNull('fund_id'); TableEngine::create($records, $request->all()) ->initSearch(['order_id', 'customer.name']) diff --git a/app/Http/Controllers/Pargar/PargarInboundController.php b/app/Http/Controllers/Pargar/PargarInboundController.php index 63689eb..cae2a68 100644 --- a/app/Http/Controllers/Pargar/PargarInboundController.php +++ b/app/Http/Controllers/Pargar/PargarInboundController.php @@ -568,11 +568,15 @@ class PargarInboundController extends Controller } $tradedProductId = (int) ($payload['product_id'] ?? $order->product_id ?? 0); - $tradesMainGold = $tradedProductId <= 0 - || app(ConfigService::class)->isMainWalletProduct($tradedProductId) - || ($product && self::isMeltedTradeGroup($product->group_type ?? null)); + // فقط محصولات main_wallet_products روی GoldInv آبشده اثر دارند. + // group_type=آبشده به‌تنهایی کافی نیست (مثلاً آبشده سرمایه‌گذاری / قلک). + if ($tradedProductId > 0) { + $tradesMainGold = app(ConfigService::class)->isMainWalletProduct($tradedProductId); + } else { + $tradesMainGold = $product && self::isMeltedTradeGroup($product->group_type ?? null); + } - // اثر مانده از روش پرداخت؛ وزن طلا فقط وقتی کالای معامله یا پرداخت به کیف اصلی مربوط است + // اثر مانده از روش پرداخت؛ وزن طلا فقط وقتی کالای معامله به کیف اصلی مربوط است if ($paymentKind === PargarSyncService::PAYMENT_CASH) { if ($tradesMainGold) { // خرید/فروش آبشده کیف‌اصلی با نقدی: وزن + مبلغ — حتماً تعدادی نباشد @@ -580,7 +584,7 @@ class PargarInboundController extends Controller $count = 0; $weight = $amount; } else { - // محصول غیر اصلی با پرداخت نقدی: فقط ریال + // محصول غیر اصلی با پرداخت نقدی: فقط ریال (بدون وزن طلای پرگار) $rialItemId = $sync->pargarItemIdRial(); $itemId = $rialItemId > 0 ? $rialItemId : $sync->pargarItemIdGold(); $weight = 0; diff --git a/app/Services/BalanceReportService.php b/app/Services/BalanceReportService.php index 3bf04c0..7ef5cec 100644 --- a/app/Services/BalanceReportService.php +++ b/app/Services/BalanceReportService.php @@ -257,20 +257,13 @@ class BalanceReportService public function resolveTransactionMetalId(mixed $transaction): int { - $product = $this->transactionProduct($transaction); + $product = $this->resolveTransactionProduct($transaction); if ($product !== null) { $metalId = (int) ($product->metal_id ?? 0); return $metalId > 0 ? $metalId : 1; } - $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(); } @@ -387,16 +380,11 @@ class BalanceReportService public function resolveTransactionProductLabel(mixed $transaction): ?string { - $product = $this->transactionProduct($transaction); + $product = $this->resolveTransactionProduct($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(); @@ -487,17 +475,37 @@ class BalanceReportService $metalId = $this->resolveTransactionMetalId($row); $metalLabel = $this->metalLabel($metalId); $currentTypeStr = trim((string) ($this->lineValue($row, 'type_str') ?? '')); - $displayType = $this->resolveTransactionTitleLabel($row) - ?? $this->resolveTransactionDisplayTypeStr($row, $metalId); - - if ($displayType === null && $this->shouldReplaceGenericTransactionLabel($currentTypeStr)) { - $displayType = $this->resolveTransactionDisplayTypeStr($row, $metalId); + $titleLabel = $this->resolveTransactionTitleLabel($row); + $productAware = $this->resolveTransactionDisplayTypeStr($row, $metalId); + $product = $this->resolveTransactionProduct($row); + $productTitle = $this->resolveTransactionProductLabel($row); + $productId = (int) ($this->lineValue($row, 'product_id') ?? 0); + if ($productId <= 0 && $product !== null) { + $productId = (int) ($product->id ?? 0); } + // عنوان ژنریک مثل «خرید طلا» را با «خرید {نام محصول}» عوض کن + if ($productAware !== null && ( + $titleLabel === null + || $this->shouldReplaceGenericTransactionLabel($titleLabel) + || $this->shouldReplaceGenericTransactionLabel($currentTypeStr) + )) { + $displayType = $productAware; + } else { + $displayType = $titleLabel; + } + + $productPayload = $this->productTablePayload($product); + if (is_array($row)) { $row['metal_gold'] = $metalMap; $row['transaction_metal_id'] = $metalId; $row['transaction_metal_label'] = $metalLabel; + $row['product_id'] = $productId > 0 ? $productId : null; + $row['product_title'] = $productTitle; + if ($productPayload !== null && empty($row['product'])) { + $row['product'] = $productPayload; + } if ($displayType !== null) { $row['type_str'] = $displayType; } @@ -512,6 +520,11 @@ class BalanceReportService $row->metal_gold = $metalMap; $row->transaction_metal_id = $metalId; $row->transaction_metal_label = $metalLabel; + $row->product_id = $productId > 0 ? $productId : null; + $row->product_title = $productTitle; + if ($product instanceof Product && empty($this->lineValue($row, 'product'))) { + $row->setRelation('product', $product); + } if ($displayType !== null) { $row->setAttribute('type_str', $displayType); } @@ -523,11 +536,11 @@ class BalanceReportService private function transactionProduct(mixed $transaction): ?object { - if (is_object($transaction) && isset($transaction->product)) { + if (is_object($transaction) && isset($transaction->product) && $transaction->product) { return $transaction->product; } - if (is_array($transaction) && isset($transaction['product'])) { + if (is_array($transaction) && isset($transaction['product']) && $transaction['product']) { $product = $transaction['product']; return is_object($product) ? $product : (object) $product; @@ -536,6 +549,54 @@ class BalanceReportService return null; } + private function resolveTransactionProduct(mixed $transaction): ?object + { + $product = $this->transactionProduct($transaction); + if ($product !== null && ! empty($product->title ?? null)) { + return $product; + } + + $walletProduct = $this->transactionWalletProduct($transaction); + if ($walletProduct !== null && ! empty($walletProduct->title ?? null)) { + return $walletProduct; + } + + $productId = (int) ($this->lineValue($transaction, 'product_id') ?? 0); + if ($productId > 0) { + return Product::withTrashed() + ->select(['id', 'title', 'group_type', 'unit', 'direct_price', 'amount', 'trade_by_quantity', 'metal_id', 'file_id']) + ->find($productId); + } + + return $product ?? $walletProduct; + } + + /** + * @return array|null + */ + private function productTablePayload(?object $product): ?array + { + if ($product === null) { + return null; + } + + $id = (int) ($product->id ?? 0); + if ($id <= 0) { + return null; + } + + return [ + 'id' => $id, + 'title' => $product->title ?? null, + 'group_type' => $product->group_type ?? null, + 'unit' => $product->unit ?? null, + 'amount' => $product->amount ?? null, + 'trade_by_quantity' => (bool) ($product->trade_by_quantity ?? false), + 'direct_price' => $product->direct_price ?? null, + 'metal_id' => isset($product->metal_id) ? (int) $product->metal_id : null, + ]; + } + private function transactionWalletProduct(mixed $transaction): ?object { $wallet = $this->lineValue($transaction, 'wallet'); @@ -614,6 +675,7 @@ class BalanceReportService $productId = (int) ($product->id ?? 0); } $productTitle = $product !== null ? trim((string) ($product->title ?? '')) : ''; + $productPayload = $this->productTablePayload($product); $storedSource = trim((string) ($this->lineValue($row, 'fee_source') ?? '')); $storedLabel = trim((string) ($this->lineValue($row, 'fee_source_label') ?? '')); @@ -661,6 +723,9 @@ class BalanceReportService foreach ($payload as $key => $value) { $row[$key] = $value; } + if ($productPayload !== null && empty($row['product'])) { + $row['product'] = $productPayload; + } return; } @@ -669,6 +734,13 @@ class BalanceReportService foreach ($payload as $key => $value) { $row->{$key} = $value; } + if ($productPayload !== null && empty($this->lineValue($row, 'product'))) { + if ($product instanceof Product) { + $row->setRelation('product', $product); + } else { + $row->product = $productPayload; + } + } } } @@ -751,7 +823,7 @@ class BalanceReportService $productId = (int) ($this->lineValue($order, 'product_id') ?? 0); if ($productId > 0) { - $byId = Product::query() + $byId = Product::withTrashed() ->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']) ->find($productId); if ($byId) { @@ -773,6 +845,16 @@ class BalanceReportService if ($product) { return is_object($product) ? $product : (object) $product; } + + $txProductId = (int) ($this->lineValue($tx, 'product_id') ?? 0); + if ($txProductId > 0) { + $byTx = Product::withTrashed() + ->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price']) + ->find($txProductId); + if ($byTx) { + return $byTx; + } + } } return null; diff --git a/app/Services/PargarInboundWalletService.php b/app/Services/PargarInboundWalletService.php index 1ad91d5..665525d 100644 --- a/app/Services/PargarInboundWalletService.php +++ b/app/Services/PargarInboundWalletService.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use Rooyesh\Wallet\interfaces\TransactionInterface; use Rooyesh\Wallet\interfaces\WalletInterface; +use Rooyesh\Wallet\models\Wallet; /** * همگام‌سازی کیف با پرگار — فقط با دکمهٔ ادمین. @@ -280,7 +281,30 @@ class PargarInboundWalletService $walletInterface = new WalletInterface((int) $customer->id, $ownerType); $wallet = $walletInterface->wallet; - $goldDiff = $goldInv - (float) ($wallet->gold ?? 0); + $balanceService = app(WalletBalanceService::class); + $mainGold = $balanceService->effectiveMainWalletGold((int) $customer->id, $ownerType); + $nonMainProductGold = $this->sumNonMainProductWalletGold((int) $customer->id, $ownerType); + + // GoldInv پرگار = آبشدهٔ کیف اصلی. طلای محصولات جدا (مثل قلک سرمایه‌گذاری) نباید کپی شود. + $goldDiff = $goldInv - $mainGold; + + // تکرار شناخته‌شده: مانده ≈ طلای محصول جدا و کیف اصلی هم همان مقدار را دارد + if ( + $nonMainProductGold >= 0.01 + && abs($goldInv - $nonMainProductGold) < 0.05 + && abs($mainGold - $nonMainProductGold) < 0.05 + ) { + $goldDiff = -$mainGold; + } elseif ( + $goldDiff > 0.01 + && $nonMainProductGold >= 0.01 + && abs($goldInv - $nonMainProductGold) < 0.05 + && $mainGold < 0.01 + ) { + // GoldInv فقط همان موجودی محصول است — روی کیف اصلی نریز + $goldDiff = 0.0; + } + $rialDiff = $rialInv - (float) ($wallet->cash ?? 0); if (abs($goldDiff) < 0.01 && abs($rialDiff) < 1) { @@ -316,6 +340,8 @@ class PargarInboundWalletService $data['applied_gold_delta'] = $goldDiff; $data['applied_rial_delta'] = $rialDiff; $data['applied_at'] = now()->toDateTimeString(); + $data['main_gold_before'] = $mainGold; + $data['non_main_product_gold'] = $nonMainProductGold; $log->data = json_encode($data, JSON_UNESCAPED_UNICODE); $log->desc = 'اعمال ماندهٔ پرگار روی کیف اپ'; $log->save(); @@ -328,6 +354,26 @@ class PargarInboundWalletService ]; } + /** + * جمع طلای کیف‌های محصول خارج از main_wallet_products (مثل قلک سرمایه‌گذاری). + */ + private function sumNonMainProductWalletGold(int $customerId, int $ownerType): float + { + $mainWalletProductIds = app(ConfigService::class)->mainWalletProductIds(); + + $query = Wallet::query() + ->where('owner', $customerId) + ->where('owner_type', $ownerType) + ->whereNull('fund_id') + ->whereNotNull('product_id'); + + if ($mainWalletProductIds !== []) { + $query->whereNotIn('product_id', $mainWalletProductIds); + } + + return round((float) $query->sum('gold'), 3); + } + private function hasPendingOutboundWalletSync(int $customerId): bool { return PargarSyncLog::query()