From 669ad2f97f90bc826a7ebd5ef4c04d33804ae492 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Jul 2025 13:18:37 +0000 Subject: [PATCH] update for Moadian plugin --- hesabixCore/composer.json | 1 + hesabixCore/composer.lock | 607 +++++++++++- hesabixCore/migrations/.gitignore | 0 hesabixCore/migrations/.idea/.gitignore | 8 - hesabixCore/migrations/.idea/deployment.xml | 4 - hesabixCore/migrations/.idea/hesabixCore.iml | 51 - hesabixCore/migrations/.idea/modules.xml | 8 - hesabixCore/migrations/.idea/php.xml | 100 -- hesabixCore/migrations/.idea/symfony2.xml | 7 - hesabixCore/migrations/.idea/vcs.xml | 6 - .../src/Controller/CommodityController.php | 43 +- .../Plugins/TaxSettingsController.php | 936 +++++++++++++++--- hesabixCore/src/Entity/Commodity.php | 45 + hesabixCore/src/Entity/PluginTaxInvoice.php | 14 + .../src/Entity/PluginTaxsettingsKey.php | 5 + hesabixCore/src/Service/Explore.php | 3 + webUI/src/views/acc/commodity/mod.vue | 126 ++- .../views/acc/plugins/tax/invoices/list.vue | 406 +++++++- webUI/src/views/acc/plugins/tax/settings.vue | 721 ++++++++++---- webUI/src/views/acc/sell/list.vue | 4 + 20 files changed, 2562 insertions(+), 533 deletions(-) delete mode 100644 hesabixCore/migrations/.gitignore delete mode 100644 hesabixCore/migrations/.idea/.gitignore delete mode 100644 hesabixCore/migrations/.idea/deployment.xml delete mode 100644 hesabixCore/migrations/.idea/hesabixCore.iml delete mode 100644 hesabixCore/migrations/.idea/modules.xml delete mode 100644 hesabixCore/migrations/.idea/php.xml delete mode 100644 hesabixCore/migrations/.idea/symfony2.xml delete mode 100644 hesabixCore/migrations/.idea/vcs.xml diff --git a/hesabixCore/composer.json b/hesabixCore/composer.json index 9d3e9cdf..66aedb91 100644 --- a/hesabixCore/composer.json +++ b/hesabixCore/composer.json @@ -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.*", diff --git a/hesabixCore/composer.lock b/hesabixCore/composer.lock index b8798bcf..fa625a3a 100644 --- a/hesabixCore/composer.lock +++ b/hesabixCore/composer.lock @@ -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", diff --git a/hesabixCore/migrations/.gitignore b/hesabixCore/migrations/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/hesabixCore/migrations/.idea/.gitignore b/hesabixCore/migrations/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/hesabixCore/migrations/.idea/.gitignore +++ /dev/null @@ -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 diff --git a/hesabixCore/migrations/.idea/deployment.xml b/hesabixCore/migrations/.idea/deployment.xml deleted file mode 100644 index 4a679291..00000000 --- a/hesabixCore/migrations/.idea/deployment.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/hesabixCore/migrations/.idea/hesabixCore.iml b/hesabixCore/migrations/.idea/hesabixCore.iml deleted file mode 100644 index 705b269e..00000000 --- a/hesabixCore/migrations/.idea/hesabixCore.iml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/hesabixCore/migrations/.idea/modules.xml b/hesabixCore/migrations/.idea/modules.xml deleted file mode 100644 index cda9d1ce..00000000 --- a/hesabixCore/migrations/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/hesabixCore/migrations/.idea/php.xml b/hesabixCore/migrations/.idea/php.xml deleted file mode 100644 index ac983419..00000000 --- a/hesabixCore/migrations/.idea/php.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/hesabixCore/migrations/.idea/symfony2.xml b/hesabixCore/migrations/.idea/symfony2.xml deleted file mode 100644 index 32980606..00000000 --- a/hesabixCore/migrations/.idea/symfony2.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/hesabixCore/migrations/.idea/vcs.xml b/hesabixCore/migrations/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/hesabixCore/migrations/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/hesabixCore/src/Controller/CommodityController.php b/hesabixCore/src/Controller/CommodityController.php index e2c2d4c0..c216f9a2 100644 --- a/hesabixCore/src/Controller/CommodityController.php +++ b/hesabixCore/src/Controller/CommodityController.php @@ -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') { diff --git a/hesabixCore/src/Controller/Plugins/TaxSettingsController.php b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php index 6cef847f..5bf4ab46 100644 --- a/hesabixCore/src/Controller/Plugins/TaxSettingsController.php +++ b/hesabixCore/src/Controller/Plugins/TaxSettingsController.php @@ -14,9 +14,13 @@ use Symfony\Component\Routing\Annotation\Route; use App\Entity\PluginTaxsettingsKey; use App\Entity\HesabdariDoc; use App\Entity\PluginTaxInvoice; +use DateTime; +use DateInterval; class TaxSettingsController extends AbstractController { + private $moadian_base_url = 'https://sandboxrc.tax.gov.ir/'; + #[Route('/api/plugins/tax/settings/get', name: 'plugin_tax_settings_get', methods: ['GET'])] public function plugin_tax_settings_get(EntityManagerInterface $em, Access $access): JsonResponse { @@ -26,9 +30,9 @@ class TaxSettingsController extends AbstractController } $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; - $userId = $this->getUser()->getId(); + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; - // دریافت تنظیمات از جدول اختصاصی $repo = $em->getRepository(PluginTaxsettingsKey::class); $entity = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]); @@ -51,21 +55,21 @@ class TaxSettingsController extends AbstractController $params = $request->getPayload()->all(); $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; - $userId = $this->getUser()->getId(); + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; - // بررسی وجود رکورد قبلی $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->setCreatedAt(new DateTime()); } $entity->setPrivateKey($params['privateKey'] ?? ''); $entity->setTaxMemoryId($params['taxMemoryId'] ?? null); $entity->setEconomicCode($params['economicCode'] ?? null); - $entity->setUpdatedAt(new \DateTime()); + $entity->setUpdatedAt(new DateTime()); $em->persist($entity); $em->flush(); @@ -77,40 +81,36 @@ class TaxSettingsController extends AbstractController 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']; } @@ -124,7 +124,6 @@ class TaxSettingsController extends AbstractController $params = $request->getPayload()->all(); - // بررسی فیلدهای اجباری if (empty($params['nationalId']) || empty($params['nameFa']) || empty($params['nameEn']) || empty($params['email'])) { return $this->json([ 'success' => false, @@ -137,7 +136,6 @@ class TaxSettingsController extends AbstractController $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); @@ -158,18 +156,17 @@ class TaxSettingsController extends AbstractController private function generateCSR(string $privateKey, array $params): string { - // تولید CSR واقعی با OpenSSL $dn = [ + "serialNumber" => $params['nationalId'], "countryName" => "IR", - "stateOrProvinceName" => "Tehran", - "localityName" => "Tehran", - "organizationName" => $params['nameEn'], - "organizationalUnitName" => "Tax Department", - "commonName" => $params['nameFa'], + "stateOrProvinceName" => "تهران", + "localityName" => "تهران", + "organizationName" => "Non-Governmental", + "organizationalUnitName" => $params['nameFa'], + "commonName" => $params['nameEn'] . " [Stamp]", "emailAddress" => $params['email'] ]; - // اضافه کردن شناسه ملی به عنوان extension $config = [ "req" => [ "distinguished_name" => $dn, @@ -182,7 +179,6 @@ class TaxSettingsController extends AbstractController ] ]; - // ایجاد CSR $res = openssl_csr_new($dn, $privateKey, [ 'config' => $config, 'digest_alg' => 'sha256', @@ -220,10 +216,10 @@ class TaxSettingsController extends AbstractController } $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; - $userId = $this->getUser()->getId(); + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; try { - // دریافت اطلاعات فاکتور از دیتابیس $invoiceRepo = $em->getRepository(HesabdariDoc::class); $invoice = $invoiceRepo->findOneBy([ 'code' => $invoiceCode, @@ -238,7 +234,6 @@ class TaxSettingsController extends AbstractController ]); } - // دریافت تنظیمات مالیاتی $taxRepo = $em->getRepository(PluginTaxsettingsKey::class); $taxSettings = $taxRepo->findOneBy([ 'business_id' => $businessId, @@ -252,42 +247,38 @@ class TaxSettingsController extends AbstractController ]); } - // اینجا کد ارسال به سامانه مودیان قرار می‌گیرد - // فعلاً فقط پیام موفقیت برمی‌گردانیم - $result = $this->sendInvoiceToTaxSystem($invoice, $taxSettings, $em, $businessId, $userId); + $result = $this->saveInvoiceToSql($invoice, $taxSettings, $em, $businessId, $userId); if ($result['success']) { - $log->insert('ارسال به سامانه مودیان', 'فاکتور ' . $invoiceCode . ' به سامانه مودیان ارسال شد', $this->getUser(), $businessId); - + $log->insert('اضافه به لیست ارسال', 'فاکتور ' . $invoiceCode . ' به لیست ارسال به سامانه مودیان اضافه شد', $this->getUser(), $businessId); + return $this->json([ 'success' => true, - 'message' => 'فاکتور با موفقیت به سامانه مودیان ارسال شد', + 'message' => 'فاکتور با موفقیت به لیست ارسال اضافه شد', 'data' => $result['data'] ?? null ]); } else { return $this->json([ 'success' => false, - 'message' => $result['message'] ?? 'خطا در ارسال به سامانه مودیان' + 'message' => $result['message'] ?? 'خطا در اضافه کردن به لیست ارسال' ]); } - } catch (\Exception $e) { - $log->insert('خطا در ارسال به سامانه مودیان', 'خطا در ارسال فاکتور ' . $invoiceCode . ': ' . $e->getMessage(), $this->getUser(), $businessId); - + $log->insert('خطا در اضافه به لیست ارسال', 'خطا در اضافه کردن فاکتور ' . $invoiceCode . ' به لیست ارسال: ' . $e->getMessage(), $this->getUser(), $businessId); + return $this->json([ 'success' => false, - 'message' => 'خطا در ارسال به سامانه مودیان: ' . $e->getMessage() + 'message' => 'خطا در اضافه کردن به لیست ارسال: ' . $e->getMessage() ]); } } - private function sendInvoiceToTaxSystem($invoice, $taxSettings, $em, $businessId, $userId): array + private function saveInvoiceToSql($invoice, $taxSettings, $em, $businessId, $userId, $actualSend = false): array { try { - // بررسی اینکه آیا این فاکتور قبلاً ارسال شده یا نه $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); $existingRecord = $taxInvoiceRepo->findByInvoiceCodeAndBusiness($invoice->getCode(), $businessId); - + if ($existingRecord) { return [ 'success' => false, @@ -295,16 +286,14 @@ class TaxSettingsController extends AbstractController ]; } - // ایجاد رکورد جدید $taxInvoice = new PluginTaxInvoice(); $taxInvoice->setBusiness($em->getRepository(\App\Entity\Business::class)->find($businessId)); $taxInvoice->setUser($em->getRepository(\App\Entity\User::class)->find($userId)); $taxInvoice->setInvoice($invoice); $taxInvoice->setInvoiceCode($invoice->getCode()); - $taxInvoice->setAmount($invoice->getAmount()); + // $taxInvoice->setAmount($invoice->getAmount()); $taxInvoice->setStatus('pending'); - - // دریافت اطلاعات مشتری + $customerName = null; $customerId = null; $rows = $invoice->getHesabdariRows(); @@ -318,79 +307,28 @@ class TaxSettingsController extends AbstractController $taxInvoice->setCustomerName($customerName); $taxInvoice->setCustomerId($customerId); - // ذخیره رکورد $em->persist($taxInvoice); $em->flush(); - // اینجا کد واقعی ارسال به سامانه مودیان قرار می‌گیرد - // فعلاً یک پیام موفقیت برمی‌گردانیم - - // مثال کد ارسال به API سامانه مودیان: - /* - $invoiceData = - invoiceNumber => $invoice->getCode(), - date => $invoice->getDate(), - totalAmount => $invoice->getAmount(), - customerName=> $customerName, - customerNationalId' => $customerNationalId, - // سایر اطلاعات فاکتور - ]; - - $response = $this->callTaxSystemAPI($invoiceData, $taxSettings); - - if ($response['success']) { - // به‌روزرسانی وضعیت به sent - $taxInvoice->setStatus('sent'); - $taxInvoice->setSentAt(new \DateTimeImmutable()); - $taxInvoice->setTaxSystemInvoiceNumber($response['data']['invoiceNumber'] ?? null); - $taxInvoice->setTaxSystemReferenceNumber($response['data']['referenceNumber'] ?? null); - $taxInvoice->setResponseData(json_encode($response['data'])); - $em->flush(); - - return [ - success' => true, - data' => $response['data] ]; - } else { - // به‌روزرسانی وضعیت به failed - $taxInvoice->setStatus('failed'); - $taxInvoice->setErrorMessage($response['message']); - $em->flush(); - - return [ - success' => false, - message' => $response['message] ]; - } - */ - - // فعلاً برای تست، پیام موفقیت برمی‌گردانیم - $taxInvoice->setStatus('sent'); - $taxInvoice->setSentAt(new \DateTimeImmutable()); - $taxInvoice->setTaxSystemInvoiceNumber('TAX-' . $invoice->getCode()); - $taxInvoice->setTaxSystemReferenceNumber('REF-' . $invoice->getCode()); - $taxInvoice->setResponseData(json_encode(['status' => 'success', 'message' => 'Test response'])); - $em->flush(); - return [ 'success' => true, 'data' => [ 'invoiceNumber' => $invoice->getCode(), - 'taxSystemInvoiceNumber' => $taxInvoice->getTaxSystemInvoiceNumber(), - 'taxSystemReferenceNumber' => $taxInvoice->getTaxSystemReferenceNumber(), - 'sentAt' => $taxInvoice->getSentAt()->format('Y-m-d H:i:s') + 'status' => 'pending', + 'message' => 'فاکتور با موفقیت به لیست ارسال اضافه شد' ] ]; } catch (\Exception $e) { - // در صورت خطا، وضعیت را به failed تغییر دهید if (isset($taxInvoice)) { $taxInvoice->setStatus('failed'); $taxInvoice->setErrorMessage($e->getMessage()); $em->flush(); } - + return [ 'success' => false, - 'message' => 'خطا در ارسال به سامانه مودیان: ' . $e->getMessage() + 'message' => 'خطا در اضافه کردن به لیست ارسال: ' . $e->getMessage() ]; } } @@ -404,7 +342,7 @@ class TaxSettingsController extends AbstractController } $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; - + try { $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); $invoices = $taxInvoiceRepo->findByBusiness($businessId); @@ -412,8 +350,7 @@ class TaxSettingsController extends AbstractController $result = []; foreach ($invoices as $taxInvoice) { $invoice = $taxInvoice->getInvoice(); - - // دریافت اطلاعات کامل فاکتور اصلی + $invoiceDetails = null; if ($invoice) { $invoiceDetails = [ @@ -430,7 +367,7 @@ class TaxSettingsController extends AbstractController 'discountPercent' => $invoice->getDiscountPercent() ]; } - + $result[] = [ 'id' => $taxInvoice->getId(), 'invoiceNumber' => $taxInvoice->getInvoiceCode(), @@ -444,7 +381,7 @@ class TaxSettingsController extends AbstractController 'createdAt' => $taxInvoice->getCreatedAt()->format('Y-m-d H:i:s'), 'uniqueTaxNumber' => $taxInvoice->getTaxSystemInvoiceNumber(), 'referenceUniqueTaxNumber' => $taxInvoice->getTaxSystemReferenceNumber(), - 'invoiceType' => $this->getInvoiceType($invoice), + 'invoiceType' => $taxInvoice->getInvoiceType(), 'invoiceDetails' => $invoiceDetails ]; } @@ -462,23 +399,778 @@ class TaxSettingsController extends AbstractController } } - private function getInvoiceType($invoice): string + private function base64UrlEncode(string $data): string { - if (!$invoice) { - return 'اصلی'; - } + return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); + } - switch ($invoice->getType()) { - case 'sell': - return 'اصلی'; - case 'return_sell': - return 'برگشت از فروش'; - case 'correction': - return 'اصلاحی'; - case 'cancel': - return 'ابطالی'; - default: - return 'اصلی'; + private function extractInvoiceData($doc): array + { + try { + $person = null; + $discountAll = 0; + $transferCost = 0; + $items = []; + $totalInvoice = 0; + $taxPercent = $doc->getTaxPercent() ? (float) $doc->getTaxPercent() / 100 : 0; + $discountType = $doc->getDiscountType() ?? 'fixed'; + $discountPercent = $doc->getDiscountPercent() ?? 0; + $payments = []; + + $relatedDocs = $doc->getRelatedDocs(); + + foreach ($relatedDocs as $relatedDoc) { + if ($relatedDoc->getType() === 'sell_receive') { + $payment = [ + 'type' => null, + 'amount' => $relatedDoc->getAmount(), + 'reference' => '', + 'description' => $relatedDoc->getDes(), + 'bank' => null, + 'cashdesk' => null, + 'salary' => null + ]; + + foreach ($relatedDoc->getHesabdariRows() as $row) { + if ($row->getBank()) { + $payment['type'] = 'bank'; + $payment['bank'] = $row->getBank()->getId(); + } elseif ($row->getCashdesk()) { + $payment['type'] = 'cashdesk'; + $payment['cashdesk'] = $row->getCashdesk()->getId(); + } elseif ($row->getSalary()) { + $payment['type'] = 'salary'; + $payment['salary'] = $row->getSalary()->getId(); + } + } + + $payments[] = $payment; + } + } + + foreach ($doc->getHesabdariRows() as $row) { + if ($row->getPerson()) { + $person = $row->getPerson(); + } elseif ($row->getRef() && $row->getRef()->getCode() == '104') { + $discountAll = $row->getBd(); + } elseif ($row->getRef() && $row->getRef()->getCode() == '61') { + $transferCost = $row->getBs(); + } elseif ($row->getCommodity()) { + $basePrice = $row->getBs(); + $itemDiscount = $row->getDiscount() ?? 0; + $itemDiscountType = $row->getDiscountType() ?? 'fixed'; + $itemDiscountPercent = $row->getDiscountPercent() ?? 0; + $itemTax = $row->getTax() ?? 0; + + if ($itemDiscountType === 'percent' && $itemDiscountPercent > 0) { + $originalPrice = $basePrice / (1 - ($itemDiscountPercent / 100)); + $itemDiscount = round(($originalPrice * $itemDiscountPercent) / 100); + } else { + $originalPrice = $basePrice + $itemDiscount; + } + + $unitPrice = $row->getCommdityCount() > 0 ? $originalPrice / $row->getCommdityCount() : 0; + + $netPrice = $basePrice; + $totalInvoice += $netPrice; + + $items[] = [ + 'name' => [ + 'id' => $row->getCommodity()->getId(), + 'name' => $row->getCommodity()->getName(), + 'code' => $row->getCommodity()->getCode() + ], + 'count' => $row->getCommdityCount(), + 'price' => $unitPrice, + 'discountPercent' => $itemDiscountPercent, + 'discountAmount' => $itemDiscount, + 'total' => $netPrice, + 'description' => $row->getDes(), + 'showPercentDiscount' => $itemDiscountType === 'percent', + 'tax' => $itemTax + ]; + } + } + + $totalDiscount = 0; + if ($discountType === 'percent') { + $totalDiscount = round(($totalInvoice * $discountPercent) / 100); + } else { + $totalDiscount = $discountAll; + } + + $finalTotal = $totalInvoice - $totalDiscount + $transferCost; + $totalTax = 0; + foreach ($items as $item) { + $totalTax += $item['tax']; + } + $finalTotal += $totalTax; + + return [ + 'result' => 1, + 'data' => [ + 'id' => $doc->getCode(), + 'date' => $doc->getDate(), + 'person' => $person ? [ + 'id' => $person->getId(), + 'name' => $person->getNikename(), + 'code' => $person->getCode() + ] : null, + 'des' => $doc->getDes(), + 'totalInvoice' => $totalInvoice, + 'taxPercent' => $taxPercent, + 'discountType' => $discountType, + 'discountPercent' => $discountPercent, + 'totalDiscount' => $totalDiscount, + 'shippingCost' => $transferCost, + 'showTotalPercentDiscount' => $discountType === 'percent', + 'items' => $items, + 'finalTotal' => $finalTotal, + 'payments' => $payments + ] + ]; + + } catch (\Exception $e) { + return [ + 'result' => 0, + 'message' => 'خطا در استخراج اطلاعات فاکتور: ' . $e->getMessage(), + 'data' => null + ]; } } -} \ No newline at end of file + private function validateInvoiceForTax($invoice): array + { + try { + if (!$invoice) { + return [ + 'valid' => false, + 'message' => 'فاکتور معتبر نیست' + ]; + } + + $extractedData = $this->extractInvoiceData($invoice); + if ($extractedData['result'] !== 1) { + return [ + 'valid' => false, + 'message' => $extractedData['message'] + ]; + } + + $data = $extractedData['data']; + $errors = []; + + if (empty($data['items'])) { + return [ + 'valid' => false, + 'message' => 'فاکتور فاقد اقلام است' + ]; + } + + $rowNumber = 1; + foreach ($data['items'] as $item) { + $commodity = $item['name']; + + if (empty($commodity['code'])) { + $errors[] = "ردیف {$rowNumber}: کد کالا/خدمت تعریف نشده است"; + } + + $taxCode = null; + if (method_exists($invoice, 'getHesabdariRows')) { + $rows = $invoice->getHesabdariRows(); + foreach ($rows as $row) { + if ($row->getCommodity() && $row->getCommodity()->getId() == $commodity['id']) { + $commodityObj = $row->getCommodity(); + if (method_exists($commodityObj, 'getTaxCode')) { + $taxCode = $commodityObj->getTaxCode(); + } + break; + } + } + } + if (empty($taxCode)) { + $errors[] = "کالا/خدمت {$rowNumber}: کد مالیاتی تعریف نشده است"; + } + + $taxUnit = null; + if (method_exists($invoice, 'getHesabdariRows')) { + $rows = $invoice->getHesabdariRows(); + foreach ($rows as $row) { + if ($row->getCommodity() && $row->getCommodity()->getId() == $commodity['id']) { + $commodityObj = $row->getCommodity(); + if (method_exists($commodityObj, 'getTaxUnit')) { + $taxUnit = $commodityObj->getTaxUnit(); + } + break; + } + } + } + if (empty($taxUnit)) { + $errors[] = "کالا/خدمت {$rowNumber}: واحد مالیاتی تعریف نشده است"; + } + + $rowNumber++; + } + + $totalTax = 0; + foreach ($data['items'] as $item) { + $totalTax += $item['tax']; + } + + if (fmod($totalTax, 1) != 0) { + $errors[] = "مبلغ مالیات بر ارزش افزوده نباید اعشار داشته باشد"; + } + + if ($data['shippingCost'] > 0) { + $errors[] = "هزینه حمل باید صفر باشد"; + } + + if (!empty($errors)) { + return [ + 'valid' => false, + 'message' => implode('، ', $errors) + ]; + } + + return [ + 'valid' => true, + 'message' => 'فاکتور معتبر است' + ]; + + } catch (\Exception $e) { + return [ + 'valid' => false, + 'message' => 'خطا اعتبارسنجی سامانه مالیاتی: ' . $e->getMessage() + ]; + } + } + + #[Route('/api/plugins/tax/invoice/send/{id}', name: 'plugin_tax_invoice_send', methods: ['POST'])] + public function sendTaxInvoice(int $id, Access $access, Log $log, EntityManagerInterface $em): JsonResponse + { + $acc = $access->hasRole('plugTaxSettings'); + if (!$acc) { + throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.'); + } + + $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; + + $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); + $taxInvoice = $taxInvoiceRepo->findOneBy([ + 'id' => $id, + 'business' => $businessId + ]); + + if (!$taxInvoice) { + return $this->json([ + 'success' => false, + 'message' => 'فاکتور مالیاتی مورد نظر یافت نشد' + ]); + } + + $invoiceStatus = $taxInvoice->getStatus(); + + if ($invoiceStatus !== 'pending' && $invoiceStatus !== 'error') { + return $this->json([ + 'success' => false, + 'message' => 'فقط فاکتورهای ارسال نشده یا خطا دار قابل ارسال هستند' + ]); + } + + $repo = $em->getRepository(PluginTaxsettingsKey::class); + $taxSettings = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]); + + if (!$taxSettings || !$taxSettings->getPrivateKey() || !$taxSettings->getTaxMemoryId()) { + return $this->json([ + 'success' => false, + 'message' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.' + ]); + } + + try { + $username = $taxSettings->getTaxMemoryId(); + $privateKey = $taxSettings->getPrivateKey(); + + if (!$username || !$privateKey) { + return $this->json([ + 'success' => false, + 'message' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.' + ]); + } + + $moadian = new \SnappMarketPro\Moadian\Moadian( + '', + $privateKey, + '', + $username, + $this->moadian_base_url + ); + + $serverInfo = $moadian->getServerInformation(); + if (!isset($serverInfo['result']['data']['publicKeys'][0])) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در دریافت اطلاعات سرور مودیان' + ]); + } + + $taxOrgPublicKey = $serverInfo['result']['data']['publicKeys'][0]['key']; + $taxOrgKeyId = $serverInfo['result']['data']['publicKeys'][0]['id']; + + $moadian = new \SnappMarketPro\Moadian\Moadian( + $taxOrgPublicKey, + $privateKey, + $taxOrgKeyId, + $username, + $this->moadian_base_url + ); + + $token = $moadian->login(); + + if (!$token) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در اتصال به سامانه مودیان، لطفاً تنظیمات را بررسی کنید.' + ]); + } + + $moadian->setToken($token); + + $invoice = $taxInvoice->getInvoice(); + + try { + if (!$invoice) { + throw new \Exception('فاکتور معتبر نیست'); + } + + $validationResult = $this->validateInvoiceForTax($invoice); + if (!$validationResult['valid']) { + throw new \Exception($validationResult['message']); + } + + $invoiceDto = $this->buildInvoiceDto($invoice, $moadian, $taxSettings->getEconomicCode()); + if (!$invoiceDto) { + throw new \Exception('خطا در آماده‌سازی فاکتور: خطا در ساخت DTO فاکتور'); + } + + $response = $moadian->sendInvoices([$invoiceDto]); + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + } + + if (isset($response['result'][0]['referenceNumber']) && !empty($response['result'])) { + $taxInvoice->setStatus('sent'); + $taxInvoice->setTaxSystemInvoiceNumber($response['result'][0]['referenceNumber']); + $taxInvoice->setSentAt(new \DateTimeImmutable()); + $em->persist($taxInvoice); + $em->flush(); + + $log->insert( + 'ارسال فاکتور مالیاتی', + 'فاکتور مالیاتی شماره ' . $taxInvoice->getInvoiceCode() . ' به سامانه مودیان ارسال شد.', + $this->getUser(), + $businessId + ); + + if ($invoiceStatus === 'error') { + $taxInvoice->setInvoiceType('اصلاحی'); + $em->persist($taxInvoice); + $em->flush(); + } + + return $this->json([ + 'success' => true, + 'data' => $response, + 'invoiceCode' => $taxInvoice->getInvoiceCode(), + 'referenceNumber' => $response['result'][0]['referenceNumber'] ?? null + ]); + } else { + return $this->json([ + 'success' => false, + 'message' => 'خطا در ارسال فاکتور: ' . $response['result'][0]['error'], + 'response' => $response + ]); + } + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در ارسال فاکتور', + 'trace' => $e->getTraceAsString() + ]); + } + } + + #[Route('/api/plugins/tax/invoice/delete/{id}', name: 'plugin_tax_invoice_delete', methods: ['DELETE'])] + public function deleteTaxInvoice(int $id, Access $access, Log $log, EntityManagerInterface $em): JsonResponse + { + $acc = $access->hasRole('plugTaxSettings'); + if (!$acc) { + throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.'); + } + + $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; + + try { + $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); + $taxInvoice = $taxInvoiceRepo->findOneBy([ + 'id' => $id, + 'business' => $businessId + ]); + + if (!$taxInvoice) { + return $this->json([ + 'success' => false, + 'message' => 'فاکتور مالیاتی مورد نظر یافت نشد' + ]); + } + + if ($taxInvoice->getStatus() !== 'pending' && $taxInvoice->getStatus() !== 'error') { + return $this->json([ + 'success' => false, + 'message' => 'فقط فاکتورهای ارسال نشده یا خطا دار قابل حذف هستند' + ]); + } + + $invoiceCode = $taxInvoice->getInvoiceCode(); + + $em->remove($taxInvoice); + $em->flush(); + + $log->insert( + 'فاکتور مالیاتی', + 'فاکتور مالیاتی شماره ' . $invoiceCode . ' حذف شد.', + $this->getUser(), + $businessId + ); + + return $this->json([ + 'success' => true, + 'message' => 'فاکتور مالیاتی با موفقیت حذف شد' + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در حذف فاکتور مالیاتی: ' . $e->getMessage() + ]); + } + } + + #[Route('/api/plugins/tax/inquire-status', name: 'plugin_tax_inquire_status', methods: ['POST'])] + public function inquireInvoiceStatus(Request $request, Access $access, EntityManagerInterface $em): JsonResponse + { + $acc = $access->hasRole('plugTaxSettings'); + if (!$acc) { + throw $this->createAccessDeniedException('شما دسترسی لازم را ندارید.'); + } + + $params = $request->getPayload()->all(); + $referenceNumbers = $params['referenceNumbers'] ?? []; + + if (empty($referenceNumbers)) { + return $this->json([ + 'success' => false, + 'message' => 'شماره‌های ارجاع الزامی است' + ]); + } + + $businessId = is_object($acc['bid']) ? $acc['bid']->getId() : $acc['bid']; + $user = $this->getUser(); + $userId = $user instanceof \App\Entity\User ? $user->getId() : null; + + $repo = $em->getRepository(PluginTaxsettingsKey::class); + $taxSettings = $repo->findOneBy(['business_id' => $businessId, 'user_id' => $userId]); + + if (!$taxSettings || !$taxSettings->getPrivateKey() || !$taxSettings->getTaxMemoryId()) { + return $this->json([ + 'success' => false, + 'message' => 'تنظیمات مالیاتی تکمیل نشده است. لطفاً ابتدا تنظیمات را تکمیل کنید.' + ]); + } + + try { + $username = $taxSettings->getTaxMemoryId(); + $privateKey = $taxSettings->getPrivateKey(); + $taxOrgPublicKey = ''; + $taxOrgKeyId = ''; + + $moadian = new \SnappMarketPro\Moadian\Moadian( + $taxOrgPublicKey, + $privateKey, + $taxOrgKeyId, + $username, + $this->moadian_base_url + ); + + $token = $moadian->login(); + $moadian->setToken($token); + + $response = $moadian->inquireByReferenceNumbers($referenceNumbers); + + if (isset($response['result']['data']) && is_array($response['result']['data'])) { + foreach ($response['result']['data'] as $invoiceData) { + $referenceNumber = $invoiceData['referenceNumber'] ?? null; + $status = $invoiceData['status'] ?? null; + $errors = $invoiceData['data']['error'] ?? []; + $warnings = $invoiceData['data']['warning'] ?? []; + + if ($referenceNumber) { + $taxInvoiceRepo = $em->getRepository(PluginTaxInvoice::class); + $taxInvoice = $taxInvoiceRepo->findOneBy([ + 'taxSystemInvoiceNumber' => $referenceNumber, + 'business' => $businessId + ]); + + if ($taxInvoice) { + if ($status === 'FAILED') { + $taxInvoice->setStatus('error'); + + $errorData = [ + 'errors' => $errors, + 'warnings' => $warnings, + 'status' => $status, + 'timestamp' => time() + ]; + $taxInvoice->setErrorMessage(json_encode($errorData, JSON_UNESCAPED_UNICODE)); + } elseif ($status === 'SUCCESS') { + $taxInvoice->setStatus('accepted'); + $taxInvoice->setErrorMessage(null); + } + + $em->persist($taxInvoice); + } + } + } + $em->flush(); + } + + return $this->json([ + 'success' => true, + 'data' => $response['result']['data'] ?? [] + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در دریافت وضعیت فاکتور: ' . $e->getMessage() + ]); + } + } + + private function getCommodityTaxCodeFromInvoice($invoice, $commodityId): string + { + try { + $rows = $invoice->getHesabdariRows(); + foreach ($rows as $row) { + if ($row->getCommodity() && $row->getCommodity()->getId() == $commodityId) { + $commodity = $row->getCommodity(); + if (method_exists($commodity, 'getTaxCode') && $commodity->getTaxCode()) { + return $commodity->getTaxCode(); + } + if (method_exists($commodity, 'getCode') && $commodity->getCode()) { + return $commodity->getCode(); + } + break; + } + } + return '2720000166053'; + } catch (\Exception $e) { + return '2720000166053'; + } + } + + private function getCommodityUnitFromInvoice($invoice, $commodityId): string + { + try { + $rows = $invoice->getHesabdariRows(); + foreach ($rows as $row) { + if ($row->getCommodity() && $row->getCommodity()->getId() == $commodityId) { + $commodity = $row->getCommodity(); + if (method_exists($commodity, 'getTaxUnit') && $commodity->getTaxUnit()) { + return $commodity->getTaxUnit(); + } + break; + } + } + return '1627'; + } catch (\Exception $e) { + return '1627'; + } + } + + private function calculateVra($itemTotal, $itemTax, $invoice): int + { + try { + if ($itemTotal <= 0) { + return 0; + } + + if ($itemTax <= 0) { + return 0; + } + + $vra = round(($itemTax / $itemTotal) * 100, 2); + + $invoiceType = $invoice->getType() ?? 'sell'; + + switch ($invoiceType) { + case 'return_sell': + case 'return_buy': + case 'correction': + case 'cancel': + return 0; + case 'export': + return 0; + default: + break; + } + + $taxPercent = $invoice->getTaxPercent() ?? 9; + $expectedVra = (int) $taxPercent; + + if ($vra > 0 && abs($vra - $expectedVra) <= 1) { + return $expectedVra; + } + + return (int) $vra; + } catch (\Exception $e) { + return 9; + } + } + + private function buildInvoiceDto($invoice, $moadian, $moadianTaxId): ?\SnappMarketPro\Moadian\Dto\InvoiceDto + { + try { + if (!$moadian || !$moadianTaxId) { + throw new \Exception('کلاس Moadian معتبر نیست یا شماره اقتصادی معتبر نیست'); + } + + $extractedData = $this->extractInvoiceData($invoice); + if ($extractedData['result'] !== 1) { + throw new \Exception($extractedData['message']); + } + + $data = $extractedData['data']; + $taxId = $moadianTaxId; + $internalId = $invoice->getId() ?? 1; + + $date = $data['date'] ?? 'now'; + // if (is_string($date)) { + // $dateTime = new DateTime($date); + // } elseif ($date instanceof DateTime) { + // $dateTime = $date; + // } else { + // $dateTime = new DateTime(); + // } + + if (!method_exists($moadian, 'generateTaxId') || !method_exists($moadian, 'normalizeInvoiceNumber')) { + throw new \Exception('متدهای مورد نیاز در کلاس Moadian وجود ندارد'); + } + + $totalTax = 0; + foreach ($data['items'] as $item) { + $totalTax += $item['tax']; + } + + $dateTime = new DateTime(); + $header = (new \SnappMarketPro\Moadian\Dto\InvoiceHeaderDto()) + ->setTaxid($moadian->generateTaxId($dateTime, $internalId)) + ->setIndati2m($dateTime->getTimestamp() * 1000) + ->setIndatim($dateTime->getTimestamp() * 1000) + ->setInty(2) + ->setInno($moadian->normalizeInvoiceNumber($internalId)) + ->setIrtaxid(null) + ->setInp(1) + ->setIns(1) + ->setTins($taxId) + ->setTob(1) + ->setBid(null) + ->setTinb(null) + ->setSbc(null) + ->setBpc(null) + ->setBbc(null) + ->setFt(null) + ->setBpn(null) + ->setScln(null) + ->setScc(null) + ->setCrn(null) + ->setBillid(null) + ->setTprdis($data['totalInvoice']) + ->setTdis($data['totalDiscount']) + ->setTadis($data['totalInvoice'] - $data['totalDiscount']) + ->setTvam($totalTax) + ->setTodam($data['shippingCost']) + ->setTbill($data['finalTotal']) + ->setSetm(null) + ->setCap(null) + ->setInsp(null) + ->setTvop(null) + ->setTax17(0); + $bodyItems = []; + + foreach ($data['items'] as $item) { + $itemTax = $item['tax']; + $itemTotal = $item['total'] + $itemTax; + + $vra = $this->calculateVra($item['total'], $itemTax, $invoice); + + $bodyDto = (new \SnappMarketPro\Moadian\Dto\InvoiceBodyDto()) + ->setSstid($this->getCommodityTaxCodeFromInvoice($invoice, $item['name']['id'])) + ->setSstt($item['name']['name']) + ->setAm($item['count']) + ->setMu($this->getCommodityUnitFromInvoice($invoice, $item['name']['id'])) + ->setFee($item['price']) + ->setCfee(null) + ->setCut(null) + ->setExr(null) + ->setPrdis($item['total']) + ->setDis($item['discountAmount']) + ->setAdis($item['total'] - $item['discountAmount']) + ->setVra($vra) + ->setVam($itemTax) + ->setOdt(null) + ->setOdr(null) + ->setOdam(null) + ->setOlt(null) + ->setOlr(null) + ->setOlam(null) + ->setConsfee(null) + ->setSpro(null) + ->setBros(null) + ->setTcpbs(null) + ->setCop(null) + ->setVop(null) + ->setBsrn(null) + ->setTsstam($itemTotal); + + $bodyItems[] = $bodyDto; + } + + $paymentDto = (new \SnappMarketPro\Moadian\Dto\InvoicePaymentDto()) + ->setIinn(null) + ->setAcn(null) + ->setTrmn(null) + ->setTrn(null) + ->setPcn(null) + ->setPid(null) + ->setPdt(null); + + $invoiceDto = new \SnappMarketPro\Moadian\Dto\InvoiceDto(); + $invoiceDto->setHeader($header); + $invoiceDto->setBody($bodyItems); + $invoiceDto->setPayments([$paymentDto]); + + return $invoiceDto; + } catch (\Exception $e) { + return null; + } + } + + +} + diff --git a/hesabixCore/src/Entity/Commodity.php b/hesabixCore/src/Entity/Commodity.php index 2b87f304..b89dcd43 100644 --- a/hesabixCore/src/Entity/Commodity.php +++ b/hesabixCore/src/Entity/Commodity.php @@ -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 */ diff --git a/hesabixCore/src/Entity/PluginTaxInvoice.php b/hesabixCore/src/Entity/PluginTaxInvoice.php index 737f8308..7aa500f4 100644 --- a/hesabixCore/src/Entity/PluginTaxInvoice.php +++ b/hesabixCore/src/Entity/PluginTaxInvoice.php @@ -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; + } } \ No newline at end of file diff --git a/hesabixCore/src/Entity/PluginTaxsettingsKey.php b/hesabixCore/src/Entity/PluginTaxsettingsKey.php index f7d88e6f..3ee17371 100644 --- a/hesabixCore/src/Entity/PluginTaxsettingsKey.php +++ b/hesabixCore/src/Entity/PluginTaxsettingsKey.php @@ -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; } diff --git a/hesabixCore/src/Service/Explore.php b/hesabixCore/src/Service/Explore.php index 79ad1e1e..15444be1 100644 --- a/hesabixCore/src/Service/Explore.php +++ b/hesabixCore/src/Service/Explore.php @@ -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(), diff --git a/webUI/src/views/acc/commodity/mod.vue b/webUI/src/views/acc/commodity/mod.vue index 19a79af1..c8a5b352 100755 --- a/webUI/src/views/acc/commodity/mod.vue +++ b/webUI/src/views/acc/commodity/mod.vue @@ -202,16 +202,27 @@
-
- - +
+ + +
-
@@ -220,7 +231,108 @@
@@ -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 diff --git a/webUI/src/views/acc/plugins/tax/invoices/list.vue b/webUI/src/views/acc/plugins/tax/invoices/list.vue index 0cae98b3..19764b22 100644 --- a/webUI/src/views/acc/plugins/tax/invoices/list.vue +++ b/webUI/src/views/acc/plugins/tax/invoices/list.vue @@ -58,12 +58,12 @@ @@ -78,6 +78,38 @@ + + + + + + + + + + + + + + + @@ -98,11 +151,120 @@ {{ snackbar.text }} + + + + + mdi-alert-circle + خطاهای فاکتور شماره {{ errorsDialog.invoiceNumber }} + + + + + + +
+ فاکتور دارای خطا است. +
لطفاً خطاهای زیر را برطرف کنید و مجدداً ارسال کنید:
+
+
+ +
+
+ mdi-close-circle + خطاها ({{ errorsDialog.errors.length }} مورد): +
+ + + + +
+ + کد خطا: {{ error.code }} + +
+ {{ error.message }} +
+
+
+
+ + + + +
+ + + + + + + بستن + + + ویرایش فاکتور + + +
+
\ No newline at end of file + }, + mounted() { + this.loadSettings(); + } +}; + + + \ No newline at end of file diff --git a/webUI/src/views/acc/sell/list.vue b/webUI/src/views/acc/sell/list.vue index 0f8ce488..164e2051 100755 --- a/webUI/src/views/acc/sell/list.vue +++ b/webUI/src/views/acc/sell/list.vue @@ -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({