diff --git a/app/Http/Controllers/Admin/ZarinController.php b/app/Http/Controllers/Admin/ZarinController.php index 736d307..f82f51a 100644 --- a/app/Http/Controllers/Admin/ZarinController.php +++ b/app/Http/Controllers/Admin/ZarinController.php @@ -64,7 +64,7 @@ class ZarinController extends Controller return empty($token) ? null : $token; } - private function zarinRequest(array $params) + private function zarinRequest(array $params, int $timeout = 15) { $url = $this->zarinAccountingUrl(); $token = $this->zarinToken(); @@ -76,10 +76,50 @@ class ZarinController extends Controller $params['token'] = $token; return Http::withoutVerifying() - ->timeout(15) + ->timeout($timeout) ->get($url, $params); } + /** + * decode پاسخ HTTP زرین — خطاهای متنی (مثل ERROR: radif is empty) را هم برمی‌گرداند. + * + * @return array{0: ?array, 1: ?string} [json, error] + */ + private function zarinDecodeResponse($http): array + { + if (!$http) { + return [null, 'تنظیمات zarin_api یا zarin_token خالی است.']; + } + + $body = (string) $http->body(); + $json = $http->json(); + if (!is_array($json) && $body !== '') { + $decoded = json_decode($body, true); + if (is_array($decoded)) { + $json = $decoded; + } + } + + if (!$http->successful()) { + $detail = null; + if (is_array($json)) { + $detail = $json['detail'] ?? $json['msg'] ?? $json['message'] ?? $json['error'] ?? null; + } + if ($detail === null || $detail === '') { + $detail = trim($body) !== '' ? mb_substr(trim($body), 0, 300) : null; + } + $error = 'HTTP ' . $http->status() . ($detail ? (': ' . $detail) : ''); + + return [is_array($json) ? $json : null, $error]; + } + + if (!is_array($json)) { + return [null, 'پاسخ زرین JSON نیست (طول ' . strlen($body) . ')']; + } + + return [$json, null]; + } + private function zarinDefaultNoeKar() { $value = (int) $this->Config('zarin_noe_kar'); @@ -94,15 +134,10 @@ class ZarinController extends Controller return $value > 0 ? $value : 52; } - /** نام گروه حساب مشتری در زرین — برای جداسازی چند پنل روی یک سرور زرین */ + /** نام گروه حساب مشتری در زرین — فقط از تنظیم zarin_name_g */ private function zarinCustomerGroupName(): string { - $nameG = trim((string) $this->Config('zarin_name_g')); - if ($nameG !== '') { - return $nameG; - } - - return trim((string) $this->Config('name_fa')); + return trim((string) $this->Config('zarin_name_g')); } /** @@ -182,30 +217,71 @@ class ZarinController extends Controller return $body; } + /** + * پیدا کردن کار/کالای محصول در زرین. + * اول infokarbarcodi (اگر روی آن سرور فعال باشد)؛ وگرنه infosakhte (لیست کامل). + */ private function findZarinKarForProduct(Product $product, $goroheKar = null) { $goroheKar = $goroheKar ?: $this->zarinDefaultGoroheKar(); - $productName = $this->normalizeZarinName($product->title); + $fromBarcode = $this->findZarinKarInInfokarbarcodi($productName, $goroheKar); + if ($fromBarcode) { + return $fromBarcode; + } + + return $this->findZarinKarInInfosakhte($productName, $goroheKar); + } + + private function matchZarinKarRow(string $productName, array $row): ?array + { + $name = $row['nameKar'] + ?? $row['nam_kar_sakhte'] + ?? $row['nam'] + ?? $row['name'] + ?? $row['namKar'] + ?? $row['title'] + ?? ''; + + $rowName = $this->normalizeZarinName($name); + $idKar = $this->extractUsableZarinKarId($row); + + if (!$idKar) { + return null; + } + + if ( + $rowName === $productName || + $this->containsText($rowName, $productName) || + $this->containsText($productName, $rowName) + ) { + return [ + 'idKar' => $idKar, + 'row' => $row, + ]; + } + + return null; + } + + private function findZarinKarInInfokarbarcodi(string $productName, $goroheKar): ?array + { for ($page = 1; $page <= 10; $page++) { - $params = [ + $response = $this->zarinRequest([ 'kind' => 'infokarbarcodi', 'id' => $goroheKar, 'vazneat' => 0, 'pageNum' => $page, 'detailCount' => 250, - ]; - - $response = $this->zarinRequest($params); + ]); + // روی بعضی سرورهای زرین جدول barcodetala نیست → 400؛ برو سراغ infosakhte if (!$response || !$response->successful()) { return null; } - $body = $response->json(); - $rows = $this->zarinRowsFromBody($body); - + $rows = $this->zarinRowsFromBody($response->json()); if (empty($rows)) { break; } @@ -214,32 +290,9 @@ class ZarinController extends Controller if (!is_array($row)) { continue; } - - $name = $row['nameKar'] - ?? $row['nam_kar_sakhte'] - ?? $row['nam'] - ?? $row['name'] - ?? $row['namKar'] - ?? $row['title'] - ?? ''; - - $rowName = $this->normalizeZarinName($name); - - $idKar = $this->extractUsableZarinKarId($row); - - if (!$idKar) { - continue; - } - - if ( - $rowName === $productName || - $this->containsText($rowName, $productName) || - $this->containsText($productName, $rowName) - ) { - return [ - 'idKar' => $idKar, - 'row' => $row, - ]; + $hit = $this->matchZarinKarRow($productName, $row); + if ($hit) { + return $hit; } } @@ -251,6 +304,49 @@ class ZarinController extends Controller return null; } + /** لیست کارهای ساخته — فیلد nam_kar_sakhte / id (تست‌شده روی API زرین) */ + private function findZarinKarInInfosakhte(string $productName, $goroheKar): ?array + { + $response = $this->zarinRequest([ + 'kind' => 'infosakhte', + ], 30); + + if (!$response || !$response->successful()) { + return null; + } + + $rows = $this->zarinRowsFromBody($response->json()); + if (empty($rows)) { + return null; + } + + $noeKar = (string) $this->zarinDefaultNoeKar(); + $gorohe = (string) $goroheKar; + $preferred = null; + $fallback = null; + + foreach ($rows as $row) { + if (!is_array($row)) { + continue; + } + $hit = $this->matchZarinKarRow($productName, $row); + if (!$hit) { + continue; + } + + $idGp = (string) ($row['idGp'] ?? $row['idgp'] ?? ''); + if ($idGp !== '' && ($idGp === $gorohe || $idGp === $noeKar)) { + $preferred = $hit; + break; + } + if ($fallback === null) { + $fallback = $hit; + } + } + + return $preferred ?? $fallback; + } + private function getZarinKarById($idKar) { if (empty($idKar) || !is_numeric($idKar)) { @@ -370,7 +466,7 @@ class ZarinController extends Controller return [ 'ok' => false, - 'message' => 'زرین کالا را ساخت، اما idKar قابل استفاده برای sabtSanadTedadi از لیست infokarbarcodi پیدا نشد.', + 'message' => 'زرین کالا را ساخت، اما idKar قابل استفاده از infosakhte/infokarbarcodi پیدا نشد.', 'status_code' => 422, 'created_pkFelez' => $createdPkFelez, 'zarin_response' => $body, @@ -441,35 +537,117 @@ class ZarinController extends Controller return response()->json($zarin); } - /** همه مشتریان زرین را از API می‌گیرد و برمی‌گرداند */ + /** + * لیست حساب‌های زرین — فقط listhesabha (Get100Account لیست نیست و ۴۰۰ می‌دهد). + * + * @return array{rows: array, error: ?string, http_status: ?int} + */ private function fetchAllZarinCustomers(): array { + if (!$this->zarinAccountingUrl() || !$this->zarinToken()) { + return [ + 'rows' => [], + 'error' => 'تنظیمات zarin_api یا zarin_token خالی است.', + 'http_status' => null, + ]; + } + try { - $response = $this->zarinRequest([ - 'kind' => 'Get100Account', - ]); - - if ($response && $response->successful()) { - $rows = $this->zarinRowsFromBody($response->json()); - if (!empty($rows)) { - return $rows; - } - } - + // listhesabha کامل است (~۳۰۰ ردیف / چند ثانیه)؛ timeout بالاتر از درخواست‌های کوتاه $response = $this->zarinRequest([ 'kind' => 'listhesabha', - ]); + ], 45); - if ($response && $response->successful()) { - return $this->zarinRowsFromBody($response->json()); + if (!$response) { + return [ + 'rows' => [], + 'error' => 'درخواست به زرین ساخته نشد (آدرس/توکن).', + 'http_status' => null, + ]; } - return []; + $status = $response->status(); + $body = $response->body(); + + if (!$response->successful()) { + $snippet = mb_substr(trim((string) $body), 0, 180); + $msg = "listhesabha ناموفق (HTTP {$status})"; + if ($snippet !== '') { + $msg .= ': ' . $snippet; + } + + return [ + 'rows' => [], + 'error' => $msg, + 'http_status' => $status, + ]; + } + + $json = $response->json(); + if (!is_array($json)) { + return [ + 'rows' => [], + 'error' => 'پاسخ listhesabha JSON معتبر نیست. طول بدنه: ' . strlen((string) $body), + 'http_status' => $status, + ]; + } + + $rows = $this->zarinRowsFromBody($json); + + if (empty($rows)) { + return [ + 'rows' => [], + 'error' => 'listhesabha خالی بود یا کلید data پیدا نشد. کلیدها: ' + . implode(',', array_keys($json)), + 'http_status' => $status, + ]; + } + + return [ + 'rows' => $rows, + 'error' => null, + 'http_status' => $status, + ]; } catch (\Exception $e) { - return []; + Log::warning('zarin listhesabha failed', ['message' => $e->getMessage()]); + + return [ + 'rows' => [], + 'error' => 'خطا در ارتباط با زرین: ' . $e->getMessage(), + 'http_status' => null, + ]; } } + /** نام حساب از پاسخ listhesabha (فیلد nam، نه name) */ + private function extractZarinName(array $item): string + { + $nam = trim((string) ($item['nam'] ?? $item['Nam'] ?? '')); + if ($nam !== '') { + // "تستی * هوم" → "تستی هوم" + $nam = preg_replace('/\s*\*\s*/u', ' ', $nam) ?? $nam; + + return trim(preg_replace('/\s+/u', ' ', $nam) ?? $nam); + } + + $name = trim((string) ($item['name'] ?? $item['Name'] ?? '')); + $family = trim((string) ($item['family'] ?? $item['Family'] ?? '')); + $full = trim($name . ' ' . $family); + + return $full !== '' ? $full : $name; + } + + private function extractZarinGroup(array $item): string + { + return trim((string) ( + $item['gorooh'] + ?? $item['GoroupName'] + ?? $item['group'] + ?? $item['Group'] + ?? '' + )); + } + public function customerSyncStatus(Request $request): \Illuminate\Http\JsonResponse { $panelTotal = Customer::whereNull('deleted_at')->count(); @@ -489,13 +667,18 @@ class ZarinController extends Controller $zarinTotal = null; $zarinOnlyCount = null; + $zarinFetchError = null; + $zarinHttpStatus = null; $zarinOnlyResult = ['data' => [], 'total' => 0, 'current_page' => 1, 'last_page' => 1]; $debugSample = []; $include = $request->boolean('include_zarin') || $request->boolean('include_tahesab'); if ($include) { - $zarinCustomers = $this->fetchAllZarinCustomers(); + $fetched = $this->fetchAllZarinCustomers(); + $zarinCustomers = $fetched['rows']; + $zarinFetchError = $fetched['error']; + $zarinHttpStatus = $fetched['http_status']; $zarinTotal = count($zarinCustomers); $panelMobiles = Customer::whereNull('deleted_at') @@ -527,15 +710,12 @@ class ZarinController extends Controller if ($tel && isset($panelMobiles[$tel])) { continue; } - $name = $item['name'] ?? $item['Name'] ?? ''; - $family = $item['family'] ?? $item['Family'] ?? ''; - $fullName = trim($name . ' ' . $family); $zarinOnlyList[] = [ 'code' => $code, - 'name' => $fullName !== '' ? $fullName : ($item['Name'] ?? ''), + 'name' => $this->extractZarinName($item), 'tel' => $phone, 'gid' => $item['GID'] ?? null, - 'group' => $item['GoroupName'] ?? ($item['group'] ?? ''), + 'group' => $this->extractZarinGroup($item), ]; } $zarinOnlyCount = count($zarinOnlyList); @@ -553,6 +733,8 @@ class ZarinController extends Controller 'panel_only' => $panelOnly, 'zarin_total' => $zarinTotal, 'zarin_only_count' => $zarinOnlyCount, + 'zarin_fetch_error' => $zarinFetchError, + 'zarin_http_status' => $zarinHttpStatus, 'panel_only_list' => $panelOnlyList, 'zarin_only_list' => $zarinOnlyResult, ]; @@ -566,7 +748,7 @@ class ZarinController extends Controller private function extractZarinPhone(array $item): string { - $candidates = ['mobile', 'Mobile', 'tel', 'Tel', 'Tell', 'Phone', 'CellPhone', 'Cell', 'MobileNo', 'Telephone', 'Mobile1']; + $candidates = ['mobile', 'Mobile', 'tell', 'Tell', 'tel', 'Tel', 'Phone', 'CellPhone', 'Cell', 'MobileNo', 'Telephone', 'Mobile1']; foreach ($candidates as $key) { if (isset($item[$key]) && trim((string) $item[$key]) !== '') { return trim((string) $item[$key]); @@ -923,9 +1105,10 @@ class ZarinController extends Controller return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; } - $response = $http->json(); - if (!is_array($response)) { - $response = json_decode($http->body(), true); + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; } } catch (\Exception $e) { $this->sync()->markFailed($zarin, $e->getMessage()); @@ -1018,7 +1201,11 @@ class ZarinController extends Controller return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; } - $response = $http->json(); + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; + } Log::info('Zarin wallet response', [ 'data' => $data, @@ -1133,7 +1320,11 @@ class ZarinController extends Controller return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; } - $response = $http->json(); + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; + } } catch (\Exception $e) { $this->sync()->markFailed($zarin, $e->getMessage()); return $e->getMessage(); @@ -1230,7 +1421,11 @@ class ZarinController extends Controller return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; } - $response = $http->json(); + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; + } } catch (\Exception $e) { $this->sync()->markFailed($zarin, $e->getMessage()); return $e->getMessage(); @@ -1296,9 +1491,12 @@ class ZarinController extends Controller return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; } - $response = $http->json(); - $data = is_array($response) ? $this->extractResponseRow($response) : []; + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + return $httpError; + } + $data = $this->extractResponseRow($response); $idHesab = $data['idHesab'] ?? null; if (!empty($idHesab)) { @@ -1306,11 +1504,12 @@ class ZarinController extends Controller $customer->save(); return $idHesab; } + + $detail = $data['msg'] ?? $data['message'] ?? $data['detail'] ?? null; + return $detail ? (string) $detail : 'zarin api error!!!'; } catch (\Exception $e) { return $e->getMessage(); } - - return 'zarin api error!!!'; } finally { $lock->release(); } @@ -1322,10 +1521,21 @@ class ZarinController extends Controller 'kind' => 'version', ]); - if (!$http) { - return $this->zarinConfigError(); + [$json, $error] = $this->zarinDecodeResponse($http); + + if ($error) { + return response()->json([ + 'ok' => false, + 'message' => $error, + 'body' => $json, + ], 422); } - return $http->json() ?? $http->body(); + return response()->json([ + 'ok' => true, + 'version' => $json['version'] ?? null, + 'message' => $json['message'] ?? 'OK', + 'raw' => $json, + ]); } } diff --git a/database/migrations/2026_07_23_000002_add_zarin_accounting_settings.php b/database/migrations/2026_07_23_000002_add_zarin_accounting_settings.php index 9526dfd..f4ba76a 100644 --- a/database/migrations/2026_07_23_000002_add_zarin_accounting_settings.php +++ b/database/migrations/2026_07_23_000002_add_zarin_accounting_settings.php @@ -35,9 +35,9 @@ return new class extends Migration 'type' => 'number', ], 'zarin_name_g' => [ - 'value' => '', + 'value' => 'مشتری نقدی (خریدار)', 'title_fa' => 'نام گروه حساب مشتریان زرین', - 'description' => 'پارامتر nameG در addhesab. اگر چند پنل یک توکن زرین دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند (name_fa).', + 'description' => 'پارامتر nameG در addhesab — معادل گروه حساب در زرین. برای اپ خانگی: «مشتری نقدی (خریدار)» (گروه ۶). گروه همکار استفاده نشود چون مظنه فرق دارد.', 'type' => 'text', ], 'zarin_uid_prefix' => [ @@ -65,14 +65,24 @@ return new class extends Migration continue; } - // فقط متادیتا — value دست‌نخورده - DB::table('settings')->where('key', $key)->update([ + // فقط متادیتا — value دست‌نخورده (به‌جز zarin_name_g خالی → گروه ۶) + $update = [ 'title_fa' => $meta['title_fa'], 'description' => $meta['description'], 'type' => $meta['type'], 'can_edit' => 0, 'is_active' => 1, - ]); + ]; + DB::table('settings')->where('key', $key)->update($update); + + if ($key === 'zarin_name_g') { + DB::table('settings') + ->where('key', $key) + ->where(function ($q) { + $q->whereNull('value')->orWhereRaw("TRIM(value) = ''"); + }) + ->update(['value' => $meta['value']]); + } } if (DB::table('settings')->where('key', 'accounting_provider')->exists()) { diff --git a/database/sql/deploy_settings_const.sql b/database/sql/deploy_settings_const.sql index dc976ed..8741f1e 100644 --- a/database/sql/deploy_settings_const.sql +++ b/database/sql/deploy_settings_const.sql @@ -531,7 +531,7 @@ SELECT 'zarin_gorohe_kar', '52', 'گروه کار پیش‌فرض زرین', 0, WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'zarin_gorohe_kar' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'zarin_name_g', '', 'نام گروه حساب مشتریان زرین', 0, 1, 'پارامتر nameG در addhesab. اگر چند پنل یک توکن زرین دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند (name_fa).', 'text' +SELECT 'zarin_name_g', 'مشتری نقدی (خریدار)', 'نام گروه حساب مشتریان زرین', 0, 1, 'پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).', 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'zarin_name_g' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) @@ -1758,7 +1758,9 @@ UPDATE `settings` SET `title_fa` = 'آدرس API زرین', `description` = 'آ UPDATE `settings` SET `title_fa` = 'توکن زرین', `description` = 'توکن مکمل API زرین که در نرم‌افزار زرین تعریف می‌شود.' WHERE `key` = 'zarin_token'; UPDATE `settings` SET `title_fa` = 'نوع کار پیش‌فرض زرین', `type` = 'number', `description` = 'کد noeKar هنگام ساخت کار ساخته در زرین.' WHERE `key` = 'zarin_noe_kar'; UPDATE `settings` SET `title_fa` = 'گروه کار پیش‌فرض زرین', `type` = 'number', `description` = 'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. برای جداسازی چند پنل روی یک زرین، برای هر پنل مقدار متفاوت بگذارید.' WHERE `key` = 'zarin_gorohe_kar'; -UPDATE `settings` SET `title_fa` = 'نام گروه حساب مشتریان زرین', `type` = 'text', `description` = 'پارامتر nameG در addhesab. اگر چند پنل یک توکن زرین دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند (name_fa).' WHERE `key` = 'zarin_name_g'; +UPDATE `settings` SET `title_fa` = 'نام گروه حساب مشتریان زرین', `type` = 'text', `description` = 'پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).' WHERE `key` = 'zarin_name_g'; +-- فقط اگر خالی است، پیش‌فرض گروه ۶ را بگذار (value موجود دست نخورده) +UPDATE `settings` SET `value` = 'مشتری نقدی (خریدار)' WHERE `key` = 'zarin_name_g' AND (`value` IS NULL OR TRIM(`value`) = ''); UPDATE `settings` SET `title_fa` = 'پیشوند عددی uid زرین', `type` = 'text', `description` = 'فقط عدد. وقتی چند پنل یک زرین دارند باید متفاوت باشد تا uid تکراری نشود.' WHERE `key` = 'zarin_uid_prefix'; UPDATE `settings` SET `value` = 'pargar' WHERE `key` = 'accounting_provider' diff --git a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql index b012a6b..5fb1dba 100644 --- a/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql +++ b/database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql @@ -486,7 +486,7 @@ SELECT 'zarin_gorohe_kar', '52', 'گروه کار پیش‌فرض زرین', 0, WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'zarin_gorohe_kar' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) -SELECT 'zarin_name_g', '', 'نام گروه حساب مشتریان زرین', 0, 1, 'پارامتر nameG در addhesab. اگر چند پنل یک توکن زرین دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند (name_fa).', 'text' +SELECT 'zarin_name_g', 'مشتری نقدی (خریدار)', 'نام گروه حساب مشتریان زرین', 0, 1, 'پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).', 'text' WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'zarin_name_g' LIMIT 1); INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`) @@ -1449,7 +1449,9 @@ UPDATE `settings` SET `title_fa` = 'آدرس API زرین', `description` = 'آ UPDATE `settings` SET `title_fa` = 'توکن زرین', `description` = 'توکن مکمل API زرین که در نرم‌افزار زرین تعریف می‌شود.' WHERE `key` = 'zarin_token'; UPDATE `settings` SET `title_fa` = 'نوع کار پیش‌فرض زرین', `type` = 'number', `description` = 'کد noeKar هنگام ساخت کار ساخته در زرین.' WHERE `key` = 'zarin_noe_kar'; UPDATE `settings` SET `title_fa` = 'گروه کار پیش‌فرض زرین', `type` = 'number', `description` = 'کد gorooheKar هنگام ساخت/جستجوی کار در زرین. برای جداسازی چند پنل روی یک زرین، برای هر پنل مقدار متفاوت بگذارید.' WHERE `key` = 'zarin_gorohe_kar'; -UPDATE `settings` SET `title_fa` = 'نام گروه حساب مشتریان زرین', `type` = 'text', `description` = 'پارامتر nameG در addhesab. اگر چند پنل یک توکن زرین دارند، برای هر پنل نام گروه متفاوت بگذارید. خالی = نام برند (name_fa).' WHERE `key` = 'zarin_name_g'; +UPDATE `settings` SET `title_fa` = 'نام گروه حساب مشتریان زرین', `type` = 'text', `description` = 'پارامتر nameG در addhesab — گروه حساب زرین. برای اپ خانگی: مشتری نقدی (خریدار) = گروه ۶. گروه همکار استفاده نشود (مظنه متفاوت است).' WHERE `key` = 'zarin_name_g'; +-- فقط اگر خالی است، پیش‌فرض گروه ۶ را بگذار (value موجود دست نخورده) +UPDATE `settings` SET `value` = 'مشتری نقدی (خریدار)' WHERE `key` = 'zarin_name_g' AND (`value` IS NULL OR TRIM(`value`) = ''); UPDATE `settings` SET `title_fa` = 'پیشوند عددی uid زرین', `type` = 'text', `description` = 'فقط عدد. وقتی چند پنل یک زرین دارند باید متفاوت باشد تا uid تکراری نشود.' WHERE `key` = 'zarin_uid_prefix'; UPDATE `settings` SET `value` = 'pargar' WHERE `key` = 'accounting_provider' diff --git a/routes/admin.php b/routes/admin.php index 5200f1d..3b38e76 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -202,6 +202,7 @@ Route::middleware('auth:user')->group(function (){ Route::post('tahesab/{id}/retry', [TahesabController::class, 'retrySanad']); Route::post('customer/{id}/tahesab/register', [TahesabController::class, 'registerCustomer']); + Route::get('zarin/health', [ZarinController::class, 'healthCheck']); Route::post('wallet/zarin', [ZarinController::class, 'wallet']); Route::post('zarin/table', [ZarinController::class, 'table']); Route::get('zarin/customer-sync/status', [ZarinController::class, 'customerSyncStatus']);