"boolean", "gold" => "float", "is_archive" => "boolean", "finger_print" => "boolean", "conditional_order_active" => "boolean", "conditional_order_limit" => "integer", "profit_amount_by_lvl" => "array", ]; static $TYPE = [ 1=>'همکار', 2=>'خانگی', 3=>'سرمایه گذار' ]; static $TYPES = [ 1=>'همکار', 2=>'خانگی', 3=>'سرمایه گذار' ]; public function setNameAttribute($value = null) { $fname = $this->attributes['fname'] ?? ''; $lname = $this->attributes['lname'] ?? ''; $this->attributes['name'] = trim("$fname $lname"); } public function wallet() { return $this->hasMany(Wallet::class ,'owner')->with(['fund','product'])->select(['cash', 'fund_id','product_id','gold', 'owner', 'owner_type']); } public function orders() { return $this->hasMany(Order::class, 'customer_id'); } public function user() { return $this->belongsTo(User::class)->select(['id','name']); } public function referral() { return $this->belongsTo(Customer::class, 'referral_to_id', 'referral_id'); } /** معرف واقعی — با جستجوی صریح و جلوگیری از برگشت خود کاربر */ public function referrer(): ?self { if (empty($this->referral_to_id)) { return null; } return static::query() ->where('referral_id', $this->referral_to_id) ->where('id', '!=', $this->id) ->first(); } // hash password public function setPasswordAttribute($value){ if ($value === null || $value === '') { return; } $this->attributes['password'] = Hash::make($value); } public function getJWTIdentifier() { return $this->getKey(); } public function getJWTCustomClaims() { return []; } public function tahesab() { return $this->hasMany(Tahesab::class, 'customer_id', 'id'); } public function customer_settings() { return $this->hasMany(CustomersSetting::class, 'agent_id'); } public function package() { return $this->belongsTo(Package::class); } public function agent() { return $this->belongsTo(Customer::class, 'agent_id'); } protected static function booted(): void { static::saving(function (self $customer) { $mobile = self::normalizeMobile($customer->mobile); if ($mobile === null) { return; } $customer->mobile = $mobile; self::assertMobileUnique($mobile, $customer->exists ? (int) $customer->id : null); }); } public static function normalizeMobile(?string $mobile): ?string { $digits = preg_replace('/\D+/', '', trim((string) $mobile)); if ($digits === '') { return null; } if (str_starts_with($digits, '98') && strlen($digits) >= 12) { $digits = substr($digits, 2); } $digits = ltrim($digits, '0'); if ($digits === '') { return null; } return '0' . $digits; } public static function findActiveByMobile(?string $mobile, ?int $exceptId = null): ?self { $mobile = self::normalizeMobile($mobile); if ($mobile === null) { return null; } $query = static::query() ->whereNull('deleted_at') ->whereIn('mobile', self::mobileLookupVariants($mobile)); if ($exceptId !== null) { $query->where('id', '<>', $exceptId); } return $query->orderByDesc('id')->first(); } /** * مشتریِ ازقبل‌واردشده از حسابداری که هنوز وارد اپ نشده — قابل claim در ثبت‌نام. * شامل soft-deleted با کد حسابداری (برای جلوگیری از ساخت ردیف تکراری). */ public static function findClaimableImportedByMobile(?string $mobile): ?self { $normalized = self::normalizeMobile($mobile); if ($normalized === null) { return null; } $variants = self::mobileLookupVariants($normalized); $active = static::query() ->whereNull('deleted_at') ->whereIn('mobile', $variants) ->whereNull('last_seen_at') ->orderByDesc('id') ->first(); if ($active) { return $active; } return static::onlyTrashed() ->whereIn('mobile', $variants) ->whereNull('last_seen_at') ->where(function ($q) { $q->where(function ($inner) { $inner->whereNotNull('pargar_account_id') ->where('pargar_account_id', '!=', '') ->whereNotIn('pargar_account_id', ['0', '-1']); })->orWhere(function ($inner) { $inner->whereNotNull('moshtari_code') ->where('moshtari_code', '!=', '') ->whereNotIn('moshtari_code', ['0', '-1']); }); }) ->orderByDesc('id') ->first(); } public static function isMobileTaken(?string $mobile, ?int $exceptId = null): bool { return self::isMobileTakenOnTable(self::class, $mobile, $exceptId); } public static function isMobileTakenOnTable(string $modelClass, ?string $mobile, ?int $exceptId = null): bool { $normalized = self::normalizeMobile($mobile); if ($normalized === null) { return false; } if ($exceptId !== null) { $current = $modelClass::query()->whereNull('deleted_at')->find($exceptId); if ($current !== null && self::normalizeMobile($current->mobile ?? '') === $normalized) { return false; } } $query = $modelClass::query() ->whereNull('deleted_at') ->whereIn('mobile', self::mobileLookupVariants($normalized)); if ($exceptId !== null) { $query->where('id', '<>', $exceptId); } return $query->exists(); } public static function mobileLookupVariants(string $normalized): array { $digits = ltrim($normalized, '0'); return array_values(array_unique(array_filter([ $normalized, $digits, '0'.$digits, '98'.$digits, '+98'.$digits, ]))); } public static function assertMobileUnique(?string $mobile, ?int $exceptId = null): void { if (self::isMobileTaken($mobile, $exceptId)) { throw new UnprocessableEntityHttpException('این شماره موبایل قبلاً ثبت شده است'); } } /** کد معتبر حسابداری (پرگار/ته‌حساب) — خالی و 0 و -1 نامعتبرند */ public static function isValidAccountingCode(mixed $value): bool { if ($value === null) { return false; } $code = trim((string) $value); return $code !== '' && ! in_array($code, ['0', '-1'], true); } public function hasValidPargarAccountId(): bool { return self::isValidAccountingCode($this->pargar_account_id); } public function hasValidMoshtariCode(): bool { return self::isValidAccountingCode($this->moshtari_code); } public function hasAccountingLink(): bool { return $this->hasValidPargarAccountId() || $this->hasValidMoshtariCode(); } /** هنوز وارد اپ نشده — معمولاً از import حسابداری آمده */ public function isUnclaimedAppIdentity(): bool { return $this->last_seen_at === null; } }