From ddbbf1f102281e1ec6366afc483c989626d037fb Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Fri, 11 Jul 2025 06:56:42 +0000 Subject: [PATCH 01/22] merge upper in commodity cat with pos app --- .../src/Controller/CommodityController.php | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/hesabixCore/src/Controller/CommodityController.php b/hesabixCore/src/Controller/CommodityController.php index e2c2d4c..f7ba64e 100644 --- a/hesabixCore/src/Controller/CommodityController.php +++ b/hesabixCore/src/Controller/CommodityController.php @@ -143,7 +143,8 @@ class CommodityController extends AbstractController $count += $row->getCommdityCount(); } else { $count -= $row->getCommdityCount(); - } } + } + } $temp['count'] = $count; } return $temp; @@ -1071,17 +1072,41 @@ class CommodityController extends AbstractController if ($content = $request->getContent()) { $params = json_decode($content, true); } - if (!array_key_exists('upper', $params) || !array_key_exists('text', $params)) + + if (!array_key_exists('text', $params)) return $this->json(['result' => -1]); - + if ($this->isDefaultCategoryName($params['text'])) { return $this->json([ - 'result' => 4, + 'result' => 4, 'message' => 'این نام برای دسته‌بندی مجاز نیست', 'errorCode' => 'DEFAULT_CATEGORY_NAME' ]); } - + if (!array_key_exists('upper', $params)) { + $upper = $entityManager->getRepository(CommodityCat::class)->findOneBy([ + 'upper' => null, + 'bid' => $acc['bid'] + ]); + if (!$upper) { + $upper = new CommodityCat(); + $upper->setBid($acc['bid']); + $upper->setUpper(null); + $upper->setName('دسته بندی ها'); + $upper->setRoot(true); + $entityManager->persist($upper); + $entityManager->flush(); + } + $cat = new CommodityCat(); + $cat->setBid($acc['bid']); + $cat->setRoot(false); + $cat->setName($params['text']); + $cat->setUpper($upper->getId()); + $entityManager->persist($cat); + $entityManager->flush(); + return $this->json(['result' => 1, 'id' => $cat->getId()]); + } + $upper = $entityManager->getRepository(CommodityCat::class)->find($params['upper']); if ($upper) { if ($upper->getBid() == $acc['bid']) { @@ -1109,22 +1134,22 @@ class CommodityController extends AbstractController } if (!array_key_exists('id', $params) || !array_key_exists('text', $params)) return $this->json(['result' => -1]); - + if ($this->isDefaultCategoryName($params['text'])) { return $this->json([ - 'result' => 4, + 'result' => 4, 'message' => 'این نام برای دسته‌بندی مجاز نیست', 'errorCode' => 'DEFAULT_CATEGORY_NAME' ]); } - + $node = $entityManager->getRepository(CommodityCat::class)->find($params['id']); if ($node) { if ($node->getBid() == $acc['bid']) { // بررسی دسته‌بندی پیش‌فرض if ($this->isDefaultCategoryName($node->getName())) { return $this->json([ - 'result' => 4, + 'result' => 4, 'message' => 'ویرایش دسته‌بندی پیش‌فرض مجاز نیست', 'errorCode' => 'DEFAULT_CATEGORY_EDIT' ]); @@ -1588,7 +1613,7 @@ class CommodityController extends AbstractController // بررسی دسته‌بندی پیش‌فرض if ($this->isDefaultCategoryName($category->getName())) { return $this->json([ - 'Success' => false, + 'Success' => false, 'message' => 'حذف دسته‌بندی پیش‌فرض مجاز نیست', 'errorCode' => 'DEFAULT_CATEGORY_DELETE' ], 400); From 82740193cb97881b5d05fcfb259aaab50f827c24 Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Fri, 11 Jul 2025 10:25:25 +0000 Subject: [PATCH 02/22] bug fix in wallet deposit --- hesabixCore/src/Controller/AdminController.php | 1 + webUI/src/views/user/manager/wallet/list.vue | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hesabixCore/src/Controller/AdminController.php b/hesabixCore/src/Controller/AdminController.php index 705f21b..2306469 100644 --- a/hesabixCore/src/Controller/AdminController.php +++ b/hesabixCore/src/Controller/AdminController.php @@ -592,6 +592,7 @@ class AdminController extends AbstractController $temp['cardPan'] = $item->getCardPan(); $temp['refID'] = $item->getRefID(); $temp['shaba'] = $item->getShaba(); + $temp['amount'] = $item->getAmount(); $temp['dateSubmit'] = $jdate->jdate('Y/n/d H:i', $item->getDateSubmit()); $temp['gatePay'] = $item->getGatePay(); $resp[] = $temp; diff --git a/webUI/src/views/user/manager/wallet/list.vue b/webUI/src/views/user/manager/wallet/list.vue index bc5f063..8b6a2e6 100644 --- a/webUI/src/views/user/manager/wallet/list.vue +++ b/webUI/src/views/user/manager/wallet/list.vue @@ -42,7 +42,7 @@ {{ item.totalIncome }} {{ calculateStatus(item) }} - + diff --git a/webUI/src/views/user/manager/settings/system.vue b/webUI/src/views/user/manager/settings/system.vue index f5bca66..9b9bd29 100644 --- a/webUI/src/views/user/manager/settings/system.vue +++ b/webUI/src/views/user/manager/settings/system.vue @@ -49,6 +49,7 @@ export default defineComponent({ inquiryZohalAPIKey: '', enablePostalCodeToAddress: false, inquiryPanelEnable: false, + postalCodeToAddressFee: 0, }, loading: true, } @@ -152,7 +153,7 @@ export default defineComponent({ - + - + + + From 227767b0d674f444d8c24f0c85a7ab3a94b072da Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Wed, 16 Jul 2025 15:11:53 +0000 Subject: [PATCH 08/22] progress in inquery panel --- .../src/Controller/AdminController.php | 8 + .../src/Controller/BusinessController.php | 3 + .../Plugins/TaxSettingsController.php | 201 ++++++ .../inquiry/PlugInquiryMainController.php | 2 - hesabixCore/src/Entity/Permission.php | 15 + .../src/Entity/PluginTaxsettingsKey.php | 53 ++ webUI/src/i18n/en_lang.ts | 1 + webUI/src/i18n/fa_lang.ts | 10 +- webUI/src/router/index.ts | 18 + webUI/src/views/acc/App.vue | 31 +- webUI/src/views/acc/inquiry/panel.vue | 24 + webUI/src/views/acc/router/index.js | 6 + webUI/src/views/acc/settings/tax-settings.vue | 321 +++++++++ .../src/views/acc/settings/user_perm_edit.vue | 29 +- webUI/src/views/acc/tax/invoices/list.vue | 173 +++++ webUI/src/views/acc/tax/tax-settings.vue | 321 +++++++++ .../views/user/manager/settings/system.vue | 613 +++++++++++++++--- 17 files changed, 1742 insertions(+), 87 deletions(-) create mode 100644 hesabixCore/src/Controller/Plugins/TaxSettingsController.php create mode 100644 hesabixCore/src/Entity/PluginTaxsettingsKey.php create mode 100644 webUI/src/views/acc/inquiry/panel.vue create mode 100644 webUI/src/views/acc/settings/tax-settings.vue create mode 100644 webUI/src/views/acc/tax/invoices/list.vue create mode 100644 webUI/src/views/acc/tax/tax-settings.vue diff --git a/hesabixCore/src/Controller/AdminController.php b/hesabixCore/src/Controller/AdminController.php index 230dab4..26414b7 100644 --- a/hesabixCore/src/Controller/AdminController.php +++ b/hesabixCore/src/Controller/AdminController.php @@ -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]); diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php index b04d04d..400aebb 100644 --- a/hesabixCore/src/Controller/BusinessController.php +++ b/hesabixCore/src/Controller/BusinessController.php @@ -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); diff --git a/hesabixCore/src/Controller/Plugins/TaxSettingsController.php b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php new file mode 100644 index 0000000..9e0d2e4 --- /dev/null +++ b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php @@ -0,0 +1,201 @@ +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; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php b/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php index 5d915a6..575f9c0 100644 --- a/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php +++ b/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php @@ -128,6 +128,4 @@ class PlugInquiryMainController extends AbstractController ]); } } - - } \ No newline at end of file diff --git a/hesabixCore/src/Entity/Permission.php b/hesabixCore/src/Entity/Permission.php index e563135..47aedd1 100644 --- a/hesabixCore/src/Entity/Permission.php +++ b/hesabixCore/src/Entity/Permission.php @@ -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; + } + } diff --git a/hesabixCore/src/Entity/PluginTaxsettingsKey.php b/hesabixCore/src/Entity/PluginTaxsettingsKey.php new file mode 100644 index 0000000..5a2e67c --- /dev/null +++ b/hesabixCore/src/Entity/PluginTaxsettingsKey.php @@ -0,0 +1,53 @@ +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; } +} \ No newline at end of file diff --git a/webUI/src/i18n/en_lang.ts b/webUI/src/i18n/en_lang.ts index e0613b5..e087b45 100644 --- a/webUI/src/i18n/en_lang.ts +++ b/webUI/src/i18n/en_lang.ts @@ -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', }, diff --git a/webUI/src/i18n/fa_lang.ts b/webUI/src/i18n/fa_lang.ts index 07db454..6981662 100644 --- a/webUI/src/i18n/fa_lang.ts +++ b/webUI/src/i18n/fa_lang.ts @@ -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: "زحل", diff --git a/webUI/src/router/index.ts b/webUI/src/router/index.ts index 9c3d0b3..49519b0 100644 --- a/webUI/src/router/index.ts +++ b/webUI/src/router/index.ts @@ -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'), + }, ], }, { diff --git a/webUI/src/views/acc/App.vue b/webUI/src/views/acc/App.vue index 452c610..e98424f 100644 --- a/webUI/src/views/acc/App.vue +++ b/webUI/src/views/acc/App.vue @@ -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 { {{ $t('drawer.user_perms') }} {{ getShortcutKey('/acc/business/users') }} - + @@ -788,6 +791,13 @@ export default { + + + + {{ $t('drawer.inquiry') }} + {{ getShortcutKey('/acc/inquiry/panel') }} + + + + + + + {{ $t('drawer.tax_invoices') }} + + + + + {{ $t('drawer.tax_settings') }} + + + diff --git a/webUI/src/views/acc/inquiry/panel.vue b/webUI/src/views/acc/inquiry/panel.vue new file mode 100644 index 0000000..30cc2cc --- /dev/null +++ b/webUI/src/views/acc/inquiry/panel.vue @@ -0,0 +1,24 @@ + + + + + + diff --git a/webUI/src/views/acc/router/index.js b/webUI/src/views/acc/router/index.js index dea1a69..43cd189 100644 --- a/webUI/src/views/acc/router/index.js +++ b/webUI/src/views/acc/router/index.js @@ -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', diff --git a/webUI/src/views/acc/settings/tax-settings.vue b/webUI/src/views/acc/settings/tax-settings.vue new file mode 100644 index 0000000..5be06e2 --- /dev/null +++ b/webUI/src/views/acc/settings/tax-settings.vue @@ -0,0 +1,321 @@ + + + \ No newline at end of file diff --git a/webUI/src/views/acc/settings/user_perm_edit.vue b/webUI/src/views/acc/settings/user_perm_edit.vue index fda5de7..f54e41c 100644 --- a/webUI/src/views/acc/settings/user_perm_edit.vue +++ b/webUI/src/views/acc/settings/user_perm_edit.vue @@ -601,6 +601,32 @@ + + + + افزونه تنظیمات مالیاتی + + + + + + + + + + + + + @@ -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', diff --git a/webUI/src/views/acc/tax/invoices/list.vue b/webUI/src/views/acc/tax/invoices/list.vue new file mode 100644 index 0000000..dc2d0da --- /dev/null +++ b/webUI/src/views/acc/tax/invoices/list.vue @@ -0,0 +1,173 @@ + + + \ No newline at end of file diff --git a/webUI/src/views/acc/tax/tax-settings.vue b/webUI/src/views/acc/tax/tax-settings.vue new file mode 100644 index 0000000..eda7804 --- /dev/null +++ b/webUI/src/views/acc/tax/tax-settings.vue @@ -0,0 +1,321 @@ + + + \ No newline at end of file diff --git a/webUI/src/views/user/manager/settings/system.vue b/webUI/src/views/user/manager/settings/system.vue index 9b9bd29..b8e2ac6 100644 --- a/webUI/src/views/user/manager/settings/system.vue +++ b/webUI/src/views/user/manager/settings/system.vue @@ -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({ - \ No newline at end of file + \ No newline at end of file From 2a8ea8cb4ab098001e805a0bd98bf9c6d7f50ff8 Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Thu, 17 Jul 2025 09:07:31 +0000 Subject: [PATCH 09/22] almost finish inquiry panel --- .../src/Controller/BusinessController.php | 3 + .../inquiry/PlugInquiryMainController.php | 202 ++- .../src/Entity/AccountToShebaInquiry.php | 63 + hesabixCore/src/Entity/CardToShebaInquiry.php | 63 + hesabixCore/src/Entity/Permission.php | 14 + .../AccountToShebaInquiryRepository.php | 46 + .../CardToShebaInquiryRepository.php | 46 + hesabixCore/src/Service/Inquiry.php | 290 +++++ webUI/src/views/acc/App.vue | 4 +- webUI/src/views/acc/inquiry/README.md | 64 + webUI/src/views/acc/inquiry/panel.vue | 1156 ++++++++++++++++- .../src/views/acc/settings/user_perm_edit.vue | 17 +- 12 files changed, 1954 insertions(+), 14 deletions(-) create mode 100644 hesabixCore/src/Entity/AccountToShebaInquiry.php create mode 100644 hesabixCore/src/Entity/CardToShebaInquiry.php create mode 100644 hesabixCore/src/Repository/AccountToShebaInquiryRepository.php create mode 100644 hesabixCore/src/Repository/CardToShebaInquiryRepository.php create mode 100644 webUI/src/views/acc/inquiry/README.md diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php index 400aebb..3a31682 100644 --- a/hesabixCore/src/Controller/BusinessController.php +++ b/hesabixCore/src/Controller/BusinessController.php @@ -545,6 +545,7 @@ class BusinessController extends AbstractController 'plugHrmDocs' => true, 'plugGhestaManager' => true, 'plugTaxSettings' => true, + 'inquiry' => true, ]; } elseif ($perm) { $result = [ @@ -589,6 +590,7 @@ class BusinessController extends AbstractController 'plugHrmDocs' => $perm->isPlugHrmDocs(), 'plugGhestaManager' => $perm->isPlugGhestaManager(), 'plugTaxSettings' => $perm->isPlugTaxSettings(), + 'inquiry' => $perm->isInquiry(), ]; } return $this->json($result); @@ -659,6 +661,7 @@ class BusinessController extends AbstractController $perm->setPlugHrmDocs($params['plugHrmDocs']); $perm->setPlugGhestaManager($params['plugGhestaManager']); $perm->setPlugTaxSettings($params['plugTaxSettings']); + $perm->setInquiry($params['inquiry']); $entityManager->persist($perm); $entityManager->flush(); $log->insert('تنظیمات پایه', 'ویرایش دسترسی‌های کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business); diff --git a/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php b/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php index 575f9c0..2e6ecce 100644 --- a/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php +++ b/hesabixCore/src/Controller/Plugins/inquiry/PlugInquiryMainController.php @@ -32,19 +32,21 @@ class PlugInquiryMainController extends AbstractController #[Route('/api/plugins/inquiry/settings/get', name: 'plugin_inquiry_settings_get', methods: ['GET'])] public function plugin_inquiry_settings_get(registryMGR $registryMGR): JsonResponse { - $resp['inquiryPanel'] = $registryMGR->get('system', key: 'inquiryPanel'); $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); - } #[Route('/api/plugins/inquiry/postalcode-to-address', name: 'plugin_inquiry_postalcode_to_address', methods: ['POST'])] public function plugin_inquiry_postalcode_to_address(Inquiry $inquiry, Access $access, Request $request, registryMGR $registryMGR, Log $log): JsonResponse { - $acc = $access->hasRole('join'); + $acc = $access->hasRole('inquiry'); if (!$acc) { return $this->json([ 'success' => false, @@ -128,4 +130,198 @@ class PlugInquiryMainController extends AbstractController ]); } } + + #[Route('/api/plugins/inquiry/card-to-sheba', name: 'plugin_inquiry_card_to_sheba', methods: ['POST'])] + public function plugin_inquiry_card_to_sheba(Inquiry $inquiry, Access $access, Request $request, registryMGR $registryMGR, Log $log): JsonResponse + { + $acc = $access->hasRole('inquiry'); + if (!$acc) { + return $this->json([ + 'success' => false, + 'message' => 'شما دسترسی به این سرویس را ندارید' + ]); + } + + // بررسی فعال بودن سرویس + if (!$registryMGR->get('system', key: 'enableCardToSheba')) { + return $this->json([ + 'success' => false, + 'message' => 'این سرویس در حال حاضر غیرفعال است' + ]); + } + + // دریافت شماره کارت از درخواست + $data = json_decode($request->getContent(), true); + $cardNumber = $data['card_number'] ?? null; + if (!$cardNumber) { + return $this->json([ + 'success' => false, + 'message' => 'شماره کارت ارسال نشده است' + ]); + } + + // فراخوانی سرویس استعلام کارت به شبا + $result = $inquiry->cardToSheba($cardNumber); + + // بررسی نتیجه و بازگرداندن پاسخ مناسب + if (isset($result['result']) && $result['result'] == 1) { + $isFromCache = isset($result['response_body']['message']) && + strpos($result['response_body']['message'], 'از حافظه موقت') !== false; + + // ثبت لاگ بر اساس منبع داده + $logMessage = $isFromCache + ? "استعلام کارت به شبا {$cardNumber} از حافظه موقت (بدون کسر کارمزد)" + : "استعلام کارت به شبا {$cardNumber} از API (کسر کارمزد: " . $registryMGR->get('system', key: 'cardToShebaFee') . " ریال)"; + + $log->insert( + 'استعلام', + $logMessage, + $acc['user'], + $acc['bid'] + ); + + // فقط در صورت عدم وجود در کش، کارمزد کسر شود + if (!$isFromCache) { + if ($acc['bid']->getSmsCharge() < $registryMGR->get('system', key: 'cardToShebaFee')) { + // ثبت لاگ عدم موجودی کافی + $log->insert( + 'استعلام', + "عدم موجودی کافی برای استعلام کارت به شبا {$cardNumber}", + $acc['user'], + $acc['bid'] + ); + + return $this->json([ + 'success' => false, + 'message' => 'موجودی شما برای این سرویس کافی نیست' + ]); + } + + $business = $acc['bid']; + $business->setSmsCharge($business->getSmsCharge() - $registryMGR->get('system', key: 'cardToShebaFee')); + $this->entityManager->persist($business); + $this->entityManager->flush(); + } + + return $this->json([ + 'success' => true, + 'data' => $result['response_body']['data'] ?? null, + 'message' => $result['response_body']['message'] ?? 'موفق', + 'from_cache' => $isFromCache + ]); + } else { + // ثبت لاگ خطا + $errorMessage = $result['message'] ?? 'خطا در استعلام کارت به شبا'; + $log->insert( + 'استعلام', + "خطا در استعلام کارت به شبا {$cardNumber}: {$errorMessage}", + $acc['user'], + $acc['bid'] + ); + + return $this->json([ + 'success' => false, + 'message' => $errorMessage, + 'error_code' => $result['error_code'] ?? null + ]); + } + } + + #[Route('/api/plugins/inquiry/account-to-sheba', name: 'plugin_inquiry_account_to_sheba', methods: ['POST'])] + public function plugin_inquiry_account_to_sheba(Inquiry $inquiry, Access $access, Request $request, registryMGR $registryMGR, Log $log): JsonResponse + { + $acc = $access->hasRole('inquiry'); + if (!$acc) { + return $this->json([ + 'success' => false, + 'message' => 'شما دسترسی به این سرویس را ندارید' + ]); + } + + // بررسی فعال بودن سرویس + if (!$registryMGR->get('system', key: 'enableAccountToSheba')) { + return $this->json([ + 'success' => false, + 'message' => 'این سرویس در حال حاضر غیرفعال است' + ]); + } + + // دریافت داده‌ها از درخواست + $data = json_decode($request->getContent(), true); + $bankCode = $data['bank_code'] ?? null; + $accountNumber = $data['account_number'] ?? null; + + if (!$bankCode || !$accountNumber) { + return $this->json([ + 'success' => false, + 'message' => 'کد بانک و شماره حساب الزامی است' + ]); + } + + // فراخوانی سرویس استعلام حساب به شبا + $result = $inquiry->accountToSheba($bankCode, $accountNumber); + + // بررسی نتیجه و بازگرداندن پاسخ مناسب + if (isset($result['result']) && $result['result'] == 1) { + $isFromCache = isset($result['response_body']['message']) && + strpos($result['response_body']['message'], 'از حافظه موقت') !== false; + + // ثبت لاگ بر اساس منبع داده + $logMessage = $isFromCache + ? "استعلام حساب به شبا {$bankCode}/{$accountNumber} از حافظه موقت (بدون کسر کارمزد)" + : "استعلام حساب به شبا {$bankCode}/{$accountNumber} از API (کسر کارمزد: " . $registryMGR->get('system', key: 'accountToShebaFee') . " ریال)"; + + $log->insert( + 'استعلام', + $logMessage, + $acc['user'], + $acc['bid'] + ); + + // فقط در صورت عدم وجود در کش، کارمزد کسر شود + if (!$isFromCache) { + if ($acc['bid']->getSmsCharge() < $registryMGR->get('system', key: 'accountToShebaFee')) { + // ثبت لاگ عدم موجودی کافی + $log->insert( + 'استعلام', + "عدم موجودی کافی برای استعلام حساب به شبا {$bankCode}/{$accountNumber}", + $acc['user'], + $acc['bid'] + ); + + return $this->json([ + 'success' => false, + 'message' => 'موجودی شما برای این سرویس کافی نیست' + ]); + } + + $business = $acc['bid']; + $business->setSmsCharge($business->getSmsCharge() - $registryMGR->get('system', key: 'accountToShebaFee')); + $this->entityManager->persist($business); + $this->entityManager->flush(); + } + + return $this->json([ + 'success' => true, + 'data' => $result['response_body']['data'] ?? null, + 'message' => $result['response_body']['message'] ?? 'موفق', + 'from_cache' => $isFromCache + ]); + } else { + // ثبت لاگ خطا + $errorMessage = $result['message'] ?? 'خطا در استعلام حساب به شبا'; + $log->insert( + 'استعلام', + "خطا در استعلام حساب به شبا {$bankCode}/{$accountNumber}: {$errorMessage}", + $acc['user'], + $acc['bid'] + ); + + return $this->json([ + 'success' => false, + 'message' => $errorMessage, + 'error_code' => $result['error_code'] ?? null + ]); + } + } } \ No newline at end of file diff --git a/hesabixCore/src/Entity/AccountToShebaInquiry.php b/hesabixCore/src/Entity/AccountToShebaInquiry.php new file mode 100644 index 0000000..1e9667a --- /dev/null +++ b/hesabixCore/src/Entity/AccountToShebaInquiry.php @@ -0,0 +1,63 @@ +id; + } + + public function getCacheKey(): ?string + { + return $this->cacheKey; + } + + public function setCacheKey(string $cacheKey): static + { + $this->cacheKey = $cacheKey; + return $this; + } + + public function getShebaData(): array + { + return $this->shebaData; + } + + public function setShebaData(array $shebaData): static + { + $this->shebaData = $shebaData; + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + return $this; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Entity/CardToShebaInquiry.php b/hesabixCore/src/Entity/CardToShebaInquiry.php new file mode 100644 index 0000000..6c27140 --- /dev/null +++ b/hesabixCore/src/Entity/CardToShebaInquiry.php @@ -0,0 +1,63 @@ +id; + } + + public function getCardNumber(): ?string + { + return $this->cardNumber; + } + + public function setCardNumber(string $cardNumber): static + { + $this->cardNumber = $cardNumber; + return $this; + } + + public function getShebaData(): array + { + return $this->shebaData; + } + + public function setShebaData(array $shebaData): static + { + $this->shebaData = $shebaData; + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + return $this; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Entity/Permission.php b/hesabixCore/src/Entity/Permission.php index 47aedd1..1d0b414 100644 --- a/hesabixCore/src/Entity/Permission.php +++ b/hesabixCore/src/Entity/Permission.php @@ -132,6 +132,9 @@ class Permission #[ORM\Column(nullable: true)] private ?bool $plugTaxSettings = null; + #[ORM\Column(nullable: true)] + private ?bool $inquiry = null; + public function getId(): ?int { return $this->id; @@ -605,4 +608,15 @@ class Permission return $this; } + public function isInquiry(): ?bool + { + return $this->inquiry; + } + + public function setInquiry(?bool $inquiry): static + { + $this->inquiry = $inquiry; + + return $this; + } } diff --git a/hesabixCore/src/Repository/AccountToShebaInquiryRepository.php b/hesabixCore/src/Repository/AccountToShebaInquiryRepository.php new file mode 100644 index 0000000..45280bf --- /dev/null +++ b/hesabixCore/src/Repository/AccountToShebaInquiryRepository.php @@ -0,0 +1,46 @@ + + * + * @method AccountToShebaInquiry|null find($id, $lockMode = null, $lockVersion = null) + * @method AccountToShebaInquiry|null findOneBy(array $criteria, array $orderBy = null) + * @method AccountToShebaInquiry[] findAll() + * @method AccountToShebaInquiry[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class AccountToShebaInquiryRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, AccountToShebaInquiry::class); + } + + public function findByCacheKey(string $cacheKey): ?AccountToShebaInquiry + { + return $this->findOneBy(['cacheKey' => $cacheKey]); + } + + public function save(AccountToShebaInquiry $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(AccountToShebaInquiry $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } +} \ No newline at end of file diff --git a/hesabixCore/src/Repository/CardToShebaInquiryRepository.php b/hesabixCore/src/Repository/CardToShebaInquiryRepository.php new file mode 100644 index 0000000..6a2dd38 --- /dev/null +++ b/hesabixCore/src/Repository/CardToShebaInquiryRepository.php @@ -0,0 +1,46 @@ + + * + * @method CardToShebaInquiry|null find($id, $lockMode = null, $lockVersion = null) + * @method CardToShebaInquiry|null findOneBy(array $criteria, array $orderBy = null) + * @method CardToShebaInquiry[] findAll() + * @method CardToShebaInquiry[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class CardToShebaInquiryRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, CardToShebaInquiry::class); + } + + public function findByCardNumber(string $cardNumber): ?CardToShebaInquiry + { + return $this->findOneBy(['cardNumber' => $cardNumber]); + } + + public function save(CardToShebaInquiry $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(CardToShebaInquiry $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } +} \ No newline at end of file diff --git a/hesabixCore/src/Service/Inquiry.php b/hesabixCore/src/Service/Inquiry.php index 2b5581c..6fe3fc4 100644 --- a/hesabixCore/src/Service/Inquiry.php +++ b/hesabixCore/src/Service/Inquiry.php @@ -3,6 +3,8 @@ namespace App\Service; use App\Entity\PostalCodeInquiry; +use App\Entity\CardToShebaInquiry; +use App\Entity\AccountToShebaInquiry; use Doctrine\ORM\EntityManagerInterface; class Inquiry @@ -137,6 +139,130 @@ class Inquiry ]; } + public function cardToSheba($cardNumber) + { + // بررسی دیتابیس برای کش + $existingInquiry = $this->entityManager->getRepository(CardToShebaInquiry::class)->findByCardNumber($cardNumber); + + if ($existingInquiry) { + // اگر در دیتابیس موجود است، از آن استفاده کن + $shebaData = $existingInquiry->getShebaData(); + return [ + 'result' => 1, + 'response_body' => [ + 'data' => $shebaData, + 'message' => 'موفق (از حافظه موقت)', + 'error_code' => null + ] + ]; + } + + // اگر در دیتابیس موجود نیست، از API استفاده کن + $registryMGR = new RegistryMGR($this->entityManager); + $inquiryPanel = $registryMGR->get('system', key: 'inquiryPanel'); + if($inquiryPanel == 'zohal'){ + $inquiryZohalAPIKey = $registryMGR->get('system', key: 'inquiryZohalAPIKey'); + + // بررسی وجود API Key + if (empty($inquiryZohalAPIKey)) { + return [ + 'result' => 0, + 'message' => 'API Key تنظیم نشده است', + 'error_code' => 'API_KEY_MISSING' + ]; + } + + // استفاده از API زحل برای تبدیل کارت به شبا + $url = "https://service.zohal.io/api/v0/services/inquiry/card_to_iban"; + + // آماده‌سازی داده‌های JSON + $postData = json_encode([ + 'card_number' => $cardNumber + ]); + + // تنظیمات cURL + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($postData), + 'Authorization: Bearer ' . $inquiryZohalAPIKey, + 'X-API-Key: ' . $inquiryZohalAPIKey + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + // بررسی خطای cURL + if ($curlError) { + return [ + 'result' => 0, + 'message' => 'خطا در ارتباط با سرور: ' . $curlError, + 'error_code' => 'CURL_ERROR' + ]; + } + + if ($httpCode === 200) { + $data = json_decode($response, true); + + // بررسی کدهای result + if (isset($data['result'])) { + switch ($data['result']) { + case 1: + // موفق - ذخیره در دیتابیس + if (isset($data['response_body']['data'])) { + $this->saveCardToShebaToDatabase($cardNumber, $data['response_body']['data']); + } + return $data; + case 4: + return [ + 'result' => 4, + 'message' => 'توکن غیر فعال شده است', + 'error_code' => 'TOKEN_INACTIVE' + ]; + case 5: + return [ + 'result' => 5, + 'message' => 'سرویس در دسترسی نمی‌باشد', + 'error_code' => 'SERVICE_UNAVAILABLE' + ]; + case 6: + return [ + 'result' => 6, + 'message' => 'فراخوانی وب‌سرویس با پارامترهای ورودی صحیح نمی‌باشد', + 'error_code' => 'INVALID_PARAMETERS' + ]; + default: + return [ + 'result' => $data['result'], + 'message' => $data['message'] ?? 'خطای نامشخص', + 'error_code' => $data['error_code'] ?? 'UNKNOWN_ERROR' + ]; + } + } + + return $data; + } else { + return [ + 'result' => 0, + 'message' => 'خطا در ارتباط با سرویس تبدیل کارت به شبا (کد خطا: ' . $httpCode . ')', + 'error_code' => 'HTTP_ERROR_' . $httpCode + ]; + } + } + + return [ + 'result' => 0, + 'message' => 'سرویس تبدیل کارت به شبا فعال نیست', + 'error_code' => 'SERVICE_NOT_ACTIVE' + ]; + } + private function saveToDatabase(string $postalCode, array $addressData): void { try { @@ -155,4 +281,168 @@ class Inquiry error_log('خطا در ذخیره استعلام کد پستی: ' . $e->getMessage()); } } + + public function accountToSheba($bankCode, $accountNumber) + { + // بررسی دیتابیس برای کش + $cacheKey = $bankCode . '_' . $accountNumber; + $existingInquiry = $this->entityManager->getRepository(AccountToShebaInquiry::class)->findByCacheKey($cacheKey); + + if ($existingInquiry) { + // اگر در دیتابیس موجود است، از آن استفاده کن + $shebaData = $existingInquiry->getShebaData(); + return [ + 'result' => 1, + 'response_body' => [ + 'data' => $shebaData, + 'message' => 'موفق (از حافظه موقت)', + 'error_code' => null + ] + ]; + } + + // اگر در دیتابیس موجود نیست، از API استفاده کن + $registryMGR = new RegistryMGR($this->entityManager); + $inquiryPanel = $registryMGR->get('system', key: 'inquiryPanel'); + if($inquiryPanel == 'zohal'){ + $inquiryZohalAPIKey = $registryMGR->get('system', key: 'inquiryZohalAPIKey'); + + // بررسی وجود API Key + if (empty($inquiryZohalAPIKey)) { + return [ + 'result' => 0, + 'message' => 'API Key تنظیم نشده است', + 'error_code' => 'API_KEY_MISSING' + ]; + } + + // استفاده از API زحل برای تبدیل حساب به شبا + $url = "https://service.zohal.io/api/v0/services/inquiry/account_to_iban"; + + // آماده‌سازی داده‌های JSON + $postData = json_encode([ + 'bank_code' => $bankCode, + 'bank_account' => $accountNumber + ]); + + // تنظیمات cURL + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($postData), + 'Authorization: Bearer ' . $inquiryZohalAPIKey, + 'X-API-Key: ' . $inquiryZohalAPIKey + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + // بررسی خطای cURL + if ($curlError) { + return [ + 'result' => 0, + 'message' => 'خطا در ارتباط با سرور: ' . $curlError, + 'error_code' => 'CURL_ERROR' + ]; + } + + if ($httpCode === 200) { + $data = json_decode($response, true); + + // بررسی کدهای result + if (isset($data['result'])) { + switch ($data['result']) { + case 1: + // موفق - ذخیره در دیتابیس + if (isset($data['response_body']['data'])) { + $this->saveAccountToShebaToDatabase($cacheKey, $data['response_body']['data']); + } + return $data; + case 4: + return [ + 'result' => 4, + 'message' => 'توکن غیر فعال شده است', + 'error_code' => 'TOKEN_INACTIVE' + ]; + case 5: + return [ + 'result' => 5, + 'message' => 'سرویس در دسترسی نمی‌باشد', + 'error_code' => 'SERVICE_UNAVAILABLE' + ]; + case 6: + return [ + 'result' => 6, + 'message' => 'فراخوانی وب‌سرویس با پارامترهای ورودی صحیح نمی‌باشد', + 'error_code' => 'INVALID_PARAMETERS' + ]; + default: + return [ + 'result' => $data['result'], + 'message' => $data['message'] ?? 'خطای نامشخص', + 'error_code' => $data['error_code'] ?? 'UNKNOWN_ERROR' + ]; + } + } + + return $data; + } else { + return [ + 'result' => 0, + 'message' => 'خطا در ارتباط با سرویس تبدیل حساب به شبا (کد خطا: ' . $httpCode . ')', + 'error_code' => 'HTTP_ERROR_' . $httpCode + ]; + } + } + + return [ + 'result' => 0, + 'message' => 'سرویس تبدیل حساب به شبا فعال نیست', + 'error_code' => 'SERVICE_NOT_ACTIVE' + ]; + } + + private function saveCardToShebaToDatabase(string $cardNumber, array $shebaData): void + { + try { + $inquiry = new CardToShebaInquiry(); + $inquiry->setCardNumber($cardNumber); + $inquiry->setShebaData($shebaData); + $inquiry->setUpdatedAt(new \DateTimeImmutable()); + + $this->entityManager->persist($inquiry); + $this->entityManager->flush(); + + // ثبت لاگ ذخیره موفق در دیتابیس + error_log("شماره کارت {$cardNumber} با موفقیت در حافظه موقت ذخیره شد"); + } catch (\Exception $e) { + // در صورت خطا در ذخیره، فقط لاگ کن و ادامه بده + error_log('خطا در ذخیره استعلام کارت به شبا: ' . $e->getMessage()); + } + } + + private function saveAccountToShebaToDatabase(string $cacheKey, array $shebaData): void + { + try { + $inquiry = new AccountToShebaInquiry(); + $inquiry->setCacheKey($cacheKey); + $inquiry->setShebaData($shebaData); + $inquiry->setUpdatedAt(new \DateTimeImmutable()); + + $this->entityManager->persist($inquiry); + $this->entityManager->flush(); + + // ثبت لاگ ذخیره موفق در دیتابیس + error_log("حساب با کلید {$cacheKey} با موفقیت در حافظه موقت ذخیره شد"); + } catch (\Exception $e) { + // در صورت خطا در ذخیره، فقط لاگ کن و ادامه بده + error_log('خطا در ذخیره استعلام حساب به شبا: ' . $e->getMessage()); + } + } } diff --git a/webUI/src/views/acc/App.vue b/webUI/src/views/acc/App.vue index e98424f..97c4321 100644 --- a/webUI/src/views/acc/App.vue +++ b/webUI/src/views/acc/App.vue @@ -791,8 +791,8 @@ export default { - - + + {{ $t('drawer.inquiry') }} {{ getShortcutKey('/acc/inquiry/panel') }} diff --git a/webUI/src/views/acc/inquiry/README.md b/webUI/src/views/acc/inquiry/README.md new file mode 100644 index 0000000..e161ce8 --- /dev/null +++ b/webUI/src/views/acc/inquiry/README.md @@ -0,0 +1,64 @@ +# پنل استعلامات + +این بخش شامل قابلیت‌های مختلف استعلام و تبدیل اطلاعات است که بر اساس تنظیمات سیستم فعال یا غیرفعال می‌شوند. + +## قابلیت‌های موجود + +### 1. تبدیل کد پستی به آدرس +- **وضعیت**: فعال/غیرفعال بر اساس تنظیمات سیستم +- **کارمزد**: قابل تنظیم در بخش مدیریت +- **API Endpoint**: `/api/plugins/inquiry/postalcode-to-address` +- **عملکرد**: تبدیل کد پستی 10 رقمی به آدرس کامل + +### 2. تبدیل شماره کارت به شبا +- **وضعیت**: فعال/غیرفعال بر اساس تنظیمات سیستم +- **کارمزد**: قابل تنظیم در بخش مدیریت +- **API Endpoint**: `/api/plugins/inquiry/card-to-sheba` +- **عملکرد**: تبدیل شماره کارت 16 رقمی به شماره شبا (در حال توسعه) + +### 3. تبدیل حساب به شبا +- **وضعیت**: فعال/غیرفعال بر اساس تنظیمات سیستم +- **کارمزد**: قابل تنظیم در بخش مدیریت +- **API Endpoint**: `/api/plugins/inquiry/account-to-sheba` +- **عملکرد**: تبدیل شماره حساب بانکی به شماره شبا (در حال توسعه) + +## ویژگی‌های رابط کاربری + +### نمایش سرویس‌های فعال +- نمایش کارت‌های رنگی برای هر سرویس فعال +- نمایش کارمزد هر سرویس +- نشان‌گذاری وضعیت فعال/غیرفعال + +### دیالوگ‌های استعلام +- فرم‌های اعتبارسنجی شده +- نمایش کارمزد قبل از استعلام +- نمایش نتیجه در قالب پیام‌های زیبا + +### مدیریت خطاها +- بررسی موجودی کافی +- نمایش پیام‌های خطای مناسب +- لاگ کردن عملیات‌ها + +## تنظیمات مورد نیاز + +برای فعال‌سازی این قابلیت‌ها، مدیر سیستم باید در بخش تنظیمات سیستم موارد زیر را تنظیم کند: + +1. **فعال‌سازی پنل استعلامات**: `inquiryPanelEnable` +2. **انتخاب پنل**: `inquiryPanel` (فعلاً فقط زحل) +3. **کلید API پنل**: `inquiryZohalAPIKey` +4. **فعال‌سازی هر سرویس**: `enablePostalCodeToAddress`, `enableCardToSheba`, `enableAccountToSheba` +5. **تعیین کارمزد**: `postalCodeToAddressFee`, `cardToShebaFee`, `accountToShebaFee` + +## نکات فنی + +- تمام درخواست‌ها نیاز به احراز هویت دارند +- کارمزد از موجودی SMS کاربر کسر می‌شود +- نتایج در کش ذخیره می‌شوند تا از تکرار درخواست‌های مشابه جلوگیری شود +- تمام عملیات لاگ می‌شوند + +## توسعه آینده + +- تکمیل سرویس‌های تبدیل کارت و حساب به شبا +- اضافه کردن سرویس‌های جدید +- بهبود رابط کاربری +- اضافه کردن گزارش‌گیری \ No newline at end of file diff --git a/webUI/src/views/acc/inquiry/panel.vue b/webUI/src/views/acc/inquiry/panel.vue index 30cc2cc..318b924 100644 --- a/webUI/src/views/acc/inquiry/panel.vue +++ b/webUI/src/views/acc/inquiry/panel.vue @@ -1,24 +1,1166 @@ - diff --git a/webUI/src/views/acc/settings/user_perm_edit.vue b/webUI/src/views/acc/settings/user_perm_edit.vue index f54e41c..97bd05b 100644 --- a/webUI/src/views/acc/settings/user_perm_edit.vue +++ b/webUI/src/views/acc/settings/user_perm_edit.vue @@ -59,6 +59,7 @@ + @@ -161,6 +162,18 @@ :disabled="loadingSwitches.archiveView" > + + + @@ -627,7 +640,6 @@ - Date: Thu, 17 Jul 2025 09:09:09 +0000 Subject: [PATCH 10/22] remove header from inquiry panel --- webUI/src/views/acc/inquiry/panel.vue | 8 -------- 1 file changed, 8 deletions(-) diff --git a/webUI/src/views/acc/inquiry/panel.vue b/webUI/src/views/acc/inquiry/panel.vue index 318b924..9b21066 100644 --- a/webUI/src/views/acc/inquiry/panel.vue +++ b/webUI/src/views/acc/inquiry/panel.vue @@ -11,15 +11,7 @@ - -
-
-
-

دسترسی به سرویس‌های مختلف استعلام و تبدیل اطلاعات

-
-
-
From 574bdd1a5b2da1d542559b32e46f8efa95288f82 Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Thu, 17 Jul 2025 09:13:17 +0000 Subject: [PATCH 11/22] change snackbar location in some files --- webUI/src/views/acc/component/notes.vue | 2 +- webUI/src/views/acc/inquiry/panel.vue | 2 +- webUI/src/views/acc/presell/viewInvoice.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webUI/src/views/acc/component/notes.vue b/webUI/src/views/acc/component/notes.vue index a8f4e7b..9c91308 100644 --- a/webUI/src/views/acc/component/notes.vue +++ b/webUI/src/views/acc/component/notes.vue @@ -211,7 +211,7 @@ export default defineComponent({ v-model="snackbar" :color="snackbarColor" timeout="3000" - location="top" + location="bottom" > {{ snackbarText }}