progress in inquery panel

This commit is contained in:
Hesabix 2025-07-16 15:11:53 +00:00
parent 2cb7b8945c
commit 227767b0d6
17 changed files with 1742 additions and 87 deletions

View file

@ -455,6 +455,10 @@ class AdminController extends AbstractController
$resp['enablePostalCodeToAddress'] = $registryMGR->get('system', key: 'enablePostalCodeToAddress');
$resp['inquiryPanelEnable'] = $registryMGR->get('system', key: 'inquiryPanelEnable');
$resp['postalCodeToAddressFee'] = $registryMGR->get('system', key: 'postalCodeToAddressFee');
$resp['enableCardToSheba'] = $registryMGR->get('system', key: 'enableCardToSheba');
$resp['cardToShebaFee'] = $registryMGR->get('system', key: 'cardToShebaFee');
$resp['enableAccountToSheba'] = $registryMGR->get('system', key: 'enableAccountToSheba');
$resp['accountToShebaFee'] = $registryMGR->get('system', key: 'accountToShebaFee');
return $this->json($resp);
}
@ -484,6 +488,10 @@ class AdminController extends AbstractController
$registryMGR->update('system', 'enablePostalCodeToAddress', $params['enablePostalCodeToAddress']);
$registryMGR->update('system', 'inquiryPanelEnable', $params['inquiryPanelEnable']);
$registryMGR->update('system', 'postalCodeToAddressFee', $params['postalCodeToAddressFee']);
$registryMGR->update('system', 'enableCardToSheba', $params['enableCardToSheba']);
$registryMGR->update('system', 'cardToShebaFee', $params['cardToShebaFee']);
$registryMGR->update('system', 'enableAccountToSheba', $params['enableAccountToSheba']);
$registryMGR->update('system', 'accountToShebaFee', $params['accountToShebaFee']);
$entityManager->persist($item);
$entityManager->flush();
return $this->json(['result' => 1]);

View file

@ -544,6 +544,7 @@ class BusinessController extends AbstractController
'plugRepservice' => true,
'plugHrmDocs' => true,
'plugGhestaManager' => true,
'plugTaxSettings' => true,
];
} elseif ($perm) {
$result = [
@ -587,6 +588,7 @@ class BusinessController extends AbstractController
'plugAccproPresell' => $perm->isPlugAccproPresell(),
'plugHrmDocs' => $perm->isPlugHrmDocs(),
'plugGhestaManager' => $perm->isPlugGhestaManager(),
'plugTaxSettings' => $perm->isPlugTaxSettings(),
];
}
return $this->json($result);
@ -656,6 +658,7 @@ class BusinessController extends AbstractController
$perm->setPlugRepservice($params['plugRepservice']);
$perm->setPlugHrmDocs($params['plugHrmDocs']);
$perm->setPlugGhestaManager($params['plugGhestaManager']);
$perm->setPlugTaxSettings($params['plugTaxSettings']);
$entityManager->persist($perm);
$entityManager->flush();
$log->insert('تنظیمات پایه', 'ویرایش دسترسی‌های کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);

View file

@ -0,0 +1,201 @@
<?php
namespace App\Controller\Plugins;
use App\Service\Access;
use App\Service\Extractor;
use App\Service\Log;
use App\Service\registryMGR;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\PluginTaxsettingsKey;
class TaxSettingsController extends AbstractController
{
#[Route('/api/plugins/tax/settings/get', name: 'plugin_tax_settings_get', methods: ['GET'])]
public function plugin_tax_settings_get(EntityManagerInterface $em, Access $access): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.');
}
$businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid'];
$userId = $this->getUser()->getId();
// دریافت تنظیمات از جدول اختصاصی
$repo = $em->getRepository(PluginTaxsettingsKey::class);
$entity = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]);
$settings = [
'taxMemoryId' => $entity ? $entity->getTaxMemoryId() : '',
'economicCode' => $entity ? $entity->getEconomicCode() : '',
'privateKey' => $entity ? $entity->getPrivateKey() : '',
];
return $this->json($settings);
}
#[Route('/api/plugins/tax/settings/save', name: 'plugin_tax_settings_save', methods: ['POST'])]
public function plugin_tax_settings_save(Request $request, registryMGR $registryMGR, Access $access, Log $log, EntityManagerInterface $em): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.');
}
$params = $request->getPayload()->all();
$businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid'];
$userId = $this->getUser()->getId();
// بررسی وجود رکورد قبلی
$repo = $em->getRepository(PluginTaxsettingsKey::class);
$entity = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]);
if (!$entity) {
$entity = new PluginTaxsettingsKey();
$entity->setBusinessId($businessId);
$entity->setUserId($userId);
$entity->setCreatedAt(new \DateTime());
}
$entity->setPrivateKey($params['privateKey'] ?? '');
$entity->setTaxMemoryId($params['taxMemoryId'] ?? null);
$entity->setEconomicCode($params['economicCode'] ?? null);
$entity->setUpdatedAt(new \DateTime());
$em->persist($entity);
$em->flush();
$log->insert('تنظیمات مالیاتی', 'تنظیمات مالیاتی ذخیره شد (در جدول اختصاصی)', $this->getUser(), $businessId);
return $this->json(['success' => true, 'message' => 'تنظیمات با موفقیت ذخیره شد']);
}
private function generatePrivateKey(): string
{
// تولید کلید خصوصی واقعی با OpenSSL
$config = [
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
];
$res = openssl_pkey_new($config);
if (!$res) {
throw new \Exception('خطا در تولید کلید خصوصی: ' . openssl_error_string());
}
$privateKey = '';
if (!openssl_pkey_export($res, $privateKey)) {
throw new \Exception('خطا در استخراج کلید خصوصی: ' . openssl_error_string());
}
openssl_pkey_free($res);
return $privateKey;
}
private function generatePublicKey(string $privateKey): string
{
// استخراج کلید عمومی از کلید خصوصی
$res = openssl_pkey_get_private($privateKey);
if (!$res) {
throw new \Exception('خطا در خواندن کلید خصوصی: ' . openssl_error_string());
}
$keyDetails = openssl_pkey_get_details($res);
if (!$keyDetails) {
throw new \Exception('خطا در استخراج جزئیات کلید: ' . openssl_error_string());
}
openssl_pkey_free($res);
return $keyDetails['key'];
}
#[Route('/api/plugins/tax/settings/generate-csr', name: 'plugin_tax_settings_generate_csr', methods: ['POST'])]
public function plugin_tax_settings_generate_csr(Request $request, registryMGR $registryMGR, Access $access, Log $log): JsonResponse
{
$acc = $access->hasRole('plugTaxSettings');
if (!$acc) {
throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.');
}
$params = $request->getPayload()->all();
// بررسی فیلدهای اجباری
if (empty($params['nationalId']) || empty($params['nameFa']) || empty($params['nameEn']) || empty($params['email'])) {
return $this->json([
'success' => false,
'message' => 'تمام فیلدها الزامی هستند'
]);
}
try {
$privateKey = $this->generatePrivateKey();
$publicKey = $this->generatePublicKey($privateKey);
$csr = $this->generateCSR($privateKey, $params);
// هیچ ذخیره‌ای در دیتابیس انجام نمی‌شود
$businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid'];
$log->insert('تنظیمات مالیاتی', 'کلید و CSR تولید شد (بدون ذخیره)', $this->getUser(), $businessId);
return $this->json([
'success' => true,
'message' => 'کلید و CSR با موفقیت تولید شد',
'privateKey' => $privateKey,
'publicKey' => $publicKey,
'csr' => $csr
]);
} catch (\Exception $e) {
return $this->json([
'success' => false,
'message' => 'خطا در تولید کلید و CSR: ' . $e->getMessage()
]);
}
}
private function generateCSR(string $privateKey, array $params): string
{
// تولید CSR واقعی با OpenSSL
$dn = [
"countryName" => "IR",
"stateOrProvinceName" => "Tehran",
"localityName" => "Tehran",
"organizationName" => $params['nameEn'],
"organizationalUnitName" => "Tax Department",
"commonName" => $params['nameFa'],
"emailAddress" => $params['email']
];
// اضافه کردن شناسه ملی به عنوان extension
$config = [
"req" => [
"distinguished_name" => $dn,
"req_extensions" => "v3_req",
"x509_extensions" => "v3_req"
],
"v3_req" => [
"subjectAltName" => "email:" . $params['email'],
"subjectKeyIdentifier" => "hash"
]
];
// ایجاد CSR
$res = openssl_csr_new($dn, $privateKey, [
'config' => $config,
'digest_alg' => 'sha256',
'req_extensions' => 'v3_req'
]);
if (!$res) {
throw new \Exception('خطا در تولید CSR: ' . openssl_error_string());
}
$csr = '';
if (!openssl_csr_export($res, $csr)) {
throw new \Exception('خطا در استخراج CSR: ' . openssl_error_string());
}
return $csr;
}
}

View file

@ -128,6 +128,4 @@ class PlugInquiryMainController extends AbstractController
]);
}
}
}

View file

@ -129,6 +129,9 @@ class Permission
#[ORM\Column(nullable: true)]
private ?bool $plugGhestaManager = null;
#[ORM\Column(nullable: true)]
private ?bool $plugTaxSettings = null;
public function getId(): ?int
{
return $this->id;
@ -590,4 +593,16 @@ class Permission
return $this;
}
public function isPlugTaxSettings(): ?bool
{
return $this->plugTaxSettings;
}
public function setPlugTaxSettings(?bool $plugTaxSettings): static
{
$this->plugTaxSettings = $plugTaxSettings;
return $this;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: "plugin_taxsettings_keys")]
class PluginTaxsettingsKey
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: "integer")]
private $id;
#[ORM\Column(type: "integer")]
private $business_id;
#[ORM\Column(type: "integer")]
private $user_id;
#[ORM\Column(type: "text", nullable: true)]
private $private_key;
#[ORM\Column(type: "string", length: 64, nullable: true)]
private $tax_memory_id;
#[ORM\Column(type: "string", length: 64, nullable: true)]
private $economic_code;
#[ORM\Column(type: "datetime")]
private $created_at;
#[ORM\Column(type: "datetime")]
private $updated_at;
// Getters and setters ...
public function getId() { return $this->id; }
public function getBusinessId() { return $this->business_id; }
public function setBusinessId($val) { $this->business_id = $val; }
public function getUserId() { return $this->user_id; }
public function setUserId($val) { $this->user_id = $val; }
public function getPrivateKey() { return $this->private_key; }
public function setPrivateKey($val) { $this->private_key = $val; }
public function getTaxMemoryId() { return $this->tax_memory_id; }
public function setTaxMemoryId($val) { $this->tax_memory_id = $val; }
public function getEconomicCode() { return $this->economic_code; }
public function setEconomicCode($val) { $this->economic_code = $val; }
public function getCreatedAt() { return $this->created_at; }
public function setCreatedAt($val) { $this->created_at = $val; }
public function getUpdatedAt() { return $this->updated_at; }
public function setUpdatedAt($val) { $this->updated_at = $val; }
}

View file

@ -86,6 +86,7 @@ const en_lang = {
cheque_output: "Cheque Output",
presells: "Presells",
presell_view: "View Presell",
inquiry: "Inquiries",
hrm: 'HR & Payroll',
hrm_docs: 'Payroll Document',
},

View file

@ -173,6 +173,7 @@ const fa_lang = {
reports: "گزارشات",
settings: "تنظیمات",
bid_settings: "تنظیمات کسب‌و‌کار",
tax_settings: "تنظیمات مالیاتی",
print_settings: "چاپ اسناد",
user_perms: "کاربران و دسترسی‌ها",
avatar_settings: "نمایه و مهر کسب‌و‌کار",
@ -193,13 +194,16 @@ const fa_lang = {
plugins_invoices: "صورت حساب‌ها",
repservice: "مدیریت تعمیرگاه",
repservice_reqs: "درخواست‌ها",
inquiry: "استعلامات",
hrm: 'منابع انسانی',
hrm_docs: 'سند حقوق',
buysellByPerson: "گزارش خرید و فروش های اشخاص",
tax_system: "سامانه مودیان مالیاتی",
tax_invoices: "صورتحساب‌ ها",
},
time: {
month: "{id} ماه",
},
},
calendar: {
shamsi: "هجری شمسی",
gregorian: "میلادی",
@ -818,6 +822,10 @@ const fa_lang = {
inquiry_zohal_api_key_label: "کلید API زحل",
enable_postalcode_to_address: "تبدیل کد پستی به آدرس",
postalcode_to_address_fee: "کارمزد تبدیل کد پستی به آدرس",
enable_card_to_sheba: "تبدیل شماره کارت به شبا",
card_to_sheba_fee: "کارمزد تبدیل شماره کارت به شبا",
enable_account_to_sheba: "تبدیل حساب به شبا",
account_to_sheba_fee: "کارمزد تبدیل حساب به شبا",
inquiry_panel_enable: "فعال سازی پنل سامانه استعلامات",
inquiry_panel: "پنل سامانه استعلامات",
inquiry_panel_zohal: "زحل",

View file

@ -510,6 +510,18 @@ const router = createRouter({
component: () =>
import('../views/acc/settings/extramoneys.vue'),
},
{
path: 'plugins/tax/settings',
name: 'business_tax_settings',
component: () =>
import('../views/acc/tax/tax-settings.vue'),
},
{
path: 'plugins/tax/invoices/list',
name: 'tax_invoices_list',
component: () =>
import('../views/acc/tax/invoices/list.vue'),
},
{
path: 'business/logs',
name: 'business_logs',
@ -988,6 +1000,12 @@ const router = createRouter({
component: () =>
import('../views/acc/plugins/hrm/docs/view.vue'),
},
{
path: 'inquiry/panel',
name: 'inquiry_panel',
component: () =>
import('../views/acc/inquiry/panel.vue'),
},
],
},
{

View file

@ -169,8 +169,9 @@ export default {
{ path: '/acc/business/extramoneys', key: '-', label: this.$t('drawer.extra_moneys'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('accpro') },
{ path: '/acc/business/logs', key: '=', label: this.$t('drawer.history'), ctrl: true, shift: true, permission: () => this.permissions.log },
{ path: '/acc/plugin/repservice/order/list', key: '[', label: this.$t('drawer.repservice_reqs'), ctrl: true, shift: true, permission: () => this.permissions.plugRepservice && this.isPluginActive('repservice') },
{ path: '/acc/sms/panel', key: ']', label: this.$t('drawer.sms_panel'), ctrl: true, shift: true, permission: () => this.permissions.owner },
{ path: '/acc/inquiry/panel', key: ']', label: this.$t('drawer.inquiry'), ctrl: true, shift: true, permission: () => true },
{ path: '/acc/printers/list', key: ';', label: this.$t('drawer.cloud_printers'), ctrl: true, shift: true, permission: () => this.permissions.owner },
{ path: '/acc/sms/panel', key: '`', label: this.$t('drawer.sms_panel'), ctrl: true, shift: true, permission: () => this.permissions.owner },
{ path: '/acc/archive/list', key: '\'', label: this.$t('drawer.archive_files'), ctrl: true, shift: true, permission: () => this.permissions.archiveUpload || this.permissions.archiveMod || this.permissions.archiveDelete },
{ path: '/acc/archive/order/new', key: ',', label: this.$t('drawer.archive_order'), ctrl: true, shift: true, permission: () => this.permissions.owner },
{ path: '/acc/archive/order/list', key: '.', label: this.$t('drawer.archive_log'), ctrl: true, shift: true, permission: () => this.permissions.owner },
@ -179,6 +180,8 @@ export default {
{ path: '/acc/plugin-center/invoice', key: '`', label: this.$t('drawer.plugins_invoices'), ctrl: true, shift: true, permission: () => this.permissions.owner },
{ path: '/acc/hrm/docs/list', key: 'H', label: this.$t('drawer.hrm_docs'), ctrl: true, shift: true, permission: () => this.isPluginActive('hrm') && this.permissions.plugHrmDocs },
{ path: '/acc/plugins/ghesta/list', key: 'G', label: this.$t('drawer.ghesta_invoices'), ctrl: true, shift: true, permission: () => this.isPluginActive('ghesta') && this.permissions.plugGhestaManager },
{ path: '/acc/plugins/tax/invoices/list', key: 'L', label: this.$t('drawer.tax_invoices'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('taxsettings') },
{ path: '/acc/plugins/tax/settings', key: 'T', label: this.$t('drawer.tax_settings'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('taxsettings') },
];
},
restorePermissions(shortcuts) {
@ -741,7 +744,7 @@ export default {
<v-list-item-title>
{{ $t('drawer.user_perms') }}
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/business/users') }}</span>
</v-list-item-title>
</v-list-item-title>
</v-list-item>
<v-list-item v-if="permissions.owner" to="/acc/business/apis">
<v-list-item-title>
@ -788,6 +791,13 @@ export default {
</template>
</v-list-item>
</v-list-group>
<v-list-item class="text-dark" to="/acc/inquiry/panel">
<template v-slot:prepend><v-icon icon="mdi-magnify" color="primary"></v-icon></template>
<v-list-item-title>
{{ $t('drawer.inquiry') }}
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/inquiry/panel') }}</span>
</v-list-item-title>
</v-list-item>
<v-list-group v-show="isPluginActive('hrm') && permissions.plugHrmDocs">
<template v-slot:activator="{ props }">
<v-list-item class="text-dark" v-bind="props" :title="$t('drawer.hrm')">
@ -828,6 +838,23 @@ export default {
</template>
</v-list-item>
</v-list-group>
<v-list-group v-show="isPluginActive('taxsettings') && permissions.plugTaxSettings">
<template v-slot:activator="{ props }">
<v-list-item class="text-dark" v-bind="props" :title="$t('drawer.tax_system')">
<template v-slot:prepend><v-icon icon="mdi-file-document-multiple" color="primary"></v-icon></template>
</v-list-item>
</template>
<v-list-item to="/acc/plugins/tax/invoices/list">
<v-list-item-title>
{{ $t('drawer.tax_invoices') }}
</v-list-item-title>
</v-list-item>
<v-list-item to="/acc/plugins/tax/settings">
<v-list-item-title>
{{ $t('drawer.tax_settings') }}
</v-list-item-title>
</v-list-item>
</v-list-group>
<v-list-item class="text-dark" v-if="permissions.owner" to="/acc/sms/panel">
<template v-slot:prepend><v-icon icon="mdi-message-cog" color="primary"></v-icon></template>
<v-list-item-title>

View file

@ -0,0 +1,24 @@
<template>
<div>
<h1>پنل استعلامات</h1>
</div>
</template>
<script>
export default {
name: 'panel',
data() {
return {
// دادههای اولیه
}
},
methods: {
// متدها
}
}
</script>
<style scoped>
/* استایل‌های اختصاصی */
</style>

View file

@ -241,6 +241,12 @@ const router = createRouter({
component: () =>
import ('../views/settings/extramoneys.vue'),
},
{
path: '/acc/business/tax-settings',
name: 'business_tax_settings',
component: () =>
import ('../views/settings/tax-settings.vue'),
},
{
path: '/acc/business/logs',
name: 'business_logs',

View file

@ -0,0 +1,321 @@
<template>
<div>
<v-toolbar color="toolbar" title="تنظیمات مالیاتی">
<template v-slot:prepend>
<v-tooltip text="بازگشت" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-btn :loading="loading" @click="saveSettings()" icon="" color="green">
<v-tooltip activator="parent" text="ذخیره تنظیمات" location="bottom" />
<v-icon icon="mdi-content-save"></v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-card :loading="loading" :disabled="loading">
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-btn
color="primary"
@click="showCSRDialog = true"
prepend-icon="mdi-key-plus"
>
ساخت کلید و CSR
</v-btn>
</v-col>
<v-col cols="12" md="6">
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="settings.taxMemoryId"
label="شناسه یکتای حافظه مالیاتی"
hide-details
density="compact"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="settings.economicCode"
label="کد اقتصادی"
hide-details
density="compact"
></v-text-field>
</v-col>
</v-row>
</v-col>
</v-row>
<v-row class="mt-4">
<v-col cols="12">
<v-textarea
v-model="settings.privateKey"
label="Private Key"
rows="15"
variant="outlined"
hide-details
placeholder="کلید خصوصی اینجا قرار می‌گیرد..."
></v-textarea>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
<!-- Dialog برای ساخت کلید و CSR -->
<v-dialog v-model="showCSRDialog" max-width="600px">
<v-card>
<v-card-title class="text-h6">
ساخت کلید و CSR
</v-card-title>
<v-card-text>
<v-form ref="csrForm">
<div class="mb-4">
<div class="text-subtitle-2 mb-2">شخص</div>
<v-radio-group
v-model="csrData.personType"
inline
hide-details
>
<v-radio
v-for="type in personTypes"
:key="type.value"
:label="type.title"
:value="type.value"
:disabled="type.value === 'natural'"
></v-radio>
</v-radio-group>
</div>
<v-text-field
v-model="csrData.nationalId"
label="شناسه ملی"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.nameFa"
label="نام (فارسی)"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.nameEn"
label="نام (انگلیسی)"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.email"
label="ایمیل"
type="email"
hide-details
class="mb-4"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="showCSRDialog = false" variant="text">
انصراف
</v-btn>
<v-btn @click="generateCSR()" color="primary" :loading="csrLoading">
تایید
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showResultDialog" max-width="900px">
<v-card>
<v-card-title class="text-h6 pb-0">ساخت کلید و CSR</v-card-title>
<v-card-text>
<v-alert type="info" color="blue" class="mb-4" icon="mdi-alert">
<span class="font-weight-bold">توجه: لطفا این اطلاعات را دانلود کنید و در یک جای امن نگهداری کنید. به دلایل امنیتی اطلاعات شما را نگهداری نمیکنیم، در صورتی که این اطلاعات را گم کنید، امکان بازیابی آن وجود ندارد.</span>
</v-alert>
<v-row>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">CSR</div>
<v-textarea readonly rows="10" :value="resultData.csr" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.csr)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.csr, 'csr.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">Public Key</div>
<v-textarea readonly rows="10" :value="resultData.publicKey" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.publicKey)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.publicKey, 'public_key.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">Private Key</div>
<v-textarea readonly rows="10" :value="resultData.privateKey" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.privateKey)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.privateKey, 'private_key.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="showResultDialog = false" color="primary">بستن</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
import axios from 'axios';
import Swal from 'sweetalert2';
export default {
name: 'TaxSettings',
data: () => ({
loading: false,
csrLoading: false,
showCSRDialog: false,
showResultDialog: false,
settings: {
taxMemoryId: '',
economicCode: '',
privateKey: '',
},
csrData: {
personType: 'legal',
nationalId: '',
nameFa: '',
nameEn: '',
email: '',
},
resultData: {
csr: '',
publicKey: '',
privateKey: ''
},
personTypes: [
{ title: 'حقیقی', value: 'natural' },
{ title: 'حقوقی', value: 'legal' }
],
snackbar: {
show: false,
text: '',
color: 'success'
}
}),
methods: {
async loadSettings() {
this.loading = true;
try {
const response = await axios.get('/api/plugins/tax-settings/get');
this.settings = {
...this.settings,
...response.data
};
} catch (error) {
this.showSnackbar('خطا در بارگذاری تنظیمات', 'error');
} finally {
this.loading = false;
}
},
async saveSettings() {
this.loading = true;
try {
const dataToSave = { ...this.settings };
await axios.post('/api/plugins/tax-settings/save', dataToSave);
this.showSnackbar('تنظیمات با موفقیت ذخیره شد', 'success');
} catch (error) {
this.showSnackbar('خطا در ذخیره تنظیمات', 'error');
} finally {
this.loading = false;
}
},
async generateCSR() {
this.csrLoading = true;
try {
const response = await axios.post('/api/plugins/tax-settings/generate-csr', this.csrData);
if (response.data.success) {
// this.settings.privateKey = response.data.privateKey;
// نمایش دیالوگ نتیجه
this.resultData.csr = response.data.csr;
this.resultData.privateKey = response.data.privateKey;
this.resultData.publicKey = response.data.publicKey || '';
this.showResultDialog = true;
this.showCSRDialog = false;
this.showSnackbar('کلید و CSR با موفقیت تولید شد', 'success');
} else {
this.showSnackbar(response.data.message || 'خطا در تولید کلید و CSR', 'error');
}
} catch (error) {
this.showSnackbar('خطا در تولید کلید و CSR', 'error');
} finally {
this.csrLoading = false;
}
},
copyToClipboard(text) {
navigator.clipboard.writeText(text);
this.showSnackbar('کپی شد');
},
downloadFile(content, filename) {
const blob = new Blob([content], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
},
showSnackbar(text, color = 'success') {
this.snackbar = {
show: true,
text,
color
};
}
},
mounted() {
this.loadSettings();
}
};
</script>

View file

@ -601,6 +601,32 @@
</v-card>
</v-col>
</v-row>
<v-row v-if="isPluginActive('taxsettings')" class="mt-4">
<v-col cols="12">
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه تنظیمات مالیاتی</v-card-title>
</v-col>
<v-col cols="12" md="4">
<v-card variant="outlined" class="h-100">
<v-card-text>
<v-list>
<v-list-item>
<v-switch
v-model="info.plugTaxSettings"
label="مدیریت تنظیمات مالیاتی"
@change="savePerms('plugTaxSettings')"
hide-details
color="success"
density="comfortable"
:loading="loadingSwitches.plugTaxSettings"
:disabled="loadingSwitches.plugTaxSettings"
></v-switch>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
@ -679,7 +705,8 @@ export default {
plugNoghreSell: false,
plugCCAdmin: false,
plugHrmDocs: false,
plugGhestaManager: false
plugGhestaManager: false,
plugTaxSettings: false
};
axios.post('/api/business/get/user/permissions',

View file

@ -0,0 +1,173 @@
<template>
<div>
<v-toolbar color="toolbar" title="صورتحساب‌های ارسالی به سامانه مودیان مالیاتی">
<template v-slot:prepend>
<v-tooltip text="بازگشت" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-btn :loading="loading" @click="loadData()" icon="" color="primary">
<v-tooltip activator="parent" text="بازخوانی" location="bottom" />
<v-icon icon="mdi-refresh"></v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-card :loading="loading" :disabled="loading">
<v-card-text>
<v-alert type="info" color="blue" class="mb-4" icon="mdi-information">
<span class="font-weight-bold">این بخش برای نمایش لیست صورتحسابهایی است که به سامانه مودیان مالیاتی ارسال شدهاند.</span>
</v-alert>
<v-data-table
:headers="headers"
:items="invoices"
:loading="loading"
class="elevation-1"
:items-per-page="10"
:items-per-page-options="[10, 25, 50, 100]"
>
<template v-slot:item.status="{ item }">
<v-chip
:color="getStatusColor(item.status)"
:text="getStatusText(item.status)"
size="small"
></v-chip>
</template>
<template v-slot:item.actions="{ item }">
<v-btn
icon="mdi-eye"
variant="text"
size="small"
@click="viewInvoice(item)"
color="primary"
></v-btn>
</template>
</v-data-table>
</v-card-text>
</v-card>
</v-container>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'TaxInvoicesList',
data: () => ({
loading: false,
invoices: [],
headers: [
{ title: 'شماره فاکتور', key: 'invoiceNumber', sortable: true },
{ title: 'تاریخ', key: 'date', sortable: true },
{ title: 'مشتری', key: 'customerName', sortable: true },
{ title: 'مبلغ کل', key: 'totalAmount', sortable: true },
{ title: 'وضعیت ارسال', key: 'status', sortable: true },
{ title: 'تاریخ ارسال', key: 'sentDate', sortable: true },
{ title: 'عملیات', key: 'actions', sortable: false }
],
snackbar: {
show: false,
text: '',
color: 'success'
}
}),
methods: {
async loadData() {
this.loading = true;
try {
// اینجا باید API مربوط به دریافت لیست صورتحسابهای ارسالی را فراخوانی کنید
// const response = await axios.get('/api/plugins/tax-settings/invoices');
// this.invoices = response.data;
// فعلاً دادههای نمونه
this.invoices = [
{
id: 1,
invoiceNumber: 'INV-001',
date: '1402/12/15',
customerName: 'شرکت نمونه',
totalAmount: '1,500,000',
status: 'sent',
sentDate: '1402/12/16'
},
{
id: 2,
invoiceNumber: 'INV-002',
date: '1402/12/14',
customerName: 'فروشگاه نمونه',
totalAmount: '2,300,000',
status: 'pending',
sentDate: '-'
}
];
} catch (error) {
this.showSnackbar('خطا در بارگذاری داده‌ها', 'error');
} finally {
this.loading = false;
}
},
getStatusColor(status) {
switch (status) {
case 'sent':
return 'success';
case 'pending':
return 'warning';
case 'failed':
return 'error';
default:
return 'grey';
}
},
getStatusText(status) {
switch (status) {
case 'sent':
return 'ارسال شده';
case 'pending':
return 'در انتظار';
case 'failed':
return 'ناموفق';
default:
return 'نامشخص';
}
},
viewInvoice(item) {
// اینجا میتوانید به صفحه جزئیات فاکتور بروید
this.showSnackbar('نمایش جزئیات فاکتور: ' + item.invoiceNumber);
},
showSnackbar(text, color = 'success') {
this.snackbar = {
show: true,
text,
color
};
}
},
mounted() {
this.loadData();
}
};
</script>

View file

@ -0,0 +1,321 @@
<template>
<div>
<v-toolbar color="toolbar" title="تنظیمات مالیاتی">
<template v-slot:prepend>
<v-tooltip text="بازگشت" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-btn :loading="loading" @click="saveSettings()" icon="" color="green">
<v-tooltip activator="parent" text="ذخیره تنظیمات" location="bottom" />
<v-icon icon="mdi-content-save"></v-icon>
</v-btn>
</v-toolbar>
<v-container>
<v-card :loading="loading" :disabled="loading">
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-btn
color="primary"
@click="showCSRDialog = true"
prepend-icon="mdi-key-plus"
>
ساخت کلید و CSR
</v-btn>
</v-col>
<v-col cols="12" md="6">
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="settings.taxMemoryId"
label="شناسه یکتای حافظه مالیاتی"
hide-details
density="compact"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="settings.economicCode"
label="کد اقتصادی"
hide-details
density="compact"
></v-text-field>
</v-col>
</v-row>
</v-col>
</v-row>
<v-row class="mt-4">
<v-col cols="12">
<v-textarea
v-model="settings.privateKey"
label="Private Key"
rows="15"
variant="outlined"
hide-details
placeholder="کلید خصوصی اینجا قرار می‌گیرد..."
></v-textarea>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
<!-- Dialog برای ساخت کلید و CSR -->
<v-dialog v-model="showCSRDialog" max-width="600px">
<v-card>
<v-card-title class="text-h6">
ساخت کلید و CSR
</v-card-title>
<v-card-text>
<v-form ref="csrForm">
<div class="mb-4">
<div class="text-subtitle-2 mb-2">شخص</div>
<v-radio-group
v-model="csrData.personType"
inline
hide-details
>
<v-radio
v-for="type in personTypes"
:key="type.value"
:label="type.title"
:value="type.value"
:disabled="type.value === 'natural'"
></v-radio>
</v-radio-group>
</div>
<v-text-field
v-model="csrData.nationalId"
label="شناسه ملی"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.nameFa"
label="نام (فارسی)"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.nameEn"
label="نام (انگلیسی)"
hide-details
class="mb-4"
></v-text-field>
<v-text-field
v-model="csrData.email"
label="ایمیل"
type="email"
hide-details
class="mb-4"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="showCSRDialog = false" variant="text">
انصراف
</v-btn>
<v-btn @click="generateCSR()" color="primary" :loading="csrLoading">
تایید
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showResultDialog" max-width="900px">
<v-card>
<v-card-title class="text-h6 pb-0">ساخت کلید و CSR</v-card-title>
<v-card-text>
<v-alert type="info" color="blue" class="mb-4" icon="mdi-alert">
<span class="font-weight-bold">توجه: لطفا این اطلاعات را دانلود کنید و در یک جای امن نگهداری کنید. به دلایل امنیتی اطلاعات شما را نگهداری نمیکنیم، در صورتی که این اطلاعات را گم کنید، امکان بازیابی آن وجود ندارد.</span>
</v-alert>
<v-row>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">CSR</div>
<v-textarea readonly rows="10" :value="resultData.csr" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.csr)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.csr, 'csr.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">Public Key</div>
<v-textarea readonly rows="10" :value="resultData.publicKey" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.publicKey)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.publicKey, 'public_key.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
<v-col cols="12" md="4">
<div class="mb-2 font-weight-bold">Private Key</div>
<v-textarea readonly rows="10" :value="resultData.privateKey" variant="outlined"></v-textarea>
<v-row class="mt-2">
<v-col cols="6">
<v-btn color="success" block @click="copyToClipboard(resultData.privateKey)"><v-icon start>mdi-content-copy</v-icon>کپی</v-btn>
</v-col>
<v-col cols="6">
<v-btn color="success" block @click="downloadFile(resultData.privateKey, 'private_key.txt')"><v-icon start>mdi-download</v-icon>دانلود</v-btn>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="showResultDialog = false" color="primary">بستن</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
{{ snackbar.text }}
<template v-slot:actions>
<v-btn
color="white"
variant="text"
@click="snackbar.show = false"
>
بستن
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script>
import axios from 'axios';
import Swal from 'sweetalert2';
export default {
name: 'TaxSettings',
data: () => ({
loading: false,
csrLoading: false,
showCSRDialog: false,
showResultDialog: false,
settings: {
taxMemoryId: '',
economicCode: '',
privateKey: '',
},
csrData: {
personType: 'legal',
nationalId: '',
nameFa: '',
nameEn: '',
email: '',
},
resultData: {
csr: '',
publicKey: '',
privateKey: ''
},
personTypes: [
{ title: 'حقیقی', value: 'natural' },
{ title: 'حقوقی', value: 'legal' }
],
snackbar: {
show: false,
text: '',
color: 'success'
}
}),
methods: {
async loadSettings() {
this.loading = true;
try {
const response = await axios.get('/api/plugins/tax-settings/get');
this.settings = {
...this.settings,
...response.data
};
} catch (error) {
this.showSnackbar('خطا در بارگذاری تنظیمات', 'error');
} finally {
this.loading = false;
}
},
async saveSettings() {
this.loading = true;
try {
const dataToSave = { ...this.settings };
await axios.post('/api/plugins/tax-settings/save', dataToSave);
this.showSnackbar('تنظیمات با موفقیت ذخیره شد', 'success');
} catch (error) {
this.showSnackbar('خطا در ذخیره تنظیمات', 'error');
} finally {
this.loading = false;
}
},
async generateCSR() {
this.csrLoading = true;
try {
const response = await axios.post('/api/plugins/tax-settings/generate-csr', this.csrData);
if (response.data.success) {
// this.settings.privateKey = response.data.privateKey;
// نمایش دیالوگ نتیجه
this.resultData.csr = response.data.csr;
this.resultData.privateKey = response.data.privateKey;
this.resultData.publicKey = response.data.publicKey || '';
this.showResultDialog = true;
this.showCSRDialog = false;
this.showSnackbar('کلید و CSR با موفقیت تولید شد', 'success');
} else {
this.showSnackbar(response.data.message || 'خطا در تولید کلید و CSR', 'error');
}
} catch (error) {
this.showSnackbar('خطا در تولید کلید و CSR', 'error');
} finally {
this.csrLoading = false;
}
},
copyToClipboard(text) {
navigator.clipboard.writeText(text);
this.showSnackbar('کپی شد');
},
downloadFile(content, filename) {
const blob = new Blob([content], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
},
showSnackbar(text, color = 'success') {
this.snackbar = {
show: true,
text,
color
};
}
},
mounted() {
this.loadSettings();
}
};
</script>

View file

@ -7,6 +7,12 @@ export default defineComponent({
name: "system",
data: () => {
return {
activeTab: 0,
tabs: [
{ title: 'تنظیمات پایه', icon: 'mdi-cog' },
{ title: 'درگاه‌های پرداخت', icon: 'mdi-credit-card' },
{ title: 'پنل استعلامات', icon: 'mdi-magnify' }
],
gatepays: [
{
title: 'زرین‌پال',
@ -50,6 +56,10 @@ export default defineComponent({
enablePostalCodeToAddress: false,
inquiryPanelEnable: false,
postalCodeToAddressFee: 0,
enableCardToSheba: false,
cardToShebaFee: 0,
enableAccountToSheba: false,
accountToShebaFee: 0,
},
loading: true,
}
@ -65,6 +75,8 @@ export default defineComponent({
...data,
enablePostalCodeToAddress: data.enablePostalCodeToAddress === '1' || data.enablePostalCodeToAddress === true,
inquiryPanelEnable: data.inquiryPanelEnable === '1' || data.inquiryPanelEnable === true,
enableCardToSheba: data.enableCardToSheba === '1' || data.enableCardToSheba === true,
enableAccountToSheba: data.enableAccountToSheba === '1' || data.enableAccountToSheba === true,
activeGateway: data.activeGateway || 'zarinpal',
inquiryPanel: data.inquiryPanel || 'zohal'
};
@ -85,6 +97,39 @@ export default defineComponent({
return;
}
// Validation: if postal code to address is enabled, fee must be set
if (this.systemInfo.enablePostalCodeToAddress && this.systemInfo.postalCodeToAddressFee < 0) {
Swal.fire({
text: 'کارمزد تبدیل کد پستی به آدرس نمی‌تواند منفی باشد.',
icon: 'error',
confirmButtonText: 'قبول',
});
this.loading = false;
return;
}
// Validation: if card to sheba is enabled, fee must be set
if (this.systemInfo.enableCardToSheba && this.systemInfo.cardToShebaFee < 0) {
Swal.fire({
text: 'کارمزد تبدیل شماره کارت به شبا نمی‌تواند منفی باشد.',
icon: 'error',
confirmButtonText: 'قبول',
});
this.loading = false;
return;
}
// Validation: if account to sheba is enabled, fee must be set
if (this.systemInfo.enableAccountToSheba && this.systemInfo.accountToShebaFee < 0) {
Swal.fire({
text: 'کارمزد تبدیل حساب به شبا نمی‌تواند منفی باشد.',
icon: 'error',
confirmButtonText: 'قبول',
});
this.loading = false;
return;
}
axios.post('/api/admin/settings/system/info/save', this.systemInfo).then((resp) => {
this.loading = false;
if (resp.data.result == 1) {
@ -94,6 +139,13 @@ export default defineComponent({
confirmButtonText: 'قبول',
});
}
}).catch((error) => {
this.loading = false;
Swal.fire({
text: 'خطا در ذخیره تنظیمات. لطفاً دوباره تلاش کنید.',
icon: 'error',
confirmButtonText: 'قبول',
});
})
}
@ -107,92 +159,491 @@ export default defineComponent({
<template>
<v-toolbar color="toolbar" :title="$t('pages.manager.system_settings_basic')">
<v-spacer></v-spacer>
<v-tooltip text="ذخیره تنظیمات" location="bottom">
<template v-slot:activator="{ props }">
<v-btn
v-bind="props"
icon
@click="submit()"
:loading="loading"
color="primary"
class="ml-2"
>
<v-icon>mdi-content-save</v-icon>
</v-btn>
</template>
</v-tooltip>
</v-toolbar>
<v-container class="pa-0">
<v-card :loading="loading" :disabled="loading">
<v-card-text class="">
<v-row class="mb-2">
<v-col cols="12" sm="12" md="12">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.app_site')"
v-model="systemInfo.appSite" type="text" prepend-inner-icon="mdi-card-text"
:rules="[() => systemInfo.appSite.length > 0 || $t('validator.required')]"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-select v-model="systemInfo.activeGateway" hide-details="auto" prepend-inner-icon="mdi-signal" :items="gatepays" item-title="title"
item-value="value" label="Select" single-line>
</v-select>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.zarinpal_api')"
v-model="systemInfo.zarinpal" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.parsian_api')"
v-model="systemInfo.parsianGatewayAPI" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.payping_api')"
v-model="systemInfo.paypingKey" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.bitpay_api')"
v-model="systemInfo.bitpayKey" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
</v-row>
<v-row class="mb-2">
<v-col cols="12" sm="12" md="12">
<v-subheader>{{ $t('pages.manager.inquiry_panel') }}</v-subheader>
</v-col>
<v-tabs v-model="activeTab" color="primary" grow>
<v-tab
v-for="(tab, index) in tabs"
:key="index"
:value="index"
class="text-none"
>
<v-icon start>{{ tab.icon }}</v-icon>
{{ tab.title }}
</v-tab>
</v-tabs>
<v-col cols="12" sm="12" md="6">
<v-select v-model="systemInfo.inquiryPanel" hide-details="auto" prepend-inner-icon="mdi-signal" :items="inquiryPanel" item-title="title"
item-value="value" label="Select" single-line>
</v-select>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.inquiry_zohal_api_key')"
v-model="systemInfo.inquiryZohalAPIKey" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-switch
v-model="systemInfo.inquiryPanelEnable"
:label="$t('pages.manager.inquiry_panel_enable')"
color="primary"
hide-details="auto"
inset
></v-switch>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-switch
v-model="systemInfo.enablePostalCodeToAddress"
:label="$t('pages.manager.enable_postalcode_to_address')"
color="primary"
hide-details="auto"
inset
></v-switch>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field
v-model.number="systemInfo.postalCodeToAddressFee"
:label="$t('pages.manager.postalcode_to_address_fee')"
type="number"
min="0"
prepend-inner-icon="mdi-currency-usd"
hide-details="auto"
suffix="ریال"
></v-text-field>
</v-col>
</v-row>
<v-row class="mb-2">
<v-col cols="12" sm="12" md="12">
<v-btn type="submit" @click="submit()" color="primary" prepend-icon="mdi-content-save" :loading="loading">
{{ $t('dialog.save') }}
</v-btn>
</v-col>
</v-row>
</v-card-text>
<v-window v-model="activeTab">
<!-- تب اول: تنظیمات پایه -->
<v-window-item :value="0">
<v-card-text class="pa-8">
<v-row class="mb-6">
<v-col cols="12">
<v-card variant="outlined" class="pa-6" elevation="0">
<v-card-text class="pa-0">
<v-row>
<v-col cols="12" sm="12" md="8" lg="6">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.app_site')"
v-model="systemInfo.appSite"
type="text"
prepend-inner-icon="mdi-link"
:rules="[() => systemInfo.appSite.length > 0 || $t('validator.required')]"
variant="outlined"
density="comfortable"
placeholder="https://example.com"
></v-text-field>
<div class="text-caption text-medium-emphasis mt-2 d-flex align-center">
<v-icon size="16" class="mr-1">mdi-information</v-icon>
آدرس اصلی سایت که در سیستم استفاده میشود
</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-window-item>
<!-- تب دوم: درگاههای پرداخت -->
<v-window-item :value="1">
<v-card-text class="pa-8">
<v-row class="mb-6">
<v-col cols="12">
<v-card variant="outlined" class="pa-6" elevation="0">
<v-card-text class="pa-0">
<v-row>
<v-col cols="12" sm="12" md="6" lg="4">
<v-select
v-model="systemInfo.activeGateway"
hide-details="auto"
prepend-inner-icon="mdi-check-circle"
:items="gatepays"
item-title="title"
item-value="value"
label="درگاه فعال"
single-line
variant="outlined"
density="comfortable"
></v-select>
<div class="text-caption text-medium-emphasis mt-2 d-flex align-center">
<v-icon size="16" class="mr-1">mdi-information</v-icon>
درگاه پرداخت پیشفرض سیستم
</div>
</v-col>
</v-row>
<v-divider class="my-6"></v-divider>
<div class="d-flex align-center mb-4">
<v-icon size="20" color="secondary" class="mr-2">mdi-key</v-icon>
<h4 class="text-subtitle-1 font-weight-medium">کلیدهای API</h4>
</div>
<v-row>
<v-col cols="12" sm="12" md="6" lg="4">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.zarinpal_api')"
v-model="systemInfo.zarinpal"
type="text"
prepend-inner-icon="mdi-shield-key"
variant="outlined"
density="comfortable"
placeholder="کلید API زرین‌پال"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" lg="4">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.parsian_api')"
v-model="systemInfo.parsianGatewayAPI"
type="text"
prepend-inner-icon="mdi-shield-key"
variant="outlined"
density="comfortable"
placeholder="کلید API پارسیان"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" lg="4">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.payping_api')"
v-model="systemInfo.paypingKey"
type="text"
prepend-inner-icon="mdi-shield-key"
variant="outlined"
density="comfortable"
placeholder="کلید API پی‌پینگ"
></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="6" lg="4">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.bitpay_api')"
v-model="systemInfo.bitpayKey"
type="text"
prepend-inner-icon="mdi-shield-key"
variant="outlined"
density="comfortable"
placeholder="کلید API بیت‌پی"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-window-item>
<!-- تب سوم: پنل استعلامات -->
<v-window-item :value="2">
<v-card-text class="pa-8">
<v-row class="mb-6">
<v-col cols="12">
<!-- تنظیمات اصلی پنل -->
<v-card variant="outlined" class="pa-6 mb-6" elevation="0">
<v-card-title class="text-subtitle-1 font-weight-medium pb-3 d-flex align-center">
<v-icon start class="mr-2" color="secondary">mdi-cog</v-icon>
تنظیمات اصلی
</v-card-title>
<v-card-text class="pa-0">
<v-row>
<v-col cols="12" sm="12" md="6">
<v-switch
v-model="systemInfo.inquiryPanelEnable"
:label="$t('pages.manager.inquiry_panel_enable')"
color="primary"
hide-details="auto"
inset
density="compact"
></v-switch>
<div class="text-caption text-medium-emphasis mt-1 d-flex align-center">
<v-icon size="16" class="mr-1">mdi-information</v-icon>
فعال/غیرفعال کردن پنل استعلامات
</div>
</v-col>
<v-col cols="12" sm="12" md="6">
<v-select
v-model="systemInfo.inquiryPanel"
hide-details="auto"
prepend-inner-icon="mdi-view-dashboard"
:items="inquiryPanel"
item-title="title"
item-value="value"
label="انتخاب پنل"
single-line
variant="outlined"
density="comfortable"
></v-select>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-text-field
class=""
hide-details="auto"
:label="$t('pages.manager.inquiry_zohal_api_key')"
v-model="systemInfo.inquiryZohalAPIKey"
type="text"
prepend-inner-icon="mdi-key"
variant="outlined"
density="comfortable"
placeholder="کلید API پنل زحل"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- سرویسهای تبدیل -->
<div class="d-flex align-center mb-6">
<div class="d-flex align-center bg-primary-lighten-5 pa-3 rounded-lg">
<v-icon size="28" color="primary" class="mr-3">mdi-sync</v-icon>
<div>
<h4 class="text-h6 font-weight-medium text-primary mb-1">سرویسهای تبدیل</h4>
<p class="text-caption text-medium-emphasis mb-0">مدیریت قابلیتهای تبدیل اطلاعات</p>
</div>
</div>
</div>
<v-row class="mb-4">
<!-- تبدیل کد پستی به آدرس -->
<v-col cols="12" sm="12" md="4">
<v-card
variant="outlined"
class="service-card h-100"
elevation="0"
:class="{ 'service-card-active': systemInfo.enablePostalCodeToAddress }"
>
<div class="service-card-header bg-success-lighten-5 pa-4">
<div class="d-flex align-center justify-space-between mb-3">
<div class="d-flex align-center">
<v-icon size="32" color="success" class="mr-3">mdi-map-marker</v-icon>
<div>
<h5 class="text-subtitle-1 font-weight-medium text-success mb-1">تبدیل کد پستی به آدرس</h5>
<p class="text-caption text-medium-emphasis mb-0">تبدیل خودکار کد پستی به آدرس کامل</p>
</div>
</div>
<v-chip
:color="systemInfo.enablePostalCodeToAddress ? 'success' : 'grey'"
size="small"
variant="flat"
>
{{ systemInfo.enablePostalCodeToAddress ? 'فعال' : 'غیرفعال' }}
</v-chip>
</div>
</div>
<v-card-text class="pa-4">
<v-row>
<v-col cols="12" class="mb-3">
<v-switch
v-model="systemInfo.enablePostalCodeToAddress"
:label="$t('pages.manager.enable_postalcode_to_address')"
color="success"
hide-details="auto"
inset
density="comfortable"
></v-switch>
</v-col>
<v-col cols="12">
<v-text-field
v-model.number="systemInfo.postalCodeToAddressFee"
:label="$t('pages.manager.postalcode_to_address_fee')"
type="number"
min="0"
prepend-inner-icon="mdi-currency-usd"
hide-details="auto"
suffix="ریال"
density="comfortable"
variant="outlined"
:disabled="!systemInfo.enablePostalCodeToAddress"
:rules="[v => v >= 0 || 'کارمزد نمی‌تواند منفی باشد']"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<!-- تبدیل شماره کارت به شبا -->
<v-col cols="12" sm="12" md="4">
<v-card
variant="outlined"
class="service-card h-100"
elevation="0"
:class="{ 'service-card-active': systemInfo.enableCardToSheba }"
>
<div class="service-card-header bg-info-lighten-5 pa-4">
<div class="d-flex align-center justify-space-between mb-3">
<div class="d-flex align-center">
<v-icon size="32" color="info" class="mr-3">mdi-credit-card</v-icon>
<div>
<h5 class="text-subtitle-1 font-weight-medium text-info mb-1">تبدیل شماره کارت به شبا</h5>
<p class="text-caption text-medium-emphasis mb-0">تبدیل شماره کارت بانکی به شماره شبا</p>
</div>
</div>
<v-chip
:color="systemInfo.enableCardToSheba ? 'info' : 'grey'"
size="small"
variant="flat"
>
{{ systemInfo.enableCardToSheba ? 'فعال' : 'غیرفعال' }}
</v-chip>
</div>
</div>
<v-card-text class="pa-4">
<v-row>
<v-col cols="12" class="mb-3">
<v-switch
v-model="systemInfo.enableCardToSheba"
:label="$t('pages.manager.enable_card_to_sheba')"
color="info"
hide-details="auto"
inset
density="comfortable"
></v-switch>
</v-col>
<v-col cols="12">
<v-text-field
v-model.number="systemInfo.cardToShebaFee"
:label="$t('pages.manager.card_to_sheba_fee')"
type="number"
min="0"
prepend-inner-icon="mdi-currency-usd"
hide-details="auto"
suffix="ریال"
density="comfortable"
variant="outlined"
:disabled="!systemInfo.enableCardToSheba"
:rules="[v => v >= 0 || 'کارمزد نمی‌تواند منفی باشد']"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<!-- تبدیل حساب به شبا -->
<v-col cols="12" sm="12" md="4">
<v-card
variant="outlined"
class="service-card h-100"
elevation="0"
:class="{ 'service-card-active': systemInfo.enableAccountToSheba }"
>
<div class="service-card-header bg-warning-lighten-5 pa-4">
<div class="d-flex align-center justify-space-between mb-3">
<div class="d-flex align-center">
<v-icon size="32" color="warning" class="mr-3">mdi-bank</v-icon>
<div>
<h5 class="text-subtitle-1 font-weight-medium text-warning mb-1">تبدیل حساب به شبا</h5>
<p class="text-caption text-medium-emphasis mb-0">تبدیل شماره حساب بانکی به شماره شبا</p>
</div>
</div>
<v-chip
:color="systemInfo.enableAccountToSheba ? 'warning' : 'grey'"
size="small"
variant="flat"
>
{{ systemInfo.enableAccountToSheba ? 'فعال' : 'غیرفعال' }}
</v-chip>
</div>
</div>
<v-card-text class="pa-4">
<v-row>
<v-col cols="12" class="mb-3">
<v-switch
v-model="systemInfo.enableAccountToSheba"
:label="$t('pages.manager.enable_account_to_sheba')"
color="warning"
hide-details="auto"
inset
density="comfortable"
></v-switch>
</v-col>
<v-col cols="12">
<v-text-field
v-model.number="systemInfo.accountToShebaFee"
:label="$t('pages.manager.account_to_sheba_fee')"
type="number"
min="0"
prepend-inner-icon="mdi-currency-usd"
hide-details="auto"
suffix="ریال"
density="comfortable"
variant="outlined"
:disabled="!systemInfo.enableAccountToSheba"
:rules="[v => v >= 0 || 'کارمزد نمی‌تواند منفی باشد']"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-window-item>
</v-window>
</v-card>
</v-container>
</template>
<style scoped></style>
<style scoped>
.service-card {
transition: all 0.3s ease;
border: 2px solid transparent;
position: relative;
overflow: hidden;
}
.service-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1) !important;
}
.service-card-active {
border-color: var(--v-primary-base);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
}
.service-card-active::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--v-primary-base), var(--v-secondary-base));
}
.service-card-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
position: relative;
}
.service-card-header::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.1), transparent);
}
/* انیمیشن برای chip ها */
.v-chip {
transition: all 0.3s ease;
}
.v-chip:hover {
transform: scale(1.05);
}
/* استایل برای فیلدهای غیرفعال */
.v-text-field--disabled {
opacity: 0.6;
}
/* بهبود ظاهر switch */
.v-switch {
margin-bottom: 8px;
}
/* استایل برای آیکون‌ها */
.service-card .v-icon {
transition: transform 0.3s ease;
}
.service-card:hover .v-icon {
transform: scale(1.1);
}
</style>