feat(accounting): document colleague trades on configured accounts

Post Montzar-covered buy/sell to Zarin/Tahesab/Pargar using montzar_account_* settings, and harden public storage URL/MIME handling for uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 16:59:27 +03:30
co-authored by Cursor
parent ad78545ebd
commit 885ed33e6e
10 changed files with 457 additions and 34 deletions
+32 -3
View File
@@ -40,9 +40,7 @@ class FileController extends Controller
$path = $this->createDir();
$originalName = $uploaded->getClientOriginalName();
$ext = $uploaded->getClientOriginalExtension()
?: $uploaded->extension()
?: 'jpg';
$ext = $this->resolveUploadExtension($uploaded);
$title = $request->input('title') ?: $originalName;
$file_name_src = md5(time().$originalName);
$file_name = $file_name_src.'.'.$ext;
@@ -70,4 +68,35 @@ class FileController extends Controller
return response()->json($payload);
}
/**
* پسوند واقعی فایل (MIME)، نه اسم اصلی مرورگر — آپلود بنر WebP است.
*/
private function resolveUploadExtension(\Illuminate\Http\UploadedFile $uploaded): string
{
$mime = strtolower((string) ($uploaded->getMimeType() ?: $uploaded->getClientMimeType()));
$fromMime = match (true) {
str_contains($mime, 'webp') => 'webp',
str_contains($mime, 'png') => 'png',
str_contains($mime, 'jpeg'), str_contains($mime, 'jpg') => 'jpg',
str_contains($mime, 'gif') => 'gif',
str_contains($mime, 'svg') => 'svg',
default => null,
};
if ($fromMime) {
return $fromMime;
}
$client = strtolower((string) $uploaded->getClientOriginalExtension());
if (in_array($client, ['webp', 'png', 'jpg', 'jpeg', 'gif', 'svg'], true)) {
return $client === 'jpeg' ? 'jpg' : $client;
}
$guessed = strtolower((string) $uploaded->extension());
if (in_array($guessed, ['webp', 'png', 'jpg', 'jpeg', 'gif', 'svg'], true)) {
return $guessed === 'jpeg' ? 'jpg' : $guessed;
}
return 'jpg';
}
}
@@ -453,10 +453,25 @@ class TahesabController extends Controller
}
$customer = Customer::where('id', $data['customer_id'])->first();
$accountOverride = trim((string) ($data['accounting_account_code'] ?? ''));
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInTahesab($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
if (! $isColleagueLeg) {
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInTahesab($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
}
}
if (! $customer) {
return false;
}
$hesabCode = $isColleagueLeg
? (int) $accountOverride
: (int) ($customer->moshtari_code ?? 0);
if ($hesabCode <= 0) {
return false;
}
$kind = $mapper->resolveKind($product);
@@ -490,7 +505,14 @@ class TahesabController extends Controller
$action = $isBuy ? 'خرید' : 'فروش';
$kindFa = $mapper->kindLabel($kind);
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
$sharh = trim((string) ($data['sharh_override'] ?? ''));
if ($sharh === '') {
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}");
}
$orderId = is_object($data['order'] ?? null)
? ($data['order']->id ?? null)
: ($data['order']['id'] ?? null);
$data['handler'] = 'storeTrade';
if ($existing && empty($existing->data)) {
@@ -500,21 +522,21 @@ class TahesabController extends Controller
'customer_id' => $customer->id,
'desc' => $sharh,
'amount' => $mapper->isCountDoc($product) ? $count : $vazn,
'order_id' => $data['order']['id'] ?? null,
'order_id' => $orderId,
'date' => $data['pay_at'] ?? Carbon::now(),
'type' => $mapper->syncTypeLabel($product),
'created_at' => Carbon::now(),
'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
]);
if ($mapper->isCountDoc($product) && $title === '') {
if ($mapper->isCountDoc($product) && $title === '' && ! $isColleagueLeg) {
$this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است');
return false;
}
$body = $this->buildBuySaleBody(
$kind,
(int) $customer->moshtari_code,
$hesabCode,
(int) $year,
(int) $month,
(int) $day,
+33 -11
View File
@@ -1394,29 +1394,49 @@ class ZarinController extends Controller
}
$customer = Customer::where('id', $data['customer_id'])->first();
$accountOverride = trim((string) ($data['accounting_account_code'] ?? ''));
$isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== '';
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInZarin($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
if (! $isColleagueLeg) {
if ($customer && ($customer->moshtari_code === null)) {
$this->createCustomerInZarin($customer->id);
$customer = Customer::where('id', $data['customer_id'])->first();
}
if (!$customer || empty($customer->moshtari_code)) {
return false;
}
} elseif (! $customer) {
return false;
}
if (!$customer || empty($customer->moshtari_code)) {
return false;
$accountCustomer = $customer;
if ($isColleagueLeg) {
$accountCustomer = clone $customer;
$accountCustomer->moshtari_code = $accountOverride;
}
$rawAmount = (float) $data['amount'];
$count = $mapper->accountingQty($product, $rawAmount, $data);
$vazn = $mapper->accountingWeight($product, $rawAmount, $data);
$isBuy = (int) ($data['direction'] ?? 0) === 1;
$orderId = $data['order']['id'] ?? ($existing->order_id ?? null);
$orderId = is_object($data['order'] ?? null)
? ($data['order']->id ?? null)
: ($data['order']['id'] ?? ($existing->order_id ?? null));
$title = $mapper->productTitle($product);
$action = $isBuy ? 'خرید' : 'فروش';
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}");
$sharh = trim((string) ($data['sharh_override'] ?? ''));
if ($sharh === '') {
$sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}");
}
$syncAmount = $mapper->isCountDoc($product) ? $count : $vazn;
$uidSource = $orderId ?? null;
if (!empty($data['line'])) {
$uidSource = ($orderId ?? '0') . 'L' . (int) $data['line'];
}
if ($isColleagueLeg) {
$uidSource = ($uidSource ?? '0') . 'C';
}
$data['handler'] = 'storeTrade';
if ($existing && empty($existing->data)) {
@@ -1440,6 +1460,8 @@ class ZarinController extends Controller
]);
}
$uidKind = $isColleagueLeg ? '11' : '10';
try {
if ($mapper->isCountDoc($product)) {
$fi = (float) ($data['price'] ?? 0);
@@ -1453,25 +1475,25 @@ class ZarinController extends Controller
$result = $this->postZarinCountDoc(
$zarin,
$data,
$customer,
$accountCustomer,
$product,
$count,
$isBuy ? -1 : 1,
$fi,
$sharh,
$this->zarinUid('10', $uidSource ?? $zarin->id)
$this->zarinUid($uidKind, $uidSource ?? $zarin->id)
);
} else {
$result = $this->postZarinWeightDoc(
$zarin,
$data,
$customer,
$accountCustomer,
$vazn,
$isBuy ? 'f' : 'k',
(float) ($data['price'] ?? 0),
$sharh,
$mapper->jensFelez($product) ?? 0,
$this->zarinUid('10', $uidSource ?? $zarin->id)
$this->zarinUid($uidKind, $uidSource ?? $zarin->id)
);
}
} catch (\Exception $e) {
@@ -198,6 +198,25 @@ class PargarInboundController extends Controller
}
}
$colleagueLogs = PargarSyncLog::query()
->where('type', PargarSyncService::TYPE_ORDER)
->where('sync_status', PargarSyncService::STATUS_PENDING)
->where('data', 'like', '%"colleague_leg":true%')
->orderBy('id')
->limit(100)
->get();
foreach ($colleagueLogs as $log) {
$order = Order::find((int) ($log->order_id ?? 0));
if (! $order) {
continue;
}
$payload = $this->orderExportPayload($order, $log);
if ($payload !== null) {
$documents->push($payload);
}
}
return response()->json([
'state' => 'true',
'msg' => '',
@@ -583,12 +602,23 @@ class PargarInboundController extends Controller
/**
* @return array<string, mixed>|null
*/
private function orderExportPayload(Order $order): ?array
private function orderExportPayload(Order $order, ?PargarSyncLog $forcedLog = null): ?array
{
$sync = $this->sync();
$log = PargarSyncLog::where('order_id', $order->id)->latest('id')->first();
$log = $forcedLog ?? PargarSyncLog::where('order_id', $order->id)
->where(function ($q) {
$q->whereNull('data')
->orWhere('data', 'not like', '%"colleague_leg":true%');
})
->latest('id')
->first();
$payload = $log ? (json_decode((string) $log->data, true) ?: []) : [];
// اگر لاگ اجباری همکار نیست و لاگ مشتری نبود، سند مشتری نساز
if ($forcedLog === null && $log && ! empty($payload['colleague_leg'])) {
return null;
}
$paymentKind = (string) ($payload['payment_kind'] ?? PargarSyncService::PAYMENT_CASH);
if ($paymentKind === PargarSyncService::PAYMENT_OTHER_PRODUCT) {
return null;
@@ -602,6 +632,8 @@ class PargarInboundController extends Controller
$isCustomerBuy = (int) ($order->direction ?? 1) === 1;
$customerDirection = (int) ($order->direction ?? 0);
$fromCart = (bool) ($payload['from_cart'] ?? $this->isCartOrder($order));
$isColleagueLeg = ! empty($payload['colleague_leg']);
$colleagueAccountId = trim((string) ($payload['colleague_account_id'] ?? ''));
$product = $sync->resolveOrderProduct($order, $payload);
$number = (float) ($payload['number'] ?? 0);
@@ -609,6 +641,22 @@ class PargarInboundController extends Controller
$totalPrice = (float) ($order->total_price ?? $order->payment_amount ?? 0);
$unitPrice = (float) ($order->price ?? 0);
if ($isColleagueLeg) {
if (isset($payload['direction'])) {
$isCustomerBuy = (int) $payload['direction'] === 1;
$customerDirection = (int) $payload['direction'];
}
if (isset($payload['total_price'])) {
$totalPrice = (float) $payload['total_price'];
}
if (isset($payload['price'])) {
$unitPrice = (float) $payload['price'];
}
if (isset($payload['amount'])) {
$amount = abs((float) $payload['amount']);
}
}
$isCountBased = $product
? self::isCountBasedProduct(self::productUnitPayload($product))
: false;
@@ -660,11 +708,19 @@ class PargarInboundController extends Controller
$totalPrice = 0;
}
$accountId = $isColleagueLeg && $colleagueAccountId !== ''
? (int) $colleagueAccountId
: (int) ($customer->pargar_account_id ?? 0);
$description = $isColleagueLeg
? (((int) ($payload['direction'] ?? $customerDirection) === 1) ? 'خرید از اپلیکیشن همکار' : 'فروش از اپلیکیشن همکار')
: self::accountingShopTradeDesc($customerDirection, $fromCart);
return [
'OrderId' => (string) $order->id,
'OrderId' => $isColleagueLeg ? ((string) $order->id).'-C' : (string) $order->id,
'OrderType' => $isCustomerBuy ? -1 : 1,
'PriceType' => 1,
'CustomerAccountId' => (int) ($customer->pargar_account_id ?? 0),
'CustomerAccountId' => $accountId,
'ItemId' => $itemId,
'ItemName' => $itemName,
'OrderDate' => $order->pay_at
@@ -675,7 +731,7 @@ class PargarInboundController extends Controller
'Weight' => $weight,
'Price' => $totalPrice,
'Carat' => 750,
'Description' => self::accountingShopTradeDesc($customerDirection, $fromCart),
'Description' => $description,
];
}
@@ -50,9 +50,37 @@ class PublicStorageController extends Controller
*/
private function candidatePaths(string $path): array
{
return [
storage_path('app/public/'.$path),
public_path('storage/'.$path),
];
$relative = [$path, ...$this->alternateExtensionPaths($path)];
$full = [];
foreach ($relative as $rel) {
$full[] = storage_path('app/public/'.$rel);
$full[] = storage_path('app/public/storage/'.$rel);
$full[] = public_path('storage/'.$rel);
}
return $full;
}
/**
* آپلود ادمین WebP می‌سازد ولی const گاهی همان هش را با .png نگه می‌دارد.
*
* @return list<string>
*/
private function alternateExtensionPaths(string $path): array
{
if (! preg_match('/^(.*)\.([a-z0-9]+)$/i', $path, $matches)) {
return [];
}
$stem = $matches[1];
$ext = strtolower($matches[2]);
$out = [];
foreach (['webp', 'png', 'jpg', 'jpeg', 'gif'] as $alt) {
if ($alt !== $ext) {
$out[] = $stem.'.'.$alt;
}
}
return $out;
}
}
@@ -263,6 +263,15 @@ class SingleOrderController extends Controller
report($e);
}
}
if (isset($data['order'])) {
try {
app(\App\Services\ColleagueAccountingService::class)->recordCoveredTrade($data);
} catch (\Throwable $e) {
report($e);
}
}
$response['reward'] = $this->give_order_reward($data, $wallet, $wallet_interface);
if ((int) $data['direction'] === 1 && $customer) {
$referrer = Customer::where('referral_id', $customer->referral_to_id)->first();
+13 -2
View File
@@ -51,7 +51,9 @@ class File extends Model
$trimmed = trim($path);
if (preg_match('/^https?:\/\//i', $trimmed)) {
return self::rewriteWrongPublicUrl($trimmed) ?? $trimmed;
$rewritten = self::rewriteWrongPublicUrl($trimmed) ?? $trimmed;
return self::preferHttpsUrl($rewritten);
}
$relative = self::normalizeRelativeStoragePath($trimmed);
@@ -163,7 +165,16 @@ class File extends Model
$base = 'https://'.ltrim($base, '/');
}
return $base;
return self::preferHttpsUrl($base);
}
private static function preferHttpsUrl(string $url): string
{
if (str_contains($url, 'localhost') || str_contains($url, '127.0.0.1')) {
return $url;
}
return (string) preg_replace('/^http:\/\//i', 'https://', $url);
}
private static function baseUrlFromSettings(): string
+191
View File
@@ -0,0 +1,191 @@
<?php
namespace App\Services;
use App\Http\Controllers\Admin\TahesabController;
use App\Http\Controllers\Admin\ZarinController;
use App\Models\Order;
use App\Models\PargarSyncLog;
use App\Models\Tahesab;
use App\Models\Zarin;
use Illuminate\Support\Facades\Schema;
/**
* سند حسابداری سمت همکار (Montzar) — جدا از سند مشتری.
* کد حساب از settings: montzar_account_{zarin|tahesab|pargar}
*/
class ColleagueAccountingService
{
public function __construct(
private ConfigService $config,
) {}
/**
* بعد از پوشش سفارش روی همکار، سند را روی کد حساب همکار ثبت/صف می‌کند.
*
* @param array<string, mixed> $data
*/
public function recordCoveredTrade(array $data): void
{
if (! $this->config->usesMontzarPricing()) {
return;
}
$order = $data['order'] ?? null;
if (! $order instanceof Order || ! $order->id) {
return;
}
if (! $this->orderCoveredByColleague($order, $data)) {
return;
}
$provider = $this->config->accountingProvider();
$accountCode = $this->colleagueAccountCode($provider);
if ($accountCode === null) {
return;
}
if ($this->alreadyQueued($order, $provider)) {
return;
}
$payload = $this->buildColleaguePayload($data, $order, $accountCode);
try {
match ($provider) {
ConfigService::ACCOUNTING_ZARIN => app(ZarinController::class)->storeTrade($payload),
ConfigService::ACCOUNTING_TAHESAB => app(TahesabController::class)->storeTrade($payload),
ConfigService::ACCOUNTING_PARGAR => $this->queuePargarColleagueTrade($order, $payload, $accountCode),
default => null,
};
} catch (\Throwable $e) {
report($e);
}
}
public function colleagueAccountCode(string $provider): ?string
{
$key = match ($provider) {
ConfigService::ACCOUNTING_ZARIN => 'montzar_account_zarin',
ConfigService::ACCOUNTING_TAHESAB => 'montzar_account_tahesab',
ConfigService::ACCOUNTING_PARGAR => 'montzar_account_pargar',
default => null,
};
if ($key === null) {
return null;
}
$code = trim($this->config->getString($key, ''));
if ($code === '' || in_array($code, ['0', '-1'], true)) {
return null;
}
return $code;
}
/**
* @param array<string, mixed> $data
*/
private function orderCoveredByColleague(Order $order, array $data): bool
{
if (Schema::hasColumn('orders', 'provider_id') && ! empty($order->provider_id)) {
return true;
}
if (! empty($data['provider_id'])) {
return true;
}
return Schema::hasColumn('orders', 'total_price_montzar')
&& $order->total_price_montzar !== null
&& (float) $order->total_price_montzar > 0;
}
private function alreadyQueued(Order $order, string $provider): bool
{
return match ($provider) {
ConfigService::ACCOUNTING_ZARIN => Zarin::query()
->where('order_id', $order->id)
->where('desc', 'like', '%همکار%')
->exists(),
ConfigService::ACCOUNTING_TAHESAB => Tahesab::query()
->where('order_id', $order->id)
->where('desc', 'like', '%همکار%')
->exists(),
ConfigService::ACCOUNTING_PARGAR => PargarSyncLog::query()
->where('order_id', $order->id)
->where('data', 'like', '%"colleague_leg":true%')
->exists(),
default => true,
};
}
/**
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function buildColleaguePayload(array $data, Order $order, string $accountCode): array
{
$amount = abs((float) ($order->amount ?? $data['amount'] ?? 0));
$montzarUnit = Schema::hasColumn('orders', 'total_price_montzar')
? (float) ($order->total_price_montzar ?? 0)
: 0.0;
$price = $montzarUnit > 0
? $montzarUnit
: (float) ($order->price ?? $data['price'] ?? 0);
$total = $amount * $price;
$direction = (int) ($order->direction ?? $data['direction'] ?? 0);
$action = $direction === 1 ? 'خرید' : 'فروش';
$sharh = "{$action} از اپلیکیشن همکار";
$payload = $data;
$payload['order'] = $order;
$payload['customer_id'] = (int) ($order->customer_id ?? $data['customer_id'] ?? 0);
$payload['amount'] = $amount;
$payload['price'] = $price;
$payload['total_price'] = $total;
$payload['direction'] = $direction;
$payload['product_id'] = $order->product_id ?? ($data['product_id'] ?? null);
$payload['accounting_account_code'] = $accountCode;
$payload['colleague_leg'] = true;
$payload['sharh_override'] = $sharh;
$payload['pay_at'] = $order->pay_at ?? $order->created_at ?? ($data['pay_at'] ?? now());
return $payload;
}
/**
* @param array<string, mixed> $payload
*/
private function queuePargarColleagueTrade(Order $order, array $payload, string $accountCode): void
{
if (! $this->config->isPargarAccounting()) {
return;
}
$direction = (int) ($payload['direction'] ?? 0);
$desc = (string) ($payload['sharh_override'] ?? 'معامله با اپلیکیشن همکار');
PargarSyncLog::create([
'customer_id' => $order->customer_id,
'amount' => $payload['amount'] ?? $order->amount,
'desc' => $desc,
'order_id' => $order->id,
'date' => $order->pay_at ?? $order->created_at,
'type' => PargarSyncService::TYPE_ORDER,
'sync_status' => PargarSyncService::STATUS_PENDING,
'data' => json_encode([
'handler' => 'exportColleagueOrder',
'colleague_leg' => true,
'colleague_account_id' => $accountCode,
'direction' => $direction,
'price' => $payload['price'] ?? $order->price,
'total_price' => $payload['total_price'] ?? $order->total_price,
'payment_kind' => PargarSyncService::PAYMENT_CASH,
'product_id' => $order->product_id,
'from_cart' => false,
], JSON_UNESCAPED_UNICODE),
]);
}
}
+32 -1
View File
@@ -555,6 +555,31 @@ class PargarSyncService
continue;
}
if (str_ends_with(strtoupper($id), '-C')) {
$baseId = (int) substr($id, 0, -2);
if ($baseId <= 0) {
continue;
}
$log = PargarSyncLog::query()
->where('order_id', $baseId)
->where('type', self::TYPE_ORDER)
->where('sync_status', self::STATUS_PENDING)
->where('data', 'like', '%"colleague_leg":true%')
->latest('id')
->first();
if ($log) {
$this->markSuccess($log, $id, ['OrderId' => $id]);
if ($log->customer_id) {
$customerIds[(int) $log->customer_id] = true;
}
$confirmed++;
}
continue;
}
$order = Order::find($id);
if (! $order) {
continue;
@@ -563,7 +588,13 @@ class PargarSyncService
$order->pargar_confirmed_at = now();
$order->save();
$log = PargarSyncLog::where('order_id', $order->id)->latest('id')->first();
$log = PargarSyncLog::where('order_id', $order->id)
->where(function ($q) {
$q->whereNull('data')
->orWhere('data', 'not like', '%"colleague_leg":true%');
})
->latest('id')
->first();
if ($log) {
$this->markSuccess($log, (string) $order->id, ['OrderId' => (string) $order->id]);
}
+25 -1
View File
@@ -281,6 +281,21 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de
SELECT 'montzar_product_id', '25', 'کد محصول همکار', 1, 1, NULL
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_product_id' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'montzar_account_zarin', '', 'کد حساب همکار در زرین', 1, 1,
'کد حساب طرف‌حساب همکار در زرین؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_account_zarin' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'montzar_account_tahesab', '', 'کد حساب همکار در ته‌حساب', 1, 1,
'کد حساب طرف‌حساب همکار در ته‌حساب؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_account_tahesab' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'montzar_account_pargar', '', 'کد حساب همکار در پرگار', 1, 1,
'کد حساب طرف‌حساب همکار در پرگار؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.'
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_account_pargar' LIMIT 1);
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
SELECT 'montzar_base_url', 'https://limangold.ir', 'آدرس سرور همکار', 1, 1, NULL
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_base_url' LIMIT 1);
@@ -1301,7 +1316,9 @@ UPDATE `settings` SET `type` = 'text' WHERE `key` IN (
'server_url', 'key', 'main_product', 'zaarbox_api_url', 'zaarbox_price_key', 'chart_id',
'etehadie_api_url', 'montzar_mobile', 'montzar_password', 'montzar_base_url',
'montzar_token', 'montzar_diff', 'montzar_live_price', 'montzar_mazaneh',
'montzar_truly_buy_mazaneh', 'montzar_truly_sell_mazaneh', 'startDay', 'endDay',
'montzar_truly_buy_mazaneh', 'montzar_truly_sell_mazaneh',
'montzar_account_zarin', 'montzar_account_tahesab', 'montzar_account_pargar',
'startDay', 'endDay',
'payping_start_url', 'payping_verify_url', 'payping_gateway_items', 'payping_fin_url',
'payping_fin_token', 'tahesab_host', 'tahesab_token', 'tahesab_db',
'pargar_username', 'pargar_password', 'pargar_api_token',
@@ -1735,6 +1752,9 @@ UPDATE `settings` SET `description` = 'روشن: تطبیق کد ملی و مو
UPDATE `settings` SET `description` = 'شماره موبایل حساب API سرویس همکار قیمت. برای دریافت توکن و لیست محصولات.' WHERE `key` = 'montzar_mobile';
UPDATE `settings` SET `description` = 'رمز عبور حساب API سرویس همکار قیمت. محرمانه نگه دارید.' WHERE `key` = 'montzar_password';
UPDATE `settings` SET `description` = 'شناسه محصول آبشده در API همکار. باید با محصول مرجع در سرویس خارجی یکسان باشد.' WHERE `key` = 'montzar_product_id';
UPDATE `settings` SET `description` = 'کد حساب طرف‌حساب همکار در زرین؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.' WHERE `key` = 'montzar_account_zarin';
UPDATE `settings` SET `description` = 'کد حساب طرف‌حساب همکار در ته‌حساب؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.' WHERE `key` = 'montzar_account_tahesab';
UPDATE `settings` SET `description` = 'کد حساب طرف‌حساب همکار در پرگار؛ سند خرید/فروش پوشش‌شده روی همکار روی این حساب ثبت می‌شود.' WHERE `key` = 'montzar_account_pargar';
-- [super-admin can_edit=0]
-- UPDATE `settings` SET `description` = 'آدرس پایه سرور API همکار (بدون اسلش انتهایی). مثال: https://example.com' WHERE `key` = 'montzar_base_url';
UPDATE `settings` SET `description` = 'توکن دسترسی API همکار. پس از ورود موفق توسط سیستم پر می‌شود؛ دستی ویرایش نکنید.' WHERE `key` = 'montzar_token';
@@ -2060,6 +2080,9 @@ UPDATE `settings` SET `title_fa` = 'باز بودن فروش همکار' WHERE `
UPDATE `settings` SET `title_fa` = 'پاسخ سفارش همکار' WHERE `key` = 'montzar_order_response';
UPDATE `settings` SET `title_fa` = 'رمز API همکار' WHERE `key` = 'montzar_password';
UPDATE `settings` SET `title_fa` = 'کد محصول همکار' WHERE `key` = 'montzar_product_id';
UPDATE `settings` SET `title_fa` = 'کد حساب همکار در زرین' WHERE `key` = 'montzar_account_zarin';
UPDATE `settings` SET `title_fa` = 'کد حساب همکار در ته‌حساب' WHERE `key` = 'montzar_account_tahesab';
UPDATE `settings` SET `title_fa` = 'کد حساب همکار در پرگار' WHERE `key` = 'montzar_account_pargar';
UPDATE `settings` SET `title_fa` = 'توکن همکار (خودکار)' WHERE `key` = 'montzar_token';
UPDATE `settings` SET `title_fa` = 'مظنه خرید همکار' WHERE `key` = 'montzar_truly_buy_mazaneh';
UPDATE `settings` SET `title_fa` = 'مظنه فروش همکار' WHERE `key` = 'montzar_truly_sell_mazaneh';
@@ -2244,6 +2267,7 @@ UPDATE `settings` SET `can_edit` = 1 WHERE `key` IN (
'min_first_buy', 'min_withdraw', 'min_deposit', 'minimum_sell',
'montzar_mobile', 'montzar_open_buy', 'montzar_open_sell',
'montzar_password', 'montzar_product_id',
'montzar_account_zarin', 'montzar_account_tahesab', 'montzar_account_pargar',
'open_register',
'referral_id', 'referral_show', 'referral_limit', 'referral_gift_on_first_order', 'referral_gift_buy_from_montzar',
'register_jibit_verify', 'register_referral_bonus_message', 'register_referral_sms_to_referrer',