diff --git a/.cursor/skills/abshode-domain-lookup/learned-notes.md b/.cursor/skills/abshode-domain-lookup/learned-notes.md index a271e19..271ef35 100644 --- a/.cursor/skills/abshode-domain-lookup/learned-notes.md +++ b/.cursor/skills/abshode-domain-lookup/learned-notes.md @@ -3,6 +3,7 @@ فرمت هر خط: - YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی +- 2026-11-14 | کیف ریال owner_type ناهماهنگ | `WalletBalanceService::absorbMismatchedOwnerTypeBalances` + `WalletInterface` | واریز روی owner_type=0 در دارایی نوع مشتری دیده نمی‌شود؛ با باز شدن کیف به نوع فعلی منتقل می‌شود - 2026-11-05 | کد کاربری مشتری بعدی | `Customer::nextSequentialId` در `creating` | کد کاربری=`customers.id`؛ بعد از UPDATE دستی id، AUTO_INCREMENT پایین نمی‌آید (۵۵۱ به‌جای ۲۴). ایجاد مشتری = اولین id خالی، نه شمارندهٔ InnoDB - 2026-11-04 | فرمت کد تفصیلی شایگان | `ShayeganSyncService::customerTafsilNumber` | کل `shayegan_kol_customer` (121) + کد کاربر ۷رقمی: مشتری 1 → `1210000001`. کد ذخیره‌شدهٔ قبلی عوض نمی‌شود - 2026-11-02 | جدول اسناد شایگان | `shayeganTable.vue` + `ShayeganController::table` | BaseTable؛ پاسخ بدون `query` را خود فرانت به `{rows,query}` برمی‌گرداند. ستون عملیات `ops` است تا در RTL هشدار fixed ندهد diff --git a/app/Http/Controllers/Customer/WalletController.php b/app/Http/Controllers/Customer/WalletController.php index 308e775..7e94590 100644 --- a/app/Http/Controllers/Customer/WalletController.php +++ b/app/Http/Controllers/Customer/WalletController.php @@ -89,6 +89,7 @@ class WalletController extends Controller $customer = auth('customer')->user(); $customerId = (int) $customer->id; $ownerType = (int) $customer->type; + $balanceService->absorbMismatchedOwnerTypeBalances($customerId, $ownerType); $wallets = Wallet::query() ->where('owner', $customerId) diff --git a/app/Services/AssetSummaryService.php b/app/Services/AssetSummaryService.php index df04dc7..7310c96 100644 --- a/app/Services/AssetSummaryService.php +++ b/app/Services/AssetSummaryService.php @@ -26,6 +26,10 @@ class AssetSummaryService public function buildForCustomer(int $customerId, int $ownerType, bool $includeInactiveProducts = false): array { + if ($customerId > 0 && $ownerType > 0) { + $this->walletBalanceService->absorbMismatchedOwnerTypeBalances($customerId, $ownerType); + } + $catalogQuery = Product::query()->with('file'); if (!$includeInactiveProducts) { $catalogQuery->where('active', 1); diff --git a/app/Services/WalletBalanceService.php b/app/Services/WalletBalanceService.php index b69cb96..c1762c7 100644 --- a/app/Services/WalletBalanceService.php +++ b/app/Services/WalletBalanceService.php @@ -7,6 +7,8 @@ use App\Models\Price; use App\Models\Product; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +use Rooyesh\Wallet\interfaces\TransactionInterface; +use Rooyesh\Wallet\interfaces\WalletInterface; use Rooyesh\Wallet\models\Wallet; /** @@ -17,6 +19,11 @@ use Rooyesh\Wallet\models\Wallet; */ class WalletBalanceService { + /** @var array */ + private static array $absorbedOwnerTypes = []; + + private static bool $absorbingOwnerType = false; + public function __construct( private ConfigService $configService, private BalanceReportService $balanceReportService @@ -161,6 +168,85 @@ class WalletBalanceService return true; } + /** + * ماندهٔ کیف‌هایی که owner_typeشان با نوع فعلی مشتری یکی نیست + * (مثلاً واریز وقتی type هنوز ۰ بوده) به کیف همان محصول/قلک روی نوع فعلی منتقل می‌شود. + */ + public function absorbMismatchedOwnerTypeBalances(int $ownerId, int $canonicalType): bool + { + if ($ownerId <= 0 || $canonicalType <= 0 || self::$absorbingOwnerType) { + return false; + } + + $key = $ownerId.':'.$canonicalType; + if (isset(self::$absorbedOwnerTypes[$key])) { + return false; + } + self::$absorbedOwnerTypes[$key] = true; + + $foreign = Wallet::query() + ->where('owner', $ownerId) + ->where('owner_type', '!=', $canonicalType) + ->where(function ($query) { + $query->where('cash', '!=', 0)->orWhere('gold', '!=', 0); + }) + ->get(); + + if ($foreign->isEmpty()) { + return false; + } + + self::$absorbingOwnerType = true; + try { + foreach ($foreign as $row) { + DB::transaction(function () use ($row, $ownerId, $canonicalType) { + $locked = Wallet::query()->whereKey($row->id)->lockForUpdate()->first(); + if (!$locked) { + return; + } + + $cash = (int) round((float) $locked->cash); + $gold = (float) $locked->gold; + if ($cash === 0 && abs($gold) < 0.0000001) { + return; + } + + $from = new WalletInterface($ownerId, (int) $locked->owner_type, $locked->fund_id, $locked->product_id); + $to = new WalletInterface($ownerId, $canonicalType, $locked->fund_id, $locked->product_id); + $note = 'انتقال مانده به کیف اصلی مشتری'; + + if ($cash !== 0) { + $payload = [ + 'cash' => abs($cash), + 'gold' => 0, + 'note' => $note, + 'expire_at' => now(), + 'active' => true, + ]; + new TransactionInterface($from, $payload, 7, false, $cash < 0); + new TransactionInterface($to, $payload, 7, false, $cash > 0); + } + + if (abs($gold) >= 0.0000001) { + $payload = [ + 'gold' => abs($gold), + 'cash' => 0, + 'note' => $note, + 'expire_at' => now(), + 'active' => true, + ]; + new TransactionInterface($from, $payload, 7, $gold < 0, false); + new TransactionInterface($to, $payload, 7, $gold > 0, false); + } + }); + } + } finally { + self::$absorbingOwnerType = false; + } + + return true; + } + /** ادغام همهٔ کیف‌های legacy محصولات main_wallet_products برای یک کاربر */ public function consolidateAllMainWalletProductsForOwner(int $ownerId, int $ownerType): void { diff --git a/packages/rooyesh/wallet/src/interfaces/WalletInterface.php b/packages/rooyesh/wallet/src/interfaces/WalletInterface.php index 46de601..f97f428 100644 --- a/packages/rooyesh/wallet/src/interfaces/WalletInterface.php +++ b/packages/rooyesh/wallet/src/interfaces/WalletInterface.php @@ -44,6 +44,22 @@ class WalletInterface ]); $this->set_hash(); } + + $this->absorbMismatchedOwnerTypeBalances(); + } + + /** واریزِ مانده روی owner_type قدیمی را به نوع فعلی مشتری برمی‌گرداند. */ + private function absorbMismatchedOwnerTypeBalances(): void + { + $ownerType = (int) $this->owner_type; + $ownerId = (int) $this->owner_id; + if ($ownerType <= 0 || $ownerId <= 0 || !$this->wallet) { + return; + } + + app(\App\Services\WalletBalanceService::class) + ->absorbMismatchedOwnerTypeBalances($ownerId, $ownerType); + $this->wallet->refresh(); } public function validate(): bool