fix some bugs from zarin

This commit is contained in:
2026-07-28 14:02:23 +03:30
parent 745f7fcfd6
commit 074b48de95
5 changed files with 315 additions and 90 deletions
+291 -81
View File
@@ -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<int, 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,
]);
}
}
@@ -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()) {
+4 -2
View File
@@ -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'
@@ -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'
+1
View File
@@ -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']);