Merge origin/Abshode_Khanegi and resolve WalletController conflict.

Keep unified charge API from remote while preserving main-wallet metal detection for deposits and transaction labels via BalanceReportService.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-24 01:22:56 +03:30
co-authored by Cursor
24 changed files with 1762 additions and 375 deletions
+190 -9
View File
@@ -20,8 +20,9 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use App\Http\Controllers\Admin\TahesabController;
use App\Models\Setting;
use App\Models\User;
use App\Services\BalanceReportService;
use App\Services\ConfigService;
use App\Services\TransactionMetalService;
use App\Services\WalletBalanceService;
use Hekmatinasser\Verta\Verta;
@@ -221,10 +222,172 @@ class WalletController extends Controller
return response()->json(true);
}
public function index(Request $request, TransactionMetalService $transactionMetalService)
/** گزینه‌های شارژ کیف پول — نقدی + محصولات فعال */
public function chargeTargets()
{
$records = Transaction::with(['customer', 'wallet.product:id,metal_id', 'product:id,metal_id'])
->whereNull('fund_id');
$configService = app(ConfigService::class);
$mainWalletProductIds = $configService->mainWalletProductIds();
$balanceReport = app(BalanceReportService::class);
$targets = [
[
'key' => 'money',
'kind' => 'money',
'label' => 'نقدی (ریال)',
'unit' => 'ریال',
],
];
$products = Product::query()
->where('active', 1)
->orderBy('metal_id')
->orderBy('id')
->get(['id', 'title', 'unit', 'metal_id', 'group_type', 'trade_by_quantity', 'amount', 'direct_price']);
foreach ($products as $product) {
$usesMainWallet = in_array((int) $product->id, $mainWalletProductIds, true);
$label = (string) $product->title;
if ($usesMainWallet) {
$label .= ' (کیف اصلی)';
}
$targets[] = [
'key' => 'product:' . $product->id,
'kind' => 'metal',
'product_id' => (int) $product->id,
'label' => $label,
'unit' => $this->resolveProductChargeUnit($product),
'metal_id' => max(1, (int) ($product->metal_id ?? 1)),
'uses_main_wallet' => $usesMainWallet,
'trade_by_quantity' => (bool) $product->trade_by_quantity,
'group_type' => (string) ($product->group_type ?? ''),
];
}
return response()->json([
'targets' => $targets,
'catalogMetals' => $balanceReport->catalogMetalsMeta(),
]);
}
/**
* شارژ یکپارچه کیف پول — نقدی یا محصول (با رعایت کیف اصلی برای آبشده).
*/
public function charge_wallet(Request $request)
{
$productId = (int) $request->input('product_id', 0);
if ($productId > 0) {
return $this->chargeProductMetal($request, $productId);
}
return $this->charge($request);
}
private function chargeProductMetal(Request $request, int $productId)
{
$data = $request->all();
$product = Product::findOrFail($productId);
$customer = Customer::findOrFail($data['customer_id']);
$deposit = ((float) ($data['value'] ?? 0)) > 0;
$effectiveProductId = Controller::effectiveProductWalletId($productId);
$wallet_interface = new WalletInterface(
(int) $data['customer_id'],
(int) $customer->type,
null,
$effectiveProductId
);
$wallet = $wallet_interface->wallet;
if (!$deposit && $wallet !== null && (float) $wallet->gold < abs((float) $data['value'])) {
throw new UnprocessableEntityHttpException('مقدار موجودی کمتر از درخواست شماست');
}
$goldBlock = 0;
$user = auth()->user();
$unitLabel = $this->resolveProductChargeUnit($product);
$Withdraw = Withdraw::create([
'user_id' => $user->id,
'customer_id' => $customer->id,
'gold' => $data['value'],
'note' => $data['note'] ?? null,
'sts' => 4,
]);
new TransactionInterface($wallet_interface, [
'cash' => 0,
'gold' => $data['value'],
'product_id' => $productId,
'expire_at' => now(),
'note' => $data['note'] ?? null,
'withdraw_id' => $Withdraw->id,
'active' => true,
], 7, $deposit, false, $goldBlock);
if ($this->Config('send_to_tahesab') == 'true') {
try {
$data['direction'] = $deposit ? 1 : 0;
$tahesab = new TahesabController();
$tahesab->walletGold($data);
} catch (\Exception $e) {
return response($e->getMessage(), 500);
}
}
$status = $deposit ? ' شارژ شد ' : ' کسر شد ';
$formatted = self::formatSmsGoldWeight($data['value']);
if ($this->Config('charge_notif') == 'true') {
$message = 'کیف پول کاربر ' . $customer->name . ' به مقدار ' . $formatted . ' ' . $unitLabel . ' در تاریخ ' . verta()->format('Y/n/j H:i:s') . $status . 'بابت' . "\n"
. ($data['note'] ?? '')
. "\n"
. $this->Config('name_fa');
User::where('send_sms', 1)->get()->each(function ($admin) use ($message) {
$this->send($admin->mobile, $message);
});
}
$message = 'کیف پول شما به اندازه ' . $formatted . ' ' . $unitLabel . ' در تاریخ ' . verta()->format('Y/n/j H:i:s') . $status . ' بابت' . "\n"
. ($data['note'] ?? '')
. "\n"
. $this->Config('name_fa');
$this->Send($customer->mobile, $message);
return response()->json(true);
}
private function resolveProductChargeUnit(Product $product): string
{
$payload = Controller::productUnitPayload($product);
if (
Controller::usesQuantityTrade($payload)
&& !Controller::isWeightBasedProductUnit($payload['unit'] ?? null)
) {
$unit = trim((string) ($payload['unit'] ?? ''));
return $unit !== '' ? $unit : 'عدد';
}
if (Controller::isWeightBasedProductUnit($payload['unit'] ?? null)) {
return trim((string) $payload['unit']);
}
return Controller::productStorageWeightUnit($product);
}
public function index(Request $request)
{
$balanceReport = app(BalanceReportService::class);
$balanceService = app(WalletBalanceService::class);
$catalogMetalIds = $balanceService->catalogMetalIds();
$records = Transaction::with([
'customer',
'wallet.product:id,metal_id',
'product: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'])
->select([
@@ -232,9 +395,13 @@ class WalletController extends Controller
])->export($records);
if (isset($records['rows'])) {
$records['rows'] = $transactionMetalService->enrichTransactionRows($records['rows']);
foreach ($records['rows'] as &$row) {
$balanceReport->enrichTransactionTableRow($row, $catalogMetalIds);
}
unset($row);
}
$records['catalogMetals'] = $transactionMetalService->catalogMetals();
$records['catalogMetals'] = $balanceReport->catalogMetalsMeta();
return response()->json($records);
}
@@ -289,28 +456,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);