diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index 6399f80..dbea549 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\DB\Te\TableEngine; use App\Http\Controllers\Controller; +use App\Models\Address; use App\Models\Customer; use App\Models\Order; use App\Models\Price; @@ -21,6 +22,7 @@ use App\Http\Controllers\Admin\TahesabController; use App\Models\Setting; use App\Models\User; use App\Models\TransactionsAdmin; +use App\Services\AssetSummaryService; use App\Services\BalanceReportService; use App\Services\ConfigService; use App\Services\WalletBalanceService; @@ -623,6 +625,266 @@ class WalletController extends Controller return response()->json($withdraw); } + public function indexPhysicalWithdraw(Request $request) + { + $records = Withdraw::with(['customer', 'Address', 'Transaction.product']); + Withdraw::scopePhysicalWithdrawals($records); + + TableEngine::create($records, $request->all()) + ->initSearch(['customer.name', 'customer.mobile']) + ->select([ + '*', Withdraw::$SELECT_STS_STR, Withdraw::$SELECT_DAJ + ])->export($records); + + return response()->json($records); + } + + public function physicalWithdrawOptions($customerId, AssetSummaryService $assetSummaryService) + { + $customer = Customer::findOrFail($customerId); + $summary = $assetSummaryService->buildForCustomer((int) $customer->id, (int) $customer->type); + $inventoryByProduct = collect($summary['inventory_cards'] ?? [])->keyBy('product_id'); + $configService = app(ConfigService::class); + $mainWalletProductIds = $configService->mainWalletProductIds(); + + $products = Product::where(function ($query) use ($customer, $mainWalletProductIds) { + $query->whereHas('wallet', function ($q) use ($customer) { + $q->where('owner', $customer->id); + }); + if ($mainWalletProductIds !== []) { + $query->orWhereIn('id', $mainWalletProductIds); + } + })->with(['wallet' => function ($query) use ($customer) { + $query->where('owner', $customer->id); + }])->get(); + + $representativeId = null; + if ($mainWalletProductIds !== []) { + foreach ($mainWalletProductIds as $pid) { + if ($products->firstWhere('id', $pid)) { + $representativeId = (int) $pid; + break; + } + } + + $products = $products->filter(function ($product) use ($mainWalletProductIds, $representativeId) { + if (in_array((int) $product->id, array_map('intval', $mainWalletProductIds), true)) { + return (int) $product->id === $representativeId; + } + + return true; + })->values(); + } + + $productRows = $products->map(function ($product) use ($customer, $assetSummaryService, $inventoryByProduct, $representativeId) { + $card = $inventoryByProduct->get((int) $product->id); + $displayQty = $card !== null ? (float) ($card['quantity'] ?? 0) : 0.0; + + $walletInterface = new WalletInterface( + (int) $customer->id, + (int) $customer->type, + null, + $assetSummaryService->effectiveProductWalletIdForCustomer( + (int) $customer->id, + (int) $customer->type, + (int) $product->id + ) + ); + $wallet = $walletInterface->wallet; + if (!$wallet) { + return null; + } + + return [ + 'product_id' => (int) $product->id, + 'title' => (string) $product->title, + 'wallet_id' => (int) $wallet->id, + 'available' => $displayQty, + 'unit' => self::productQuantityUnit([ + 'unit' => $product->unit ?? null, + 'direct_price' => $product->direct_price ?? null, + 'group_type' => $product->group_type ?? null, + ]), + 'is_main_wallet' => $representativeId !== null && (int) $product->id === $representativeId, + ]; + })->filter(function ($row) { + return $row !== null && ($row['available'] ?? 0) > 0 && $row['title'] !== 'آبشده نقدی'; + })->values(); + + $addresses = Address::query() + ->where('customer_id', $customer->id) + ->orderByDesc('default_address') + ->orderByDesc('id') + ->get(['id', 'province', 'city', 'address', 'phone', 'zip', 'default_address']); + + return response()->json([ + 'products' => $productRows, + 'addresses' => $addresses, + ]); + } + + public function createPhysicalWithdraw(Request $request, AssetSummaryService $assetSummaryService) + { + $data = $request->validate([ + 'customer_id' => 'required|integer|exists:customers,id', + 'address_id' => 'required|integer|exists:addresses,id', + 'wallets' => 'required|array|min:1', + 'wallets.*.wallet_id' => 'required|integer|exists:wallets,id', + 'wallets.*.value' => 'required|numeric|min:0.0001', + ]); + + $customer = Customer::findOrFail($data['customer_id']); + $address = Address::find($data['address_id']); + if (!$address || (int) $address->customer_id !== (int) $customer->id) { + return response()->json(['message' => 'آدرس معتبر نیست یا متعلق به این مشتری نمی‌باشد'], 403); + } + + $withdraw = DB::transaction(function () use ($data, $customer, $address, $assetSummaryService) { + $withdraw = new Withdraw(); + $withdraw->note = 'برداشت فیزیکی'; + $withdraw->address_id = $address->id; + $withdraw->customer_id = $customer->id; + $withdraw->user_id = auth('user')->id(); + $withdraw->sts = 50; + $withdraw->save(); + + foreach ($data['wallets'] as $walletRow) { + $wallet = Wallet::with('product')->findOrFail($walletRow['wallet_id']); + if ((int) $wallet->owner !== (int) $customer->id) { + throw new UnprocessableEntityHttpException('کیف پول انتخاب‌شده متعلق به این مشتری نیست'); + } + if (!$wallet->product) { + throw new UnprocessableEntityHttpException('محصولی در کیف پول مورد نظر یافت نشد'); + } + + $productId = (int) $wallet->product_id; + $value = (float) $walletRow['value']; + $effectiveProductId = $assetSummaryService->effectiveProductWalletIdForCustomer( + (int) $customer->id, + (int) $customer->type, + $productId + ); + $availableGold = $assetSummaryService->productDisplayQuantity( + (int) $customer->id, + (int) $customer->type, + $productId + ); + + if ($value > $availableGold) { + throw new UnprocessableEntityHttpException( + "مشتری به اندازه کافی از {$wallet->product->title} ندارد" + ); + } + + $walletInterface = new WalletInterface( + (int) $customer->id, + (int) $customer->type, + null, + $effectiveProductId + ); + + new TransactionInterface($walletInterface, [ + 'gold' => $value, + 'number' => (float) $wallet->product->amount * $value, + 'product_id' => $wallet->product->id, + 'expire_at' => now(), + 'active' => true, + 'title' => " برداشت {$wallet->product->title}", + 'note' => " برداشت محصول {$wallet->product->title} شماره سند {$withdraw->id}", + 'withdraw_id' => $withdraw->id, + ], 6, false); + } + + return $withdraw; + }); + + $this->broadcastCustomerWalletAssets($customer); + + return response()->json(['id' => $withdraw->id]); + } + + public function adjustPhysicalWithdraw(Request $request, $id, AssetSummaryService $assetSummaryService) + { + $payload = $request->validate([ + 'transaction_id' => 'required|integer|exists:transactions,id', + 'direction' => 'required|in:increase,decrease', + ]); + + $withdraw = Withdraw::with(['Transaction.product'])->findOrFail($id); + if (!Withdraw::isPhysicalWithdrawNote($withdraw->note)) { + return response()->json(['message' => 'این سند برداشت فیزیکی نیست'], 422); + } + if ((int) $withdraw->sts !== 50) { + return response()->json(['message' => 'تغییر مقدار فقط در وضعیت «در انتظار ارسال» مجاز است'], 422); + } + + $transaction = Transaction::with('product')->findOrFail($payload['transaction_id']); + if ((int) $transaction->withdraw_id !== (int) $withdraw->id) { + return response()->json(['message' => 'تراکنش به این سند تعلق ندارد'], 422); + } + + $product = $transaction->product; + if (!$product) { + return response()->json(['message' => 'محصول تراکنش یافت نشد'], 422); + } + + $step = 1.0; + $currentUnits = abs((float) $transaction->gold); + $direction = $payload['direction']; + + if ($direction === 'decrease' && $currentUnits <= $step) { + return response()->json(['message' => 'حداقل مقدار قابل برداشت یک واحد است'], 422); + } + + $customer = Customer::findOrFail($withdraw->customer_id); + $effectiveProductId = $assetSummaryService->effectiveProductWalletIdForCustomer( + (int) $customer->id, + (int) $customer->type, + (int) $product->id + ); + $walletInterface = new WalletInterface( + (int) $customer->id, + (int) $customer->type, + null, + $effectiveProductId + ); + $wallet = $walletInterface->wallet; + if (!$wallet) { + return response()->json(['message' => 'کیف پول مشتری یافت نشد'], 422); + } + + if ($direction === 'increase') { + $available = $assetSummaryService->productDisplayQuantity( + (int) $customer->id, + (int) $customer->type, + (int) $product->id + ); + if ($available < $step) { + return response()->json(['message' => "موجودی {$product->title} کافی نیست"], 422); + } + + $wallet->gold -= $step; + $transaction->gold = -($currentUnits + $step); + $transaction->number = (float) $transaction->number + ((float) $product->amount * $step); + } else { + $wallet->gold += $step; + $transaction->gold = -($currentUnits - $step); + $transaction->number = max(0, (float) $transaction->number - ((float) $product->amount * $step)); + } + + $transaction->balance_gold = $wallet->gold; + $wallet->save(); + $transaction->save(); + $walletInterface->set_hash(); + + $this->broadcastCustomerWalletAssets($customer); + + return response()->json([ + 'message' => 'مقدار برداشت به‌روزرسانی شد', + 'transaction' => $transaction->fresh('product'), + ]); + } + public function completeWithdraw(Request $request) { $request->validate([ diff --git a/app/Models/Withdraw.php b/app/Models/Withdraw.php index a8ea536..4e13092 100644 --- a/app/Models/Withdraw.php +++ b/app/Models/Withdraw.php @@ -34,6 +34,7 @@ class Withdraw extends Model 50 => 'در انتظار ارسال', 51 => 'ارسال شده', 52 => 'لغو شده', + 53 => 'در انتظار تعیین مجدد قیمت', ]; public function customer() @@ -56,6 +57,18 @@ class Withdraw extends Model return $this->hasOne('App\Models\BankAccount', 'id', 'bank_account_id'); } + /** برداشت فیزیکی — درخواست تحویل محصول */ + public static function scopePhysicalWithdrawals($query) + { + return $query->where('note', 'like', '%برداشت فیزیکی%') + ->whereBetween('sts', [49, 70]); + } + + public static function isPhysicalWithdrawNote(?string $note): bool + { + return $note !== null && str_contains($note, 'برداشت فیزیکی'); + } + public static function applyFlowFilter($query, ?string $flowType = 'all') { if (!$flowType || $flowType === 'all') { diff --git a/routes/admin.php b/routes/admin.php index 3f9006c..df64414 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -122,6 +122,10 @@ Route::middleware('auth:user')->group(function (){ Route::put('/wallet/withdraw/{id}',[WalletController::class,'updateWithdraw']); Route::get('/wallet/withdraw/{id}',[WalletController::class,'getWithdraw']); Route::post('/wallet/withdraw/complete',[WalletController::class,'completeWithdraw']); + Route::post('/wallet/withdraw_physical/table',[WalletController::class,'indexPhysicalWithdraw']); + Route::get('/wallet/withdraw_physical/options/{customerId}',[WalletController::class,'physicalWithdrawOptions']); + Route::post('/wallet/withdraw_physical',[WalletController::class,'createPhysicalWithdraw']); + Route::post('/wallet/withdraw_physical/{id}/adjust',[WalletController::class,'adjustPhysicalWithdraw']); //---------------order Route::post('order/table', [OrderController::class, 'table']);