Files
2026-07-26 21:17:57 +03:30

469 lines
17 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\PargarSyncLog;
use App\Services\ConfigService;
use App\Services\PargarInboundWalletService;
use App\Services\PargarSyncService;
use Illuminate\Http\Request;
class PargarController extends Controller
{
protected function sync(): PargarSyncService
{
return app(PargarSyncService::class);
}
public function table(Request $request)
{
$query = PargarSyncLog::with('customer')->orderByDesc('id');
\App\DB\Te\TableEngine::create($query, $request)
->initSearch(['desc', 'type'])
->select(['*'])
->export($query);
return response()->json($query);
}
public function show($id)
{
return response()->json(PargarSyncLog::with('customer')->findOrFail($id));
}
/** اسناد پنل که پرگار هنوز تحویل نگرفته — ماندهٔ این مشتری‌ها قفل است */
public function stuckOutbound(Request $request)
{
$minutes = (int) $request->query('minutes', 15);
$minutes = max(0, min($minutes, 60 * 24 * 30));
return response()->json($this->sync()->stuckOutboundDocuments($minutes));
}
public function customerSyncStatus(Request $request)
{
$panelTotal = Customer::whereNull('deleted_at')->count();
$panelRegistered = Customer::whereNull('deleted_at')->whereNotNull('pargar_account_id')->count();
$panelOnly = $panelTotal - $panelRegistered;
$panelOnlyQuery = Customer::whereNull('deleted_at')
->whereNull('pargar_account_id')
->select('id', 'name', 'mobile', 'created_at');
if ($search = $request->input('search')) {
$panelOnlyQuery->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('mobile', 'like', "%{$search}%");
});
}
$panelOnlyList = $panelOnlyQuery->orderBy('id')->paginate(20);
$inboundOnlyQuery = PargarSyncLog::query()
->where('type', 'customer')
->where('sync_status', PargarSyncService::STATUS_SUCCESS)
->whereNotNull('pargar_ref')
->latest('id');
$inboundOnlyCount = (clone $inboundOnlyQuery)->count();
$inboundSample = $inboundOnlyQuery->limit(100)->get(['id', 'customer_id', 'pargar_ref', 'created_at']);
return response()->json([
'panel_total' => $panelTotal,
'panel_registered' => $panelRegistered,
'panel_only' => $panelOnly,
'pargar_total' => $panelRegistered + $inboundOnlyCount,
'pargar_only_count' => max(0, $inboundOnlyCount),
'panel_only_list' => $panelOnlyList,
'pargar_only_list' => [
'data' => $inboundSample->map(fn ($row) => [
'code' => $row->pargar_ref,
'name' => $row->customer?->name ?? '',
'tel' => $row->customer?->mobile ?? '',
'gid' => null,
'group' => '',
])->values(),
'total' => $inboundOnlyCount,
'current_page' => 1,
'last_page' => 1,
],
]);
}
public function syncPanelToPargar()
{
$customers = Customer::whereNull('deleted_at')
->whereNull('pargar_account_id')
->get(['id', 'name', 'mobile']);
$success = 0;
$failed = 0;
$errors = [];
foreach ($customers as $customer) {
$result = $this->sync()->registerCustomer($customer);
if ($result['ok'] ?? false) {
$success++;
} else {
$failed++;
$errors[] = [
'name' => $customer->name,
'mobile' => $customer->mobile,
'error' => $result['message'] ?? '',
];
}
}
return response()->json([
'ok' => true,
'success' => $success,
'failed' => $failed,
'errors' => $errors,
]);
}
public function importFromPargar(Request $request)
{
$validated = $request->validate([
'customers' => 'required|array',
'customers.*.code' => 'required',
'customers.*.name' => 'required|string',
'customers.*.tel' => 'nullable|string',
]);
$success = 0;
$failed = 0;
$errors = [];
foreach ($validated['customers'] as $item) {
$mobile = Customer::normalizeMobile($item['tel'] ?? '') ?? '';
$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;
}
if ($mobile !== '') {
$byMobile = Customer::findActiveByMobile($mobile);
if ($byMobile) {
$currentAccount = (string) ($byMobile->pargar_account_id ?? '');
if ($currentAccount !== '' && $currentAccount !== $accountId) {
$failed++;
$errors[] = [
'name' => $item['name'],
'code' => $item['code'],
'error' => 'شماره موبایل تکراری است',
];
continue;
}
$byMobile->pargar_account_id = $accountId;
if ($fullName !== '') {
$byMobile->fname = $fullName;
$byMobile->name = $fullName;
}
$byMobile->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $byMobile->id);
$success++;
continue;
}
}
$byAccount = Customer::whereNull('deleted_at')
->where('pargar_account_id', $accountId)
->first();
if ($byAccount) {
$failed++;
$errors[] = [
'name' => $item['name'],
'code' => $item['code'],
'error' => 'کد حساب پرگار قبلاً ثبت شده است',
];
continue;
}
try {
$customer = new Customer();
$customer->fname = $fullName;
$customer->lname = '';
$customer->name = $fullName;
$customer->mobile = $mobile;
$customer->active = true;
$customer->pargar_account_id = $accountId;
$customer->referral_id = $this->uniqueReferralId();
$customer->type = 2;
$customer->password = $mobile;
$customer->save();
app(PargarInboundWalletService::class)->queueWalletSyncAfterResponse((int) $customer->id);
$success++;
} catch (\Throwable $e) {
$failed++;
$errors[] = ['name' => $item['name'], 'code' => $item['code'], 'error' => $e->getMessage()];
}
}
return response()->json([
'ok' => true,
'success' => $success,
'failed' => $failed,
'errors' => $errors,
]);
}
public function rollbackImport(Request $request)
{
$baseQuery = fn () => Customer::whereNotNull('pargar_account_id')
->where(function ($q) {
$q->whereNull('name')->orWhere('name', '');
});
$count = $baseQuery()->count();
$sample = $baseQuery()
->select('id', 'fname', 'name', 'mobile', 'pargar_account_id', 'created_at')
->orderByDesc('id')
->limit(20)
->get();
if ($request->boolean('dry_run')) {
return response()->json([
'dry_run' => true,
'count' => $count,
'sample' => $sample,
]);
}
$deleted = $baseQuery()->forceDelete();
return response()->json([
'ok' => true,
'deleted' => $deleted,
]);
}
public function retrySanad($id)
{
$log = PargarSyncLog::with('customer')->findOrFail($id);
$result = $this->sync()->retry($log);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
public function registerCustomer($customerId)
{
$customer = Customer::findOrFail($customerId);
$result = $this->sync()->registerCustomer($customer);
return response()->json($result, ($result['ok'] ?? false) ? 200 : 422);
}
public function syncCustomerWallet(Request $request, $customerId)
{
if (! app(ConfigService::class)->isPargarAccounting()) {
return response()->json([
'ok' => false,
'message' => 'حسابداری پرگار فعال نیست',
], 422);
}
$customer = Customer::findOrFail($customerId);
if (trim((string) ($customer->pargar_account_id ?? '')) === '') {
return response()->json([
'ok' => false,
'message' => 'این مشتری کد حساب پرگار ندارد',
], 422);
}
$force = $request->boolean('force');
$result = app(PargarInboundWalletService::class)->syncWalletFromPargar($customer, $force);
$summary = $this->summarizeWalletSyncResult($result);
return response()->json([
'ok' => $summary['ok'],
'message' => $summary['message'],
'result' => $result,
]);
}
/**
* همگام‌سازی ماندهٔ کیف برای همهٔ مشتریان دارای کد پرگار — تکه‌تکه (offset/limit).
*/
public function syncAllWallets(Request $request)
{
if (! app(ConfigService::class)->isPargarAccounting()) {
return response()->json([
'ok' => false,
'message' => 'حسابداری پرگار فعال نیست',
], 422);
}
$limit = max(1, min(50, (int) $request->input('limit', 20)));
$offset = max(0, (int) $request->input('offset', 0));
$force = $request->boolean('force');
$base = Customer::query()
->whereNull('deleted_at')
->whereNotNull('pargar_account_id')
->where('pargar_account_id', '!=', '');
$total = (clone $base)->count();
$customers = (clone $base)
->orderBy('id')
->offset($offset)
->limit($limit)
->get(['id', 'name', 'mobile', 'pargar_account_id', 'type']);
$walletService = app(PargarInboundWalletService::class);
$success = 0;
$changed = 0;
$failed = 0;
$errors = [];
foreach ($customers as $customer) {
try {
$result = $walletService->syncWalletFromPargar($customer, $force);
$summary = $this->summarizeWalletSyncResult($result);
$docErrors = $result['documents']['errors'] ?? [];
$hardFail = is_array($docErrors) && $docErrors !== []
&& ! ($result['mande']['applied'] ?? false);
if ($hardFail) {
$failed++;
$errors[] = [
'id' => $customer->id,
'name' => $customer->name,
'mobile' => $customer->mobile,
'error' => $summary['message'],
];
continue;
}
$success++;
if ($summary['ok']) {
$changed++;
}
} catch (\Throwable $e) {
$failed++;
$errors[] = [
'id' => $customer->id,
'name' => $customer->name,
'mobile' => $customer->mobile,
'error' => $e->getMessage() !== '' ? $e->getMessage() : 'خطای ناشناخته',
];
}
}
$processed = $customers->count();
$nextOffset = $offset + $processed;
return response()->json([
'ok' => true,
'message' => $total === 0
? 'مشتری دارای کد پرگار یافت نشد'
: "دستهٔ {$offset} تا {$nextOffset} از {$total}",
'total' => $total,
'offset' => $offset,
'processed' => $processed,
'next_offset' => $nextOffset,
'done' => $nextOffset >= $total,
'success' => $success,
'changed' => $changed,
'failed' => $failed,
'errors' => $errors,
]);
}
/**
* @param array{
* documents: array{applied: int, skipped: int, batches: int, acknowledged: int, errors: list<string>},
* mande: array{applied: bool, gold_delta?: float, rial_delta?: float, message: string}
* } $result
* @return array{ok: bool, message: string}
*/
private function summarizeWalletSyncResult(array $result): array
{
$acknowledged = (int) ($result['documents']['acknowledged'] ?? 0);
$skippedDocs = (int) ($result['documents']['skipped'] ?? 0);
$batches = (int) ($result['documents']['batches'] ?? 0);
$appliedMande = (bool) ($result['mande']['applied'] ?? false);
$mandeMessage = (string) ($result['mande']['message'] ?? '');
$docErrors = $result['documents']['errors'] ?? [];
$ok = $appliedMande || $acknowledged > 0;
if ($appliedMande && $acknowledged > 0) {
$message = "مانده هم‌تراز شد ({$acknowledged} دسته سند مصرف شد)";
} elseif ($appliedMande) {
$message = 'مانده با پرگار هم‌تراز شد';
} elseif ($acknowledged > 0) {
$message = $mandeMessage !== ''
? "{$acknowledged} دسته سند مصرف شد — {$mandeMessage}"
: "{$acknowledged} دسته سند مصرف شد";
} elseif ($batches > 0 && $skippedDocs > 0) {
$message = $mandeMessage !== ''
? $mandeMessage
: 'اسناد تکراری بودند؛ تغییری لازم نبود';
} else {
$message = $mandeMessage !== ''
? $mandeMessage
: 'سند یا ماندهٔ جدیدی برای اعمال نبود';
}
if (is_array($docErrors) && $docErrors !== []) {
$message .= ' — '.implode('؛ ', array_slice($docErrors, 0, 2));
}
return [
'ok' => $ok,
'message' => $message,
];
}
private function uniqueReferralId(): string
{
do {
$referral = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz'), 0, 4);
} while (Customer::where('referral_id', $referral)->exists());
return $referral;
}
}