From 7f74a951d3c5ddf53ab27ea830f11e06a7e33da0 Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 29 Aug 2026 14:04:13 +0330 Subject: [PATCH] script added --- .../EraseBadBuyAndRechargeCommand.php | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 app/Console/Commands/EraseBadBuyAndRechargeCommand.php diff --git a/app/Console/Commands/EraseBadBuyAndRechargeCommand.php b/app/Console/Commands/EraseBadBuyAndRechargeCommand.php new file mode 100644 index 0000000..85fd865 --- /dev/null +++ b/app/Console/Commands/EraseBadBuyAndRechargeCommand.php @@ -0,0 +1,347 @@ +option('order'); + $customerId = (int) $this->option('customer'); + $rechargeRial = (int) round((float) $this->option('recharge-rial')); + $skipRecharge = (bool) $this->option('skip-recharge'); + $execute = (bool) $this->option('execute'); + + $order = $this->resolveOrder($orderId, $customerId); + if (!$order) { + return self::FAILURE; + } + + $customer = Customer::find($order->customer_id); + if (!$customer) { + $this->error("مشتری سفارش #{$order->id} یافت نشد."); + + return self::FAILURE; + } + + if ((int) $order->direction !== 1) { + $this->error("سفارش #{$order->id} خرید نیست (direction={$order->direction})."); + + return self::FAILURE; + } + + $txs = Transaction::query() + ->where('order_id', $order->id) + ->orderBy('id') + ->get(); + + if ($txs->isEmpty()) { + $this->warn("هیچ تراکنشی با order_id={$order->id} پیدا نشد — فقط سفارش حذف می‌شود."); + } + + $goldByWallet = []; + $cashByWallet = []; + foreach ($txs as $tx) { + $wid = (int) $tx->wallet; + $goldByWallet[$wid] = ($goldByWallet[$wid] ?? 0.0) + (float) $tx->gold; + $cashByWallet[$wid] = ($cashByWallet[$wid] ?? 0.0) + (float) $tx->cash; + } + + // فقط طلای واریزشده را برمی‌داریم؛ ریال کسرشده را با شارژ جدا برمی‌گردانیم تا «شارژ ۱۰ میلیون» در تاریخچه بماند. + $goldToStrip = []; + foreach ($goldByWallet as $wid => $delta) { + if ($delta > 1e-12) { + $goldToStrip[$wid] = $delta; + } + } + if ($goldToStrip === [] && (float) $order->amount > 0) { + $main = $this->mainWallet((int) $customer->id, (int) $customer->type); + if ($main) { + $goldToStrip[(int) $main->id] = (float) $order->amount; + } + } + + $adminRows = TransactionsAdmin::withTrashed()->where('order_id', $order->id)->get(); + $zarinRows = Schema::hasTable('zarin') + ? Zarin::query()->where('order_id', $order->id)->get() + : collect(); + $tahesabRows = Schema::hasTable('tahesab') + ? Tahesab::query()->where('order_id', $order->id)->get() + : collect(); + + $payment = (float) ($order->payment_amount ?: $order->total_price); + if ($rechargeRial <= 0) { + $rechargeRial = (int) round($payment > 0 ? $payment : 100_000_000); + } + + $this->info('=== پیش‌نمایش پاک‌کردن خرید اشتباه ==='); + $this->line("مشتری: {$customer->id} — " . trim(($customer->fname ?? '') . ' ' . ($customer->lname ?? '') . ' ' . ($customer->name ?? ''))); + $this->line("سفارش: #{$order->id} | amount={$order->amount} گرم | price={$order->price} | payment={$payment} | provider_id=" . ($order->provider_id ?? '—')); + $this->line('تراکنش‌های سفارش: ' . $txs->pluck('id')->implode(', ')); + foreach ($txs as $tx) { + $this->line(" tx#{$tx->id} wallet={$tx->wallet} type={$tx->type} gold={$tx->gold} cash={$tx->cash} title=" . ($tx->title ?? '')); + } + $this->line('دفترکل محلی (transactions_admins): ' . $adminRows->count()); + $this->line('اسناد zarin محلی: ' . $zarinRows->count()); + $this->line('اسناد tahesab محلی: ' . $tahesabRows->count()); + + $this->newLine(); + $this->info('--- تغییر موجودی طلا (بدون ثبت تراکنش برگشتی) ---'); + foreach ($goldToStrip as $wid => $g) { + $w = Wallet::find($wid); + if (!$w) { + $this->error("کیف #{$wid} یافت نشد."); + + return self::FAILURE; + } + $before = (float) $w->gold; + $after = $before - $g; + $this->line("wallet#{$wid} gold: {$before} → {$after} (−{$g})"); + if ($after < -1e-6) { + $this->error('بعد از کسر، طلا منفی می‌شود — متوقف شد.'); + + return self::FAILURE; + } + } + + if (!$skipRecharge) { + $main = $this->mainWallet((int) $customer->id, (int) $customer->type); + $cashBefore = (float) ($main?->cash ?? 0); + $this->newLine(); + $this->info('--- شارژ مجدد ریالی ---'); + $this->line('مبلغ: ' . number_format($rechargeRial) . ' ریال (= ' . number_format($rechargeRial / 10) . ' تومان)'); + $this->line('کیف اصلی cash: ' . number_format($cashBefore) . ' → ' . number_format($cashBefore + $rechargeRial)); + $this->warn('سند حسابداری راه دور برای این شارژ ساخته نمی‌شود (فقط کیف + withdraw + تراکنش).'); + } + + $this->newLine(); + $this->warn('Montzar / زرین راه دور: این دستور سفارش همکار و سند آنلاین را لغو نمی‌کند.'); + + if (!$execute) { + $this->newLine(); + $this->warn('حالت پیش‌نمایش — برای اجرا:'); + $this->line(' php artisan wallet:erase-bad-buy-recharge --order=' . $order->id . ' --execute'); + + return self::SUCCESS; + } + + $adminUserId = (int) ($this->option('user') ?: 0); + if ($adminUserId <= 0) { + $adminUserId = (int) (User::query()->orderBy('id')->value('id') ?: 0); + } + if (!$skipRecharge && $adminUserId <= 0) { + $this->error('کاربر ادمین برای withdraw پیدا نشد؛ --user بدهید.'); + + return self::FAILURE; + } + + DB::transaction(function () use ( + $order, + $customer, + $txs, + $goldToStrip, + $adminRows, + $zarinRows, + $tahesabRows, + $skipRecharge, + $rechargeRial, + $adminUserId + ) { + foreach ($goldToStrip as $wid => $g) { + $w = Wallet::query()->lockForUpdate()->find($wid); + if (!$w) { + throw new \RuntimeException("کیف #{$wid} یافت نشد"); + } + $w->gold = (float) $w->gold - $g; + if ($w->gold < -1e-6) { + throw new \RuntimeException("طلای کیف #{$wid} منفی شد"); + } + $w->save(); + } + + $txIds = $txs->pluck('id')->all(); + if ($txIds !== []) { + if (Schema::hasTable('transaction_metas')) { + TransactionMeta::query()->whereIn('transaction', $txIds)->delete(); + } + Transaction::query()->whereIn('id', $txIds)->delete(); + } + + foreach ($adminRows as $row) { + $row->forceDelete(); + } + foreach ($zarinRows as $row) { + $row->delete(); + } + foreach ($tahesabRows as $row) { + $row->delete(); + } + + // پاداش معرفی / دفترکل وابسته به همین سفارش + if (Schema::hasColumn('transactions_admins', 'order_id')) { + TransactionsAdmin::withTrashed()->where('order_id', $order->id)->forceDelete(); + } + + Order::query()->where('id', $order->id)->delete(); + + if (!$skipRecharge) { + $walletInterface = new WalletInterface((int) $customer->id, (int) $customer->type); + $withdraw = Withdraw::create([ + 'user_id' => $adminUserId, + 'customer_id' => $customer->id, + 'cash' => $rechargeRial, + 'note' => 'شارژ اصلاحی پس از حذف خرید اشتباه #' . $order->id, + 'sts' => 4, + ]); + + new TransactionInterface($walletInterface, [ + 'gold' => 0, + 'cash' => $rechargeRial, + 'expire_at' => now(), + 'note' => $withdraw->note, + 'withdraw_id' => $withdraw->id, + 'active' => true, + ], 3, true, true, 0); + + $tx = Transaction::query()->where('withdraw_id', $withdraw->id)->latest('id')->first(); + TransactionsAdmin::create([ + 'customer_id' => $customer->id, + 'customer_to_id' => $customer->id, + 'user_id' => $adminUserId, + 'wallet' => null, + 'transaction_id' => $tx?->id, + 'cash' => $rechargeRial, + 'note' => $withdraw->note, + 'type' => 1, + 'gold' => 0, + 'balance_cash' => 0, + 'withdraw_id' => $withdraw->id, + ]); + } + }); + + try { + app(WalletBroadcastService::class)->broadcastForCustomer( + (int) $customer->id, + (int) $customer->type + ); + } catch (\Throwable $e) { + report($e); + } + + $this->newLine(); + $this->info('انجام شد: سفارش و تراکنش‌هایش پاک شدند' . ($skipRecharge ? '.' : ' و شارژ ریالی ثبت شد.')); + + $main = $this->mainWallet((int) $customer->id, (int) $customer->type); + if ($main) { + $this->line('موجودی فعلی کیف اصلی — cash=' . number_format((float) $main->cash) . ' | gold=' . round((float) $main->gold, 6)); + } + + return self::SUCCESS; + } + + private function resolveOrder(int $orderId, int $customerId): ?Order + { + if ($orderId > 0) { + $order = Order::find($orderId); + if (!$order) { + $this->error("سفارش #{$orderId} یافت نشد."); + + return null; + } + + return $order; + } + + if ($customerId <= 0) { + $this->error('--order یا --customer الزامی است.'); + + return null; + } + + // نزدیک به کیس ۱۰ میلیون تومان / ~۰٫۱۰۵ گرم + $candidates = Order::query() + ->where('customer_id', $customerId) + ->where('direction', 1) + ->where('sts', 2) + ->whereBetween('amount', [0.09, 0.12]) + ->where(function ($q) { + $q->whereBetween('payment_amount', [90_000_000, 110_000_000]) + ->orWhereBetween('total_price', [90_000_000, 110_000_000]); + }) + ->orderByDesc('id') + ->get(); + + if ($candidates->isEmpty()) { + $this->error("برای مشتری {$customerId} سفارشی شبیه ۱۰ میلیون / ~۰٫۱۰۵ گرم پیدا نشد. --order بدهید."); + $this->line('آخرین خریدها:'); + Order::query() + ->where('customer_id', $customerId) + ->where('direction', 1) + ->orderByDesc('id') + ->limit(8) + ->get(['id', 'amount', 'price', 'payment_amount', 'total_price', 'created_at']) + ->each(function (Order $o) { + $this->line(" #{$o->id} amount={$o->amount} price={$o->price} pay={$o->payment_amount} total={$o->total_price} at={$o->created_at}"); + }); + + return null; + } + + if ($candidates->count() > 1) { + $this->warn('چند نامزد پیدا شد؛ اولی (جدیدتر) انتخاب می‌شود. در صورت نیاز --order بدهید:'); + foreach ($candidates as $o) { + $this->line(" #{$o->id} amount={$o->amount} pay={$o->payment_amount} price={$o->price}"); + } + } + + return $candidates->first(); + } + + private function mainWallet(int $customerId, int $ownerType): ?Wallet + { + return Wallet::query() + ->where('owner', $customerId) + ->where('owner_type', $ownerType) + ->whereNull('fund_id') + ->whereNull('product_id') + ->first(); + } +}