Allow erase-bad-buy when the orders row was already deleted.
Find orphan buy transactions or accept --gold/--transaction so wallet gold can still be stripped and cash recharged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Models\Withdraw;
|
|||||||
use App\Models\Zarin;
|
use App\Models\Zarin;
|
||||||
use App\Services\WalletBroadcastService;
|
use App\Services\WalletBroadcastService;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
||||||
@@ -20,110 +21,134 @@ use Rooyesh\Wallet\models\TransactionMeta;
|
|||||||
use Rooyesh\Wallet\models\Wallet;
|
use Rooyesh\Wallet\models\Wallet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* پاککردن کامل یک خرید اشتباه (مثل ۱۰ میلیون → ۱۰۵ سوت) و شارژ مجدد ریالی.
|
* پاککردن اثر خرید اشتباه و شارژ مجدد ریالی.
|
||||||
*
|
*
|
||||||
* منطق:
|
* اگر ردیف orders دستی پاک شده باشد هم کار میکند:
|
||||||
* ۱) طلای همان سفارش از کیف کم میشود (بدون ثبت تراکنش برگشتی)
|
* تراکنشهای باقیمانده (یا --gold/--transaction) را پیدا میکند.
|
||||||
* ۲) سفارش + تراکنشها + اسناد محلی مرتبط hard-delete میشوند
|
|
||||||
* ۳) مبلغ ریالی (--recharge-rial، پیشفرض ۱۰۰٬۰۰۰٬۰۰۰ = ۱۰ میلیون تومان) شارژ میشود
|
|
||||||
*
|
|
||||||
* توجه: سند همکار (Montzar) و سند راه دور زرین/تهحساب با این دستور لغو نمیشود.
|
|
||||||
*/
|
*/
|
||||||
class EraseBadBuyAndRechargeCommand extends Command
|
class EraseBadBuyAndRechargeCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'wallet:erase-bad-buy-recharge
|
protected $signature = 'wallet:erase-bad-buy-recharge
|
||||||
{--order= : شناسه سفارش خرید اشتباه}
|
{--order= : شناسه سفارش (اگر هنوز هست)}
|
||||||
{--customer= : شناسه مشتری (اگر order ندادید؛ نزدیکترین خرید ۱۰ میلیون / ~۰٫۱۰۵ گرم را پیدا میکند)}
|
{--customer= : شناسه مشتری — برای پیدا کردن سفارش یا تراکنش یتیم}
|
||||||
|
{--transaction= : شناسه تراکنش خرید (وقتی سفارش پاک شده)}
|
||||||
|
{--gold= : گرم طلایی که باید از کیف کم شود (مثلاً 0.105) اگر تراکنش هم نیست}
|
||||||
{--recharge-rial=100000000 : مبلغ شارژ مجدد به ریال (پیشفرض ۱۰ میلیون تومان)}
|
{--recharge-rial=100000000 : مبلغ شارژ مجدد به ریال (پیشفرض ۱۰ میلیون تومان)}
|
||||||
{--user= : شناسه ادمین برای ردیف withdraw شارژ (پیشفرض اولین کاربر)}
|
{--user= : شناسه ادمین برای withdraw شارژ}
|
||||||
{--skip-recharge : فقط پاککردن خرید؛ شارژ نکن}
|
{--skip-recharge : فقط پاککردن؛ شارژ نکن}
|
||||||
{--execute : اجرای واقعی (بدون این فلگ فقط پیشنمایش)}';
|
{--execute : اجرای واقعی}';
|
||||||
|
|
||||||
protected $description = 'پاککردن اثر خرید اشتباه و شارژ مجدد ریالی (بدون ردپای سفارش)';
|
protected $description = 'پاککردن اثر خرید اشتباه و شارژ مجدد ریالی (حتی اگر orders حذف شده باشد)';
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$orderId = (int) $this->option('order');
|
$orderId = (int) $this->option('order');
|
||||||
$customerId = (int) $this->option('customer');
|
$customerId = (int) $this->option('customer');
|
||||||
|
$transactionId = (int) $this->option('transaction');
|
||||||
|
$goldOpt = $this->option('gold');
|
||||||
|
$goldForced = $goldOpt !== null && $goldOpt !== '' ? (float) $goldOpt : null;
|
||||||
$rechargeRial = (int) round((float) $this->option('recharge-rial'));
|
$rechargeRial = (int) round((float) $this->option('recharge-rial'));
|
||||||
$skipRecharge = (bool) $this->option('skip-recharge');
|
$skipRecharge = (bool) $this->option('skip-recharge');
|
||||||
$execute = (bool) $this->option('execute');
|
$execute = (bool) $this->option('execute');
|
||||||
|
|
||||||
$order = $this->resolveOrder($orderId, $customerId);
|
$resolved = $this->resolveTarget($orderId, $customerId, $transactionId, $goldForced);
|
||||||
if (!$order) {
|
if ($resolved === null) {
|
||||||
return self::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$customer = Customer::find($order->customer_id);
|
/** @var Customer $customer */
|
||||||
if (!$customer) {
|
$customer = $resolved['customer'];
|
||||||
$this->error("مشتری سفارش #{$order->id} یافت نشد.");
|
/** @var Order|null $order */
|
||||||
|
$order = $resolved['order'];
|
||||||
return self::FAILURE;
|
/** @var Collection<int, Transaction> $txs */
|
||||||
}
|
$txs = $resolved['txs'];
|
||||||
|
$orphanOrderId = $resolved['orphan_order_id'];
|
||||||
if ((int) $order->direction !== 1) {
|
$label = $resolved['label'];
|
||||||
$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 = [];
|
$goldByWallet = [];
|
||||||
$cashByWallet = [];
|
|
||||||
foreach ($txs as $tx) {
|
foreach ($txs as $tx) {
|
||||||
$wid = (int) $tx->wallet;
|
$wid = (int) $tx->wallet;
|
||||||
$goldByWallet[$wid] = ($goldByWallet[$wid] ?? 0.0) + (float) $tx->gold;
|
$goldByWallet[$wid] = ($goldByWallet[$wid] ?? 0.0) + (float) $tx->gold;
|
||||||
$cashByWallet[$wid] = ($cashByWallet[$wid] ?? 0.0) + (float) $tx->cash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// فقط طلای واریزشده را برمیداریم؛ ریال کسرشده را با شارژ جدا برمیگردانیم تا «شارژ ۱۰ میلیون» در تاریخچه بماند.
|
|
||||||
$goldToStrip = [];
|
$goldToStrip = [];
|
||||||
foreach ($goldByWallet as $wid => $delta) {
|
foreach ($goldByWallet as $wid => $delta) {
|
||||||
if ($delta > 1e-12) {
|
if ($delta > 1e-12) {
|
||||||
$goldToStrip[$wid] = $delta;
|
$goldToStrip[$wid] = $delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($goldToStrip === [] && (float) $order->amount > 0) {
|
|
||||||
|
if ($goldToStrip === []) {
|
||||||
$main = $this->mainWallet((int) $customer->id, (int) $customer->type);
|
$main = $this->mainWallet((int) $customer->id, (int) $customer->type);
|
||||||
if ($main) {
|
$fallbackGold = $goldForced
|
||||||
$goldToStrip[(int) $main->id] = (float) $order->amount;
|
?? ($order ? (float) $order->amount : null)
|
||||||
|
?? 0.105;
|
||||||
|
if ($main && $fallbackGold > 0) {
|
||||||
|
$goldToStrip[(int) $main->id] = $fallbackGold;
|
||||||
|
$this->warn("تراکنش طلای مثبت پیدا نشد — از مقدار {$fallbackGold} گرم روی کیف اصلی استفاده میشود.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$adminRows = TransactionsAdmin::withTrashed()->where('order_id', $order->id)->get();
|
if ($goldToStrip === []) {
|
||||||
$zarinRows = Schema::hasTable('zarin')
|
$this->error('مقداری برای کسر طلا مشخص نشد. --gold=0.105 بدهید.');
|
||||||
? Zarin::query()->where('order_id', $order->id)->get()
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$refOrderId = $order?->id ?? $orphanOrderId;
|
||||||
|
$adminRows = $refOrderId
|
||||||
|
? TransactionsAdmin::withTrashed()->where('order_id', $refOrderId)->get()
|
||||||
: collect();
|
: collect();
|
||||||
$tahesabRows = Schema::hasTable('tahesab')
|
$zarinRows = ($refOrderId && Schema::hasTable('zarin'))
|
||||||
? Tahesab::query()->where('order_id', $order->id)->get()
|
? Zarin::query()->where('order_id', $refOrderId)->get()
|
||||||
|
: collect();
|
||||||
|
$tahesabRows = ($refOrderId && Schema::hasTable('tahesab'))
|
||||||
|
? Tahesab::query()->where('order_id', $refOrderId)->get()
|
||||||
: collect();
|
: collect();
|
||||||
|
|
||||||
$payment = (float) ($order->payment_amount ?: $order->total_price);
|
// تراکنشهای ادمین لینکشده به همین transaction idها
|
||||||
|
$txIds = $txs->pluck('id')->all();
|
||||||
|
if ($txIds !== []) {
|
||||||
|
$adminByTx = TransactionsAdmin::withTrashed()
|
||||||
|
->whereIn('transaction_id', $txIds)
|
||||||
|
->get();
|
||||||
|
$adminRows = $adminRows->merge($adminByTx)->unique('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment = (float) ($order?->payment_amount ?: $order?->total_price ?: 0);
|
||||||
|
if ($payment <= 0) {
|
||||||
|
$cashTaken = 0.0;
|
||||||
|
foreach ($txs as $tx) {
|
||||||
|
if ((float) $tx->cash < 0) {
|
||||||
|
$cashTaken += abs((float) $tx->cash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$payment = $cashTaken;
|
||||||
|
}
|
||||||
if ($rechargeRial <= 0) {
|
if ($rechargeRial <= 0) {
|
||||||
$rechargeRial = (int) round($payment > 0 ? $payment : 100_000_000);
|
$rechargeRial = (int) round($payment > 0 ? $payment : 100_000_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->info('=== پیشنمایش پاککردن خرید اشتباه ===');
|
$this->info('=== پیشنمایش پاککردن خرید اشتباه ===');
|
||||||
$this->line("مشتری: {$customer->id} — " . trim(($customer->fname ?? '') . ' ' . ($customer->lname ?? '') . ' ' . ($customer->name ?? '')));
|
$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('هدف: ' . $label);
|
||||||
$this->line('تراکنشهای سفارش: ' . $txs->pluck('id')->implode(', '));
|
if ($order) {
|
||||||
foreach ($txs as $tx) {
|
$this->line("سفارش موجود: #{$order->id} | amount={$order->amount} | price={$order->price} | payment={$payment}");
|
||||||
$this->line(" tx#{$tx->id} wallet={$tx->wallet} type={$tx->type} gold={$tx->gold} cash={$tx->cash} title=" . ($tx->title ?? ''));
|
} elseif ($orphanOrderId) {
|
||||||
|
$this->warn("سفارش #{$orphanOrderId} در جدول orders نیست (احتمالاً دستی پاک شده) — فقط اثر باقیمانده پاک میشود.");
|
||||||
|
} else {
|
||||||
|
$this->warn('بدون ردیف سفارش — فقط تراکنش/موجودی اصلاح میشود.');
|
||||||
}
|
}
|
||||||
$this->line('دفترکل محلی (transactions_admins): ' . $adminRows->count());
|
|
||||||
$this->line('اسناد zarin محلی: ' . $zarinRows->count());
|
$this->line('تراکنشها: ' . ($txIds ? implode(', ', $txIds) : '—'));
|
||||||
$this->line('اسناد tahesab محلی: ' . $tahesabRows->count());
|
foreach ($txs as $tx) {
|
||||||
|
$this->line(" tx#{$tx->id} wallet={$tx->wallet} type={$tx->type} gold={$tx->gold} cash={$tx->cash} order_id=" . ($tx->order_id ?? 'null') . ' title=' . ($tx->title ?? ''));
|
||||||
|
}
|
||||||
|
$this->line('دفترکل محلی: ' . $adminRows->count());
|
||||||
|
$this->line('zarin محلی: ' . $zarinRows->count() . ' | tahesab محلی: ' . $tahesabRows->count());
|
||||||
|
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->info('--- تغییر موجودی طلا (بدون ثبت تراکنش برگشتی) ---');
|
$this->info('--- کسر طلا از کیف (بدون تراکنش برگشتی) ---');
|
||||||
foreach ($goldToStrip as $wid => $g) {
|
foreach ($goldToStrip as $wid => $g) {
|
||||||
$w = Wallet::find($wid);
|
$w = Wallet::find($wid);
|
||||||
if (!$w) {
|
if (!$w) {
|
||||||
@@ -135,7 +160,7 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$after = $before - $g;
|
$after = $before - $g;
|
||||||
$this->line("wallet#{$wid} gold: {$before} → {$after} (−{$g})");
|
$this->line("wallet#{$wid} gold: {$before} → {$after} (−{$g})");
|
||||||
if ($after < -1e-6) {
|
if ($after < -1e-6) {
|
||||||
$this->error('بعد از کسر، طلا منفی میشود — متوقف شد.');
|
$this->error('بعد از کسر طلا منفی میشود — متوقف شد.');
|
||||||
|
|
||||||
return self::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
@@ -148,16 +173,22 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$this->info('--- شارژ مجدد ریالی ---');
|
$this->info('--- شارژ مجدد ریالی ---');
|
||||||
$this->line('مبلغ: ' . number_format($rechargeRial) . ' ریال (= ' . number_format($rechargeRial / 10) . ' تومان)');
|
$this->line('مبلغ: ' . number_format($rechargeRial) . ' ریال (= ' . number_format($rechargeRial / 10) . ' تومان)');
|
||||||
$this->line('کیف اصلی cash: ' . number_format($cashBefore) . ' → ' . number_format($cashBefore + $rechargeRial));
|
$this->line('کیف اصلی cash: ' . number_format($cashBefore) . ' → ' . number_format($cashBefore + $rechargeRial));
|
||||||
$this->warn('سند حسابداری راه دور برای این شارژ ساخته نمیشود (فقط کیف + withdraw + تراکنش).');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->warn('Montzar / زرین راه دور: این دستور سفارش همکار و سند آنلاین را لغو نمیکند.');
|
$this->warn('Montzar / زرین راه دور لغو نمیشود.');
|
||||||
|
|
||||||
if (!$execute) {
|
if (!$execute) {
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->warn('حالت پیشنمایش — برای اجرا:');
|
$this->warn('پیشنمایش — برای اجرا یکی از اینها:');
|
||||||
$this->line(' php artisan wallet:erase-bad-buy-recharge --order=' . $order->id . ' --execute');
|
if ($order) {
|
||||||
|
$this->line(" php artisan wallet:erase-bad-buy-recharge --order={$order->id} --execute");
|
||||||
|
} elseif ($txIds !== []) {
|
||||||
|
$this->line(' php artisan wallet:erase-bad-buy-recharge --customer=' . $customer->id . ' --transaction=' . $txIds[0] . ' --execute');
|
||||||
|
} else {
|
||||||
|
$g = reset($goldToStrip);
|
||||||
|
$this->line(' php artisan wallet:erase-bad-buy-recharge --customer=' . $customer->id . ' --gold=' . $g . ' --execute');
|
||||||
|
}
|
||||||
|
|
||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -167,11 +198,13 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$adminUserId = (int) (User::query()->orderBy('id')->value('id') ?: 0);
|
$adminUserId = (int) (User::query()->orderBy('id')->value('id') ?: 0);
|
||||||
}
|
}
|
||||||
if (!$skipRecharge && $adminUserId <= 0) {
|
if (!$skipRecharge && $adminUserId <= 0) {
|
||||||
$this->error('کاربر ادمین برای withdraw پیدا نشد؛ --user بدهید.');
|
$this->error('کاربر ادمین پیدا نشد؛ --user بدهید.');
|
||||||
|
|
||||||
return self::FAILURE;
|
return self::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$noteTag = $order?->id ?? $orphanOrderId ?? ($txIds[0] ?? 'manual');
|
||||||
|
|
||||||
DB::transaction(function () use (
|
DB::transaction(function () use (
|
||||||
$order,
|
$order,
|
||||||
$customer,
|
$customer,
|
||||||
@@ -182,7 +215,9 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$tahesabRows,
|
$tahesabRows,
|
||||||
$skipRecharge,
|
$skipRecharge,
|
||||||
$rechargeRial,
|
$rechargeRial,
|
||||||
$adminUserId
|
$adminUserId,
|
||||||
|
$noteTag,
|
||||||
|
$refOrderId
|
||||||
) {
|
) {
|
||||||
foreach ($goldToStrip as $wid => $g) {
|
foreach ($goldToStrip as $wid => $g) {
|
||||||
$w = Wallet::query()->lockForUpdate()->find($wid);
|
$w = Wallet::query()->lockForUpdate()->find($wid);
|
||||||
@@ -196,12 +231,12 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$w->save();
|
$w->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
$txIds = $txs->pluck('id')->all();
|
$ids = $txs->pluck('id')->all();
|
||||||
if ($txIds !== []) {
|
if ($ids !== []) {
|
||||||
if (Schema::hasTable('transaction_metas')) {
|
if (Schema::hasTable('transaction_metas')) {
|
||||||
TransactionMeta::query()->whereIn('transaction', $txIds)->delete();
|
TransactionMeta::query()->whereIn('transaction', $ids)->delete();
|
||||||
}
|
}
|
||||||
Transaction::query()->whereIn('id', $txIds)->delete();
|
Transaction::query()->whereIn('id', $ids)->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($adminRows as $row) {
|
foreach ($adminRows as $row) {
|
||||||
@@ -214,12 +249,13 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
$row->delete();
|
$row->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// پاداش معرفی / دفترکل وابسته به همین سفارش
|
if ($refOrderId && Schema::hasColumn('transactions_admins', 'order_id')) {
|
||||||
if (Schema::hasColumn('transactions_admins', 'order_id')) {
|
TransactionsAdmin::withTrashed()->where('order_id', $refOrderId)->forceDelete();
|
||||||
TransactionsAdmin::withTrashed()->where('order_id', $order->id)->forceDelete();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Order::query()->where('id', $order->id)->delete();
|
if ($order) {
|
||||||
|
Order::query()->where('id', $order->id)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
if (!$skipRecharge) {
|
if (!$skipRecharge) {
|
||||||
$walletInterface = new WalletInterface((int) $customer->id, (int) $customer->type);
|
$walletInterface = new WalletInterface((int) $customer->id, (int) $customer->type);
|
||||||
@@ -227,7 +263,7 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
'user_id' => $adminUserId,
|
'user_id' => $adminUserId,
|
||||||
'customer_id' => $customer->id,
|
'customer_id' => $customer->id,
|
||||||
'cash' => $rechargeRial,
|
'cash' => $rechargeRial,
|
||||||
'note' => 'شارژ اصلاحی پس از حذف خرید اشتباه #' . $order->id,
|
'note' => 'شارژ اصلاحی پس از حذف خرید اشتباه #' . $noteTag,
|
||||||
'sts' => 4,
|
'sts' => 4,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -267,37 +303,113 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->info('انجام شد: سفارش و تراکنشهایش پاک شدند' . ($skipRecharge ? '.' : ' و شارژ ریالی ثبت شد.'));
|
$this->info('انجام شد' . ($skipRecharge ? '.' : ' — شارژ ریالی هم ثبت شد.'));
|
||||||
|
|
||||||
$main = $this->mainWallet((int) $customer->id, (int) $customer->type);
|
$main = $this->mainWallet((int) $customer->id, (int) $customer->type);
|
||||||
if ($main) {
|
if ($main) {
|
||||||
$this->line('موجودی فعلی کیف اصلی — cash=' . number_format((float) $main->cash) . ' | gold=' . round((float) $main->gold, 6));
|
$this->line('موجودی کیف اصلی — cash=' . number_format((float) $main->cash) . ' | gold=' . round((float) $main->gold, 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveOrder(int $orderId, int $customerId): ?Order
|
/**
|
||||||
|
* @return array{customer: Customer, order: ?Order, txs: Collection, orphan_order_id: ?int, label: string}|null
|
||||||
|
*/
|
||||||
|
private function resolveTarget(int $orderId, int $customerId, int $transactionId, ?float $goldForced): ?array
|
||||||
{
|
{
|
||||||
|
// ۱) سفارش هنوز هست
|
||||||
if ($orderId > 0) {
|
if ($orderId > 0) {
|
||||||
$order = Order::find($orderId);
|
$order = Order::find($orderId);
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
$this->error("سفارش #{$orderId} یافت نشد.");
|
$this->warn("سفارش #{$orderId} در orders نیست — سراغ تراکنشهای order_id={$orderId} میرویم.");
|
||||||
|
$txs = Transaction::query()->where('order_id', $orderId)->orderBy('id')->get();
|
||||||
|
if ($txs->isEmpty() && $customerId <= 0) {
|
||||||
|
$this->error('تراکنشی با این order_id هم نیست. --customer و --gold یا --transaction بدهید.');
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$customer = $customerId > 0
|
||||||
|
? Customer::find($customerId)
|
||||||
|
: Customer::find((int) ($txs->first()?->customer_id));
|
||||||
|
if (!$customer) {
|
||||||
|
$this->error('مشتری مشخص نشد.');
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'customer' => $customer,
|
||||||
|
'order' => null,
|
||||||
|
'txs' => $txs,
|
||||||
|
'orphan_order_id' => $orderId,
|
||||||
|
'label' => "سفارش حذفشده #{$orderId}",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer = Customer::find($order->customer_id);
|
||||||
|
if (!$customer) {
|
||||||
|
$this->error('مشتری سفارش یافت نشد.');
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $order;
|
return [
|
||||||
|
'customer' => $customer,
|
||||||
|
'order' => $order,
|
||||||
|
'txs' => Transaction::query()->where('order_id', $order->id)->orderBy('id')->get(),
|
||||||
|
'orphan_order_id' => null,
|
||||||
|
'label' => "سفارش #{$order->id}",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۲) تراکنش مشخص
|
||||||
|
if ($transactionId > 0) {
|
||||||
|
$tx = Transaction::find($transactionId);
|
||||||
|
if (!$tx) {
|
||||||
|
$this->error("تراکنش #{$transactionId} یافت نشد.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$customer = Customer::find((int) $tx->customer_id);
|
||||||
|
if (!$customer) {
|
||||||
|
$this->error('مشتری تراکنش یافت نشد.');
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$txs = collect([$tx]);
|
||||||
|
if ($tx->order_id) {
|
||||||
|
$siblings = Transaction::query()
|
||||||
|
->where('order_id', $tx->order_id)
|
||||||
|
->where('id', '!=', $tx->id)
|
||||||
|
->get();
|
||||||
|
$txs = $txs->merge($siblings)->sortBy('id')->values();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'customer' => $customer,
|
||||||
|
'order' => $tx->order_id ? Order::find($tx->order_id) : null,
|
||||||
|
'txs' => $txs,
|
||||||
|
'orphan_order_id' => $tx->order_id && ! Order::find($tx->order_id) ? (int) $tx->order_id : null,
|
||||||
|
'label' => "تراکنش #{$transactionId}",
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($customerId <= 0) {
|
if ($customerId <= 0) {
|
||||||
$this->error('--order یا --customer الزامی است.');
|
$this->error('--order یا --customer یا --transaction الزامی است.');
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// نزدیک به کیس ۱۰ میلیون تومان / ~۰٫۱۰۵ گرم
|
$customer = Customer::find($customerId);
|
||||||
$candidates = Order::query()
|
if (!$customer) {
|
||||||
|
$this->error("مشتری {$customerId} یافت نشد.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۳) سفارش شبیه ۱۰ میلیون / ۰٫۱۰۵
|
||||||
|
$order = Order::query()
|
||||||
->where('customer_id', $customerId)
|
->where('customer_id', $customerId)
|
||||||
->where('direction', 1)
|
->where('direction', 1)
|
||||||
->where('sts', 2)
|
->where('sts', 2)
|
||||||
@@ -307,32 +419,100 @@ class EraseBadBuyAndRechargeCommand extends Command
|
|||||||
->orWhereBetween('total_price', [90_000_000, 110_000_000]);
|
->orWhereBetween('total_price', [90_000_000, 110_000_000]);
|
||||||
})
|
})
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->get();
|
->first();
|
||||||
|
|
||||||
if ($candidates->isEmpty()) {
|
if ($order) {
|
||||||
$this->error("برای مشتری {$customerId} سفارشی شبیه ۱۰ میلیون / ~۰٫۱۰۵ گرم پیدا نشد. --order بدهید.");
|
return [
|
||||||
$this->line('آخرین خریدها:');
|
'customer' => $customer,
|
||||||
Order::query()
|
'order' => $order,
|
||||||
->where('customer_id', $customerId)
|
'txs' => Transaction::query()->where('order_id', $order->id)->orderBy('id')->get(),
|
||||||
->where('direction', 1)
|
'orphan_order_id' => null,
|
||||||
->orderByDesc('id')
|
'label' => "سفارش #{$order->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) {
|
// ۴) تراکنش یتیم: طلا ~۰٫۱۰۵ و/یا cash ~−۱۰۰ میلیون
|
||||||
$this->warn('چند نامزد پیدا شد؛ اولی (جدیدتر) انتخاب میشود. در صورت نیاز --order بدهید:');
|
$orphanTxs = Transaction::query()
|
||||||
foreach ($candidates as $o) {
|
->where('customer_id', $customerId)
|
||||||
$this->line(" #{$o->id} amount={$o->amount} pay={$o->payment_amount} price={$o->price}");
|
->where(function ($q) {
|
||||||
|
$q->whereBetween('gold', [0.09, 0.12])
|
||||||
|
->orWhereBetween('cash', [-110_000_000, -90_000_000]);
|
||||||
|
})
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit(20)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// گروهبندی با order_id (حتی اگر سفارش پاک شده)
|
||||||
|
$byOrder = $orphanTxs->groupBy(fn ($t) => $t->order_id ?: ('tx:' . $t->id));
|
||||||
|
$best = null;
|
||||||
|
$bestScore = -1;
|
||||||
|
foreach ($byOrder as $group) {
|
||||||
|
$g = (float) $group->sum('gold');
|
||||||
|
$c = (float) $group->sum(fn ($t) => min(0, (float) $t->cash));
|
||||||
|
$score = 0;
|
||||||
|
if ($g >= 0.09 && $g <= 0.12) {
|
||||||
|
$score += 2;
|
||||||
|
}
|
||||||
|
if ($c <= -90_000_000 && $c >= -110_000_000) {
|
||||||
|
$score += 2;
|
||||||
|
}
|
||||||
|
if ($score > $bestScore) {
|
||||||
|
$bestScore = $score;
|
||||||
|
$best = $group;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $candidates->first();
|
if ($best && $bestScore > 0) {
|
||||||
|
$oid = $best->first()->order_id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'customer' => $customer,
|
||||||
|
'order' => $oid ? Order::find($oid) : null,
|
||||||
|
'txs' => $best->sortBy('id')->values(),
|
||||||
|
'orphan_order_id' => $oid && ! Order::find($oid) ? (int) $oid : null,
|
||||||
|
'label' => $oid ? "تراکنشهای order_id={$oid} (سفارش حذفشده؟)" : 'تراکنش یتیم طلا/ریال',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۵) فقط --gold: موجودی را کم کن، تراکنشی پاک نکن مگر پیدا شود
|
||||||
|
if ($goldForced !== null && $goldForced > 0) {
|
||||||
|
$this->warn("سفارش/تراکنش مشابه پیدا نشد — فقط --gold={$goldForced} روی کیف اعمال میشود.");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'customer' => $customer,
|
||||||
|
'order' => null,
|
||||||
|
'txs' => collect(),
|
||||||
|
'orphan_order_id' => null,
|
||||||
|
'label' => "کسر دستی طلا {$goldForced} گرم",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->error("برای مشتری {$customerId} نه سفارش مانده نه تراکنش شبیه ۱۰ میلیون / ۰٫۱۰۵ گرم.");
|
||||||
|
$this->line('آخرین خریدهای orders:');
|
||||||
|
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} at={$o->created_at}");
|
||||||
|
});
|
||||||
|
$this->line('آخرین تراکنشهای طلای مثبت:');
|
||||||
|
Transaction::query()
|
||||||
|
->where('customer_id', $customerId)
|
||||||
|
->where('gold', '>', 0)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit(10)
|
||||||
|
->get(['id', 'order_id', 'type', 'gold', 'cash', 'title', 'created_at'])
|
||||||
|
->each(function (Transaction $t) {
|
||||||
|
$this->line(" tx#{$t->id} order_id={$t->order_id} type={$t->type} gold={$t->gold} cash={$t->cash} at={$t->created_at}");
|
||||||
|
});
|
||||||
|
$this->newLine();
|
||||||
|
$this->warn('اگر فقط طلا مانده و تراکنش را هم پاک کردهاید:');
|
||||||
|
$this->line(" php artisan wallet:erase-bad-buy-recharge --customer={$customerId} --gold=0.105 --execute");
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function mainWallet(int $customerId, int $ownerType): ?Wallet
|
private function mainWallet(int $customerId, int $ownerType): ?Wallet
|
||||||
|
|||||||
Reference in New Issue
Block a user