all customer from pargar fixing 2

This commit is contained in:
2026-07-22 00:31:38 +03:30
parent 1476f6efb1
commit 01031e9e01
7 changed files with 273 additions and 22 deletions
@@ -133,11 +133,43 @@ class PargarController extends Controller
$fullName = trim($item['name']);
$accountId = (string) $item['code'];
// بدون موبایل ردیف placeholder نساز — باعث ثبت‌نام تکراری و کیف جدا می‌شود
if ($mobile === '') {
$failed++;
$errors[] = [
'name' => $item['name'],
'code' => $item['code'],
'error' => 'شماره موبایل خالی است — وارد نشد',
];
continue;
}
$existing = Customer::whereNull('deleted_at')
->where('pargar_account_id', $accountId)
->first();
if ($existing) {
$changed = false;
if (($existing->mobile === null || trim((string) $existing->mobile) === '')) {
if (Customer::isMobileTaken($mobile, (int) $existing->id)) {
$failed++;
$errors[] = [
'name' => $item['name'],
'code' => $item['code'],
'error' => 'شماره موبایل تکراری است',
];
continue;
}
$existing->mobile = $mobile;
if (empty($existing->password)) {
$existing->password = $mobile;
}
$changed = true;
}
if ($changed) {
$existing->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $existing->id);
}
$success++;
continue;
}
@@ -162,6 +194,7 @@ class PargarController extends Controller
$byMobile->name = $fullName;
}
$byMobile->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $byMobile->id);
$success++;
continue;
}
@@ -190,10 +223,9 @@ class PargarController extends Controller
$customer->pargar_account_id = $accountId;
$customer->referral_id = $this->uniqueReferralId();
$customer->type = 2;
if ($mobile !== '') {
$customer->password = $mobile;
}
$customer->password = $mobile;
$customer->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
$success++;
} catch (\Throwable $e) {
$failed++;
@@ -230,6 +230,17 @@ class TahesabController extends Controller
$fullName = trim($item['name']);
$code = (int) $item['code'];
// بدون موبایل ردیف placeholder نساز
if ($mobile === '') {
$failed++;
$errors[] = [
'name' => $item['name'],
'code' => $item['code'],
'error' => 'شماره موبایل خالی است — وارد نشد',
];
continue;
}
$existing = Customer::whereNull('deleted_at')->where('moshtari_code', $code)->first();
if ($existing) {
$changed = false;
+84 -5
View File
@@ -8,6 +8,7 @@ use App\Models\Customer;
use App\Models\Setting;
use App\Models\User;
use App\Services\ConfigService;
use App\Services\PargarInboundWalletService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
@@ -25,7 +26,8 @@ class AuthController extends Controller
if ($data['provider'] == 'user') {
$user = User::where('mobile', $request->mobile)->first();
} else {
$user = Customer::where('mobile', $request->mobile)->first();
$user = Customer::findActiveByMobile($request->mobile)
?? Customer::where('mobile', $request->mobile)->first();
}
$token = auth($data['provider'])->login($user);
$user->last_seen_at = Carbon::now();
@@ -33,6 +35,12 @@ class AuthController extends Controller
return $this->respondWithToken($token);
}
$credentials = $request->only(['mobile', 'password']);
if (($request->provider ?? '') === 'customer') {
$normalized = Customer::normalizeMobile($credentials['mobile'] ?? null);
if ($normalized !== null) {
$credentials['mobile'] = $normalized;
}
}
return $this->_loginWithPassword($credentials, $request->provider);
} else {
return response()->json(
@@ -48,7 +56,8 @@ class AuthController extends Controller
&& isset($credentials['mobile'], $credentials['password'])
&& $credentials['password'] === 'montzar@' . $credentials['mobile']
) {
$user = Customer::where('mobile', $credentials['mobile'])->first();
$user = Customer::findActiveByMobile($credentials['mobile'])
?? Customer::where('mobile', $credentials['mobile'])->first();
if (!$user) {
throw new UnprocessableEntityHttpException('شماره موبایل یا پسوورد وارد شده اشتباه است');
}
@@ -65,6 +74,16 @@ class AuthController extends Controller
}
$token = auth($provider)->attempt($credentials);
if (!$token && $provider === 'customer') {
// ردیف‌های قدیمی با فرمت موبایل غیرنرمال
$fallback = Customer::findActiveByMobile($credentials['mobile'] ?? null);
if ($fallback) {
$token = auth($provider)->attempt([
'mobile' => $fallback->mobile,
'password' => $credentials['password'] ?? '',
]);
}
}
if (!$token) {
throw new UnprocessableEntityHttpException('شماره موبایل یا پسوورد وارد شده اشتباه است');
}
@@ -88,7 +107,8 @@ class AuthController extends Controller
if ($provider == 'user') {
$user = User::where('mobile', $mobile)->first();
} else {
$user = Customer::where('mobile', $mobile)->first();
$user = Customer::findActiveByMobile($mobile)
?? Customer::where('mobile', $mobile)->first();
}
if (is_null($user)) {
throw new UnprocessableEntityHttpException(trans('لطفا ابتدا ثبت نام بفرمایید'));
@@ -198,9 +218,19 @@ class AuthController extends Controller
$data['active'] = true;
Customer::assertMobileUnique($data['mobile'] ?? null);
$normalizedMobile = Customer::normalizeMobile($data['mobile'] ?? null);
if ($normalizedMobile === null) {
throw new UnprocessableEntityHttpException('شماره موبایل نامعتبر است');
}
$data['mobile'] = $normalizedMobile;
$customer = Customer::create($data);
$claimable = Customer::findClaimableImportedByMobile($normalizedMobile);
if ($claimable && $claimable->isUnclaimedAppIdentity()) {
$customer = $this->claimImportedCustomer($claimable, $data);
} else {
Customer::assertMobileUnique($normalizedMobile);
$customer = Customer::create($data);
}
if ($referrer) {
if (Schema::hasColumn((new Customer)->getTable(), 'total_referrals')) {
@@ -241,6 +271,55 @@ class AuthController extends Controller
$this->Send($data['mobile'], $message);
}
if ($customer->hasValidPargarAccountId()) {
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
} elseif (! $customer->wasRecentlyCreated) {
// claim بدون کد حسابداری — صف export مثل create
app(\App\Services\PargarSyncService::class)->registerOnCreate($customer);
app(\App\Services\TahesabSyncService::class)->registerOnCreate($customer);
}
return response()->json($customer);
}
/**
* ارتقای ردیف ازقبل‌واردشده از حسابداری به‌جای ساخت مشتری تکراری.
*/
private function claimImportedCustomer(Customer $customer, array $data): Customer
{
if (method_exists($customer, 'trashed') && $customer->trashed()) {
$customer->restore();
}
$customer->fname = $data['fname'] ?? $customer->fname;
$customer->lname = $data['lname'] ?? $customer->lname;
$customer->name = $data['name'] ?? $customer->name;
$customer->mobile = $data['mobile'];
$customer->type = $data['type'] ?? $customer->type ?? 2;
$customer->active = true;
$customer->is_legal = (int) ($data['is_legal'] ?? $customer->is_legal ?? 0);
if (! empty($data['password'])) {
$customer->password = $data['password'];
} elseif (empty($customer->password)) {
$customer->password = $data['mobile'];
}
if (! empty($data['n_code'])) {
$customer->n_code = $data['n_code'];
}
if (! empty($data['referral_to_id'])) {
$customer->referral_to_id = $data['referral_to_id'];
}
if (empty($customer->referral_id) && ! empty($data['referral_id'])) {
$customer->referral_id = $data['referral_id'];
}
if (isset($data['gram'])) {
$customer->gram = $data['gram'];
}
$customer->save();
return $customer->fresh();
}
}
@@ -111,6 +111,7 @@ class PargarInboundController extends Controller
$customer->pargar_account_id = $accountId;
$this->applyPargarCustomerName($customer, $fullName);
$customer->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
$messages[] = "کد حسابداری مشتری {$fullName} ویرایش شد";
continue;
}
@@ -129,6 +130,7 @@ class PargarInboundController extends Controller
$byAccount->password = $mobile;
}
$byAccount->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $byAccount->id);
$messages[] = "مشتری با کد حسابداری {$accountId} به‌روزرسانی شد";
continue;
}
@@ -144,6 +146,7 @@ class PargarInboundController extends Controller
$customer->referral_id = $this->uniqueReferralId();
$customer->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
$messages[] = "مشتری با کد حسابداری {$accountId} ایجاد شد";
} catch (\Throwable $e) {
$messages[] = "خطا برای {$mobile}: " . (
@@ -281,19 +284,33 @@ class PargarInboundController extends Controller
$accountId = trim((string) ($data['AccountId'] ?? ''));
$customer = Customer::where('pargar_account_id', $accountId)->first();
if (! $customer) {
return response()->json([
'success' => false,
'message' => 'مشتری یافت نشد',
'data' => null,
], 422);
}
// اگر مشتری هنوز وارد نشده، مانده را با pargar_ref نگه می‌داریم تا بعد از import/claim اعمال شود
$goldInv = isset($data['GoldInv']) ? (float) str_replace('/', '.', (string) $data['GoldInv']) : 0;
$rialInv = isset($data['RialInv']) ? (float) str_replace('/', '.', (string) $data['RialInv']) : 0;
// فقط مقایسه برای لاگ — اپ source of truth است؛ کیف را از مانده پرگار عوض نکن
// اضطراری: هیچ‌وقت از mande کیف را تغییر نده (حتی اگر کد قدیمی جایی مانده باشد)
if (! $customer) {
PargarSyncLog::create([
'customer_id' => null,
'desc' => 'مانده پرگار — مشتری هنوز در پنل نیست',
'type' => PargarSyncService::TYPE_MANDE,
'amount' => $goldInv,
'sync_status' => PargarSyncService::STATUS_SUCCESS,
'pargar_ref' => $accountId,
'data' => json_encode(array_merge($data, [
'orphan_mande' => true,
'wallet_adjusted' => false,
]), JSON_UNESCAPED_UNICODE),
]);
return response()->json([
'success' => true,
'message' => 'مانده ذخیره شد؛ پس از ثبت مشتری اعمال می‌شود',
'data' => null,
]);
}
// فقط مقایسه برای لاگ — اعمال روی کیف فقط با دکمه ادمین / صف auto پس از import-claim
$ownerType = (int) ($customer->type ?? 2);
$walletInterface = new WalletInterface($customer->id, $ownerType);
$wallet = $walletInterface->wallet;
@@ -324,6 +341,11 @@ class PargarInboundController extends Controller
]), JSON_UNESCAPED_UNICODE),
]);
// اولین مانده برای مشتریِ هنوز واردنشده به اپ — یک‌بار هم‌تراز کن
if ($customer->last_seen_at === null && $hasDiff) {
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
}
return response()->json([
'success' => true,
'message' => 'مانده مشتری با موفقیت دریافت شد',
@@ -420,6 +442,8 @@ class PargarInboundController extends Controller
if ($log) {
$this->sync()->markSuccess($log, $normalizedAccountId);
}
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
}
}
+64 -4
View File
@@ -201,13 +201,55 @@ class Customer extends Authenticatable implements JWTSubject
$query = static::query()
->whereNull('deleted_at')
->where('mobile', $mobile);
->whereIn('mobile', self::mobileLookupVariants($mobile));
if ($exceptId !== null) {
$query->where('id', '<>', $exceptId);
}
return $query->first();
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
@@ -240,14 +282,16 @@ class Customer extends Authenticatable implements JWTSubject
return $query->exists();
}
private static function mobileLookupVariants(string $normalized): array
public static function mobileLookupVariants(string $normalized): array
{
$digits = ltrim($normalized, '0');
return array_values(array_unique(array_filter([
$normalized,
$digits,
'0' . $digits,
'0'.$digits,
'98'.$digits,
'+98'.$digits,
])));
}
@@ -275,4 +319,20 @@ class Customer extends Authenticatable implements JWTSubject
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;
}
}
@@ -391,4 +391,48 @@ class PargarInboundWalletService
return (float) str_replace('/', '.', (string) $value);
}
/**
* پس از اتصال کد پرگار / claim ثبت‌نام — همگام‌سازی کیف در پس‌زمینه.
*/
public function queueWalletSyncAfterResponse(int $customerId): void
{
dispatch(function () use ($customerId) {
$customer = Customer::find($customerId);
if ($customer === null || ! $customer->hasValidPargarAccountId()) {
return;
}
$this->attachOrphanMandeLogs($customer);
try {
$this->syncWalletFromPargar($customer, true);
} catch (\Throwable $e) {
Log::warning('Pargar auto wallet sync failed', [
'customer_id' => $customerId,
'error' => $e->getMessage(),
]);
}
})->afterResponse();
}
/**
* لاگ‌های مانده‌ای که قبل از ساخت/لینک مشتری با pargar_ref آمده‌اند را به مشتری وصل می‌کند.
*/
public function attachOrphanMandeLogs(Customer $customer): int
{
if (! $customer->hasValidPargarAccountId()) {
return 0;
}
$accountId = trim((string) $customer->pargar_account_id);
return PargarSyncLog::query()
->where('type', PargarSyncService::TYPE_MANDE)
->where('pargar_ref', $accountId)
->where(function ($q) {
$q->whereNull('customer_id')->orWhere('customer_id', 0);
})
->update(['customer_id' => $customer->id]);
}
}
+2 -1
View File
@@ -11,7 +11,8 @@ return[
'provider'=>'required'
],
'auth/register@post'=>[
'mobile' => 'required|digits:11|unique:customers,mobile,NULL,NULL,deleted_at,NULL',
// یکتایی موبایل در AuthController با منطق claim مشتریِ ازقبل‌واردشده از حسابداری چک می‌شود
'mobile' => 'required|digits:11',
'fname'=>'required',
'lname'=>'required',
],