update core
This commit is contained in:
parent
3ad815ec7f
commit
04550d2171
|
@ -4,56 +4,56 @@
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-fileinfo": "*",
|
"ext-fileinfo": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"doctrine/annotations": "^1.0",
|
"doctrine/annotations": "^2.0",
|
||||||
"doctrine/dbal": "^3.9",
|
"doctrine/dbal": "^4.2",
|
||||||
"doctrine/doctrine-bundle": "^2.8",
|
"doctrine/doctrine-bundle": "^2.13",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||||
"doctrine/orm": "^2.14",
|
"doctrine/orm": "^3.2",
|
||||||
"dompdf/dompdf": "^2.0",
|
"dompdf/dompdf": "^3.0",
|
||||||
"melipayamak/php": "1.0.0",
|
"melipayamak/php": "^1.0",
|
||||||
"mpdf/mpdf": "^8.2",
|
"mpdf/mpdf": "^8.2",
|
||||||
"nelmio/api-doc-bundle": "^4.34",
|
"nelmio/api-doc-bundle": "^4.35",
|
||||||
"nelmio/cors-bundle": "^2.5",
|
"nelmio/cors-bundle": "^2.5",
|
||||||
"phpdocumentor/reflection-docblock": "^5.3",
|
"phpdocumentor/reflection-docblock": "^5.4",
|
||||||
"phpoffice/phpspreadsheet": "^1.29",
|
"phpoffice/phpspreadsheet": "^2.3",
|
||||||
"phpstan/phpdoc-parser": "^1.16",
|
"phpstan/phpdoc-parser": "^1.33",
|
||||||
"ramsey/uuid": "^4.7",
|
"ramsey/uuid": "^4.7",
|
||||||
"symfony/apache-pack": "^1.0",
|
"symfony/apache-pack": "^1.0",
|
||||||
"symfony/asset": "7.1.*",
|
"symfony/asset": "7.2.*",
|
||||||
"symfony/console": "7.1.*",
|
"symfony/console": "7.2.*",
|
||||||
"symfony/doctrine-messenger": "7.1.*",
|
"symfony/doctrine-messenger": "7.2.*",
|
||||||
"symfony/dotenv": "7.1.*",
|
"symfony/dotenv": "7.2.*",
|
||||||
"symfony/expression-language": "7.1.*",
|
"symfony/expression-language": "7.2.*",
|
||||||
"symfony/flex": "^2",
|
"symfony/flex": "^2",
|
||||||
"symfony/form": "7.1.*",
|
"symfony/form": "7.2.*",
|
||||||
"symfony/framework-bundle": "7.1.*",
|
"symfony/framework-bundle": "7.2.*",
|
||||||
"symfony/http-client": "7.1.*",
|
"symfony/http-client": "7.2.*",
|
||||||
"symfony/lock": "7.1.*",
|
"symfony/lock": "7.2.*",
|
||||||
"symfony/mailer": "7.1.*",
|
"symfony/mailer": "7.2.*",
|
||||||
"symfony/mime": "7.1.*",
|
"symfony/mime": "7.2.*",
|
||||||
"symfony/monolog-bundle": "^3.0",
|
"symfony/monolog-bundle": "^3.10",
|
||||||
"symfony/notifier": "7.1.*",
|
"symfony/notifier": "7.2.*",
|
||||||
"symfony/process": "7.1.*",
|
"symfony/process": "7.2.*",
|
||||||
"symfony/property-access": "7.1.*",
|
"symfony/property-access": "7.2.*",
|
||||||
"symfony/property-info": "7.1.*",
|
"symfony/property-info": "7.2.*",
|
||||||
"symfony/runtime": "7.1.*",
|
"symfony/runtime": "7.2.*",
|
||||||
"symfony/security-bundle": "7.1.*",
|
"symfony/security-bundle": "7.2.*",
|
||||||
"symfony/serializer": "7.1.*",
|
"symfony/serializer": "7.2.*",
|
||||||
"symfony/string": "7.1.*",
|
"symfony/string": "7.2.*",
|
||||||
"symfony/translation": "7.1.*",
|
"symfony/translation": "7.2.*",
|
||||||
"symfony/twig-bundle": "7.1.*",
|
"symfony/twig-bundle": "7.2.*",
|
||||||
"symfony/validator": "7.1.*",
|
"symfony/validator": "7.2.*",
|
||||||
"symfony/web-link": "7.1.*",
|
"symfony/web-link": "7.2.*",
|
||||||
"symfony/yaml": "7.1.*",
|
"symfony/yaml": "7.2.*",
|
||||||
"symfonycasts/verify-email-bundle": "^1.13",
|
"symfonycasts/verify-email-bundle": "^1.17",
|
||||||
"tecnickcom/tcpdf": "^6.6",
|
"tecnickcom/tcpdf": "^6.7",
|
||||||
"twig/extra-bundle": "^2.12|^3.0",
|
"twig/extra-bundle": "^3.14",
|
||||||
"twig/twig": "^2.12|^3.0"
|
"twig/twig": "^3.14"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
@ -99,19 +99,19 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": true,
|
"allow-contrib": true,
|
||||||
"require": "7.1.*",
|
"require": "7.2.*",
|
||||||
"docker": true
|
"docker": true
|
||||||
},
|
},
|
||||||
"public-dir": "../public_html"
|
"public-dir": "../public_html"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^11.4",
|
||||||
"symfony/browser-kit": "7.1.*",
|
"symfony/browser-kit": "7.2.*",
|
||||||
"symfony/css-selector": "7.1.*",
|
"symfony/css-selector": "7.2.*",
|
||||||
"symfony/debug-bundle": "7.1.*",
|
"symfony/debug-bundle": "7.2.*",
|
||||||
"symfony/maker-bundle": "^1.48",
|
"symfony/maker-bundle": "^1.62",
|
||||||
"symfony/phpunit-bridge": "^7.1",
|
"symfony/phpunit-bridge": "^7.2",
|
||||||
"symfony/stopwatch": "7.1.*",
|
"symfony/stopwatch": "7.2.*",
|
||||||
"symfony/web-profiler-bundle": "7.1.*"
|
"symfony/web-profiler-bundle": "7.2.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11385
hesabixCore/composer.lock
generated
Normal file
11385
hesabixCore/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
hesabixCore/config/packages/csrf.yaml
Normal file
11
hesabixCore/config/packages/csrf.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
form:
|
||||||
|
csrf_protection:
|
||||||
|
token_id: submit
|
||||||
|
|
||||||
|
csrf_protection:
|
||||||
|
stateless_token_ids:
|
||||||
|
- submit
|
||||||
|
- authenticate
|
||||||
|
- logout
|
|
@ -19,6 +19,7 @@ use App\Entity\Cashdesk;
|
||||||
use App\Entity\Salary;
|
use App\Entity\Salary;
|
||||||
use App\Entity\Person;
|
use App\Entity\Person;
|
||||||
use App\Service\Log;
|
use App\Service\Log;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
class CostController extends AbstractController
|
class CostController extends AbstractController
|
||||||
{
|
{
|
||||||
|
@ -138,7 +139,10 @@ class CostController extends AbstractController
|
||||||
->andWhere('r.bd != 0')
|
->andWhere('r.bd != 0')
|
||||||
->groupBy('t.id, t.name')
|
->groupBy('t.id, t.name')
|
||||||
->orderBy('total_cost', 'DESC')
|
->orderBy('total_cost', 'DESC')
|
||||||
->setParameters($parameters);
|
->setParameter('bid', $acc['bid'])
|
||||||
|
->setParameter('money', $acc['money'])
|
||||||
|
->setParameter('type', 'cost')
|
||||||
|
->setParameter('year', $acc['year']);
|
||||||
|
|
||||||
// اعمال فیلتر تاریخ فقط برای امروز و ماه
|
// اعمال فیلتر تاریخ فقط برای امروز و ماه
|
||||||
if ($period === 'today') {
|
if ($period === 'today') {
|
||||||
|
|
|
@ -1134,4 +1134,36 @@ class HesabdariController extends AbstractController
|
||||||
return $this->json($extractor->operationSuccess($pdfPid));
|
return $this->json($extractor->operationSuccess($pdfPid));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/hesabdari/tables/{id}/children', name: 'get_hesabdari_table_children', methods: ['GET'])]
|
||||||
|
public function getHesabdariTableChildren(int $id, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||||
|
{
|
||||||
|
$acc = $access->hasRole('accounting');
|
||||||
|
if (!$acc) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = $entityManager->getRepository(HesabdariTable::class)->find($id);
|
||||||
|
if (!$node) {
|
||||||
|
return $this->json(['Success' => false, 'message' => 'نود مورد نظر یافت نشد'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||||
|
'upper' => $node,
|
||||||
|
'bid' => [$acc['bid']->getId(), null] // حسابهای عمومی و خصوصی
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($children as $child) {
|
||||||
|
$result[] = [
|
||||||
|
'id' => $child->getId(),
|
||||||
|
'name' => $child->getName(),
|
||||||
|
'code' => $child->getCode(),
|
||||||
|
'type' => $child->getType(),
|
||||||
|
'children' => $this->hasChild($entityManager, $child) ? [] : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(['Success' => true, 'data' => $result]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
class IncomeController extends AbstractController
|
class IncomeController extends AbstractController
|
||||||
{
|
{
|
||||||
|
@ -111,14 +112,6 @@ class IncomeController extends AbstractController
|
||||||
$today = $jdate->jdate('Y/m/d', time());
|
$today = $jdate->jdate('Y/m/d', time());
|
||||||
$monthStart = $jdate->jdate('Y/m/01', time());
|
$monthStart = $jdate->jdate('Y/m/01', time());
|
||||||
|
|
||||||
// پارامترهای پایه
|
|
||||||
$parameters = [
|
|
||||||
'bid' => $acc['bid'],
|
|
||||||
'money' => $acc['money'],
|
|
||||||
'type' => 'income',
|
|
||||||
'year' => $acc['year'],
|
|
||||||
];
|
|
||||||
|
|
||||||
// کوئری پایه
|
// کوئری پایه
|
||||||
$qb = $entityManager->createQueryBuilder()
|
$qb = $entityManager->createQueryBuilder()
|
||||||
->select('t.name AS center_name, SUM(COALESCE(r.bs, 0)) AS total_income')
|
->select('t.name AS center_name, SUM(COALESCE(r.bs, 0)) AS total_income')
|
||||||
|
@ -132,7 +125,10 @@ class IncomeController extends AbstractController
|
||||||
->andWhere('r.bs != 0') // فقط ردیفهایی که bs صفر نیست
|
->andWhere('r.bs != 0') // فقط ردیفهایی که bs صفر نیست
|
||||||
->groupBy('t.id, t.name')
|
->groupBy('t.id, t.name')
|
||||||
->orderBy('total_income', 'DESC')
|
->orderBy('total_income', 'DESC')
|
||||||
->setParameters($parameters);
|
->setParameter('bid', $acc['bid'])
|
||||||
|
->setParameter('money', $acc['money'])
|
||||||
|
->setParameter('type', 'income')
|
||||||
|
->setParameter('year', $acc['year']);
|
||||||
|
|
||||||
// اعمال فیلتر تاریخ فقط برای امروز و ماه
|
// اعمال فیلتر تاریخ فقط برای امروز و ماه
|
||||||
if ($period === 'today') {
|
if ($period === 'today') {
|
||||||
|
|
|
@ -5,24 +5,24 @@ use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
use Doctrine\ORM\Query\Lexer;
|
use Doctrine\ORM\Query\Lexer;
|
||||||
use Doctrine\ORM\Query\Parser;
|
use Doctrine\ORM\Query\Parser;
|
||||||
use Doctrine\ORM\Query\SqlWalker;
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
use Doctrine\ORM\Query\TokenType;
|
||||||
|
|
||||||
class Cast extends FunctionNode
|
class Cast extends FunctionNode
|
||||||
{
|
{
|
||||||
private $expression;
|
private $expression;
|
||||||
|
|
||||||
public function parse(Parser $parser)
|
public function parse(Parser $parser): void
|
||||||
{
|
{
|
||||||
$parser->match(Lexer::T_IDENTIFIER); // CAST
|
$parser->match(TokenType::T_IDENTIFIER); // CAST
|
||||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
$parser->match(TokenType::T_OPEN_PARENTHESIS);
|
||||||
$this->expression = $parser->ArithmeticExpression();
|
$this->expression = $parser->ArithmeticExpression();
|
||||||
$parser->match(Lexer::T_AS);
|
$parser->match(TokenType::T_AS);
|
||||||
$parser->match(Lexer::T_IDENTIFIER); // INTEGER یا هر نوع دیگه
|
$parser->match(TokenType::T_IDENTIFIER); // INTEGER یا هر نوع دیگه
|
||||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSql(SqlWalker $sqlWalker)
|
public function getSql(SqlWalker $sqlWalker): string
|
||||||
{
|
{
|
||||||
// به جای استفاده از $this->type، مستقیماً SIGNED رو میذاریم
|
|
||||||
return 'CAST(' . $sqlWalker->walkArithmeticPrimary($this->expression) . ' AS SIGNED)';
|
return 'CAST(' . $sqlWalker->walkArithmeticPrimary($this->expression) . ' AS SIGNED)';
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,7 +39,7 @@ class HesabdariDoc
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $type = null;
|
private ?string $type = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::BIGINT)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
private ?string $code = null;
|
private ?string $code = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'hesabdariDocs')]
|
#[ORM\ManyToOne(inversedBy: 'hesabdariDocs')]
|
||||||
|
@ -50,8 +50,8 @@ class HesabdariDoc
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $des = null;
|
private ?string $des = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
#[ORM\Column(type: Types::INTEGER, nullable: true)]
|
||||||
private int $amount = 0;
|
private ?int $amount = 0;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
@ -94,7 +94,7 @@ class HesabdariDoc
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private Collection $storeroomTickets;
|
private Collection $storeroomTickets;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::ARRAY , nullable: true)]
|
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||||
private ?array $tempStatus = null;
|
private ?array $tempStatus = null;
|
||||||
|
|
||||||
#[ORM\OneToMany(mappedBy: 'doc', targetEntity: Log::class)]
|
#[ORM\OneToMany(mappedBy: 'doc', targetEntity: Log::class)]
|
||||||
|
@ -399,7 +399,6 @@ class HesabdariDoc
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getStatus(): ?string
|
public function getStatus(): ?string
|
||||||
{
|
{
|
||||||
return $this->status;
|
return $this->status;
|
||||||
|
|
|
@ -59,8 +59,8 @@ class HesabdariRow
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private ?Commodity $commodity = null;
|
private ?Commodity $commodity = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'string', length: 255,nullable: true)]
|
#[ORM\Column(type: Types::INTEGER, nullable: true)]
|
||||||
private ?string $commdityCount = null;
|
private ?int $commdityCount = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
|
@ -79,7 +79,7 @@ class HesabdariRow
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $plugin = null;
|
private ?string $plugin = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::ARRAY, nullable: true)]
|
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||||
private ?array $tempData = null;
|
private ?array $tempData = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
||||||
|
@ -91,12 +91,8 @@ class HesabdariRow
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private ?string $tax = null;
|
private ?string $tax = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
@ -224,12 +220,12 @@ class HesabdariRow
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCommdityCount(): ?string
|
public function getCommdityCount(): ?int
|
||||||
{
|
{
|
||||||
return $this->commdityCount;
|
return $this->commdityCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCommdityCount(?string $commdityCount): self
|
public function setCommdityCount(?int $commdityCount): self
|
||||||
{
|
{
|
||||||
$this->commdityCount = $commdityCount;
|
$this->commdityCount = $commdityCount;
|
||||||
|
|
||||||
|
|
|
@ -122,6 +122,18 @@
|
||||||
"./.env"
|
"./.env"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/form": {
|
||||||
|
"version": "7.2",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.2",
|
||||||
|
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/csrf.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/framework-bundle": {
|
"symfony/framework-bundle": {
|
||||||
"version": "6.2",
|
"version": "6.2",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hesabix",
|
"name": "hesabix",
|
||||||
"version": "0.45.0",
|
"version": "0.48.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -10,68 +10,62 @@
|
||||||
"type-check": "vue-tsc --noEmit"
|
"type-check": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chenfengyuan/vue-countdown": "^2.1.2",
|
"@chenfengyuan/vue-countdown": "^2.1.3",
|
||||||
"@ckeditor/ckeditor5-build-classic": "^36.0.1",
|
|
||||||
"@ckeditor/ckeditor5-image": "^36.0.1",
|
|
||||||
"@ckeditor/ckeditor5-vue": "^4.0.1",
|
|
||||||
"@ckeditor/vite-plugin-ckeditor5": "^0.1.1",
|
|
||||||
"@date-io/date-fns-jalali": "^3.2.0",
|
"@date-io/date-fns-jalali": "^3.2.0",
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
"@syncfusion/ej2-vue-dropdowns": "^21.2.5",
|
"@syncfusion/ej2-vue-dropdowns": "^29.1.38",
|
||||||
"@tiptap/extension-text-align": "^2.11.7",
|
"@tiptap/extension-text-align": "^2.11.7",
|
||||||
"@tiptap/pm": "^2.11.7",
|
|
||||||
"@tiptap/starter-kit": "^2.11.7",
|
"@tiptap/starter-kit": "^2.11.7",
|
||||||
"@tiptap/vue-3": "^2.11.7",
|
"@tiptap/vue-3": "^2.11.7",
|
||||||
"@vuelidate/core": "^2.0.0",
|
"@vuelidate/core": "^2.0.3",
|
||||||
"@vuelidate/validators": "^2.0.0",
|
"@vuelidate/validators": "^2.0.4",
|
||||||
"@vueuse/core": "^12.0.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"apexcharts": "^4.4.0",
|
"apexcharts": "^4.6.0",
|
||||||
"axios": "^1.2.3",
|
"axios": "^1.8.4",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-jalali": "^3.2.0-0",
|
"date-fns-jalali": "^3.2.0-0",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jalali-moment": "^3.3.11",
|
"jalali-moment": "^3.3.11",
|
||||||
"libphonenumber-js": "^1.10.44",
|
"libphonenumber-js": "^1.12.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"maska": "^3.0.4",
|
"maska": "^3.1.1",
|
||||||
"maz-ui": "^3.11.4",
|
"maz-ui": "^3.50.1",
|
||||||
"pinia": "^2.2.6",
|
"pinia": "^3.0.2",
|
||||||
"sweetalert2": "^11.6.13",
|
"sweetalert2": "^11.4.8",
|
||||||
"v-money3": "^3.24.0",
|
"v-money3": "^3.24.1",
|
||||||
"v-skeleton-loader": "^0.1.9",
|
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-avatar-cropper": "^6.1.1",
|
"vue-avatar-cropper": "^6.1.1",
|
||||||
"vue-currency-input": "^3.0.4",
|
"vue-currency-input": "^3.2.1",
|
||||||
"vue-i18n": "^10.0.4",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-loading-overlay": "^6.0.3",
|
"vue-loading-overlay": "^6.0.6",
|
||||||
"vue-media-upload": "^2.1.2",
|
"vue-media-upload": "^2.2.4",
|
||||||
"vue-persian-datetime-picker": "^2.10.4",
|
"vue-persian-datetime-picker": "^2.10.4",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.5.0",
|
||||||
"vue-select": "^4.0.0-beta.6",
|
"vue-select": "^4.0.0-beta.6",
|
||||||
"vue-spinner": "^1.0.4",
|
"vue-spinner": "^1.0.4",
|
||||||
"vue3-apexcharts": "^1.8.0",
|
"vue3-apexcharts": "^1.8.0",
|
||||||
"vue3-easy-data-table": "^1.5.42",
|
"vue3-easy-data-table": "^1.5.47",
|
||||||
"vue3-perfect-scrollbar": "^2.0.0",
|
"vue3-perfect-scrollbar": "^2.0.0",
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
"vue3-persian-datetime-picker": "^1.2.2",
|
||||||
"vue3-tel-input": "^1.0.4",
|
"vue3-tel-input": "^1.0.4",
|
||||||
"vue3-treeselect": "^0.1.10",
|
"vue3-treeselect": "^0.1.10",
|
||||||
"vue3-treeview": "^0.4.1",
|
"vue3-treeview": "^0.4.2",
|
||||||
"vuetify": "^3.7.4"
|
"vuetify": "^3.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/node": "^18.11.12",
|
"@types/node": "^22.14.1",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||||
"@vue/test-utils": "^2.3.2",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"sass": "^1.67.0",
|
"sass": "^1.87.0",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "^5.8.3",
|
||||||
"vite": "^4.0.0",
|
"vite": "^6.3.2",
|
||||||
"vue-tsc": "^1.0.12"
|
"vue-tsc": "^2.2.10"
|
||||||
},
|
},
|
||||||
"build:pwa": "vue-cli-service build && workbox generateSW workbox-config.js"
|
"build:pwa": "vue-cli-service build && workbox generateSW workbox-config.js"
|
||||||
}
|
}
|
||||||
|
|
303
webUI/src/components/forms/Haccountsearch.vue
Normal file
303
webUI/src/components/forms/Haccountsearch.vue
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
<template>
|
||||||
|
<v-menu
|
||||||
|
v-model="menu"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
location="bottom"
|
||||||
|
width="400"
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="props"
|
||||||
|
:model-value="selectedAccountName"
|
||||||
|
:label="label"
|
||||||
|
:rules="rules"
|
||||||
|
readonly
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
@click="openMenu"
|
||||||
|
>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
||||||
|
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
||||||
|
<div class="tree-container">
|
||||||
|
<div
|
||||||
|
v-for="item in accountData"
|
||||||
|
:key="item.id"
|
||||||
|
class="tree-node"
|
||||||
|
:class="{ 'has-children': item.children && item.children.length > 0 }"
|
||||||
|
>
|
||||||
|
<div class="tree-node-content" @click="handleNodeClick(item)">
|
||||||
|
<div class="tree-node-toggle" @click.stop="toggleNode(item)">
|
||||||
|
<v-icon v-if="item.children && item.children.length > 0">
|
||||||
|
{{ item.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-icon">
|
||||||
|
<v-icon v-if="item.children && item.children.length > 0">mdi-folder</v-icon>
|
||||||
|
<v-icon v-else>mdi-file-document</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === item.id }">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.isOpen && item.children && item.children.length > 0" class="tree-children">
|
||||||
|
<div
|
||||||
|
v-for="child in item.children"
|
||||||
|
:key="child.id"
|
||||||
|
class="tree-node"
|
||||||
|
:class="{ 'has-children': child.children && child.children.length > 0 }"
|
||||||
|
>
|
||||||
|
<div class="tree-node-content" @click="handleNodeClick(child)">
|
||||||
|
<div class="tree-node-toggle" @click.stop="toggleNode(child)">
|
||||||
|
<v-icon v-if="child.children && child.children.length > 0">
|
||||||
|
{{ child.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-icon">
|
||||||
|
<v-icon v-if="child.children && child.children.length > 0">mdi-folder</v-icon>
|
||||||
|
<v-icon v-else>mdi-file-document</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === child.id }">
|
||||||
|
{{ child.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="child.isOpen && child.children && child.children.length > 0" class="tree-children">
|
||||||
|
<div
|
||||||
|
v-for="grandChild in child.children"
|
||||||
|
:key="grandChild.id"
|
||||||
|
class="tree-node"
|
||||||
|
>
|
||||||
|
<div class="tree-node-content" @click="handleNodeClick(grandChild)">
|
||||||
|
<div class="tree-node-icon">
|
||||||
|
<v-icon>mdi-file-document</v-icon>
|
||||||
|
</div>
|
||||||
|
<div class="tree-node-label" :class="{ 'selected': selectedAccount?.id === grandChild.id }">
|
||||||
|
{{ grandChild.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-else>
|
||||||
|
در حال بارگذاری...
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { debounce } from 'lodash'; // برای دیبانس کردن loadChildren
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: 'حساب'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'select']);
|
||||||
|
|
||||||
|
const menu = ref(false);
|
||||||
|
const accountData = ref([]);
|
||||||
|
const selectedAccount = ref(null);
|
||||||
|
const cache = ref(new Map());
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
const selectedAccountName = computed(() => {
|
||||||
|
return selectedAccount.value?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// تابع برای رمزگشایی کاراکترهای یونیکد
|
||||||
|
const decodeUnicode = (str: string): string => {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(
|
||||||
|
str.replace(/\\u([\dA-F]{4})/gi, (match, grp) =>
|
||||||
|
String.fromCharCode(parseInt(grp, 16))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('خطا در رمزگشایی یونیکد:', e);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// پردازش دادهها برای رمزگشایی نامها
|
||||||
|
const processTreeData = (items: any[]): any[] => {
|
||||||
|
return items.map(item => {
|
||||||
|
if (cache.value.has(`processed-${item.id}`)) {
|
||||||
|
return cache.value.get(`processed-${item.id}`);
|
||||||
|
}
|
||||||
|
const processedItem = {
|
||||||
|
...item,
|
||||||
|
name: decodeUnicode(item.name),
|
||||||
|
children: item.children ? processTreeData(item.children) : [],
|
||||||
|
isOpen: false
|
||||||
|
};
|
||||||
|
cache.value.set(`processed-${item.id}`, processedItem);
|
||||||
|
return processedItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری تنبل زیرشاخهها با دیبانس
|
||||||
|
const loadChildren = debounce(async (node: any) => {
|
||||||
|
if (cache.value.has(node.id)) {
|
||||||
|
node.children = cache.value.get(node.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/hesabdari/tables/${node.id}/children`);
|
||||||
|
if (response.data.Success) {
|
||||||
|
const children = processTreeData(response.data.data || []);
|
||||||
|
node.children = children;
|
||||||
|
cache.value.set(node.id, children);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`خطا در بارگذاری زیرشاخههای گره ${node.id}:`, error);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
const toggleNode = (node: any) => {
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.isOpen = !node.isOpen;
|
||||||
|
if (node.isOpen && (!node.children || node.children.length === 0)) {
|
||||||
|
loadChildren(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// مدیریت انتخاب آیتمها
|
||||||
|
const handleNodeClick = (node: any) => {
|
||||||
|
selectedAccount.value = node;
|
||||||
|
emit('update:modelValue', node.id);
|
||||||
|
emit('select', node);
|
||||||
|
menu.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// باز کردن منو
|
||||||
|
const openMenu = () => {
|
||||||
|
menu.value = true;
|
||||||
|
if (!accountData.value.length && !isLoading.value) {
|
||||||
|
fetchHesabdariTables();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری اولیه گرههای ریشه
|
||||||
|
const fetchHesabdariTables = async () => {
|
||||||
|
if (cache.value.has('root')) {
|
||||||
|
accountData.value = cache.value.get('root');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/hesabdari/tables');
|
||||||
|
if (response.data.Success && response.data.data) {
|
||||||
|
accountData.value = processTreeData(response.data.data[0].children || []);
|
||||||
|
cache.value.set('root', accountData.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری حسابها:', error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// دیباگ تعداد مونتها
|
||||||
|
onMounted(() => {
|
||||||
|
fetchHesabdariTables();
|
||||||
|
});
|
||||||
|
|
||||||
|
// بررسی تغییرات در vue-router
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => {
|
||||||
|
console.log('modelValue تغییر کرد، احتمالاً به دلیل ناوبری');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content:hover {
|
||||||
|
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-toggle {
|
||||||
|
width: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-icon {
|
||||||
|
width: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: 'Vazir', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-label.selected {
|
||||||
|
color: rgb(var(--v-theme-primary));
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-children {
|
||||||
|
margin-left: 24px;
|
||||||
|
border-right: 2px solid rgba(var(--v-theme-primary), 0.1);
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-menu__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
transform-origin: center top !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-overlay__content) {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,7 +7,6 @@
|
||||||
v-model="showBarChart"
|
v-model="showBarChart"
|
||||||
:label="$t('dashboard.topCommodities.chartToggle')"
|
:label="$t('dashboard.topCommodities.chartToggle')"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
hide-details
|
hide-details
|
||||||
></v-switch>
|
></v-switch>
|
||||||
|
|
|
@ -13,10 +13,6 @@ import faIR from 'date-fns-jalali/locale/fa-IR';
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
|
||||||
// Import translations for the Persian language.
|
|
||||||
import '@ckeditor/ckeditor5-build-classic/build/translations/fa';
|
|
||||||
|
|
||||||
// Vuetify
|
// Vuetify
|
||||||
import 'vuetify/styles'
|
import 'vuetify/styles'
|
||||||
import { createVuetify } from 'vuetify'
|
import { createVuetify } from 'vuetify'
|
||||||
|
@ -156,7 +152,7 @@ app.component('v-cob', vSelect)
|
||||||
import Hdatepicker from "@/components/forms/Hdatepicker.vue";
|
import Hdatepicker from "@/components/forms/Hdatepicker.vue";
|
||||||
import calendarLocalConfig from "@/i18n/calendarLocalConfig";
|
import calendarLocalConfig from "@/i18n/calendarLocalConfig";
|
||||||
app.component('h-date-picker', Hdatepicker);
|
app.component('h-date-picker', Hdatepicker);
|
||||||
app.use(CKEditor)
|
|
||||||
app.use(Vue3PersianDatetimePicker, {
|
app.use(Vue3PersianDatetimePicker, {
|
||||||
name: 'CustomDatePicker',
|
name: 'CustomDatePicker',
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,4 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
|
<v-toolbar color="toolbar" :title="$t('dialog.accounting_doc')">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
||||||
|
icon="mdi-arrow-right" />
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip text="ثبت سند" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" variant="text" icon="mdi-content-save" color="success" @click="submitForm" :loading="loading"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip v-if="docId" text="حذف سند" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" variant="text" icon="mdi-delete" color="error" @click="deleteDialog = true" :loading="loading"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-form @submit.prevent="submitForm">
|
<v-form @submit.prevent="submitForm">
|
||||||
<v-row>
|
<v-row>
|
||||||
|
@ -17,78 +40,137 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-data-table
|
<v-table class="border rounded d-none d-sm-table mt-3" style="width: 100%;">
|
||||||
:headers="headers"
|
<thead>
|
||||||
:items="form.rows"
|
<tr style="background-color: #0D47A1; color: white; height: 40px;">
|
||||||
class="elevation-1"
|
<th class="text-center" style="font-size: 0.8rem; padding: 0 4px;">حساب</th>
|
||||||
hide-default-footer
|
<th class="text-center" style="font-size: 0.8rem; padding: 0 4px;">تفصیل</th>
|
||||||
:header-props="{ class: 'custom-header' }"
|
<th class="text-center" style="font-size: 0.8rem; padding: 0 4px;">توضیحات</th>
|
||||||
>
|
<th class="text-center" style="font-size: 0.8rem; padding: 0 4px;">بدهکار</th>
|
||||||
<template v-slot:top>
|
<th class="text-center" style="font-size: 0.8rem; padding: 0 4px;">بستانکار</th>
|
||||||
<v-toolbar flat>
|
<th class="text-center" style="width: 50px; font-size: 0.8rem; padding: 0 4px;">عملیات</th>
|
||||||
<v-toolbar-title>ردیفهای سند</v-toolbar-title>
|
</tr>
|
||||||
<v-spacer></v-spacer>
|
</thead>
|
||||||
<v-btn color="primary" @click="addRow">افزودن ردیف</v-btn>
|
<tbody>
|
||||||
</v-toolbar>
|
<template v-for="(row, index) in form.rows" :key="index">
|
||||||
</template>
|
<tr :style="{ backgroundColor: index % 2 === 0 ? '#f8f9fa' : 'white', height: '40px' }">
|
||||||
|
<td class="text-center" style="min-width: 150px; padding: 0 4px;">
|
||||||
<template v-slot:item.ref="{ item }">
|
<Haccountsearch
|
||||||
<v-menu offset-y>
|
v-model="row.ref"
|
||||||
<template v-slot:activator="{ props }">
|
:rules="[v => !!v || 'حساب الزامی است']"
|
||||||
|
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="min-width: 100px; padding: 0 4px;">
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="padding: 0 4px;">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="item.refName"
|
v-model="row.des"
|
||||||
label="حساب"
|
label="توضیحات"
|
||||||
dense
|
density="compact"
|
||||||
readonly
|
class="my-0"
|
||||||
v-bind="props"
|
style="font-size: 0.7rem;"
|
||||||
:rules="[v => !!item.ref || 'حساب الزامی است']"
|
hide-details
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</td>
|
||||||
<v-treeview
|
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||||
:items="hesabdariTables"
|
|
||||||
item-key="id"
|
|
||||||
item-text="name"
|
|
||||||
item-children="children"
|
|
||||||
selectable
|
|
||||||
return-object
|
|
||||||
v-model="item.selectedAccounts"
|
|
||||||
@update:active="selectAccount(item, $event)"
|
|
||||||
>
|
|
||||||
<template v-slot:label="{ item: treeItem }">
|
|
||||||
{{ treeItem.name }}
|
|
||||||
</template>
|
|
||||||
</v-treeview>
|
|
||||||
</v-menu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:item.bd="{ item }">
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="item.bd"
|
v-model="row.bd"
|
||||||
label="بدهکار"
|
label="بدهکار"
|
||||||
type="number"
|
type="number"
|
||||||
dense
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
hide-details
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</td>
|
||||||
|
<td class="text-center" style="width: 100px; padding: 0 4px;">
|
||||||
<template v-slot:item.bs="{ item }">
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="item.bs"
|
v-model="row.bs"
|
||||||
label="بستانکار"
|
label="بستانکار"
|
||||||
type="number"
|
type="number"
|
||||||
dense
|
density="compact"
|
||||||
@input="calculateTotals"
|
@input="calculateTotals"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.7rem;"
|
||||||
|
hide-details
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="width: 50px; padding: 0 4px;">
|
||||||
|
<v-tooltip text="حذف" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-delete" variant="text" size="x-small" color="error"
|
||||||
|
@click="removeRow(row)" style="min-width: 30px;"></v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center pa-1" style="height: 40px;">
|
||||||
|
<v-btn color="primary" prepend-icon="mdi-plus" size="x-small" @click="addRow">افزودن سطر جدید</v-btn>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
|
||||||
<template v-slot:item.des="{ item }">
|
<!-- جدول موبایل -->
|
||||||
<v-text-field v-model="item.des" label="توضیحات" dense></v-text-field>
|
<div class="d-sm-none">
|
||||||
</template>
|
<v-card v-for="(row, index) in form.rows" :key="index" class="mb-4" variant="outlined">
|
||||||
|
<v-card-text>
|
||||||
<template v-slot:item.actions="{ item }">
|
<div class="d-flex justify-space-between align-center mb-2">
|
||||||
<v-btn color="error" small @click="removeRow(item)">حذف</v-btn>
|
<span class="text-subtitle-2 font-weight-bold">ردیف:</span>
|
||||||
</template>
|
<span>{{ index + 1 }}</span>
|
||||||
</v-data-table>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<Haccountsearch
|
||||||
|
v-model="row.ref"
|
||||||
|
:rules="[v => !!v || 'حساب الزامی است']"
|
||||||
|
@account-selected="(account) => handleAccountSelect(row, account)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="row.des"
|
||||||
|
label="توضیحات"
|
||||||
|
density="compact"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.8rem;"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-space-between mb-2">
|
||||||
|
<div style="width: 48%;">
|
||||||
|
<v-text-field
|
||||||
|
v-model="row.bd"
|
||||||
|
label="بدهکار"
|
||||||
|
type="number"
|
||||||
|
density="compact"
|
||||||
|
@input="calculateTotals"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.8rem;"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
<div style="width: 48%;">
|
||||||
|
<v-text-field
|
||||||
|
v-model="row.bs"
|
||||||
|
label="بستانکار"
|
||||||
|
type="number"
|
||||||
|
density="compact"
|
||||||
|
@input="calculateTotals"
|
||||||
|
class="my-0"
|
||||||
|
style="font-size: 0.8rem;"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon="mdi-delete" variant="text" color="error" @click="removeRow(row)"></v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-btn color="primary" prepend-icon="mdi-plus" block class="mb-4" @click="addRow">افزودن ردیف جدید</v-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-row class="mt-4">
|
<v-row class="mt-4">
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
|
@ -110,21 +192,41 @@
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-alert v-if="error" type="error" class="mt-4">{{ error }}</v-alert>
|
<v-alert v-if="error" type="error" class="mt-4">{{ error }}</v-alert>
|
||||||
|
|
||||||
<v-btn type="submit" color="success" class="mt-4" :disabled="totalBd !== totalBs || !form.date">
|
|
||||||
ثبت سند
|
|
||||||
</v-btn>
|
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
|
<!-- دیالوگ تأیید حذف -->
|
||||||
|
<v-dialog v-model="deleteDialog" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5">
|
||||||
|
حذف سند
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
آیا مطمئن هستید که میخواهید این سند را حذف کنید؟
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey-darken-1" variant="text" @click="deleteDialog = false">
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="error" variant="text" @click="confirmDelete" :loading="loading">
|
||||||
|
حذف
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import moment from 'jalali-moment';
|
import moment from 'jalali-moment';
|
||||||
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue';
|
||||||
|
import Haccountsearch from '@/components/forms/Haccountsearch.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Hdatepicker,
|
Hdatepicker,
|
||||||
|
Haccountsearch
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
docId: {
|
docId: {
|
||||||
|
@ -135,7 +237,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
date: '', // تاریخ به فرمت ISO (مثلاً 2025-03-24)
|
date: '',
|
||||||
des: '',
|
des: '',
|
||||||
rows: [
|
rows: [
|
||||||
{ ref: null, refName: '', bd: '0', bs: '0', des: '', selectedAccounts: [] },
|
{ ref: null, refName: '', bd: '0', bs: '0', des: '', selectedAccounts: [] },
|
||||||
|
@ -145,13 +247,8 @@ export default {
|
||||||
totalBd: 0,
|
totalBd: 0,
|
||||||
totalBs: 0,
|
totalBs: 0,
|
||||||
error: null,
|
error: null,
|
||||||
headers: [
|
deleteDialog: false,
|
||||||
{ text: 'حساب', value: 'ref' },
|
loading: false,
|
||||||
{ text: 'بدهکار', value: 'bd' },
|
|
||||||
{ text: 'بستانکار', value: 'bs' },
|
|
||||||
{ text: 'توضیحات', value: 'des' },
|
|
||||||
{ text: 'عملیات', value: 'actions', sortable: false },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -173,7 +270,7 @@ export default {
|
||||||
async fetchDoc() {
|
async fetchDoc() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/hesabdari/doc/${this.docId}`);
|
const response = await axios.get(`/api/hesabdari/doc/${this.docId}`);
|
||||||
const serverDate = response.data.data.date; // فرض: تاریخ شمسی از سرور
|
const serverDate = response.data.data.date;
|
||||||
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY-MM-DD');
|
this.form.date = moment(serverDate, 'YYYY/MM/DD').format('YYYY-MM-DD');
|
||||||
this.form.des = response.data.data.des || '';
|
this.form.des = response.data.data.des || '';
|
||||||
this.form.rows = response.data.data.rows.map(row => ({
|
this.form.rows = response.data.data.rows.map(row => ({
|
||||||
|
@ -219,7 +316,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
date: moment(this.form.date, 'YYYY-MM-DD').locale('fa').format('YYYY/MM/DD'), // ارسال به فرمت شمسی
|
date: moment(this.form.date, 'YYYY-MM-DD').locale('fa').format('YYYY/MM/DD'),
|
||||||
des: this.form.des,
|
des: this.form.des,
|
||||||
rows: this.form.rows.map(row => ({
|
rows: this.form.rows.map(row => ({
|
||||||
ref: row.ref,
|
ref: row.ref,
|
||||||
|
@ -230,6 +327,7 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.loading = true;
|
||||||
if (this.docId) {
|
if (this.docId) {
|
||||||
await axios.put(`/api/hesabdari/doc/${this.docId}`, payload);
|
await axios.put(`/api/hesabdari/doc/${this.docId}`, payload);
|
||||||
this.$emit('saved', 'سند با موفقیت ویرایش شد');
|
this.$emit('saved', 'سند با موفقیت ویرایش شد');
|
||||||
|
@ -239,6 +337,21 @@ export default {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = error.response?.data?.message || 'خطا در ثبت سند';
|
this.error = error.response?.data?.message || 'خطا در ثبت سند';
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async confirmDelete() {
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
await axios.delete(`/api/hesabdari/doc/${this.docId}`);
|
||||||
|
this.$router.push('/acc/accounting/list');
|
||||||
|
} catch (error) {
|
||||||
|
this.error = 'خطا در حذف سند';
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
this.deleteDialog = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,42 +1,28 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue';
|
||||||
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
|
import axios from 'axios';
|
||||||
import axios from "axios";
|
import Swal from 'sweetalert2';
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import Loading from 'vue-loading-overlay';
|
import Loading from 'vue-loading-overlay';
|
||||||
import 'vue-loading-overlay/dist/css/index.css';
|
import 'vue-loading-overlay/dist/css/index.css';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "mod",
|
name: 'mod',
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
data: () => {
|
data: () => ({
|
||||||
return {
|
|
||||||
loading: true,
|
loading: true,
|
||||||
id: '',
|
id: '',
|
||||||
version: '',
|
version: '',
|
||||||
body: '',
|
body: '',
|
||||||
editor: ClassicEditor,
|
}),
|
||||||
editorConfig: {
|
|
||||||
language: 'fa',
|
|
||||||
fontFamily: {
|
|
||||||
options: [
|
|
||||||
'default',
|
|
||||||
'vazir', 'sans-serif',
|
|
||||||
'Ubuntu Mono, Courier New, Courier, monospace'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.id = this.$route.params.id;
|
this.id = this.$route.params.id;
|
||||||
if (this.id != 0) {
|
if (this.id !== '0') {
|
||||||
axios.post('/api/admin/reportchange/get/' + this.id).then((response) => {
|
axios.post('/api/admin/reportchange/get/' + this.id).then((response) => {
|
||||||
this.version = response.data.version;
|
this.version = response.data.version;
|
||||||
this.body = response.data.body;
|
this.body = response.data.body;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -48,29 +34,30 @@ export default defineComponent({
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
confirmButtonText: 'قبول',
|
confirmButtonText: 'قبول',
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
axios.post('/api/admin/reportchange/mod/' + this.id, {
|
axios
|
||||||
|
.post('/api/admin/reportchange/mod/' + this.id, {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
body: this.body
|
body: this.body,
|
||||||
}).then((response) => {
|
})
|
||||||
if (response.data.result == 1) {
|
.then((response) => {
|
||||||
|
if (response.data.result === 1) {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
text: 'گزارش ثبت شد',
|
text: 'گزارش ثبت شد',
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
confirmButtonText: 'قبول',
|
confirmButtonText: 'قبول',
|
||||||
}).then((res) => {
|
}).then(() => {
|
||||||
this.$router.push('/profile/manager/changes/list');
|
this.$router.push('/profile/manager/changes/list');
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -82,18 +69,36 @@ export default defineComponent({
|
||||||
<v-card-text class="pa-2">
|
<v-card-text class="pa-2">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="12" md="6">
|
<v-col cols="12" sm="12" md="6">
|
||||||
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.version')" v-model="version" type="text"
|
<v-text-field
|
||||||
|
hide-details="auto"
|
||||||
|
:label="$t('pages.manager.version')"
|
||||||
|
v-model="version"
|
||||||
|
type="text"
|
||||||
prepend-inner-icon="mdi-power-socket-uk"
|
prepend-inner-icon="mdi-power-socket-uk"
|
||||||
:rules="[() => version.length > 0 || $t('validator.required')]"></v-text-field>
|
:rules="[() => version.length > 0 || $t('validator.required')]"
|
||||||
|
></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="12" md="12">
|
<v-col cols="12" sm="12" md="12">
|
||||||
<h3 class="mb-2">{{ $t('app.body') }}</h3>
|
<h3 class="mb-2">{{ $t('app.body') }}</h3>
|
||||||
<ckeditor :editor="editor" v-model="body" :config="editorConfig"
|
<v-textarea
|
||||||
:rules="[() => version.length > 0 || $t('validator.required')]"></ckeditor>
|
v-model="body"
|
||||||
|
:rules="[() => body.length > 0 || $t('validator.required')]"
|
||||||
|
auto-grow
|
||||||
|
|
||||||
|
variant="outlined"
|
||||||
|
class="font-weight-regular"
|
||||||
|
style="font-family: 'Vazirmatn FD', sans-serif;"
|
||||||
|
></v-textarea>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="12" md="12">
|
<v-col cols="12" sm="12" md="12">
|
||||||
<v-btn type="submit" @click="submit()" color="primary" prepend-icon="mdi-content-save" :loading="loading"
|
<v-btn
|
||||||
:title="$t('dialog.save')">
|
type="submit"
|
||||||
|
@click="submit()"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:loading="loading"
|
||||||
|
:title="$t('dialog.save')"
|
||||||
|
>
|
||||||
{{ $t('dialog.save') }}
|
{{ $t('dialog.save') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
@ -103,4 +108,12 @@ export default defineComponent({
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.v-textarea {
|
||||||
|
font-family: 'Vazirmatn FD', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
text-align: right;
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,8 +1,13 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
|
||||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "esnext",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
}
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"]
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
"extends": "@vue/tsconfig/tsconfig.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
@ -7,9 +7,13 @@
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"verbatimModuleSyntax": true
|
"verbatimModuleSyntax": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["vite/client"]
|
||||||
},
|
},
|
||||||
|
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.config.json"
|
"path": "./tsconfig.config.json"
|
||||||
|
|
Loading…
Reference in a new issue