forked from morrning/hesabixCore
Compare commits
11 commits
c20652b8cd
...
574bdd1a5b
Author | SHA1 | Date | |
---|---|---|---|
|
574bdd1a5b | ||
|
7090ff44dc | ||
|
2a8ea8cb4a | ||
|
227767b0d6 | ||
|
2cb7b8945c | ||
|
7762613814 | ||
|
90f5a5b338 | ||
|
b1f8af83f4 | ||
|
0d18762f1e | ||
|
82740193cb | ||
|
ddbbf1f102 |
37
hesabixCore/migrations/Version20241201000000.php
Normal file
37
hesabixCore/migrations/Version20241201000000.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20241201000000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create postal_code_inquiry table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE postal_code_inquiry (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
postal_code VARCHAR(10) NOT NULL,
|
||||
address_data JSON NOT NULL,
|
||||
created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\',
|
||||
updated_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\',
|
||||
UNIQUE INDEX UNIQ_POSTAL_CODE (postal_code),
|
||||
PRIMARY KEY(id)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE postal_code_inquiry');
|
||||
}
|
||||
}
|
|
@ -450,6 +450,15 @@ class AdminController extends AbstractController
|
|||
$resp['parsianGatewayAPI'] = $registryMGR->get('system', key: 'parsianGatewayAPI');
|
||||
$resp['paypingKey'] = $registryMGR->get('system', key: 'paypingKey');
|
||||
$resp['bitpayKey'] = $registryMGR->get('system', key: 'bitpayKey');
|
||||
$resp['inquiryPanel'] = $registryMGR->get('system', key: 'inquiryPanel');
|
||||
$resp['inquiryZohalAPIKey'] = $registryMGR->get('system', key: 'inquiryZohalAPIKey');
|
||||
$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);
|
||||
}
|
||||
|
||||
|
@ -474,6 +483,15 @@ class AdminController extends AbstractController
|
|||
$registryMGR->update('system', 'parsianGatewayAPI', $params['parsianGatewayAPI']);
|
||||
$registryMGR->update('system', 'paypingKey', $params['paypingKey']);
|
||||
$registryMGR->update('system', 'bitpayKey', $params['bitpayKey']);
|
||||
$registryMGR->update('system', 'inquiryPanel', $params['inquiryPanel']);
|
||||
$registryMGR->update('system', 'inquiryZohalAPIKey', $params['inquiryZohalAPIKey']);
|
||||
$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]);
|
||||
|
@ -592,6 +610,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;
|
||||
|
|
|
@ -544,6 +544,8 @@ class BusinessController extends AbstractController
|
|||
'plugRepservice' => true,
|
||||
'plugHrmDocs' => true,
|
||||
'plugGhestaManager' => true,
|
||||
'plugTaxSettings' => true,
|
||||
'inquiry' => true,
|
||||
];
|
||||
} elseif ($perm) {
|
||||
$result = [
|
||||
|
@ -587,6 +589,8 @@ class BusinessController extends AbstractController
|
|||
'plugAccproPresell' => $perm->isPlugAccproPresell(),
|
||||
'plugHrmDocs' => $perm->isPlugHrmDocs(),
|
||||
'plugGhestaManager' => $perm->isPlugGhestaManager(),
|
||||
'plugTaxSettings' => $perm->isPlugTaxSettings(),
|
||||
'inquiry' => $perm->isInquiry(),
|
||||
];
|
||||
}
|
||||
return $this->json($result);
|
||||
|
@ -656,6 +660,8 @@ class BusinessController extends AbstractController
|
|||
$perm->setPlugRepservice($params['plugRepservice']);
|
||||
$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);
|
||||
|
|
|
@ -143,7 +143,8 @@ class CommodityController extends AbstractController
|
|||
$count += $row->getCommdityCount();
|
||||
} else {
|
||||
$count -= $row->getCommdityCount();
|
||||
} }
|
||||
}
|
||||
}
|
||||
$temp['count'] = $count;
|
||||
}
|
||||
return $temp;
|
||||
|
@ -1071,7 +1072,8 @@ 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'])) {
|
||||
|
@ -1081,6 +1083,29 @@ class CommodityController extends AbstractController
|
|||
'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) {
|
||||
|
|
201
hesabixCore/src/Controller/Plugins/TaxSettingsController.php
Normal file
201
hesabixCore/src/Controller/Plugins/TaxSettingsController.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,327 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\Plugins\inquiry;
|
||||
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Entity\PlugGhestaDoc;
|
||||
use App\Entity\PlugGhestaItem;
|
||||
use App\Entity\HesabdariDoc;
|
||||
use App\Entity\Person;
|
||||
use App\Service\Access;
|
||||
use App\Service\Provider;
|
||||
use App\Service\Printers;
|
||||
use App\Entity\PrintOptions;
|
||||
use App\Service\Log;
|
||||
use App\Entity\Business;
|
||||
use App\Service\registryMGR;
|
||||
use App\Service\Inquiry;
|
||||
|
||||
class PlugInquiryMainController extends AbstractController
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
#[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('inquiry');
|
||||
if (!$acc) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'شما دسترسی به این سرویس را ندارید'
|
||||
]);
|
||||
}
|
||||
|
||||
// دریافت کد پستی از درخواست
|
||||
$data = json_decode($request->getContent(), true);
|
||||
$postalCode = $data['postal_code'] ?? null;
|
||||
if (!$postalCode) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'کد پستی ارسال نشده است'
|
||||
]);
|
||||
}
|
||||
|
||||
// فراخوانی سرویس استعلام کد پستی
|
||||
$result = $inquiry->postalCodeToAddress($postalCode);
|
||||
|
||||
// بررسی نتیجه و بازگرداندن پاسخ مناسب
|
||||
if (isset($result['result']) && $result['result'] == 1) {
|
||||
$isFromCache = isset($result['response_body']['message']) &&
|
||||
strpos($result['response_body']['message'], 'از کش') !== false;
|
||||
|
||||
// ثبت لاگ بر اساس منبع داده
|
||||
$logMessage = $isFromCache
|
||||
? "استعلام کد پستی {$postalCode} از کش (بدون کسر کارمزد)"
|
||||
: "استعلام کد پستی {$postalCode} از API (کسر کارمزد: " . $registryMGR->get('system', key: 'postalCodeToAddressFee') . " ریال)";
|
||||
|
||||
$log->insert(
|
||||
'استعلام',
|
||||
$logMessage,
|
||||
$acc['user'],
|
||||
$acc['bid']
|
||||
);
|
||||
|
||||
// فقط در صورت عدم وجود در کش، کارمزد کسر شود
|
||||
if (!$isFromCache) {
|
||||
if ($acc['bid']->getSmsCharge() < $registryMGR->get('system', key: 'postalCodeToAddressFee')) {
|
||||
// ثبت لاگ عدم موجودی کافی
|
||||
$log->insert(
|
||||
'استعلام',
|
||||
"عدم موجودی کافی برای استعلام کد پستی {$postalCode}",
|
||||
$acc['user'],
|
||||
$acc['bid']
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'موجودی شما برای این سرویس کافی نیست'
|
||||
]);
|
||||
}
|
||||
|
||||
$business = $acc['bid'];
|
||||
$business->setSmsCharge($business->getSmsCharge() - $registryMGR->get('system', key: 'postalCodeToAddressFee'));
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'data' => $result['response_body']['data']['address'] ?? null,
|
||||
'message' => $result['response_body']['message'] ?? 'موفق',
|
||||
'from_cache' => $isFromCache
|
||||
]);
|
||||
} else {
|
||||
// ثبت لاگ خطا
|
||||
$errorMessage = $result['message'] ?? 'خطا در استعلام کد پستی';
|
||||
$log->insert(
|
||||
'استعلام',
|
||||
"خطا در استعلام کد پستی {$postalCode}: {$errorMessage}",
|
||||
$acc['user'],
|
||||
$acc['bid']
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => $errorMessage,
|
||||
'error_code' => $result['error_code'] ?? null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,9 @@ final class UpdateCoreController extends AbstractController
|
|||
public function api_admin_updatecore_run(): JsonResponse
|
||||
{
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
|
||||
$uuid = uniqid();
|
||||
$stateFile = $projectDir . '/../hesabixBackup/update_state_' . $uuid . '.json';
|
||||
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
|
||||
|
||||
if (!file_exists(dirname($stateFile))) {
|
||||
mkdir(dirname($stateFile), 0755, true);
|
||||
|
@ -41,7 +42,7 @@ final class UpdateCoreController extends AbstractController
|
|||
'COMPOSER_HOME' => '/var/www/.composer',
|
||||
]);
|
||||
|
||||
$process = new Process(['php', 'bin/console', 'hesabix:update', $stateFile], $projectDir, $env);
|
||||
$process = new Process(['php', 'hesabixCore/bin/console', 'hesabix:update', $stateFile], $gitRoot, $env);
|
||||
$process->setTimeout(7200); // افزایش تایماوت به 2 ساعت
|
||||
$process->start(function ($type, $buffer) use ($stateFile) {
|
||||
$state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => uniqid(), 'log' => ''];
|
||||
|
@ -70,7 +71,9 @@ final class UpdateCoreController extends AbstractController
|
|||
], 400);
|
||||
}
|
||||
|
||||
$stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json';
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir);
|
||||
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
|
||||
|
||||
if (!file_exists($stateFile)) {
|
||||
return new JsonResponse([
|
||||
|
@ -97,7 +100,7 @@ final class UpdateCoreController extends AbstractController
|
|||
}
|
||||
|
||||
if (!$isRunning) {
|
||||
$backupDir = $this->getParameter('kernel.project_dir') . '/../hesabixBackup';
|
||||
$backupDir = $gitRoot . '/hesabixBackup';
|
||||
$stateFiles = glob($backupDir . '/update_state_*.json');
|
||||
foreach ($stateFiles as $file) {
|
||||
if (is_file($file)) {
|
||||
|
@ -128,7 +131,9 @@ final class UpdateCoreController extends AbstractController
|
|||
return new JsonResponse(['status' => 'error', 'message' => 'UUID is required'], 400);
|
||||
}
|
||||
|
||||
$stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json';
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir);
|
||||
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
|
||||
|
||||
return new StreamedResponse(function () use ($stateFile) {
|
||||
header('Content-Type: text/event-stream');
|
||||
|
@ -167,13 +172,14 @@ final class UpdateCoreController extends AbstractController
|
|||
public function api_admin_updatecore_commits(): JsonResponse
|
||||
{
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
|
||||
|
||||
$currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $projectDir);
|
||||
$currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $gitRoot);
|
||||
$currentProcess->setTimeout(7200); // افزایش تایماوت
|
||||
$currentProcess->run();
|
||||
$currentCommit = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : 'unknown';
|
||||
|
||||
$targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $projectDir);
|
||||
$targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $gitRoot);
|
||||
$targetProcess->setTimeout(7200); // افزایش تایماوت
|
||||
$targetProcess->run();
|
||||
$targetOutput = $targetProcess->isSuccessful() ? explode("\t", trim($targetProcess->getOutput()))[0] : 'unknown';
|
||||
|
@ -428,4 +434,170 @@ final class UpdateCoreController extends AbstractController
|
|||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/admin/updatecore/current-source', name: 'api_admin_updatecore_current_source', methods: ['GET'])]
|
||||
public function api_admin_updatecore_current_source(): JsonResponse
|
||||
{
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
|
||||
$output = '';
|
||||
|
||||
try {
|
||||
// بررسی اینکه آیا پروژه یک مخزن Git است
|
||||
if (!is_dir($gitRoot . '/.git')) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'این پروژه یک مخزن Git نیست',
|
||||
'sourceUrl' => '',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// دریافت آدرس مخزن origin فعلی
|
||||
$process = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot);
|
||||
$process->setTimeout(7200); // افزایش تایماوت
|
||||
$process->run();
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'خطا در دریافت آدرس مخزن: ' . $process->getErrorOutput(),
|
||||
'sourceUrl' => '',
|
||||
], 500);
|
||||
}
|
||||
|
||||
$sourceUrl = trim($process->getOutput());
|
||||
$output .= "آدرس مخزن فعلی: $sourceUrl\n";
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'success',
|
||||
'sourceUrl' => $sourceUrl,
|
||||
'output' => $output,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'خطا در بررسی مخزن: ' . $e->getMessage(),
|
||||
'sourceUrl' => '',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/admin/updatecore/change-source', name: 'api_admin_updatecore_change_source', methods: ['POST'])]
|
||||
public function api_admin_updatecore_change_source(Request $request): JsonResponse
|
||||
{
|
||||
$sourceUrl = $request->getPayload()->get('sourceUrl');
|
||||
$output = '';
|
||||
|
||||
if (!$sourceUrl || !filter_var($sourceUrl, FILTER_VALIDATE_URL) && !preg_match('/^git@[^:]+:[^\/]+\/[^\/]+\.git$/', $sourceUrl)) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.',
|
||||
'output' => $output,
|
||||
], 400);
|
||||
}
|
||||
|
||||
$projectDir = $this->getParameter('kernel.project_dir');
|
||||
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
|
||||
|
||||
try {
|
||||
// بررسی اینکه آیا پروژه یک مخزن Git است
|
||||
if (!is_dir($gitRoot . '/.git')) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'این پروژه یک مخزن Git نیست',
|
||||
'output' => $output,
|
||||
], 400);
|
||||
}
|
||||
|
||||
$output .= "شروع تغییر آدرس مخزن...\n";
|
||||
|
||||
// دریافت آدرس مخزن فعلی
|
||||
$currentProcess = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot);
|
||||
$currentProcess->setTimeout(7200);
|
||||
$currentProcess->run();
|
||||
$currentUrl = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : '';
|
||||
|
||||
if ($currentUrl) {
|
||||
$output .= "آدرس مخزن فعلی: $currentUrl\n";
|
||||
}
|
||||
|
||||
// تغییر آدرس مخزن origin
|
||||
$changeProcess = new Process(['git', 'remote', 'set-url', 'origin', $sourceUrl], $gitRoot);
|
||||
$changeProcess->setTimeout(7200);
|
||||
$changeProcess->run();
|
||||
|
||||
if (!$changeProcess->isSuccessful()) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'خطا در تغییر آدرس مخزن: ' . $changeProcess->getErrorOutput(),
|
||||
'output' => $output,
|
||||
], 500);
|
||||
}
|
||||
|
||||
$output .= "آدرس مخزن به $sourceUrl تغییر یافت\n";
|
||||
|
||||
// بررسی اتصال به مخزن جدید
|
||||
$testProcess = new Process(['git', 'remote', 'show', 'origin'], $gitRoot);
|
||||
$testProcess->setTimeout(7200);
|
||||
$testProcess->run();
|
||||
|
||||
if (!$testProcess->isSuccessful()) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'خطا در اتصال به مخزن جدید: ' . $testProcess->getErrorOutput(),
|
||||
'output' => $output,
|
||||
], 500);
|
||||
}
|
||||
|
||||
$output .= "اتصال به مخزن جدید با موفقیت برقرار شد\n";
|
||||
|
||||
// دریافت اطلاعات مخزن جدید
|
||||
$fetchProcess = new Process(['git', 'fetch', 'origin'], $gitRoot);
|
||||
$fetchProcess->setTimeout(7200);
|
||||
$fetchProcess->run();
|
||||
|
||||
if (!$fetchProcess->isSuccessful()) {
|
||||
$output .= "هشدار: خطا در دریافت اطلاعات از مخزن جدید: " . $fetchProcess->getErrorOutput() . "\n";
|
||||
} else {
|
||||
$output .= "اطلاعات مخزن جدید با موفقیت دریافت شد\n";
|
||||
}
|
||||
|
||||
// بررسی branch های موجود
|
||||
$branchProcess = new Process(['git', 'branch', '-r'], $gitRoot);
|
||||
$branchProcess->setTimeout(7200);
|
||||
$branchProcess->run();
|
||||
|
||||
if ($branchProcess->isSuccessful()) {
|
||||
$branches = trim($branchProcess->getOutput());
|
||||
if ($branches) {
|
||||
$output .= "شاخههای موجود در مخزن جدید:\n$branches\n";
|
||||
} else {
|
||||
$output .= "هیچ شاخهای در مخزن جدید یافت نشد\n";
|
||||
}
|
||||
}
|
||||
|
||||
// پاک کردن کش Git
|
||||
$cleanProcess = new Process(['git', 'gc', '--prune=now'], $gitRoot);
|
||||
$cleanProcess->setTimeout(7200);
|
||||
$cleanProcess->run();
|
||||
|
||||
if ($cleanProcess->isSuccessful()) {
|
||||
$output .= "کش Git پاک شد\n";
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'status' => 'success',
|
||||
'message' => 'آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد',
|
||||
'output' => $output,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return new JsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => 'خطا در تغییر آدرس مخزن: ' . $e->getMessage(),
|
||||
'output' => $output,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
63
hesabixCore/src/Entity/AccountToShebaInquiry.php
Normal file
63
hesabixCore/src/Entity/AccountToShebaInquiry.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AccountToShebaInquiryRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AccountToShebaInquiryRepository::class)]
|
||||
#[ORM\Table(name: 'account_to_sheba_inquiry')]
|
||||
class AccountToShebaInquiry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 50, unique: true)]
|
||||
private ?string $cacheKey = null;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $shebaData = [];
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
63
hesabixCore/src/Entity/CardToShebaInquiry.php
Normal file
63
hesabixCore/src/Entity/CardToShebaInquiry.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\CardToShebaInquiryRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: CardToShebaInquiryRepository::class)]
|
||||
#[ORM\Table(name: 'card_to_sheba_inquiry')]
|
||||
class CardToShebaInquiry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 16, unique: true)]
|
||||
private ?string $cardNumber = null;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $shebaData = [];
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
|
@ -129,6 +129,12 @@ class Permission
|
|||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $plugGhestaManager = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $plugTaxSettings = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $inquiry = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -590,4 +596,27 @@ class Permission
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function isPlugTaxSettings(): ?bool
|
||||
{
|
||||
return $this->plugTaxSettings;
|
||||
}
|
||||
|
||||
public function setPlugTaxSettings(?bool $plugTaxSettings): static
|
||||
{
|
||||
$this->plugTaxSettings = $plugTaxSettings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isInquiry(): ?bool
|
||||
{
|
||||
return $this->inquiry;
|
||||
}
|
||||
|
||||
public function setInquiry(?bool $inquiry): static
|
||||
{
|
||||
$this->inquiry = $inquiry;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
53
hesabixCore/src/Entity/PluginTaxsettingsKey.php
Normal file
53
hesabixCore/src/Entity/PluginTaxsettingsKey.php
Normal 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; }
|
||||
}
|
83
hesabixCore/src/Entity/PostalCodeInquiry.php
Normal file
83
hesabixCore/src/Entity/PostalCodeInquiry.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\PostalCodeInquiryRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PostalCodeInquiryRepository::class)]
|
||||
#[ORM\Table(name: 'postal_code_inquiry')]
|
||||
class PostalCodeInquiry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 10, unique: true)]
|
||||
private ?string $postalCode = null;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $addressData = [];
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPostalCode(): ?string
|
||||
{
|
||||
return $this->postalCode;
|
||||
}
|
||||
|
||||
public function setPostalCode(string $postalCode): static
|
||||
{
|
||||
$this->postalCode = $postalCode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddressData(): array
|
||||
{
|
||||
return $this->addressData;
|
||||
}
|
||||
|
||||
public function setAddressData(array $addressData): static
|
||||
{
|
||||
$this->addressData = $addressData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(\DateTimeImmutable $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(\DateTimeImmutable $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\AccountToShebaInquiry;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AccountToShebaInquiry>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
46
hesabixCore/src/Repository/CardToShebaInquiryRepository.php
Normal file
46
hesabixCore/src/Repository/CardToShebaInquiryRepository.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\CardToShebaInquiry;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<CardToShebaInquiry>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
46
hesabixCore/src/Repository/PostalCodeInquiryRepository.php
Normal file
46
hesabixCore/src/Repository/PostalCodeInquiryRepository.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\PostalCodeInquiry;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<PostalCodeInquiry>
|
||||
*
|
||||
* @method PostalCodeInquiry|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method PostalCodeInquiry|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method PostalCodeInquiry[] findAll()
|
||||
* @method PostalCodeInquiry[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class PostalCodeInquiryRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, PostalCodeInquiry::class);
|
||||
}
|
||||
|
||||
public function findByPostalCode(string $postalCode): ?PostalCodeInquiry
|
||||
{
|
||||
return $this->findOneBy(['postalCode' => $postalCode]);
|
||||
}
|
||||
|
||||
public function save(PostalCodeInquiry $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(PostalCodeInquiry $entity, bool $flush = false): void
|
||||
{
|
||||
$this->getEntityManager()->remove($entity);
|
||||
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
}
|
448
hesabixCore/src/Service/Inquiry.php
Normal file
448
hesabixCore/src/Service/Inquiry.php
Normal file
|
@ -0,0 +1,448 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\PostalCodeInquiry;
|
||||
use App\Entity\CardToShebaInquiry;
|
||||
use App\Entity\AccountToShebaInquiry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class Inquiry
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $entityManager)
|
||||
{
|
||||
}
|
||||
|
||||
public function postalCodeToAddress($postalCode)
|
||||
{
|
||||
// ابتدا بررسی دیتابیس
|
||||
$existingInquiry = $this->entityManager->getRepository(PostalCodeInquiry::class)->findByPostalCode($postalCode);
|
||||
|
||||
if ($existingInquiry) {
|
||||
// اگر در دیتابیس موجود است، از آن استفاده کن
|
||||
$addressData = $existingInquiry->getAddressData();
|
||||
return [
|
||||
'result' => 1,
|
||||
'response_body' => [
|
||||
'data' => [
|
||||
'address' => $addressData
|
||||
],
|
||||
'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/postal_code_inquiry";
|
||||
|
||||
// آمادهسازی دادههای JSON
|
||||
$postData = json_encode([
|
||||
'postal_code' => $postalCode
|
||||
]);
|
||||
|
||||
// تنظیمات 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']['address'])) {
|
||||
$this->saveToDatabase($postalCode, $data['response_body']['data']['address']);
|
||||
}
|
||||
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'
|
||||
];
|
||||
}
|
||||
|
||||
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 {
|
||||
$inquiry = new PostalCodeInquiry();
|
||||
$inquiry->setPostalCode($postalCode);
|
||||
$inquiry->setAddressData($addressData);
|
||||
$inquiry->setUpdatedAt(new \DateTimeImmutable());
|
||||
|
||||
$this->entityManager->persist($inquiry);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ثبت لاگ ذخیره موفق در دیتابیس
|
||||
error_log("کد پستی {$postalCode} با موفقیت در کش ذخیره شد");
|
||||
} catch (\Exception $e) {
|
||||
// در صورت خطا در ذخیره، فقط لاگ کن و ادامه بده
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -791,11 +791,11 @@ install_software() {
|
|||
# Check if remote origin exists
|
||||
if ! git remote get-url origin >/dev/null 2>&1; then
|
||||
# Add remote repository if it doesn't exist
|
||||
git remote add origin https://github.com/morrning/hesabixCore.git || \
|
||||
git remote add origin https://source.hesabix.ir/morrning/hesabixCore.git || \
|
||||
handle_error "Failed to add remote repository"
|
||||
else
|
||||
# Update remote URL if it exists
|
||||
git remote set-url origin https://github.com/morrning/hesabixCore.git || \
|
||||
git remote set-url origin https://source.hesabix.ir/morrning/hesabixCore.git || \
|
||||
handle_error "Failed to update remote repository"
|
||||
fi
|
||||
|
||||
|
|
188
webUI/src/components/widgets/inquiry/postalcode2address.vue
Normal file
188
webUI/src/components/widgets/inquiry/postalcode2address.vue
Normal file
|
@ -0,0 +1,188 @@
|
|||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: "PostalCode2Address",
|
||||
props: {
|
||||
postalCode: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
settings: {
|
||||
inquiryPanelEnable: false,
|
||||
enablePostalCodeToAddress: false,
|
||||
postalCodeToAddressFee: 0
|
||||
},
|
||||
loading: false,
|
||||
result: null,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldShow() {
|
||||
// اگر تنظیمات هنوز لود نشده، نمایش بده
|
||||
if (!this.settings.inquiryPanelEnable && !this.settings.enablePostalCodeToAddress) {
|
||||
return true;
|
||||
}
|
||||
return (this.settings.inquiryPanelEnable === '1' || this.settings.inquiryPanelEnable === true) &&
|
||||
(this.settings.enablePostalCodeToAddress === '1' || this.settings.enablePostalCodeToAddress === true);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadSettings();
|
||||
},
|
||||
methods: {
|
||||
async loadSettings() {
|
||||
try {
|
||||
console.log('در حال دریافت تنظیمات...');
|
||||
this.loading = true;
|
||||
const response = await axios.get('/api/plugins/inquiry/settings/get');
|
||||
console.log('تنظیمات دریافت شد:', response.data);
|
||||
this.settings = response.data;
|
||||
console.log('shouldShow:', this.shouldShow);
|
||||
} catch (error) {
|
||||
console.error('خطا در دریافت تنظیمات:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
openDialog() {
|
||||
this.dialog = true;
|
||||
},
|
||||
closeDialog() {
|
||||
this.dialog = false;
|
||||
},
|
||||
async submitPostalCode() {
|
||||
this.loading = true;
|
||||
this.result = null;
|
||||
this.error = null;
|
||||
try {
|
||||
console.log('ارسال درخواست برای کد پستی:', this.postalCode);
|
||||
const response = await axios.post('/api/plugins/inquiry/postalcode-to-address', {
|
||||
postal_code: this.postalCode
|
||||
});
|
||||
console.log('پاسخ دریافتی:', response.data);
|
||||
if (response.data && response.data.success) {
|
||||
this.result = response.data.data;
|
||||
// ارسال دادهها به کامپوننت والد
|
||||
const addressDataWithPostalCode = {
|
||||
...response.data.data,
|
||||
postalCode: this.postalCode
|
||||
};
|
||||
this.$emit('address-found', addressDataWithPostalCode);
|
||||
|
||||
// نمایش پیام مناسب بر اساس منبع داده
|
||||
const message = response.data.from_cache
|
||||
? 'اطلاعات آدرس از کش دریافت شد (بدون کسر کارمزد)'
|
||||
: 'اطلاعات آدرس با موفقیت دریافت شد';
|
||||
|
||||
// بستن دیالوگ بعد از موفقیت
|
||||
this.dialog = false;
|
||||
|
||||
// نمایش پیام موفقیت
|
||||
this.$nextTick(() => {
|
||||
this.showSnackbar(message, 'success');
|
||||
});
|
||||
} else {
|
||||
this.error = (response.data && response.data.message) || "خطا در دریافت اطلاعات";
|
||||
|
||||
// مدیریت خطاهای خاص
|
||||
if (response.data && response.data.error_code === 'HTTP_ERROR_504') {
|
||||
this.error = "سرویس استعلام کد پستی در دسترس نیست. لطفاً بعداً تلاش کنید.";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('خطا در ارسال درخواست:', e);
|
||||
this.error = "خطا در ارتباط با سرور";
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
showSnackbar(text, color = 'success', timeout = 3000) {
|
||||
// ارسال event به کامپوننت والد برای نمایش snackbar
|
||||
this.$emit('show-snackbar', { text, color, timeout });
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="shouldShow">
|
||||
<v-tooltip text="تبدیل کد پستی به آدرس" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" size="x-small" color="primary" variant="text" @click="openDialog" class="px-1">
|
||||
<v-icon>mdi-map-marker</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
<v-dialog v-model="dialog" max-width="500px">
|
||||
<v-card>
|
||||
<v-toolbar color="primary" dark>
|
||||
<v-toolbar-title>تبدیل کد پستی به آدرس</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="closeDialog">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text class="pt-4">
|
||||
<div class="mb-4">
|
||||
<p class="text-body-2">
|
||||
با استفاده از این سرویس میتوانید کد پستی را به آدرس کامل تبدیل کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-alert type="info" variant="tonal" class="mb-4">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-information</v-icon>
|
||||
</template>
|
||||
<div>
|
||||
<strong>کارمزد سرویس:</strong> {{ settings.postalCodeToAddressFee.toLocaleString() }} ریال
|
||||
<br>
|
||||
<small class="text-caption">
|
||||
💡 اگر این کد پستی قبلاً استعلام شده باشد، کارمزدی کسر نمیشود
|
||||
</small>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<v-text-field
|
||||
:model-value="postalCode"
|
||||
label="کد پستی"
|
||||
placeholder="مثال: 1234567890"
|
||||
prepend-inner-icon="mdi-mailbox"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
readonly
|
||||
></v-text-field>
|
||||
|
||||
<v-btn
|
||||
color="primary"
|
||||
block
|
||||
:loading="loading"
|
||||
:disabled="!postalCode || postalCode.length < 10"
|
||||
@click="submitPostalCode"
|
||||
>
|
||||
تبدیل به آدرس
|
||||
</v-btn>
|
||||
|
||||
<!-- نمایش خطا -->
|
||||
<v-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
class="mt-4"
|
||||
border="start"
|
||||
>
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -173,6 +173,7 @@ const fa_lang = {
|
|||
reports: "گزارشات",
|
||||
settings: "تنظیمات",
|
||||
bid_settings: "تنظیمات کسبوکار",
|
||||
tax_settings: "تنظیمات مالیاتی",
|
||||
print_settings: "چاپ اسناد",
|
||||
user_perms: "کاربران و دسترسیها",
|
||||
avatar_settings: "نمایه و مهر کسبوکار",
|
||||
|
@ -193,9 +194,12 @@ const fa_lang = {
|
|||
plugins_invoices: "صورت حسابها",
|
||||
repservice: "مدیریت تعمیرگاه",
|
||||
repservice_reqs: "درخواستها",
|
||||
inquiry: "استعلامات",
|
||||
hrm: 'منابع انسانی',
|
||||
hrm_docs: 'سند حقوق',
|
||||
buysellByPerson: "گزارش خرید و فروش های اشخاص",
|
||||
tax_system: "سامانه مودیان مالیاتی",
|
||||
tax_invoices: "صورتحساب ها",
|
||||
},
|
||||
time: {
|
||||
month: "{id} ماه",
|
||||
|
@ -268,6 +272,19 @@ const fa_lang = {
|
|||
"fetchError": "خطا در دریافت",
|
||||
"cancel": "لغو",
|
||||
"confirm": "تأیید",
|
||||
"updateSourceTitle": "منبع بهروزرسانی",
|
||||
"updateSourceLabel": "آدرس منبع بهروزرسانی",
|
||||
"changeSourceButton": "تغییر منبع",
|
||||
"sourceUrlRequired": "لطفاً آدرس منبع بهروزرسانی را وارد کنید",
|
||||
"changingSourceMessage": "در حال تغییر منبع بهروزرسانی...",
|
||||
"sourceChangeSuccess": "منبع بهروزرسانی با موفقیت تغییر یافت",
|
||||
"sourceChangeError": "خطایی در تغییر منبع بهروزرسانی رخ داد",
|
||||
"notGitRepository": "این پروژه یک مخزن Git نیست",
|
||||
"invalidRepositoryUrl": "آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.",
|
||||
"repositoryUrlError": "خطا در دریافت آدرس مخزن",
|
||||
"repositoryChangeError": "خطا در تغییر آدرس مخزن",
|
||||
"repositoryConnectionError": "خطا در اتصال به مخزن جدید",
|
||||
"repositoryChangeSuccess": "آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد",
|
||||
},
|
||||
static: {
|
||||
not_found: "صفحه مورد نظر یافت نشد",
|
||||
|
@ -799,6 +816,19 @@ const fa_lang = {
|
|||
sms_settings_plug_accpro_pass_cheque_input: "واگذاری چک",
|
||||
sms_settings_reject_cheque_input: "برگشت چک",
|
||||
sms_settings_plug_accpro_reject_cheque_input: "برگشت چک",
|
||||
inquiry_zohal_api_key: "کلید API زحل",
|
||||
inquiry_zohal_api_key_des: "کلید API زحل برای دریافت اطلاعات از سامانه زحل",
|
||||
inquiry_zohal_api_key_placeholder: "کلید API زحل",
|
||||
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: "زحل",
|
||||
app_site: "آدرس اینترفیس(رابط کاربری)",
|
||||
keywords: "کلیدواژهها با کاما (,) از هم جدا شوند",
|
||||
zarinpal_api: "کد API زرینپال",
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
@ -788,6 +791,13 @@ export default {
|
|||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-list-item to="/acc/inquiry/panel">
|
||||
<template v-slot:prepend><v-icon icon="mdi-magnify"></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>
|
||||
|
|
|
@ -211,7 +211,7 @@ export default defineComponent({
|
|||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
timeout="3000"
|
||||
location="top"
|
||||
location="bottom"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
|
|
64
webUI/src/views/acc/inquiry/README.md
Normal file
64
webUI/src/views/acc/inquiry/README.md
Normal file
|
@ -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 کاربر کسر میشود
|
||||
- نتایج در کش ذخیره میشوند تا از تکرار درخواستهای مشابه جلوگیری شود
|
||||
- تمام عملیات لاگ میشوند
|
||||
|
||||
## توسعه آینده
|
||||
|
||||
- تکمیل سرویسهای تبدیل کارت و حساب به شبا
|
||||
- اضافه کردن سرویسهای جدید
|
||||
- بهبود رابط کاربری
|
||||
- اضافه کردن گزارشگیری
|
1158
webUI/src/views/acc/inquiry/panel.vue
Normal file
1158
webUI/src/views/acc/inquiry/panel.vue
Normal file
File diff suppressed because it is too large
Load diff
|
@ -141,7 +141,11 @@
|
|||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field v-model="person.postalcode" :label="$t('pages.person.postal_code')" dense
|
||||
prepend-inner-icon="mdi-mailbox" hide-details />
|
||||
prepend-inner-icon="mdi-mailbox" hide-details>
|
||||
<template v-slot:append-inner>
|
||||
<PostalCode2Address :postal-code="person.postalcode" @address-found="fillAddressFields" @show-snackbar="showSnackbarFromChild" />
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea v-model="person.address" :label="$t('pages.person.address')" dense
|
||||
|
@ -211,15 +215,29 @@
|
|||
<v-overlay :model-value="loading" contained class="align-center justify-center">
|
||||
<v-progress-circular indeterminate size="64" />
|
||||
</v-overlay>
|
||||
|
||||
<!-- Snackbar برای نمایش پیامها -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="snackbar.timeout">
|
||||
{{ snackbar.text }}
|
||||
<template v-slot:actions>
|
||||
<v-btn color="white" text @click="snackbar.show = false">
|
||||
بستن
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Swal from "sweetalert2";
|
||||
import axios from "axios";
|
||||
import { ref } from "vue";
|
||||
import PostalCode2Address from "@/components/widgets/inquiry/postalcode2address.vue";
|
||||
|
||||
export default {
|
||||
name: "insert",
|
||||
components: {
|
||||
PostalCode2Address
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: '0',
|
||||
|
@ -250,6 +268,12 @@ export default {
|
|||
accounts: [],
|
||||
prelabel: ref(null),
|
||||
speedAccess: false
|
||||
},
|
||||
snackbar: {
|
||||
show: false,
|
||||
text: '',
|
||||
color: '',
|
||||
timeout: 3000
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -362,7 +386,7 @@ export default {
|
|||
try {
|
||||
const response = await axios.post('/api/person/mod/' + this.person.code, this.person);
|
||||
this.loading = false;
|
||||
if (response.data.result === 2) {
|
||||
if (response.data && response.data.result === 2) {
|
||||
Swal.fire({
|
||||
text: this.$t('pages.person.already_exists'),
|
||||
icon: 'error',
|
||||
|
@ -386,6 +410,71 @@ export default {
|
|||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
showSnackbar(text, color = 'success', timeout = 3000) {
|
||||
this.snackbar.show = true;
|
||||
this.snackbar.text = text;
|
||||
this.snackbar.color = color;
|
||||
this.snackbar.timeout = timeout;
|
||||
},
|
||||
fillAddressFields(addressData) {
|
||||
// پر کردن فیلدهای آدرس
|
||||
this.person.keshvar = 'ایران'; // کشور به صورت پیشفرض ایران
|
||||
this.person.ostan = addressData.province || '';
|
||||
this.person.shahr = addressData.town || '';
|
||||
this.person.postalcode = addressData.postalCode || '';
|
||||
|
||||
// ساخت آدرس کامل
|
||||
let fullAddress = '';
|
||||
|
||||
if (addressData.district) {
|
||||
fullAddress += addressData.district;
|
||||
}
|
||||
|
||||
if (addressData.street) {
|
||||
if (fullAddress) fullAddress += ' - ';
|
||||
fullAddress += addressData.street;
|
||||
}
|
||||
|
||||
if (addressData.street2) {
|
||||
if (fullAddress) fullAddress += ' - ';
|
||||
fullAddress += addressData.street2;
|
||||
}
|
||||
|
||||
if (addressData.number) {
|
||||
if (fullAddress) fullAddress += ' - پلاک ';
|
||||
fullAddress += addressData.number;
|
||||
}
|
||||
|
||||
if (addressData.floor && addressData.floor !== 'همکف') {
|
||||
if (fullAddress) fullAddress += ' - طبقه ';
|
||||
fullAddress += addressData.floor;
|
||||
}
|
||||
|
||||
if (addressData.side_floor) {
|
||||
if (fullAddress) fullAddress += ' - واحد ';
|
||||
fullAddress += addressData.side_floor;
|
||||
}
|
||||
|
||||
if (addressData.building_name) {
|
||||
if (fullAddress) fullAddress += ' - ';
|
||||
fullAddress += addressData.building_name;
|
||||
}
|
||||
|
||||
if (addressData.description) {
|
||||
if (fullAddress) fullAddress += ' - ';
|
||||
fullAddress += addressData.description;
|
||||
}
|
||||
|
||||
this.person.address = fullAddress;
|
||||
|
||||
// نمایش پیام موفقیت
|
||||
this.$nextTick(() => {
|
||||
this.showSnackbar('اطلاعات آدرس با موفقیت تکمیل شد', 'success');
|
||||
});
|
||||
},
|
||||
showSnackbarFromChild(snackbarData) {
|
||||
this.showSnackbar(snackbarData.text, snackbarData.color, snackbarData.timeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="3000"
|
||||
location="top"
|
||||
location="bottom"
|
||||
>
|
||||
{{ snackbar.text }}
|
||||
<template v-slot:actions>
|
||||
|
|
|
@ -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',
|
||||
|
|
321
webUI/src/views/acc/settings/tax-settings.vue
Normal file
321
webUI/src/views/acc/settings/tax-settings.vue
Normal 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>
|
|
@ -59,6 +59,7 @@
|
|||
</v-alert>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="4">
|
||||
|
@ -161,6 +162,18 @@
|
|||
:disabled="loadingSwitches.archiveView"
|
||||
></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch
|
||||
v-model="info.inquiry"
|
||||
label="سرویس استعلام"
|
||||
@change="savePerms('inquiry')"
|
||||
hide-details
|
||||
color="success"
|
||||
density="comfortable"
|
||||
:loading="loadingSwitches.inquiry"
|
||||
:disabled="loadingSwitches.inquiry"
|
||||
></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
@ -601,8 +614,33 @@
|
|||
</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>
|
||||
</v-container>
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
|
@ -679,7 +717,9 @@ export default {
|
|||
plugNoghreSell: false,
|
||||
plugCCAdmin: false,
|
||||
plugHrmDocs: false,
|
||||
plugGhestaManager: false
|
||||
plugGhestaManager: false,
|
||||
plugTaxSettings: false,
|
||||
inquiry: false
|
||||
};
|
||||
|
||||
axios.post('/api/business/get/user/permissions',
|
||||
|
|
173
webUI/src/views/acc/tax/invoices/list.vue
Normal file
173
webUI/src/views/acc/tax/invoices/list.vue
Normal 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>
|
321
webUI/src/views/acc/tax/tax-settings.vue
Normal file
321
webUI/src/views/acc/tax/tax-settings.vue
Normal 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>
|
|
@ -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: 'زرینپال',
|
||||
|
@ -29,6 +35,13 @@ export default defineComponent({
|
|||
props: { subtitle: 'bitpay.ir' },
|
||||
},
|
||||
],
|
||||
inquiryPanel: [
|
||||
{
|
||||
title: 'پنل زحل',
|
||||
value: 'zohal',
|
||||
props: { subtitle: 'zohal.ir' },
|
||||
},
|
||||
],
|
||||
systemInfo: {
|
||||
keywords: '',
|
||||
description: '',
|
||||
|
@ -38,6 +51,15 @@ export default defineComponent({
|
|||
parsianGatewayAPI: '',
|
||||
paypingKey: '',
|
||||
bitpayKey: '',
|
||||
inquiryPanel: '',
|
||||
inquiryZohalAPIKey: '',
|
||||
enablePostalCodeToAddress: false,
|
||||
inquiryPanelEnable: false,
|
||||
postalCodeToAddressFee: 0,
|
||||
enableCardToSheba: false,
|
||||
cardToShebaFee: 0,
|
||||
enableAccountToSheba: false,
|
||||
accountToShebaFee: 0,
|
||||
},
|
||||
loading: true,
|
||||
}
|
||||
|
@ -47,12 +69,67 @@ export default defineComponent({
|
|||
this.loading = true;
|
||||
axios.post('/api/admin/settings/system/info')
|
||||
.then((response) => {
|
||||
this.systemInfo = response.data;
|
||||
// Convert string values to proper types for switches and selects
|
||||
const data = response.data;
|
||||
this.systemInfo = {
|
||||
...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'
|
||||
};
|
||||
this.loading = false;
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
this.loading = true;
|
||||
|
||||
// Validation: if inquiry panel is enabled, a panel must be selected
|
||||
if (this.systemInfo.inquiryPanelEnable && !this.systemInfo.inquiryPanel) {
|
||||
Swal.fire({
|
||||
text: 'در صورت فعال بودن پنل سامانه استعلامات، حتماً باید یک پنل انتخاب شود.',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول',
|
||||
});
|
||||
this.loading = false;
|
||||
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) {
|
||||
|
@ -62,6 +139,13 @@ export default defineComponent({
|
|||
confirmButtonText: 'قبول',
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.loading = false;
|
||||
Swal.fire({
|
||||
text: 'خطا در ذخیره تنظیمات. لطفاً دوباره تلاش کنید.',
|
||||
icon: 'error',
|
||||
confirmButtonText: 'قبول',
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -75,46 +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 ? 'red' : null" :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-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-card :loading="loading" :disabled="loading">
|
||||
<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-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>
|
|
@ -31,7 +31,7 @@
|
|||
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.distroVersion') }}:</span> {{ systemInfo.distroVersion }}</p>
|
||||
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.webServer') }}:</span> {{ systemInfo.webServer }}</p>
|
||||
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.dbName') }}:</span> {{ systemInfo.dbName }}</p>
|
||||
<p><span class="font-weight-bold primary--useStateFiletext">{{ $t('updateSoftware.dbVersion') }}:</span> {{ systemInfo.dbVersion }}</p>
|
||||
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.dbVersion') }}:</span> {{ systemInfo.dbVersion }}</p>
|
||||
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.currentEnv') }}:</span> {{ selectedEnv }}</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
@ -76,6 +76,39 @@
|
|||
<v-window-item>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<!-- بخش تنظیمات منبع بهروزرسانی -->
|
||||
<v-card class="mb-4" variant="outlined">
|
||||
<v-card-title class="text-subtitle-1">
|
||||
{{ $t('updateSoftware.updateSourceTitle') }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="8">
|
||||
<v-text-field
|
||||
v-model="updateSourceUrl"
|
||||
:label="$t('updateSoftware.updateSourceLabel')"
|
||||
placeholder="https://github.com/username/repository.git"
|
||||
outlined
|
||||
dense
|
||||
:disabled="isUpdating || isChangingSource"
|
||||
:loading="isChangingSource"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:loading="isChangingSource"
|
||||
:disabled="isUpdating || isChangingSource || !updateSourceUrl.trim()"
|
||||
@click="changeUpdateSource"
|
||||
block
|
||||
>
|
||||
{{ $t('updateSoftware.changeSourceButton') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-row justify="end" class="mb-4">
|
||||
<v-col cols="auto">
|
||||
<v-btn-group size="small">
|
||||
|
@ -201,6 +234,8 @@ export default {
|
|||
const isLoadingLogs = ref(false);
|
||||
const isClearingLogs = ref(false);
|
||||
const isPolling = ref(false);
|
||||
const updateSourceUrl = ref('');
|
||||
const isChangingSource = ref(false);
|
||||
|
||||
return {
|
||||
isUpdating,
|
||||
|
@ -229,6 +264,8 @@ export default {
|
|||
isLoadingLogs,
|
||||
isClearingLogs,
|
||||
isPolling,
|
||||
updateSourceUrl,
|
||||
isChangingSource,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -508,6 +545,32 @@ export default {
|
|||
};
|
||||
}
|
||||
},
|
||||
async fetchCurrentSource() {
|
||||
try {
|
||||
const response = await axios.get('/api/admin/updatecore/current-source', {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
timeout: 7200000 // تایماوت 2 ساعته
|
||||
});
|
||||
|
||||
if (response.data.status === 'success') {
|
||||
this.updateSourceUrl = response.data.sourceUrl || '';
|
||||
} else {
|
||||
console.error('Failed to fetch current source:', response.data.message);
|
||||
this.updateSourceUrl = '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch current source:', error);
|
||||
this.updateSourceUrl = '';
|
||||
|
||||
// نمایش پیام خطا به کاربر
|
||||
if (error.response?.data?.message) {
|
||||
this.showResultDialog = true;
|
||||
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
|
||||
this.dialogMessage = error.response.data.message;
|
||||
this.dialogColor = 'error';
|
||||
}
|
||||
}
|
||||
},
|
||||
async fetchCurrentEnv() {
|
||||
try {
|
||||
const response = await axios.get('/api/admin/updatecore/current-env', {
|
||||
|
@ -570,6 +633,42 @@ export default {
|
|||
this.isClearingLogs = false;
|
||||
}
|
||||
},
|
||||
async changeUpdateSource() {
|
||||
if (!this.updateSourceUrl.trim()) {
|
||||
this.showResultDialog = true;
|
||||
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
|
||||
this.dialogMessage = this.$t('updateSoftware.sourceUrlRequired');
|
||||
this.dialogColor = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isChangingSource = true;
|
||||
this.showOutput = true;
|
||||
this.output = this.$t('updateSoftware.changingSourceMessage') + '\n';
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/admin/updatecore/change-source', {
|
||||
sourceUrl: this.updateSourceUrl.trim()
|
||||
}, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
timeout: 7200000 // تایماوت 2 ساعته
|
||||
});
|
||||
|
||||
this.output += response.data.output || response.data.message + '\n';
|
||||
this.showResultDialog = true;
|
||||
this.dialogTitle = this.$t('updateSoftware.dialogSuccessTitle');
|
||||
this.dialogMessage = response.data.message || this.$t('updateSoftware.repositoryChangeSuccess');
|
||||
this.dialogColor = 'success';
|
||||
} catch (error) {
|
||||
this.output += 'خطا: ' + (error.response?.data?.message || error.message) + '\n';
|
||||
this.showResultDialog = true;
|
||||
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
|
||||
this.dialogMessage = error.response?.data?.message || this.$t('updateSoftware.sourceChangeError');
|
||||
this.dialogColor = 'error';
|
||||
} finally {
|
||||
this.isChangingSource = false;
|
||||
}
|
||||
},
|
||||
copyLogsToClipboard() {
|
||||
const plainLogs = this.systemLogs.replace(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+\+\d{2}:\d{2}\]/g, '\n[$&]')
|
||||
.replace(/\s+\[\]/g, ' []')
|
||||
|
@ -593,6 +692,7 @@ export default {
|
|||
this.fetchCommits();
|
||||
this.fetchSystemInfo();
|
||||
this.fetchCurrentEnv();
|
||||
this.fetchCurrentSource();
|
||||
this.buttonText = this.$t('updateSoftware.startButton');
|
||||
this.refreshLogs();
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<td>{{ item.totalIncome }}</td>
|
||||
<td>{{ calculateStatus(item) }}</td>
|
||||
<td>
|
||||
<v-tooltip v-if="calculateStatus(item) === 'در صف تسویه'" location="top">
|
||||
<v-tooltip v-if="calculateStatus(item) === 'در انتظار پرداخت'" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="text" icon v-bind="props" @click="openTransactionDialog(item)">
|
||||
<v-icon>mdi-cash-register</v-icon>
|
||||
|
@ -80,6 +80,7 @@
|
|||
<td>{{ item.bidName }}</td>
|
||||
<td>{{ item.bankAcName }}</td>
|
||||
<td>{{ item.type === 'pay' ? 'پرداخت' : 'دریافت' }}</td>
|
||||
<td>{{ $filters.formatNumber(item.amount) }}</td>
|
||||
<td>{{ item.gatePay }}</td>
|
||||
<td>{{ item.refID }}</td>
|
||||
<td>{{ item.shaba }}</td>
|
||||
|
@ -186,6 +187,7 @@ export default {
|
|||
{ title: "کسبوکار", key: "bidName", sortable: false, align: 'center' },
|
||||
{ title: "بانک", key: "bankAcName", sortable: false, align: 'center' },
|
||||
{ title: "نوع", key: "type", sortable: false, align: 'center' },
|
||||
{ title: "مبلغ", key: "amount", sortable: false, align: 'center' },
|
||||
{ title: "درگاه پرداخت", key: "gatePay", sortable: false, align: 'center' },
|
||||
{ title: "شناسه تراکنش", key: "refID", sortable: false, align: 'center' },
|
||||
{ title: "شبا", key: "shaba", sortable: false, align: 'center' },
|
||||
|
|
Loading…
Reference in a new issue