update for Moadian plugin

This commit is contained in:
root 2025-07-20 13:18:37 +00:00
parent f7bbff446f
commit 669ad2f97f
20 changed files with 2562 additions and 533 deletions

View file

@ -23,6 +23,7 @@
"phpoffice/phpspreadsheet": "^2.3",
"phpstan/phpdoc-parser": "^1.33",
"ramsey/uuid": "^4.7",
"snapp-market-pro/moadian": "^1.1",
"symfony/apache-pack": "^1.0",
"symfony/asset": "7.2.*",
"symfony/console": "7.2.*",

View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fc8e55a0f3d505b2453542a73030d32c",
"content-hash": "43db0ad2bb94569ed6d44cabf503210e",
"packages": [
{
"name": "brick/math",
@ -1563,6 +1563,331 @@
],
"time": "2025-03-06T22:45:56+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2025-03-27T13:37:11+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.2.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2025-03-27T13:27:01+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2025-03-27T12:30:47+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.2",
@ -2447,6 +2772,73 @@
},
"time": "2024-12-30T11:07:19+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512",
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512",
"shasum": ""
},
"require": {
"php": "^8"
},
"require-dev": {
"phpunit/phpunit": "^9",
"vimeo/psalm": "^4|^5"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2024-05-08T12:36:18+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
@ -2777,6 +3169,116 @@
},
"time": "2025-02-08T03:01:45+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.46",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2025-06-26T16:29:55+00:00"
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.33.0",
@ -3341,6 +3843,50 @@
},
"time": "2021-10-29T13:26:27+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "ramsey/collection",
"version": "2.1.1",
@ -3646,6 +4192,65 @@
],
"time": "2025-02-05T13:22:35+00:00"
},
{
"name": "snapp-market-pro/moadian",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/Snapp-Market-Pro/moadian.git",
"reference": "cf178ea7a0f6830c25fc636c9d2e8f0d871b6158"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Snapp-Market-Pro/moadian/zipball/cf178ea7a0f6830c25fc636c9d2e8f0d871b6158",
"reference": "cf178ea7a0f6830c25fc636c9d2e8f0d871b6158",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"guzzlehttp/guzzle": "^7.5",
"php": "^8.0",
"phpseclib/phpseclib": "~3.0",
"ramsey/uuid": "^4.7"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.17",
"monolog/monolog": "^3.3",
"phpunit/phpunit": "^10.0",
"symfony/var-dumper": "^6.2"
},
"type": "library",
"autoload": {
"psr-4": {
"SnappMarketPro\\Moadian\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ali Rahimi",
"email": "arahimihajiabadi@gmail.com"
},
{
"name": "Amir Reza Mehbakhsh",
"email": "theamirrezam75@gmail.com"
},
{
"name": "Soheil Yousefi",
"email": "myparstik@gmail.com"
}
],
"description": "PHP SDK for working with tp.tax.gov.ir (سامانه مودیان مالیاتی)",
"support": {
"issues": "https://github.com/Snapp-Market-Pro/moadian/issues",
"source": "https://github.com/Snapp-Market-Pro/moadian/tree/1.1.1"
},
"abandoned": true,
"time": "2023-11-07T20:19:53+00:00"
},
{
"name": "symfony/apache-pack",
"version": "v1.0.1",

View file

View file

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" serverName="localhost" />
</project>

View file

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="App\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/var" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/maker-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/inflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-access" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/property-info" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/password-hasher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-csrf" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-core" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/security-http" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/event-manager" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/dbal" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/deprecations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/lexer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/sql-formatter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/migrations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/persistence" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/doctrine-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laminas/laminas-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/friendsofphp/proxy-manager-lts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfonycasts/verify-email-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nelmio/cors-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/annotations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/serializer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpdoc-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/apache-pack" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/hesabixCore.iml" filepath="$PROJECT_DIR$/.idea/hesabixCore.iml" />
</modules>
</component>
</project>

View file

@ -1,100 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/flex" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/maker-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/inflector" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/symfony/security-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/vendor/symfony/property-info" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/security-csrf" />
<path value="$PROJECT_DIR$/vendor/symfony/security-core" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/doctrine/dbal" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/lexer" />
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/common" />
<path value="$PROJECT_DIR$/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/vendor/doctrine/migrations" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/doctrine/persistence" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-migrations-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/doctrine-bridge" />
<path value="$PROJECT_DIR$/vendor/symfony/stopwatch" />
<path value="$PROJECT_DIR$/vendor/laminas/laminas-code" />
<path value="$PROJECT_DIR$/vendor/friendsofphp/proxy-manager-lts" />
<path value="$PROJECT_DIR$/vendor/symfonycasts/verify-email-bundle" />
<path value="$PROJECT_DIR$/vendor/nelmio/cors-bundle" />
<path value="$PROJECT_DIR$/vendor/doctrine/annotations" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/phpstan/phpdoc-parser" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/type-resolver" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/vendor/phpdocumentor/reflection-common" />
<path value="$PROJECT_DIR$/vendor/webmozart/assert" />
<path value="$PROJECT_DIR$/vendor/symfony/apache-pack" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.1">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Symfony2PluginSettings">
<option name="pluginEnabled" value="true" />
<option name="lastServiceGeneratorLanguage" value="yaml" />
</component>
</project>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -263,6 +263,9 @@ class CommodityController extends AbstractController
'floatNumber' => $item->getUnit()->getFloatNumber(),
];
$temp['barcodes'] = $item->getBarcodes();
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
@ -327,6 +330,9 @@ class CommodityController extends AbstractController
$temp['minOrderCount'] = $item->getMinOrderCount();
$temp['dayLoading'] = $item->getDayLoading();
$temp['orderPoint'] = $item->getOrderPoint();
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
@ -418,8 +424,11 @@ class CommodityController extends AbstractController
$temp['commodityCountCheck'] = $item->isCommodityCountCheck();
$temp['minOrderCount'] = $item->getMinOrderCount();
$temp['dayLoading'] = $item->getDayLoading();
$temp['orderPoint'] = $item->getOrderPoint();
//calculate count
$temp['orderPoint'] = $item->getOrderPoint();
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
} else {
@ -731,6 +740,18 @@ class CommodityController extends AbstractController
$data->setBarcodes($params['barcodes']);
}
if (array_key_exists('taxCode', $params)) {
$data->setTaxCode($params['taxCode']);
}
if (array_key_exists('taxType', $params)) {
$data->setTaxType($params['taxType']);
}
if (array_key_exists('taxUnit', $params)) {
$data->setTaxUnit($params['taxUnit']);
}
if (array_key_exists('minOrderCount', $params)) {
$data->setMinOrderCount($params['minOrderCount']);
}
@ -862,6 +883,18 @@ class CommodityController extends AbstractController
$data->setBarcodes($params['barcodes']);
}
if (array_key_exists('taxCode', $params)) {
$data->setTaxCode($params['taxCode']);
}
if (array_key_exists('taxType', $params)) {
$data->setTaxType($params['taxType']);
}
if (array_key_exists('taxUnit', $params)) {
$data->setTaxUnit($params['taxUnit']);
}
if (array_key_exists('minOrderCount', $params)) {
$data->setMinOrderCount($params['minOrderCount']);
}
@ -1279,6 +1312,12 @@ class CommodityController extends AbstractController
$commodity->setMinOrderCount($item[5]);
if (array_key_exists(6, $item))
$commodity->setDes($item[6]);
if (array_key_exists(9, $item))
$commodity->setTaxCode($item[9]);
if (array_key_exists(10, $item))
$commodity->setTaxType($item[10]);
if (array_key_exists(11, $item))
$commodity->setTaxUnit($item[11]);
if (array_key_exists(0, $item)) {
$commodity->setKhadamat(true);
if ($item[0] == '1') {

File diff suppressed because it is too large Load diff

View file

@ -83,6 +83,15 @@ class Commodity
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $barcodes = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $taxCode = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $taxType = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $taxUnit = null;
#[ORM\OneToMany(mappedBy: 'commodity', targetEntity: PriceListDetail::class, orphanRemoval: true)]
private Collection $priceListDetails;
@ -421,6 +430,42 @@ class Commodity
return $this;
}
public function getTaxCode(): ?string
{
return $this->taxCode;
}
public function setTaxCode(?string $taxCode): static
{
$this->taxCode = $taxCode;
return $this;
}
public function getTaxType(): ?string
{
return $this->taxType;
}
public function setTaxType(?string $taxType): static
{
$this->taxType = $taxType;
return $this;
}
public function getTaxUnit(): ?string
{
return $this->taxUnit;
}
public function setTaxUnit(?string $taxUnit): static
{
$this->taxUnit = $taxUnit;
return $this;
}
/**
* @return Collection<int, PriceListDetail>
*/

View file

@ -62,6 +62,9 @@ class PluginTaxInvoice
#[ORM\Column(length: 255, nullable: true)]
private ?string $customerId = null;
#[ORM\Column(length: 50, nullable: true)]
private ?string $invoiceType = 'اصلی';
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
@ -236,4 +239,15 @@ class PluginTaxInvoice
$this->customerId = $customerId;
return $this;
}
public function getInvoiceType(): ?string
{
return $this->invoiceType;
}
public function setInvoiceType(?string $invoiceType): static
{
$this->invoiceType = $invoiceType;
return $this;
}
}

View file

@ -22,6 +22,9 @@ class PluginTaxsettingsKey
#[ORM\Column(type: "text", nullable: true)]
private $private_key;
#[ORM\Column(type: "text", nullable: true)]
private $certificate;
#[ORM\Column(type: "string", length: 64, nullable: true)]
private $tax_memory_id;
@ -42,6 +45,8 @@ class PluginTaxsettingsKey
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 getCertificate() { return $this->certificate; }
public function setCertificate($val) { $this->certificate = $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; }

View file

@ -233,6 +233,9 @@ class Explore
'orderPoint' => $item->getOrderPoint(),
'dayLoading' => $item->getDayLoading(),
'minOrderCount' => $item->getMinOrderCount(),
'taxCode' => $item->getTaxCode(),
'taxType' => $item->getTaxType(),
'taxUnit' => $item->getTaxUnit(),
'unitData' => [
'name' => $item->getUnit()->getName(),
'floatNumber' => $item->getUnit()->getFloatNumber(),

View file

@ -202,16 +202,27 @@
</div>
<div class="row">
<div class="col-sm-12 col-md-4">
<div class="form-floating mb-4">
<input v-model="data.taxCode" class="form-control" type="text">
<label class="form-label">کد مالیاتی</label>
<div class="form-floating mb-4">
<input v-model="data.taxCode" class="form-control" type="text">
<label class="form-label">کد مالیاتی</label>
</div>
</div>
</div>
<div class="col-sm-6 col-md-4">
<div class="form-floating mb-4">
<select v-model="data.taxType" class="form-select">
<option value="" disabled selected>انتخاب کنید</option>
<!-- گزینهها بعداً اضافه میشوند -->
<option value="1">۱- دارو</option>
<option value="2">۲- دخانیات</option>
<option value="3">۳- موبایل</option>
<option value="4">۴- لوازم خانگی برقی</option>
<option value="5">۵- قطعات مصرفی و یدکی وسایل نقلیه</option>
<option value="6">۶- فراورده ها و مشتقات نفتی و گازی و پتروشیمیایی</option>
<option value="7">۷- طلا اعم از شمش ،مسکوکات و مصنوعات زینتی</option>
<option value="8">۸- منسوجات و پوشاک</option>
<option value="9">۹- اسباب بازی</option>
<option value="10">۱۰- دام زنده، گوشت سفید و قرمز</option>
<option value="11">۱۱- محصولات اساسی کشاورزی</option>
<option value="12">۱۲- سایر کالا ها</option>
</select>
<label class="form-label">نوع مالیاتی</label>
</div>
@ -220,7 +231,108 @@
<div class="form-floating mb-4">
<select v-model="data.taxUnit" class="form-select">
<option value="" disabled selected>انتخاب کنید</option>
<!-- گزینهها بعداً اضافه میشوند -->
<option value="1611">لنگه</option>
<option value="1612">عدل</option>
<option value="1613">جعبه</option>
<option value="1618">توپ</option>
<option value="1619">ست</option>
<option value="1620">دست</option>
<option value="1624">کارتن</option>
<option value="1627">عدد</option>
<option value="1628">بسته</option>
<option value="1629">پاکت</option>
<option value="1631">دستگاه</option>
<option value="1640">تخته</option>
<option value="1641">رول</option>
<option value="1642">طاقه</option>
<option value="1643">جفت</option>
<option value="1645">مترمربع</option>
<option value="1649">پالت</option>
<option value="1661">دوجین</option>
<option value="1668">رینگ حلقه</option>
<option value="1673">قراص</option>
<option value="1694">قراصه bundle</option>
<option value="1637">لیتر</option>
<option value="1650">ساشه</option>
<option value="1683">کپسول</option>
<option value="1656">بندیل</option>
<option value="1630">رول حلقه</option>
<option value="163">قالب</option>
<option value="1660">شانه</option>
<option value="1647">مترمکعب</option>
<option value="1689">ثوب</option>
<option value="1690">نیم دو جین</option>
<option value="1635">قرقره</option>
<option value="164">کیلوگرم</option>
<option value="1638">بطری</option>
<option value="161">برگ</option>
<option value="1625">سطل</option>
<option value="1654">ورق</option>
<option value="1646">شاخه</option>
<option value="1644">قوطی</option>
<option value="1617">جلد</option>
<option value="162">تیوب</option>
<option value="165">متر</option>
<option value="1610">کلاف</option>
<option value="1615">کیسه</option>
<option value="1680">طغرا</option>
<option value="1639">بشکه</option>
<option value="1614">گالن</option>
<option value="1687">فاقد بسته بندی</option>
<option value="1693">کارتن master case</option>
<option value="166">صفحه</option>
<option value="1666">مخزن</option>
<option value="1626">تانکر</option>
<option value="1648">دبه</option>
<option value="1684">سبد</option>
<option value="169">تن</option>
<option value="1651">بانکه</option>
<option value="1633">سیلندر</option>
<option value="1679">فوت مربع</option>
<option value="168">حلب</option>
<option value="1665">شیت</option>
<option value="1659">چلیک</option>
<option value="1636">جام</option>
<option value="1622">گرم</option>
<option value="1616">نخ</option>
<option value="1652">شعله</option>
<option value="1678">قیراط</option>
<option value="16100">میلی لیتر</option>
<option value="16101">میلی متر</option>
<option value="16102">میلی گرم</option>
<option value="16103">ساعت</option>
<option value="16104">روز</option>
<option value="16105">تن کیلومتر</option>
<option value="1669">کیلووات ساعت</option>
<option value="1676">نفر</option>
<option value="16110">ثانیه</option>
<option value="16111">دقیقه</option>
<option value="16112">ماه</option>
<option value="16113">سال</option>
<option value="16114">قطعه</option>
<option value="16115">سانتی متر</option>
<option value="16116">سانتی متر مربع</option>
<option value="1632">فروند</option>
<option value="1653">واحد</option>
<option value="16108">لیوان</option>
<option value="16117">نوبت</option>
<option value="16118">مگا وات ساعت</option>
<option value="16119">گیگا بایت بر ثانیه</option>
<option value="1681">ویال</option>
<option value="1667">حلقه (دیسک)</option>
<option value="16120">نسخه (جلد)</option>
<option value="16121">نفر-ساعت</option>
<option value="16122">کیلومتر</option>
<option value="16125">آمپر</option>
<option value="16126">میلی آمپر</option>
<option value="16127">مثقال</option>
<option value="16128">سیر</option>
<option value="16129">دفعه (time)</option>
<option value="16130">مگایونیت</option>
<option value="16131">کادر</option>
<option value="16132">پرس</option>
<option value="16133">بلوک</option>
<option value="16134">نفر-ماه</option>
</select>
<label class="form-label">واحد مالیاتی</label>
</div>
@ -308,7 +420,7 @@ export default {
this.loadData(to.params.id);
},
methods: {
generateBarcode(){
generateBarcode() {
for (let index = 0; index < this.barcode.count; index++) {
let x = Math.random() * 1000000000000000000;
this.data.barcodes = this.data.barcodes + ';' + x

View file

@ -58,12 +58,12 @@
</span>
</template>
<template v-slot:item.uniqueTaxNumber="{ item }">
<span class="text-success font-weight-medium">
<span class="font-weight-medium">
{{ item.uniqueTaxNumber || '-' }}
</span>
</template>
<template v-slot:item.referenceUniqueTaxNumber="{ item }">
<span class="text-info font-weight-medium">
<span class="font-weight-medium">
{{ item.referenceUniqueTaxNumber || '-' }}
</span>
</template>
@ -78,6 +78,38 @@
<v-btn variant="text" size="small" color="error" icon="mdi-menu" v-bind="props" />
</template>
<v-list>
<v-list-item
v-if="item.status === 'pending'"
class="text-green-darken-4"
:title="'ارسال به سامانه'"
@click="sendInvoice(item)"
:loading="item.sending"
>
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-cloud-upload"></v-icon>
</template>
</v-list-item>
<v-list-item
v-if="item.status === 'error'"
class="text-green-darken-4"
:title="'ارسال مجدد به سامانه'"
@click="sendInvoice(item)"
:loading="item.sending"
>
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-cloud-upload"></v-icon>
</template>
</v-list-item>
<v-list-item
v-if="item.status === 'error'"
class="text-dark"
:title="'مشاهده خطاها'"
@click="showErrorsDialog(item)"
>
<template v-slot:prepend>
<v-icon color="red-darken-4" icon="mdi-alert-circle"></v-icon>
</template>
</v-list-item>
<v-list-item class="text-dark" :title="'مشاهده فاکتور'" :to="'/acc/sell/view/' + item.invoiceNumber">
<template v-slot:prepend>
<v-icon color="green-darken-4" icon="mdi-eye"></v-icon>
@ -88,6 +120,27 @@
<v-icon color="blue-darken-4" icon="mdi-account"></v-icon>
</template>
</v-list-item>
<v-list-item
v-if="item.uniqueTaxNumber && item.status === 'sent'"
class="text-dark"
:title="'بررسی وضعیت صورت حساب'"
@click="checkInvoiceStatus(item)"
:loading="item.checkingStatus"
>
<template v-slot:prepend>
<v-icon color="orange-darken-4" icon="mdi-refresh"></v-icon>
</template>
</v-list-item>
<v-list-item
v-if="item.status === 'pending' || item.status === 'error'"
class="text-red-darken-4"
:title="'حذف'"
@click="deleteInvoice(item)"
>
<template v-slot:prepend>
<v-icon color="deep-orange-accent-4" icon="mdi-trash-can"></v-icon>
</template>
</v-list-item>
</v-list>
</v-menu>
</template>
@ -98,11 +151,120 @@
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
{{ snackbar.text }}
</v-snackbar>
<v-dialog v-model="errorsDialog.show" max-width="600px" persistent>
<v-card>
<v-card-title class="d-flex align-center text-h6">
<v-icon color="red" class="me-3">mdi-alert-circle</v-icon>
خطاهای فاکتور شماره {{ errorsDialog.invoiceNumber }}
<v-spacer></v-spacer>
<v-btn icon="mdi-close" variant="text" @click="errorsDialog.show = false"></v-btn>
</v-card-title>
<v-card-text class="pa-4" style="max-height: 60vh; overflow-y: auto;">
<v-alert type="error" variant="tonal" class="mb-4" icon="mdi-alert">
<div class="text-body-1">
<strong>فاکتور دارای خطا است.</strong>
<div class="mt-2">لطفاً خطاهای زیر را برطرف کنید و مجدداً ارسال کنید:</div>
</div>
</v-alert>
<div v-if="errorsDialog.errors && errorsDialog.errors.length > 0">
<div class="text-subtitle-1 font-weight-medium mb-3 text-red-darken-2">
<v-icon size="small" color="red" class="me-2">mdi-close-circle</v-icon>
خطاها ({{ errorsDialog.errors.length }} مورد):
</div>
<v-list density="compact" class="mb-4">
<v-list-item
v-for="(error, index) in errorsDialog.errors"
:key="index"
class="mb-3 pa-3"
style="border: 1px solid #ffebee; border-radius: 8px; background-color: #fff5f5;"
>
<template v-slot:prepend>
<v-icon color="red" size="small" class="mt-1">mdi-alert</v-icon>
</template>
<v-list-item-title class="text-body-2 font-weight-medium mb-1 error-message">
<div v-if="error.code" class="error-code mb-2">
<v-chip size="small" color="red" variant="outlined" class="font-weight-bold">
کد خطا: {{ error.code }}
</v-chip>
</div>
{{ error.message }}
</v-list-item-title>
</v-list-item>
</v-list>
</div>
<!-- <div v-if="errorsDialog.warnings && errorsDialog.warnings.length > 0">
<div class="text-subtitle-1 font-weight-medium mb-3 text-orange-darken-2">
<v-icon size="small" color="orange" class="me-2">mdi-alert-circle</v-icon>
هشدارها ({{ errorsDialog.warnings.length }} مورد):
</div>
<v-list density="compact" class="mb-4">
<v-list-item
v-for="(warning, index) in errorsDialog.warnings"
:key="index"
class="mb-3 pa-3"
style="border: 1px solid #fff3e0; border-radius: 8px; background-color: #fffbf0;"
>
<template v-slot:prepend>
<v-icon color="orange" size="small" class="mt-1">mdi-information</v-icon>
</template>
<v-list-item-title class="text-body-2 font-weight-medium mb-1 warning-message">
<div v-if="warning.code" class="warning-code mb-2">
<v-chip size="small" color="orange" variant="outlined" class="font-weight-bold">
کد هشدار: {{ warning.code }}
</v-chip>
</div>
{{ warning.message }}
</v-list-item-title>
</v-list-item>
</v-list>
</div> -->
<!-- <v-alert type="info" variant="tonal" class="mb-0" icon="mdi-information">
<div class="text-body-2">
<strong>راهنمایی برای اصلاح خطاها:</strong>
<ul class="mt-2 mb-0">
<li><strong>نرخ مالیات:</strong> نرخ مالیات بر ارزش افزوده را بررسی کنید</li>
<li><strong>شماره اقتصادی:</strong> شماره اقتصادی فروشنده را در تنظیمات مالیاتی بررسی کنید</li>
<li><strong>تاریخ فاکتور:</strong> تاریخ فاکتور را در محدوده مجاز تنظیم کنید</li>
<li><strong>مشتری:</strong> اطلاعات مشتری (کد ملی/اقتصادی) را بررسی کنید</li>
<li>پس از اصلاح، فاکتور را مجدداً ارسال کنید</li>
</ul>
</div>
</v-alert> -->
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn
color="primary"
variant="outlined"
@click="errorsDialog.show = false"
prepend-icon="mdi-close"
>
بستن
</v-btn>
<v-btn
color="primary"
:to="'/acc/sell/mod/' + errorsDialog.invoiceNumber"
prepend-icon="mdi-pencil"
>
ویرایش فاکتور
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import axios from 'axios';
import Swal from 'sweetalert2';
export default {
name: 'TaxInvoicesList',
data() {
@ -114,6 +276,12 @@
text: '',
color: 'success'
},
errorsDialog: {
show: false,
invoiceNumber: '',
errors: [],
warnings: []
},
headers: [
{ title: 'عملیات', key: 'actions', sortable: false },
{ title: 'شماره فاکتور', key: 'invoiceNumber', sortable: true },
@ -136,7 +304,7 @@
if (response.data.success) {
this.invoices = response.data.data.map(invoice => ({
...invoice,
totalAmount: this.$filters?.formatNumber ? this.$filters.formatNumber(invoice.totalAmount) : invoice.totalAmount,
totalAmount: this.$filters?.formatNumber ? this.$filters.formatNumber(invoice.invoiceDetails?.amount) : invoice.invoiceDetails?.amount,
date: invoice.invoiceDetails?.date ? this.formatDate(invoice.invoiceDetails.date) : '-',
sentDate: invoice.sentDate ? this.formatDate(invoice.sentDate) : '-',
createdAt: this.formatDate(invoice.createdAt)
@ -166,10 +334,12 @@
},
getStatusColor(status) {
switch (status) {
case 'sent':
case 'accepted':
return 'success';
case 'pending':
case 'sent':
return 'warning';
case 'pending':
return 'grey';
case 'error':
return 'error';
default:
@ -178,12 +348,18 @@
},
getStatusText(status) {
switch (status) {
case 'accepted':
return 'تایید شده';
case 'sent':
return 'ارسال شده';
case 'pending':
return 'در انتظار';
return 'ارسال نشده';
case 'error':
return 'خطا';
case 'FAILED':
return 'خطا';
case 'ACCEPTED':
return 'تایید شده';
default:
return 'نامشخص';
}
@ -198,6 +374,10 @@
return 'error';
case 'ابطالی':
return 'grey';
case 'FAILED':
return 'error';
case 'ACCEPTED':
return 'success';
default:
return 'primary';
}
@ -208,6 +388,197 @@
text,
color
};
},
async checkInvoiceStatus(item) {
if (!item.uniqueTaxNumber) {
this.showSnackbar('شماره منحصر به فرد مالیاتی موجود نیست', 'error');
return;
}
item.checkingStatus = true;
try {
const response = await axios.post('/api/plugins/tax/inquire-status', {
referenceNumbers: [item.uniqueTaxNumber]
});
if (response.data.success) {
const statusData = response.data.data;
if (statusData && statusData.length > 0) {
const invoiceStatus = statusData[0];
if ((invoiceStatus.data && invoiceStatus.data.error && invoiceStatus.data.error.length > 0) ||
(invoiceStatus.data && invoiceStatus.data.warning && invoiceStatus.data.warning.length > 0)) {
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: invoiceStatus.data?.error || [],
warnings: invoiceStatus.data?.warning || []
};
} else {
let statusMessage = `وضعیت فاکتور: ${this.getStatusText(invoiceStatus.status) || 'نامشخص'}`;
this.showSnackbar(statusMessage, this.getStatusColor(invoiceStatus.status));
}
this.loadData();
} else {
this.showSnackbar('اطلاعات وضعیت یافت نشد', 'warning');
}
} else {
this.showSnackbar('خطا در بررسی وضعیت: ' + (response.data.message || 'خطای نامشخص'), 'error');
}
} catch (error) {
this.showSnackbar('خطا در بررسی وضعیت: ' + error.message, 'error');
} finally {
item.checkingStatus = false;
}
},
showErrorsDialog(item) {
if (item.errorMessage) {
try {
const errorData = JSON.parse(item.errorMessage);
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: errorData.errors || [],
warnings: errorData.warnings || []
};
return;
} catch (e) {
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: [{ message: item.errorMessage, code: 'ERROR' }],
warnings: []
};
return;
}
}
if (item.responseData) {
try {
const responseData = JSON.parse(item.responseData);
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: responseData.errors || [],
warnings: responseData.warnings || []
};
} catch (e) {
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: [{ message: 'خطا در پردازش اطلاعات', code: 'ERROR' }],
warnings: []
};
}
return;
}
this.checkInvoiceStatusForErrors(item);
},
async checkInvoiceStatusForErrors(item) {
if (!item.uniqueTaxNumber) {
this.showSnackbar('شماره منحصر به فرد مالیاتی موجود نیست', 'error');
return;
}
try {
const response = await axios.post('/api/plugins/tax/inquire-status', {
referenceNumbers: [item.uniqueTaxNumber]
});
if (response.data.success && response.data.data && response.data.data.length > 0) {
const invoiceStatus = response.data.data[0];
this.errorsDialog = {
show: true,
invoiceNumber: item.invoiceNumber,
errors: invoiceStatus.data?.error || [],
warnings: invoiceStatus.data?.warning || []
};
} else {
this.showSnackbar('اطلاعات خطا یافت نشد', 'warning');
}
} catch (error) {
this.showSnackbar('خطا در دریافت اطلاعات خطا: ' + error.message, 'error');
}
},
deleteInvoice(item) {
Swal.fire({
title: '',
text: `آیا برای حذف این مورد مطمئن هستید؟\nاین پروسه قابل برگشت نیست!`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'بله',
cancelButtonText: 'خیر'
}).then((result) => {
if (result.isConfirmed) {
this.performDelete(item);
}
});
},
async performDelete(item) {
try {
const response = await axios.delete(`/api/plugins/tax/invoice/delete/${item.id}`);
if (response.data.success) {
this.showSnackbar('فاکتور با موفقیت حذف شد', 'success');
this.loadData();
} else {
this.showSnackbar(response.data.message || 'خطا در حذف فاکتور', 'error');
}
} catch (error) {
this.showSnackbar('خطا در حذف فاکتور: ' + (error.response?.data?.message || error.message), 'error');
}
},
sendInvoice(item) {
Swal.fire({
title: '',
text: `آیا از ارسال فاکتور شماره ${item.invoiceNumber} به سامانه مودیان اطمینان دارید؟`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'بله',
cancelButtonText: 'انصراف',
}).then((result) => {
if (result.isConfirmed) {
this.performSend(item);
}
});
},
async performSend(item) {
item.sending = true;
try {
const response = await axios.post(`/api/plugins/tax/invoice/send/${item.id}`);
if (response.data.success) {
Swal.fire({
title: 'ارسال موفق',
text: 'فاکتور با موفقیت به سامانه مودیان ارسال شد',
icon: 'success',
confirmButtonText: 'باشه'
});
this.loadData();
} else {
Swal.fire({
title: 'خطا در ارسال فاکتور',
text: response.data.message || 'خطا در ارسال فاکتور',
icon: 'error',
confirmButtonText: 'باشه'
});
}
} catch (error) {
Swal.fire({
title: 'خطا در ارسال فاکتور',
text: 'خطا در ارسال فاکتور: ' + (error.response?.data?.message || error.message),
icon: 'error',
confirmButtonText: 'باشه'
});
} finally {
item.sending = false;
}
}
},
mounted() {
@ -257,4 +628,27 @@
.font-weight-bold {
font-weight: bold !important;
}
.error-message,
.warning-message {
white-space: pre-wrap !important;
word-wrap: break-word !important;
word-break: break-word !important;
line-height: 1.5 !important;
max-width: 100% !important;
overflow-wrap: break-word !important;
}
.error-code,
.warning-code {
display: flex;
justify-content: flex-start;
}
@media (max-width: 768px) {
.error-code,
.warning-code {
justify-content: center;
}
}
</style>

View file

@ -1,4 +1,4 @@
<template>
<template>
<div>
<v-toolbar color="toolbar" title="تنظیمات مالیاتی">
<template v-slot:prepend>
@ -15,131 +15,223 @@
<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-text-field v-model="settings.taxMemoryId" label="شناسه یکتای حافظه مالیاتی" hide-details
density="compact" prepend-icon="mdi-identifier" variant="outlined"></v-text-field>
</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-text-field v-model="settings.economicCode" label="کد اقتصادی" hide-details density="compact"
prepend-icon="mdi-briefcase" variant="outlined"></v-text-field>
</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-card variant="elevated" class="pa-6" color="surface">
<div class="d-flex align-center mb-4">
<v-icon color="primary" class="me-3" size="large">mdi-key</v-icon>
<h3 class="text-h5 mb-0 text-primary-darken-2">کلید خصوصی</h3>
</div>
<v-row>
<v-col cols="12" lg="5" class="mb-4 mb-lg-0">
<v-alert type="info" variant="tonal" class="mb-4" border="start" color="primary">
<template v-slot:prepend>
<v-icon icon="mdi-information-outline" color="primary" size="24"></v-icon>
</template>
<div class="text-body-1">
<div class="d-flex align-center mb-2">
<span class="font-weight-medium">نکات مهم:</span>
</div>
<ul class="mb-0 ps-6">
<li class="mb-2">فقط فایل های .key، .pem و .txt قابل قبول هستند</li>
<li class="mb-2">حداکثر حجم فایل: 1 مگابایت</li>
<li>این فایل برای امضای دیجیتال فاکتور ها استفاده میشود</li>
</ul>
</div>
</v-alert>
<v-card variant="flat" class="pa-3 pa-md-4" color="transparent">
<div class="d-flex align-center mb-3">
<v-icon color="green" class="me-2">mdi-upload</v-icon>
<span class="text-subtitle-2 text-md-subtitle-1 font-weight-medium text-black">بارگذاری فایل</span>
</div>
<div v-if="!settings.privateKey">
<v-row>
<v-col cols="12" md="8">
<v-file-input
v-model="privateKeyFile"
label="انتخاب فایل کلید خصوصی"
accept=".key,.pem,.txt"
:error-messages="fileErrors.privateKey"
@change="validatePrivateKeyFile"
variant="outlined"
density="comfortable"
placeholder="فایل کلید خصوصی خود را انتخاب کنید"
:rules="privateKeyFileRules"
color="green"
hide-details="auto"
>
<template v-slot:selection="{ fileNames }">
<template v-for="fileName in fileNames" :key="fileName">
<v-chip size="small" label color="green" class="me-1 me-md-2">
{{ fileName }}
</v-chip>
</template>
</template>
</v-file-input>
</v-col>
<v-col cols="12" md="4">
<v-btn
color="blue"
variant="outlined"
prepend-icon="mdi-clipboard-text"
@click="pasteFromClipboard"
block
size="small"
:loading="pastingFromClipboard"
class="h-100"
style="min-height: 50px; font-size: 14px;"
>
الصاق از کلیپ بورد
</v-btn>
</v-col>
</v-row>
<v-btn
color="green"
variant="flat"
prepend-icon="mdi-key-plus"
@click="showCSRDialog = true"
block
class="mt-3"
size="small"
style="min-height: 40px; font-size: 14px;"
>
ساخت کلید جدید
</v-btn>
</div>
<div v-else>
<v-alert type="success" variant="tonal" class="mb-3" color="green">
<template v-slot:prepend>
<v-icon color="green">mdi-check-circle</v-icon>
</template>
<div class="d-flex flex-column flex-sm-row align-start align-sm-center justify-space-between">
<div class="mb-2 mb-sm-0">
<strong class="text-green-darken-2 text-body-2 text-sm-body-1">کلید خصوصی بارگذاری شده</strong>
<div class="text-caption mt-1 text-green-darken-1">
فایل آماده استفاده است
</div>
</div>
<v-btn
size="small"
color="error"
variant="text"
@click="clearFiles"
prepend-icon="mdi-delete"
>
حذف
</v-btn>
</div>
</v-alert>
</div>
</v-card>
</v-col>
<v-col cols="12" lg="7">
<div v-if="settings.privateKey">
<v-card variant="flat" class="pa-3 pa-md-4" color="info-lighten-5">
<div class="d-flex align-center mb-3">
<v-icon size="small" color="info" class="me-2">mdi-file-document</v-icon>
<span class="text-subtitle-2 text-md-subtitle-1 font-weight-medium text-info-darken-2">محتوای فایل کلید خصوصی</span>
</div>
<v-textarea
:value="getPrivateKeyPreview()"
readonly
:rows="$vuetify.display.smAndDown ? 8 : 12"
variant="outlined"
density="compact"
class="font-family-monospace private-key-textarea"
bg-color="white"
color="info-darken-2"
hide-details="auto"
></v-textarea>
</v-card>
</div>
<div v-else>
<v-card variant="outlined" class="pa-4 pa-md-6 text-center" color="grey-lighten-4">
<v-icon size="48" size-md="64" color="grey-lighten-1" class="mb-3">mdi-file-document-outline</v-icon>
<div class="text-h6 text-md-h6 text-grey-darken-1 mb-2">فایل کلید خصوصی بارگذاری نشده</div>
<div class="text-body-2 text-grey-darken-2">برای مشاهده محتوای فایل، ابتدا فایل کلید خصوصی را بارگذاری کنید</div>
</v-card>
</div>
</v-col>
</v-row>
</v-card>
</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">
<v-form ref="csrForm" v-model="csrFormValid">
<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 v-model="csrData.personType" inline :error-messages="csrErrors.personType"
@update:model-value="validatePersonType">
<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-text-field v-model="csrData.nationalId" label="شناسه ملی" :error-messages="csrErrors.nationalId"
:rules="nationalIdRules" @blur="validateNationalId" @input="validateNationalId"
class="mb-4"></v-text-field>
<v-text-field v-model="csrData.nameFa" label="نام (فارسی)" :error-messages="csrErrors.nameFa"
:rules="nameFaRules" @blur="validateNameFa" @input="validateNameFa" class="mb-4"></v-text-field>
<v-text-field v-model="csrData.nameEn" label="نام (انگلیسی)" :error-messages="csrErrors.nameEn"
:rules="nameEnRules" @blur="validateNameEn" @input="validateNameEn" class="mb-4"></v-text-field>
<v-text-field v-model="csrData.email" label="ایمیل" type="email" :error-messages="csrErrors.email"
:rules="emailRules" @blur="validateEmail" @input="validateEmail" 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 @click="closeCSRDialog()" variant="text">
انصراف
</v-btn>
<v-btn @click="generateCSR()" color="primary" :loading="csrLoading">
<v-btn @click="generateCSR()" color="primary" :loading="csrLoading" :disabled="!csrFormValid">
تایید
</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>
<span class="font-weight-bold">توجه: لطفا این اطلاعات را دانلود کنید و در یک جای امن نگهداری کنید. به
دلایل
امنیتی اطلاعات شما را نگهداری نمیکنیم، در صورتی که این اطلاعات را گم کنید، امکان بازیابی آن وجود
ندارد.</span>
</v-alert>
<v-row>
<v-col cols="12" md="4">
@ -147,10 +239,12 @@
<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-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-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>
@ -159,10 +253,12 @@
<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-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-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>
@ -171,10 +267,12 @@
<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-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-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>
@ -186,136 +284,337 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
<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 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
<script>
import axios from 'axios';
import Swal from 'sweetalert2';
export default {
name: 'TaxSettings',
data: () => ({
loading: false,
csrLoading: false,
pastingFromClipboard: false,
showCSRDialog: false,
showResultDialog: false,
settings: {
taxMemoryId: '',
economicCode: '',
privateKey: '',
},
privateKeyFile: null,
fileErrors: {
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'
},
csrFormValid: true,
csrErrors: {
personType: [],
nationalId: [],
nameFa: [],
nameEn: [],
email: [],
},
nationalIdRules: [
(v) => !!v || 'شناسه ملی الزامی است',
(v) => /^[0-9]{11}$/.test(v) || 'شناسه ملی باید دقیقاً 11 رقم باشد',
(v) => /^[0-9]+$/.test(v) || 'شناسه ملی فقط باید شامل اعداد باشد',
],
nameFaRules: [
(v) => !!v || 'نام فارسی الزامی است',
(v) => v.length >= 2 || 'نام فارسی باید حداقل 2 کاراکتر باشد',
(v) => v.length <= 100 || 'نام فارسی نمی‌تواند بیشتر از 100 کاراکتر باشد',
(v) => /^[\u0600-\u06FF\s]+$/.test(v) || 'نام فارسی فقط باید شامل حروف فارسی باشد',
],
nameEnRules: [
(v) => !!v || 'نام انگلیسی الزامی است',
(v) => v.length >= 2 || 'نام انگلیسی باید حداقل 2 کاراکتر باشد',
(v) => v.length <= 100 || 'نام انگلیسی نمی‌تواند بیشتر از 100 کاراکتر باشد',
(v) => /^[a-zA-Z\s]+$/.test(v) || 'نام انگلیسی فقط باید شامل حروف انگلیسی باشد',
],
emailRules: [
(v) => !!v || 'ایمیل الزامی است',
(v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'فرمت ایمیل معتبر نیست',
(v) => v.length <= 255 || 'ایمیل نمی‌تواند بیشتر از 255 کاراکتر باشد',
],
privateKeyFileRules: [
(v) => !v || v.size <= 1024 * 1024 || 'حجم فایل کلید خصوصی نمی‌تواند بیشتر از 1 مگابایت باشد',
],
}),
methods: {
async loadSettings() {
this.loading = true;
try {
const response = await axios.get('/api/plugins/tax/settings/get');
this.settings = {
...this.settings,
...response.data
};
if (this.settings.privateKey) {
this.showSnackbar('فایل موجود بارگذاری شد', 'info');
}
} catch (error) {
this.showSnackbar('خطا در بارگذاری تنظیمات', 'error');
} finally {
this.loading = false;
}
},
mounted() {
this.loadSettings();
async saveSettings() {
this.loading = true;
try {
const dataToSave = { ...this.settings };
if (!dataToSave.privateKey) {
this.showSnackbar('فایل کلید خصوصی باید بارگذاری شود', 'warning');
this.loading = false;
return;
}
await axios.post('/api/plugins/tax/settings/save', dataToSave);
this.showSnackbar('تنظیمات با موفقیت ذخیره شد', 'success');
} catch (error) {
this.showSnackbar('خطا در ذخیره تنظیمات', 'error');
} finally {
this.loading = false;
}
},
async generateCSR() {
const isFormValid = this.validateAllFields();
if (!isFormValid) {
this.showSnackbar('لطفا خطاهای فرم را برطرف کنید', 'error');
return;
}
const { valid } = await this.$refs.csrForm.validate();
if (!valid) {
this.showSnackbar('لطفا تمام فیلد های الزامی را پر کنید', 'error');
return;
}
this.csrLoading = true;
try {
const response = await axios.post('/api/plugins/tax/settings/generate-csr', this.csrData);
if (response.data.success) {
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
};
},
validatePersonType() {
this.csrErrors.personType = [];
if (!this.csrData.personType) {
this.csrErrors.personType.push('نوع شخص الزامی است');
}
},
validateNationalId() {
this.csrErrors.nationalId = [];
if (!this.csrData.nationalId) {
this.csrErrors.nationalId.push('شناسه ملی الزامی است');
} else if (!/^[0-9]+$/.test(this.csrData.nationalId)) {
this.csrErrors.nationalId.push('شناسه ملی فقط باید شامل اعداد باشد');
} else if (!/^[0-9]{11}$/.test(this.csrData.nationalId)) {
this.csrErrors.nationalId.push('شناسه ملی باید دقیقاً 11 رقم باشد');
}
},
validateNameFa() {
this.csrErrors.nameFa = [];
if (!this.csrData.nameFa) {
this.csrErrors.nameFa.push('نام فارسی الزامی است');
} else if (this.csrData.nameFa.length < 2) {
this.csrErrors.nameFa.push('نام فارسی باید حداقل 2 کاراکتر باشد');
} else if (this.csrData.nameFa.length > 100) {
this.csrErrors.nameFa.push('نام فارسی نمی‌تواند بیشتر از 100 کاراکتر باشد');
} else if (!/^[\u0600-\u06FF\s]+$/.test(this.csrData.nameFa)) {
this.csrErrors.nameFa.push('نام فارسی فقط باید شامل حروف فارسی باشد');
}
},
validateNameEn() {
this.csrErrors.nameEn = [];
if (!this.csrData.nameEn) {
this.csrErrors.nameEn.push('نام انگلیسی الزامی است');
} else if (this.csrData.nameEn.length < 2) {
this.csrErrors.nameEn.push('نام انگلیسی باید حداقل 2 کاراکتر باشد');
} else if (this.csrData.nameEn.length > 100) {
this.csrErrors.nameEn.push('نام انگلیسی نمی‌تواند بیشتر از 100 کاراکتر باشد');
} else if (!/^[a-zA-Z\s]+$/.test(this.csrData.nameEn)) {
this.csrErrors.nameEn.push('نام انگلیسی فقط باید شامل حروف انگلیسی باشد');
}
},
validateEmail() {
this.csrErrors.email = [];
if (!this.csrData.email) {
this.csrErrors.email.push('ایمیل الزامی است');
} else if (this.csrData.email.length > 255) {
this.csrErrors.email.push('ایمیل نمی‌تواند بیشتر از 255 کاراکتر باشد');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.csrData.email)) {
this.csrErrors.email.push('فرمت ایمیل معتبر نیست');
}
},
closeCSRDialog() {
this.showCSRDialog = false;
this.$nextTick(() => {
this.$refs.csrForm.reset();
this.csrFormValid = true;
this.csrErrors = {
personType: [],
nationalId: [],
nameFa: [],
nameEn: [],
email: [],
};
});
},
validateAllFields() {
this.validatePersonType();
this.validateNationalId();
this.validateNameFa();
this.validateNameEn();
this.validateEmail();
return Object.values(this.csrErrors).every(errors => errors.length === 0);
},
validatePrivateKeyFile() {
this.fileErrors.privateKey = [];
if (this.privateKeyFile) {
if (this.privateKeyFile.size > 1024 * 1024) {
this.fileErrors.privateKey.push('حجم فایل کلید خصوصی نمی‌تواند بیشتر از 1 مگابایت باشد');
return;
}
const allowedExtensions = ['.key', '.pem', '.txt'];
const fileExtension = this.privateKeyFile.name.toLowerCase().substring(this.privateKeyFile.name.lastIndexOf('.'));
if (!allowedExtensions.includes(fileExtension)) {
this.fileErrors.privateKey.push('فقط فایل‌ های .key، .pem و .txt مجاز هستند');
return;
}
this.readPrivateKeyFile();
}
},
readPrivateKeyFile() {
const reader = new FileReader();
reader.onload = (e) => {
this.settings.privateKey = e.target.result;
};
reader.readAsText(this.privateKeyFile);
},
clearFiles() {
this.privateKeyFile = null;
this.settings.privateKey = '';
this.fileErrors = {
privateKey: [],
};
},
async pasteFromClipboard() {
this.pastingFromClipboard = true;
try {
const clipboardText = await navigator.clipboard.readText();
if (!clipboardText || clipboardText.trim() === '') {
this.showSnackbar('کلیپ‌ بورد خالی است', 'warning');
return;
}
if (clipboardText.includes('-----BEGIN PRIVATE KEY-----') ||
clipboardText.includes('-----BEGIN RSA PRIVATE KEY-----') ||
clipboardText.includes('-----BEGIN OPENSSH PRIVATE KEY-----')) {
this.settings.privateKey = clipboardText.trim();
this.showSnackbar('کلید خصوصی با موفقیت از کلیپ‌ بورد الصاق شد', 'success');
} else {
this.showSnackbar('متن کلیپ‌ بورد شامل کلید خصوصی معتبر نیست', 'error');
}
} catch (error) {
console.error('خطا در خواندن کلیپ‌بورد:', error);
this.showSnackbar('خطا در دسترسی به کلیپ‌ بورد. لطفا مجدداً تلاش کنید', 'error');
} finally {
this.pastingFromClipboard = false;
}
},
getPrivateKeyPreview() {
if (!this.settings.privateKey) return '';
return this.settings.privateKey;
}
};
</script>
},
mounted() {
this.loadSettings();
}
};
</script>
<style scoped>
.private-key-textarea :deep(.v-field__input) {
text-align: left !important;
direction: ltr !important;
}
.private-key-textarea :deep(.v-field__input textarea) {
text-align: left !important;
direction: ltr !important;
}
</style>

View file

@ -732,6 +732,10 @@ export default defineComponent({
text: this.$t('dialog.tax_send_success'),
icon: 'success',
confirmButtonText: this.$t('dialog.ok')
}).then((result) => {
if (result.isConfirmed) {
this.$router.push('/acc/plugins/tax/invoices/list');
}
});
} else {
Swal.fire({