progress in close year
This commit is contained in:
parent
cd6821969f
commit
5afd0236c6
|
@ -5,6 +5,22 @@ parameters:
|
|||
sealDir: '%kernel.project_dir%/../hesabixArchive/seal'
|
||||
SupportFilesDir: '%kernel.project_dir%/../hesabixArchive/support'
|
||||
|
||||
# تنظیمات سیستم بستن سال مالی
|
||||
close_year.accounts.profit_loss: '999999'
|
||||
close_year.accounts.retained_earnings: '999998'
|
||||
close_year.account_types.temporary: ['calc'] # حسابهای موقت (درآمد و هزینه)
|
||||
close_year.account_types.permanent: ['calc'] # حسابهای دائمی (دارایی، بدهی، سرمایه)
|
||||
close_year.defaults.tax_percent: 0
|
||||
close_year.defaults.dividend_percent: 0
|
||||
close_year.defaults.new_year_duration: 31563000
|
||||
close_year.backup.enabled: true
|
||||
close_year.backup.directory: '%kernel.project_dir%/var/backups/'
|
||||
close_year.logging.enabled: true
|
||||
close_year.logging.level: 'info'
|
||||
close_year.security.required_role: 'plugAccproCloseYear'
|
||||
close_year.security.max_retry_attempts: 3
|
||||
close_year.security.transaction_timeout: 300
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
|
@ -167,3 +183,11 @@ services:
|
|||
$jdate: '@Jdate'
|
||||
$sms: '@SMS'
|
||||
$uploadDirectory: '%SupportFilesDir%'
|
||||
|
||||
# سرویس بستن سال مالی
|
||||
App\Service\CloseYearService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$logService: '@Log'
|
||||
$provider: '@Provider'
|
||||
$params: '@parameter_bag'
|
||||
|
|
|
@ -53,6 +53,7 @@ class AccountingDocService
|
|||
return ['error' => 'شخص یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
'year' => $acc['year']
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
|
@ -65,6 +66,7 @@ class AccountingDocService
|
|||
return ['error' => 'بانک یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'bank' => $bank,
|
||||
'year' => $acc['year']
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
|
@ -77,6 +79,7 @@ class AccountingDocService
|
|||
return ['error' => 'صندوق یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'cashdesk' => $cashdesk,
|
||||
'year' => $acc['year']
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
|
@ -89,6 +92,7 @@ class AccountingDocService
|
|||
return ['error' => 'حقوق یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'salary' => $salary,
|
||||
'year' => $acc['year']
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
|
|
433
hesabixCore/src/Controller/CloseYearController.php
Normal file
433
hesabixCore/src/Controller/CloseYearController.php
Normal file
|
@ -0,0 +1,433 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\BankAccount;
|
||||
use App\Entity\Business;
|
||||
use App\Entity\Cashdesk;
|
||||
use App\Entity\HesabdariDoc;
|
||||
use App\Entity\HesabdariRow;
|
||||
use App\Entity\HesabdariTable;
|
||||
use App\Entity\Log as EntityLog;
|
||||
use App\Entity\Money;
|
||||
use App\Entity\Person;
|
||||
use App\Entity\Salary;
|
||||
use App\Entity\Year;
|
||||
use App\Service\Access;
|
||||
use App\Service\CloseYearService;
|
||||
use App\Service\Explore;
|
||||
use App\Service\Jdate;
|
||||
use App\Service\Log;
|
||||
use App\Service\Provider;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class CloseYearController extends AbstractController
|
||||
{
|
||||
private CloseYearService $closeYearService;
|
||||
|
||||
public function __construct(CloseYearService $closeYearService)
|
||||
{
|
||||
$this->closeYearService = $closeYearService;
|
||||
}
|
||||
|
||||
#[Route('/api/year/close/prepare', name: 'app_year_close_prepare')]
|
||||
public function app_year_close_prepare(Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('plugAccproCloseYear');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$currentYear = $entityManager->getRepository(Year::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'head' => true
|
||||
]);
|
||||
|
||||
if (!$currentYear) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'سال مالی فعال یافت نشد'
|
||||
]);
|
||||
}
|
||||
|
||||
// محاسبه سود و زیان
|
||||
$profitLoss = $this->closeYearService->calculateProfitLoss($currentYear);
|
||||
|
||||
// محاسبه ترازنامه
|
||||
$balanceSheet = $this->closeYearService->calculateBalanceSheet($currentYear);
|
||||
|
||||
// دریافت ساختار درختی حسابها
|
||||
$accountsTree = $this->getAccountsTree($entityManager, $acc['bid'], $currentYear);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'currentYear' => [
|
||||
'id' => $currentYear->getId(),
|
||||
'label' => $currentYear->getLabel(),
|
||||
'start' => $currentYear->getStart(),
|
||||
'end' => $currentYear->getEnd()
|
||||
],
|
||||
'profitLoss' => $profitLoss,
|
||||
'balanceSheet' => $balanceSheet,
|
||||
'accountsTree' => $accountsTree
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/year/close/execute', name: 'app_year_close_execute', methods: ['POST'])]
|
||||
public function app_year_close_execute(Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, Jdate $jdate, Provider $provider): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('plugAccproCloseYear');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
|
||||
$currentYear = $entityManager->getRepository(Year::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'head' => true
|
||||
]);
|
||||
|
||||
if (!$currentYear) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'سال مالی فعال یافت نشد'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$entityManager->beginTransaction();
|
||||
|
||||
// بستن حسابهای موقت
|
||||
$this->closeYearService->closeTemporaryAccounts($currentYear, $params, $this->getUser());
|
||||
|
||||
// ایجاد سال مالی جدید
|
||||
$newYear = $this->closeYearService->createNewYear($acc['bid'], $params);
|
||||
|
||||
// انتقال مانده حسابهای دائمی
|
||||
$this->closeYearService->transferPermanentAccounts($currentYear, $newYear, $this->getUser());
|
||||
|
||||
// ثبت سود انباشته
|
||||
$this->closeYearService->recordRetainedEarnings($currentYear, $newYear, $params, $this->getUser());
|
||||
|
||||
// تغییر وضعیت سال مالی
|
||||
$currentYear->setHead(false);
|
||||
$newYear->setHead(true);
|
||||
$entityManager->persist($currentYear);
|
||||
$entityManager->persist($newYear);
|
||||
|
||||
$entityManager->flush();
|
||||
$entityManager->commit();
|
||||
|
||||
$log->insert(
|
||||
'بستن سال مالی',
|
||||
'سال مالی ' . $currentYear->getLabel() . ' بسته شد و سال مالی جدید ' . $newYear->getLabel() . ' ایجاد شد.',
|
||||
$this->getUser(),
|
||||
$request->headers->get('activeBid'),
|
||||
null
|
||||
);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'msg' => 'سال مالی با موفقیت بسته شد',
|
||||
'newYear' => [
|
||||
'id' => $newYear->getId(),
|
||||
'label' => $newYear->getLabel()
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$entityManager->rollback();
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'خطا در بستن سال مالی: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/year/close/validate', name: 'app_year_close_validate', methods: ['POST'])]
|
||||
public function app_year_close_validate(Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('plugAccproCloseYear');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
|
||||
try {
|
||||
// اعتبارسنجی تاریخهای سال مالی جدید
|
||||
$this->closeYearService->validateNewYearDates($acc['bid'], $params);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'msg' => 'تاریخهای سال مالی معتبر است',
|
||||
'error' => false
|
||||
]);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => $e->getMessage(),
|
||||
'error' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/year/close/accounts', name: 'app_year_close_accounts')]
|
||||
public function app_year_close_accounts(Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('plugAccproCloseYear');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$currentYear = $entityManager->getRepository(Year::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'head' => true
|
||||
]);
|
||||
|
||||
if (!$currentYear) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'msg' => 'سال مالی فعال یافت نشد'
|
||||
]);
|
||||
}
|
||||
|
||||
// دریافت تمام حسابهای حسابداری به صورت درختی
|
||||
$accounts = $this->getAccountsTree($entityManager, $acc['bid'], $currentYear);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'accounts' => $accounts
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت ساختار درختی حسابهای حسابداری
|
||||
*/
|
||||
private function getAccountsTree(EntityManagerInterface $entityManager, Business $business, Year $year): array
|
||||
{
|
||||
$accountsTree = [];
|
||||
|
||||
// دریافت حسابهای عمومی اصلی (بدون parent)
|
||||
$publicMainAccounts = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||
'bid' => null,
|
||||
'upper' => null
|
||||
]);
|
||||
|
||||
foreach ($publicMainAccounts as $mainAccount) {
|
||||
$accountData = $this->buildAccountTreeData($entityManager, $mainAccount, $year, $business);
|
||||
if ($accountData) {
|
||||
$accountData['isPublic'] = true;
|
||||
$accountsTree[] = $accountData;
|
||||
}
|
||||
}
|
||||
|
||||
// دریافت حسابهای اختصاصی اصلی (بدون parent)
|
||||
$privateMainAccounts = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||
'bid' => $business,
|
||||
'upper' => null
|
||||
]);
|
||||
|
||||
foreach ($privateMainAccounts as $mainAccount) {
|
||||
$accountData = $this->buildAccountTreeData($entityManager, $mainAccount, $year, $business);
|
||||
if ($accountData) {
|
||||
$accountData['isPublic'] = false;
|
||||
$accountsTree[] = $accountData;
|
||||
}
|
||||
}
|
||||
|
||||
return $accountsTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* ساخت دادههای درختی حساب
|
||||
*/
|
||||
private function buildAccountTreeData(EntityManagerInterface $entityManager, HesabdariTable $account, Year $year, Business $business): ?array
|
||||
{
|
||||
// محاسبه مانده کل حساب و زیرمجموعههای آن
|
||||
$totalBalance = $this->calculateAccountTreeBalance($entityManager, $account, $year, $business);
|
||||
|
||||
// محاسبه مانده با در نظر گرفتن نوع حساب
|
||||
$balanceWithType = $this->calculateBalanceWithType($account->getCode(), $totalBalance);
|
||||
|
||||
// اگر مانده صفر است و زیرمجموعهای ندارد، نمایش نده
|
||||
if ($balanceWithType == 0 && !$this->hasChildren($entityManager, $account, $business)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$accountData = [
|
||||
'id' => $account->getId(),
|
||||
'name' => $account->getName(),
|
||||
'code' => $account->getCode(),
|
||||
'type' => $account->getType(),
|
||||
'balance' => $balanceWithType,
|
||||
'children' => []
|
||||
];
|
||||
|
||||
// دریافت زیرمجموعهها (عمومی و اختصاصی)
|
||||
$childAccounts = $this->getChildAccounts($entityManager, $account, $business);
|
||||
|
||||
foreach ($childAccounts as $childAccount) {
|
||||
$childData = $this->buildAccountTreeData($entityManager, $childAccount, $year, $business);
|
||||
if ($childData) {
|
||||
$childData['isPublic'] = $childAccount->getBid() === null;
|
||||
$accountData['children'][] = $childData;
|
||||
}
|
||||
}
|
||||
|
||||
return $accountData;
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبه مانده کل یک حساب و تمام زیرمجموعههای آن
|
||||
*/
|
||||
private function calculateAccountTreeBalance(EntityManagerInterface $entityManager, HesabdariTable $account, Year $year, Business $business): float
|
||||
{
|
||||
$totalBalance = 0;
|
||||
|
||||
// محاسبه مانده خود حساب
|
||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'ref' => $account,
|
||||
'year' => $year
|
||||
]);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
// محاسبه مانده بر اساس کد حساب به جای type
|
||||
$totalBalance += (float)$row->getBd() - (float)$row->getBs();
|
||||
}
|
||||
|
||||
// محاسبه مانده تمام زیرمجموعهها (عمومی و اختصاصی)
|
||||
$childAccounts = $this->getChildAccounts($entityManager, $account, $business);
|
||||
|
||||
foreach ($childAccounts as $childAccount) {
|
||||
$totalBalance += $this->calculateAccountTreeBalance($entityManager, $childAccount, $year, $business);
|
||||
}
|
||||
|
||||
return $totalBalance;
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبه مانده با در نظر گرفتن نوع حساب
|
||||
*/
|
||||
private function calculateBalanceWithType(string $code, float $balance): float
|
||||
{
|
||||
if ($this->isDebitAccount($code)) {
|
||||
return $balance; // بدهکار: بدهی - بستانکاری
|
||||
} else {
|
||||
return -$balance; // بستانکار: بستانکاری - بدهی
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی اینکه آیا حساب بدهکار است یا بستانکار
|
||||
*/
|
||||
private function isDebitAccount(string $code): bool
|
||||
{
|
||||
$codeInt = (int)$code;
|
||||
|
||||
// داراییها (کدهای 2-19، به جز 8 و 9): بدهکار
|
||||
if ($codeInt >= 2 && $codeInt <= 19 && $codeInt != 8 && $codeInt != 9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// موجودی کالا (کد 120): بدهکار
|
||||
if ($codeInt == 120) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// حسابهای کنترلی (کد 117): بدهکار
|
||||
if ($codeInt == 117) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// هزینهها (کدهای 67-111): بدهکار
|
||||
if ($codeInt >= 67 && $codeInt <= 111) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// بدهیها (کدهای 6-39): بستانکار
|
||||
if ($codeInt >= 6 && $codeInt <= 39) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// سرمایه (کدهای 40-47): بستانکار
|
||||
if ($codeInt >= 40 && $codeInt <= 47) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// درآمدها (کدهای 56-66): بستانکار
|
||||
if ($codeInt >= 56 && $codeInt <= 66) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// فروش (کدهای 52-55): بستانکار
|
||||
if ($codeInt >= 52 && $codeInt <= 55) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// بهای تمام شده (کدهای 48-51): بدهکار
|
||||
if ($codeInt >= 48 && $codeInt <= 51) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// سایر حسابها: پیشفرض بستانکار
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت زیرمجموعههای یک حساب (عمومی و اختصاصی)
|
||||
*/
|
||||
private function getChildAccounts(EntityManagerInterface $entityManager, HesabdariTable $parentAccount, Business $business): array
|
||||
{
|
||||
$childAccounts = [];
|
||||
|
||||
// دریافت زیرمجموعههای عمومی
|
||||
$publicChildren = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||
'upper' => $parentAccount,
|
||||
'bid' => null
|
||||
]);
|
||||
|
||||
foreach ($publicChildren as $child) {
|
||||
$childAccounts[] = $child;
|
||||
}
|
||||
|
||||
// دریافت زیرمجموعههای اختصاصی
|
||||
$privateChildren = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||
'upper' => $parentAccount,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
foreach ($privateChildren as $child) {
|
||||
$childAccounts[] = $child;
|
||||
}
|
||||
|
||||
return $childAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* بررسی وجود زیرمجموعه
|
||||
*/
|
||||
private function hasChildren(EntityManagerInterface $entityManager, HesabdariTable $account, Business $business): bool
|
||||
{
|
||||
// بررسی زیرمجموعههای عمومی
|
||||
$publicChildCount = $entityManager->getRepository(HesabdariTable::class)->count([
|
||||
'upper' => $account,
|
||||
'bid' => null
|
||||
]);
|
||||
|
||||
// بررسی زیرمجموعههای اختصاصی
|
||||
$privateChildCount = $entityManager->getRepository(HesabdariTable::class)->count([
|
||||
'upper' => $account,
|
||||
'bid' => $business
|
||||
]);
|
||||
|
||||
return ($publicChildCount + $privateChildCount) > 0;
|
||||
}
|
||||
}
|
|
@ -621,7 +621,7 @@ class SellController extends AbstractController
|
|||
if ($commodityId) {
|
||||
$last = $entityManager->getRepository(HesabdariRow::class)
|
||||
->findOneBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']);
|
||||
if ($last) {
|
||||
if ($last && $last->getCommdityCount() > 0 && $item->getCommdityCount() > 0) {
|
||||
$price = $last->getBd() / $last->getCommdityCount();
|
||||
$profit += ($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount();
|
||||
} else {
|
||||
|
@ -646,7 +646,7 @@ class SellController extends AbstractController
|
|||
$avg += $last->getBd();
|
||||
$count += $last->getCommdityCount();
|
||||
}
|
||||
if ($count != 0) {
|
||||
if ($count != 0 && $item->getCommdityCount() > 0) {
|
||||
$price = $avg / $count;
|
||||
$profit += ($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount();
|
||||
} else {
|
||||
|
|
1085
hesabixCore/src/Service/CloseYearService.php
Normal file
1085
hesabixCore/src/Service/CloseYearService.php
Normal file
File diff suppressed because it is too large
Load diff
99
hesabixCore/tests/CloseYearAccountsTest.php
Normal file
99
hesabixCore/tests/CloseYearAccountsTest.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Entity\Business;
|
||||
use App\Entity\HesabdariTable;
|
||||
use App\Entity\Year;
|
||||
use App\Service\CloseYearService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
class CloseYearAccountsTest extends KernelTestCase
|
||||
{
|
||||
private CloseYearService $closeYearService;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$kernel = self::bootKernel();
|
||||
$this->entityManager = $kernel->getContainer()->get('doctrine')->getManager();
|
||||
$this->closeYearService = static::getContainer()->get(CloseYearService::class);
|
||||
}
|
||||
|
||||
public function testEnsureCloseYearAccountsExist(): void
|
||||
{
|
||||
// حذف حسابهای موجود (اگر وجود دارند)
|
||||
$this->entityManager->createQueryBuilder()
|
||||
->delete(HesabdariTable::class, 'h')
|
||||
->where('h.code IN (:codes)')
|
||||
->setParameter('codes', ['999998', '999999'])
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// بررسی اینکه حسابها وجود ندارند
|
||||
$profitLossAccount = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '999999']);
|
||||
$retainedEarningsAccount = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '999998']);
|
||||
|
||||
$this->assertNull($profitLossAccount);
|
||||
$this->assertNull($retainedEarningsAccount);
|
||||
|
||||
// ایجاد یک سال مالی تست
|
||||
$business = new Business();
|
||||
$business->setName('Test Business');
|
||||
$this->entityManager->persist($business);
|
||||
|
||||
$year = new Year();
|
||||
$year->setBid($business);
|
||||
$year->setLabel('سال تست');
|
||||
$year->setStart(time());
|
||||
$year->setEnd(time() + 31536000);
|
||||
$year->setHead(true);
|
||||
$this->entityManager->persist($year);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// اجرای بستن حسابهای موقت (که خودکار حسابها را ایجاد میکند)
|
||||
$this->closeYearService->closeTemporaryAccounts($year, []);
|
||||
|
||||
// بررسی اینکه حسابها ایجاد شدهاند
|
||||
$profitLossAccount = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '999999']);
|
||||
$retainedEarningsAccount = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '999998']);
|
||||
|
||||
$this->assertNotNull($profitLossAccount);
|
||||
$this->assertNotNull($retainedEarningsAccount);
|
||||
$this->assertEquals('سود و زیان', $profitLossAccount->getName());
|
||||
$this->assertEquals('سود انباشته', $retainedEarningsAccount->getName());
|
||||
$this->assertNull($profitLossAccount->getBid()); // حساب عمومی
|
||||
$this->assertNull($retainedEarningsAccount->getBid()); // حساب عمومی
|
||||
|
||||
// پاکسازی
|
||||
$this->entityManager->remove($year);
|
||||
$this->entityManager->remove($business);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function testGetOrCreateAccount(): void
|
||||
{
|
||||
// تست ایجاد حساب جدید
|
||||
$business = new Business();
|
||||
$business->setName('Test Business');
|
||||
$this->entityManager->persist($business);
|
||||
|
||||
$account = $this->closeYearService->getOrCreateAccount($business, '999999', 'سود و زیان', 'calc');
|
||||
|
||||
$this->assertNotNull($account);
|
||||
$this->assertEquals('999999', $account->getCode());
|
||||
$this->assertEquals('سود و زیان', $account->getName());
|
||||
$this->assertEquals('calc', $account->getType());
|
||||
$this->assertNull($account->getBid()); // حساب عمومی
|
||||
|
||||
// تست دریافت حساب موجود
|
||||
$existingAccount = $this->closeYearService->getOrCreateAccount($business, '999999', 'سود و زیان', 'calc');
|
||||
$this->assertEquals($account->getId(), $existingAccount->getId());
|
||||
|
||||
// پاکسازی
|
||||
$this->entityManager->remove($business);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
159
hesabixCore/tests/CloseYearTest.php
Normal file
159
hesabixCore/tests/CloseYearTest.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Entity\Business;
|
||||
use App\Entity\HesabdariTable;
|
||||
use App\Entity\HesabdariRow;
|
||||
use App\Entity\Year;
|
||||
use App\Entity\User;
|
||||
use App\Entity\Money;
|
||||
use App\Service\CloseYearService;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
class CloseYearTest extends KernelTestCase
|
||||
{
|
||||
private CloseYearService $closeYearService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->closeYearService = static::getContainer()->get(CloseYearService::class);
|
||||
}
|
||||
|
||||
public function testIsDebitAccount(): void
|
||||
{
|
||||
// تست حسابهای دارایی (باید بدهکار باشند)
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('2')); // داراییهای جاری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('3')); // حسابهای دریافتی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('4')); // موجودی نقد و بانک
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('5')); // حسابهای بانکی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('10')); // داراییهای غیر جاری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('11')); // داراییهای ثابت
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('12')); // زمین
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('13')); // ساختمان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('14')); // وسائل نقلیه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('15')); // اثاثیه اداری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('16')); // استهلاک انباشته
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('17')); // استهلاک انباشته ساختمان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('18')); // استهلاک انباشته وسائل نقلیه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('19')); // استهلاک انباشته اثاثیه اداری
|
||||
|
||||
// تست حسابهای بدهی (باید بستانکار باشند)
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('6')); // بدهیهای جاری
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('7')); // حساب ها و اسناد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('8')); // اسناد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('9')); // حسابهای پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('22')); // سایر حساب های پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('23')); // ذخیره مالیات بر درآمد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('24')); // مالیات بر درآمد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('25')); // مالیات حقوق و دستمزد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('26')); // حق بیمه پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('27')); // حقوق و دستمزد پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('28')); // عیدی و پاداش پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('29')); // سایر هزینه های پرداختنی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('30')); // پیش دریافت ها
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('31')); // پیش دریافت فروش
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('32')); // سایر پیش دریافت ها
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('33')); // مالیات بر ارزش افزوده فروش
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('34')); // بدهیهای غیر جاری
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('35')); // حساب ها و اسناد پرداختنی بلندمدت
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('36')); // حساب های پرداختنی بلندمدت
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('37')); // اسناد پرداختنی بلندمدت
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('38')); // ذخیره مزایای پایان خدمت کارکنان
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('39')); // وام پرداختنی
|
||||
|
||||
// تست حسابهای سرمایه (باید بستانکار باشند)
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('40')); // حقوق صاحبان سهام
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('41')); // سرمایه
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('42')); // سرمایه اولیه
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('43')); // افزایش یا کاهش سرمایه
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('44')); // اندوخته قانونی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('45')); // برداشت ها
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('46')); // سهم سود و زیان
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('47')); // سود یا زیان انباشته
|
||||
|
||||
// تست حسابهای بهای تمام شده (باید بدهکار باشند)
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('48')); // بهای تمام شده کالای فروخته شده
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('49')); // بهای تمام شده کالای فروخته شده
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('50')); // برگشت از خرید
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('51')); // تخفیفات نقدی خرید
|
||||
|
||||
// تست حسابهای فروش (باید بستانکار باشند)
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('52')); // فروش
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('53')); // فروش کالا
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('54')); // برگشت از فروش
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('55')); // تخفیفات نقدی فروش
|
||||
|
||||
// تست حسابهای درآمد (باید بستانکار باشند)
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('56')); // درآمد
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('57')); // درآمد های عملیاتی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('58')); // درآمد حاصل از فروش خدمات
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('59')); // برگشت از خرید خدمات
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('60')); // درآمد اضافه کالا
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('61')); // درآمد حمل کالا
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('62')); // درآمد های غیر عملیاتی
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('63')); // درآمد حاصل از سرمایه گذاری
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('64')); // درآمد سود سپرده ها
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('65')); // سایر درآمد ها
|
||||
$this->assertFalse($this->closeYearService->isDebitAccount('66')); // درآمد تسعیر ارز
|
||||
|
||||
// تست حسابهای هزینه (باید بدهکار باشند)
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('67')); // هزینه ها
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('68')); // هزینه های پرسنلی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('69')); // هزینه حقوق و دستمزد
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('70')); // حقوق پایه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('71')); // اضافه کار
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('72')); // حق شیفت و شب کاری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('73')); // حق نوبت کاری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('74')); // حق ماموریت
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('75')); // فوق العاده مسکن و خاروبار
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('76')); // حق اولاد
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('77')); // عیدی و پاداش
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('78')); // بازخرید سنوات خدمت کارکنان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('79')); // بازخرید مرخصی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('80')); // بیمه سهم کارفرما
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('81')); // بیمه بیکاری
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('82')); // حقوق مزایای متفرقه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('83')); // سایر هزینه های کارکنان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('84')); // سفر و ماموریت
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('85')); // ایاب و ذهاب
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('86')); // سایر هزینه های کارکنان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('87')); // هزینه های عملیاتی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('88')); // خرید خدمات
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('89')); // برگشت از فروش خدمات
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('90')); // هزینه حمل کالا
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('91')); // تعمیر و نگهداری اموال و اثاثیه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('92')); // هزینه اجاره محل
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('93')); // هزینه های عمومی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('94')); // هزینه ملزومات مصرفی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('95')); // هزینه کسری و ضایعات کالا
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('96')); // بیمه دارایی های ثابت
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('97')); // هزینه های استهلاک
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('98')); // هزینه استهلاک ساختمان
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('99')); // هزینه استهلاک وسائل نقلیه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('100')); // هزینه استهلاک اثاثیه
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('101')); // هزینه های بازاریابی و توزیع و فروش
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('102')); // هزینه آگهی و تبلیغات
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('103')); // هزینه بازاریابی و پورسانت
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('104')); // سایر هزینه های توزیع و فروش
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('105')); // هزینه های غیرعملیاتی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('106')); // هزینه های بانکی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('107')); // سود و کارمزد وامها
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('108')); // کارمزد خدمات بانکی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('109')); // جرائم دیرکرد بانکی
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('110')); // هزینه تسعیر ارز
|
||||
$this->assertTrue($this->closeYearService->isDebitAccount('111')); // هزینه مطالبات سوخت شده
|
||||
}
|
||||
|
||||
public function testRouteExists(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$router = static::getContainer()->get('router');
|
||||
|
||||
// بررسی وجود route ها
|
||||
$this->assertNotNull($router->getRouteCollection()->get('app_year_close_prepare'));
|
||||
$this->assertNotNull($router->getRouteCollection()->get('app_year_close_execute'));
|
||||
$this->assertNotNull($router->getRouteCollection()->get('app_year_close_accounts'));
|
||||
}
|
||||
}
|
207
hesabixCore/tests/Controller/CloseYearControllerTest.php
Normal file
207
hesabixCore/tests/Controller/CloseYearControllerTest.php
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Entity\Business;
|
||||
use App\Entity\HesabdariTable;
|
||||
use App\Entity\HesabdariRow;
|
||||
use App\Entity\Year;
|
||||
use App\Entity\User;
|
||||
use App\Entity\Money;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CloseYearControllerTest extends WebTestCase
|
||||
{
|
||||
private $client;
|
||||
private $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->client = static::createClient();
|
||||
$this->entityManager = static::getContainer()->get('doctrine')->getManager();
|
||||
}
|
||||
|
||||
public function testPrepareCloseYear(): void
|
||||
{
|
||||
// ایجاد دادههای تست
|
||||
$business = $this->createTestBusiness();
|
||||
$year = $this->createTestYear($business);
|
||||
$user = $this->createTestUser($business);
|
||||
|
||||
// شبیهسازی احراز هویت
|
||||
$this->client->request('POST', '/api/year/close/prepare', [], [], [
|
||||
'HTTP_Authorization' => 'Bearer test-token',
|
||||
'HTTP_activeBid' => $business->getId(),
|
||||
'HTTP_activeYear' => $year->getId()
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$response = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals(1, $response['result']);
|
||||
$this->assertArrayHasKey('currentYear', $response);
|
||||
$this->assertArrayHasKey('profitLoss', $response);
|
||||
$this->assertArrayHasKey('balanceSheet', $response);
|
||||
}
|
||||
|
||||
public function testExecuteCloseYear(): void
|
||||
{
|
||||
// ایجاد دادههای تست
|
||||
$business = $this->createTestBusiness();
|
||||
$year = $this->createTestYear($business);
|
||||
$user = $this->createTestUser($business);
|
||||
|
||||
$closeData = [
|
||||
'taxPercent' => 25.0,
|
||||
'dividendPercent' => 50.0,
|
||||
'newYearLabel' => 'سال مالی تست',
|
||||
'newYearStart' => '1405/01/01',
|
||||
'newYearEnd' => '1406/01/01'
|
||||
];
|
||||
|
||||
// شبیهسازی احراز هویت
|
||||
$this->client->request('POST', '/api/year/close/execute', [], [], [
|
||||
'HTTP_Authorization' => 'Bearer test-token',
|
||||
'HTTP_activeBid' => $business->getId(),
|
||||
'HTTP_activeYear' => $year->getId()
|
||||
], json_encode($closeData));
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$response = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals(1, $response['result']);
|
||||
$this->assertArrayHasKey('newYear', $response);
|
||||
}
|
||||
|
||||
public function testGetAccounts(): void
|
||||
{
|
||||
// ایجاد دادههای تست
|
||||
$business = $this->createTestBusiness();
|
||||
$year = $this->createTestYear($business);
|
||||
$user = $this->createTestUser($business);
|
||||
$this->createTestAccounts($business, $year);
|
||||
|
||||
// شبیهسازی احراز هویت
|
||||
$this->client->request('GET', '/api/year/close/accounts', [], [], [
|
||||
'HTTP_Authorization' => 'Bearer test-token',
|
||||
'HTTP_activeBid' => $business->getId(),
|
||||
'HTTP_activeYear' => $year->getId()
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$response = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
$this->assertEquals(1, $response['result']);
|
||||
$this->assertArrayHasKey('accounts', $response);
|
||||
$this->assertIsArray($response['accounts']);
|
||||
}
|
||||
|
||||
private function createTestBusiness(): Business
|
||||
{
|
||||
$business = new Business();
|
||||
$business->setName('شرکت تست');
|
||||
$business->setCode('TEST001');
|
||||
$this->entityManager->persist($business);
|
||||
$this->entityManager->flush();
|
||||
return $business;
|
||||
}
|
||||
|
||||
private function createTestYear(Business $business): Year
|
||||
{
|
||||
$year = new Year();
|
||||
$year->setBid($business);
|
||||
$year->setLabel('سال مالی تست');
|
||||
$year->setHead(true);
|
||||
$year->setStart('1404/01/01');
|
||||
$year->setEnd('1405/01/01');
|
||||
$this->entityManager->persist($year);
|
||||
$this->entityManager->flush();
|
||||
return $year;
|
||||
}
|
||||
|
||||
private function createTestUser(Business $business): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUsername('testuser');
|
||||
$user->setEmail('test@example.com');
|
||||
$user->setPassword('password');
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createTestAccounts(Business $business, Year $year): void
|
||||
{
|
||||
// ایجاد حساب درآمد
|
||||
$incomeAccount = new HesabdariTable();
|
||||
$incomeAccount->setBid($business);
|
||||
$incomeAccount->setName('فروش');
|
||||
$incomeAccount->setCode('4001');
|
||||
$incomeAccount->setType('income');
|
||||
$this->entityManager->persist($incomeAccount);
|
||||
|
||||
// ایجاد حساب هزینه
|
||||
$expenseAccount = new HesabdariTable();
|
||||
$expenseAccount->setBid($business);
|
||||
$expenseAccount->setName('هزینههای عملیاتی');
|
||||
$expenseAccount->setCode('5001');
|
||||
$expenseAccount->setType('expense');
|
||||
$this->entityManager->persist($expenseAccount);
|
||||
|
||||
// ایجاد حساب دارایی
|
||||
$assetAccount = new HesabdariTable();
|
||||
$assetAccount->setBid($business);
|
||||
$assetAccount->setName('موجودی نقد');
|
||||
$assetAccount->setCode('1001');
|
||||
$assetAccount->setType('asset');
|
||||
$this->entityManager->persist($assetAccount);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// ایجاد ردیفهای حسابداری
|
||||
$this->createTestRows($incomeAccount, $expenseAccount, $assetAccount, $year);
|
||||
}
|
||||
|
||||
private function createTestRows(HesabdariTable $incomeAccount, HesabdariTable $expenseAccount, HesabdariTable $assetAccount, Year $year): void
|
||||
{
|
||||
// ردیف درآمد
|
||||
$incomeRow = new HesabdariRow();
|
||||
$incomeRow->setBid($year->getBid());
|
||||
$incomeRow->setYear($year);
|
||||
$incomeRow->setRef($incomeAccount);
|
||||
$incomeRow->setBs('1000000');
|
||||
$incomeRow->setBd('0');
|
||||
$incomeRow->setDes('فروش دوره');
|
||||
$this->entityManager->persist($incomeRow);
|
||||
|
||||
// ردیف هزینه
|
||||
$expenseRow = new HesabdariRow();
|
||||
$expenseRow->setBid($year->getBid());
|
||||
$expenseRow->setYear($year);
|
||||
$expenseRow->setRef($expenseAccount);
|
||||
$expenseRow->setBs('0');
|
||||
$expenseRow->setBd('600000');
|
||||
$expenseRow->setDes('هزینههای دوره');
|
||||
$this->entityManager->persist($expenseRow);
|
||||
|
||||
// ردیف دارایی
|
||||
$assetRow = new HesabdariRow();
|
||||
$assetRow->setBid($year->getBid());
|
||||
$assetRow->setYear($year);
|
||||
$assetRow->setRef($assetAccount);
|
||||
$assetRow->setBs('0');
|
||||
$assetRow->setBd('500000');
|
||||
$assetRow->setDes('موجودی نقد');
|
||||
$this->entityManager->persist($assetRow);
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
$this->entityManager->close();
|
||||
$this->entityManager = null;
|
||||
}
|
||||
}
|
|
@ -21,181 +21,730 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="text-primary"> سال مالی :
|
||||
{{ YearInfo.year.label }}
|
||||
|
||||
<div class="col-sm-12">
|
||||
<h5 class="text-primary"> سال مالی جاری:
|
||||
{{ currentYear.label }}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعات سود و زیان -->
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="block block-rounded block-mode-loading-refresh">
|
||||
<div class="block-header block-header-default bg-info text-light">
|
||||
<h3 class="block-title">سود و زیان</h3>
|
||||
</div>
|
||||
<div class="block-content p-3">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>کل درآمد:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ $filters.formatNumber(profitLoss.totalIncome) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>کل هزینه:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ $filters.formatNumber(profitLoss.totalExpense) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>سود خالص:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end" :class="profitLoss.netProfit >= 0 ? 'text-success' : 'text-danger'">
|
||||
{{ $filters.formatNumber(profitLoss.netProfit) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعات ترازنامه -->
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="block block-rounded block-mode-loading-refresh">
|
||||
<div class="block-header block-header-default bg-success text-light">
|
||||
<h3 class="block-title">دارائیها</h3>
|
||||
<div class="block-options">
|
||||
|
||||
<h3 class="block-title">ترازنامه</h3>
|
||||
</div>
|
||||
<div class="block-content p-3">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>کل داراییها:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ $filters.formatNumber(balanceSheet.totalAssets) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-content p-0">
|
||||
<table class="table table-striped table-hover table-borderless table-vcenter fs-sm text-center">
|
||||
<thead>
|
||||
<tr class="text-uppercase">
|
||||
<th>آیتم</th>
|
||||
<th>بستانکار</th>
|
||||
<th>بدهکار</th>
|
||||
<th>تراز</th>
|
||||
<th>وضعیت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-semibold">بانکها</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.banks.bd) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.banks.bs) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(Math.abs(YearInfo.banks.bs -
|
||||
YearInfo.banks.bd)) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) > 0"
|
||||
class="fw-semibold text-warning">بدهکار</span>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) < 0"
|
||||
class="fw-semibold text-success">بستانکار</span>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) == 0"
|
||||
class="fw-semibold text-dark">تسویه</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-semibold">صندوقها</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.cashdesks.bd) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.cashdesks.bs) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(Math.abs(YearInfo.cashdesks.bd -
|
||||
YearInfo.cashdesks.bs)) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="(YearInfo.cashdesks.bs - YearInfo.cashdesks.bd) > 0"
|
||||
class="fw-semibold text-warning">بدهکار</span>
|
||||
<span v-if="(YearInfo.cashdesks.bs - YearInfo.cashdesks.bd) < 0"
|
||||
class="fw-semibold text-success">بستانکار</span>
|
||||
<span v-if="(YearInfo.cashdesks.bs - YearInfo.cashdesks.bd) == 0"
|
||||
class="fw-semibold text-dark">تسویه</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-semibold">تنخواه گردانها</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.salarys.bd) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.salarys.bs) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(Math.abs(YearInfo.salarys.bd -
|
||||
YearInfo.salarys.bs)) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="(YearInfo.salarys.bs - YearInfo.salarys.bd) > 0"
|
||||
class="fw-semibold text-warning">بدهکار</span>
|
||||
<span v-if="(YearInfo.salarys.bs - YearInfo.salarys.bd) < 0"
|
||||
class="fw-semibold text-success">بستانکار</span>
|
||||
<span v-if="(YearInfo.salarys.bs - YearInfo.salarys.bd) == 0"
|
||||
class="fw-semibold text-dark">تسویه</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-semibold">بدهکاران</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.persons.bd) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(YearInfo.persons.bs) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="fs-sm text-muted">{{ $filters.formatNumber(Math.abs(YearInfo.persons.bs -
|
||||
YearInfo.persons.bd)) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) < 0"
|
||||
class="fw-semibold text-warning">بدهکار</span>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) > 0"
|
||||
class="fw-semibold text-success">بستانکار</span>
|
||||
<span v-if="(YearInfo.banks.bs - YearInfo.banks.bd) == 0"
|
||||
class="fw-semibold text-dark">تسویه</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>کل بدهیها:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ $filters.formatNumber(balanceSheet.totalLiabilities) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>کل سرمایه:</strong>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{{ $filters.formatNumber(balanceSheet.totalEquity) }} ریال
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعات حسابهای حسابداری -->
|
||||
<div class="col-sm-12">
|
||||
<div class="block block-rounded">
|
||||
<div class="block-header block-header-default bg-secondary text-light">
|
||||
<h3 class="block-title">ساختار حسابهای حسابداری</h3>
|
||||
</div>
|
||||
<div class="block-content p-3">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="account-tree">
|
||||
<div v-for="account in accountsTree" :key="account.id" class="account-item">
|
||||
<div class="account-header" :class="getAccountTypeClass(account.type)">
|
||||
<span class="account-code">{{ account.code }}</span>
|
||||
<span class="account-name">{{ account.name }}</span>
|
||||
<span class="account-balance" :class="account.balance >= 0 ? 'text-success' : 'text-danger'">
|
||||
{{ $filters.formatNumber(account.balance) }} ریال
|
||||
</span>
|
||||
<span class="account-type-badge" :class="account.isPublic ? 'badge bg-info' : 'badge bg-warning'">
|
||||
{{ account.isPublic ? 'عمومی' : 'اختصاصی' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="account.children && account.children.length > 0" class="account-children">
|
||||
<div v-for="child in account.children" :key="child.id" class="account-item child">
|
||||
<div class="account-header" :class="getAccountTypeClass(child.type)">
|
||||
<span class="account-code">{{ child.code }}</span>
|
||||
<span class="account-name">{{ child.name }}</span>
|
||||
<span class="account-balance" :class="child.balance >= 0 ? 'text-success' : 'text-danger'">
|
||||
{{ $filters.formatNumber(child.balance) }} ریال
|
||||
</span>
|
||||
<span class="account-type-badge" :class="child.isPublic ? 'badge bg-info' : 'badge bg-warning'">
|
||||
{{ child.isPublic ? 'عمومی' : 'اختصاصی' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="child.children && child.children.length > 0" class="account-children">
|
||||
<div v-for="grandChild in child.children" :key="grandChild.id" class="account-item grandchild">
|
||||
<div class="account-header" :class="getAccountTypeClass(grandChild.type)">
|
||||
<span class="account-code">{{ grandChild.code }}</span>
|
||||
<span class="account-name">{{ grandChild.name }}</span>
|
||||
<span class="account-balance" :class="grandChild.balance >= 0 ? 'text-success' : 'text-danger'">
|
||||
{{ $filters.formatNumber(grandChild.balance) }} ریال
|
||||
</span>
|
||||
<span class="account-type-badge" :class="grandChild.isPublic ? 'badge bg-info' : 'badge bg-warning'">
|
||||
{{ grandChild.isPublic ? 'عمومی' : 'اختصاصی' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- فرم بستن سال مالی -->
|
||||
<div class="col-sm-12">
|
||||
<div class="block block-rounded">
|
||||
<div class="block-header block-header-default bg-primary text-light">
|
||||
<h3 class="block-title">تنظیمات بستن سال مالی</h3>
|
||||
</div>
|
||||
<div class="block-content p-3">
|
||||
<v-form @submit.prevent="closeYear" ref="form">
|
||||
<!-- مالیات -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-percent"
|
||||
label="درصد مالیات بر درآمد"
|
||||
v-model="closeData.taxPercent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-cash"
|
||||
label="مبلغ مالیات"
|
||||
:model-value="$filters.formatNumber(taxAmount)"
|
||||
readonly
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- تقسیم سود -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-percent"
|
||||
label="درصد تقسیم سود"
|
||||
v-model="closeData.dividendPercent"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-cash"
|
||||
label="مبلغ تقسیم سود"
|
||||
:model-value="$filters.formatNumber(dividendAmount)"
|
||||
readonly
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- سود انباشته -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-cash-plus"
|
||||
label="سود انباشته"
|
||||
:model-value="$filters.formatNumber(retainedEarnings)"
|
||||
readonly
|
||||
variant="outlined"
|
||||
color="success"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- اطلاعات سال مالی جدید -->
|
||||
<h4 class="text-primary mb-3">اطلاعات سال مالی جدید</h4>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-text-box-outline"
|
||||
:label="$t('pages.create_business.fiscal_year_label')"
|
||||
:rules="[() => closeData.newYearLabel.length > 0 || $t('validator.required')]"
|
||||
v-model="closeData.newYearLabel"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-calendar"
|
||||
readonly
|
||||
:rules="[() => closeData.newYearStart.length > 0 || $t('validator.required')]"
|
||||
:label="$t('pages.create_business.fiscal_year_start')"
|
||||
v-model="closeData.newYearStart"
|
||||
class="txt_calendar_start"
|
||||
/>
|
||||
<CustomDatePicker
|
||||
custom-input=".txt_calendar_start"
|
||||
append-to="body"
|
||||
v-model="closeData.newYearStart"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<v-text-field
|
||||
prepend-inner-icon="mdi-calendar"
|
||||
readonly
|
||||
:rules="[() => closeData.newYearEnd.length > 0 || $t('validator.required')]"
|
||||
:label="$t('pages.create_business.fiscal_year_end')"
|
||||
v-model="closeData.newYearEnd"
|
||||
class="txt_calendar_end"
|
||||
/>
|
||||
<CustomDatePicker
|
||||
custom-input=".txt_calendar_end"
|
||||
append-to="body"
|
||||
v-model="closeData.newYearEnd"
|
||||
:min="closeData.newYearStart"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- دکمه بستن سال مالی -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<v-btn
|
||||
type="submit"
|
||||
@click="closeYear()"
|
||||
color="primary"
|
||||
prepend-icon="mdi-content-save"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:title="$t('drawer.close_year')"
|
||||
>
|
||||
{{ $t('drawer.close_year') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog تایید بستن سال مالی -->
|
||||
<v-dialog v-model="showConfirmDialog" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
<v-icon color="warning" class="me-2">mdi-alert-circle</v-icon>
|
||||
تایید بستن سال مالی
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p>آیا از بستن سال مالی اطمینان دارید؟</p>
|
||||
<p class="text-caption text-grey">این عملیات غیرقابل بازگشت است.</p>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" variant="text" @click="showConfirmDialog = false">
|
||||
انصراف
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="confirmCloseYear" :loading="isLoading">
|
||||
تایید
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Dialog موفقیت بستن سال مالی -->
|
||||
<v-dialog v-model="showSuccessDialog" max-width="500px" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
<v-icon color="success" class="me-2">mdi-check-circle</v-icon>
|
||||
سال مالی جدید ایجاد شد
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p>سال مالی جدید با موفقیت ایجاد شد.</p>
|
||||
<p class="text-caption text-grey">حالا میتوانید سال مالی جدید را انتخاب کنید.</p>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" @click="goToBusinessProfile">
|
||||
تایید
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar for notifications -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
:timeout="snackbar.timeout"
|
||||
location="top"
|
||||
>
|
||||
{{ snackbar.text }}
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn
|
||||
color="white"
|
||||
variant="text"
|
||||
@click="snackbar.show = false"
|
||||
>
|
||||
بستن
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import moment from 'moment-jalaali';
|
||||
|
||||
export default {
|
||||
name: "table",
|
||||
name: "CloseYear",
|
||||
components: {
|
||||
Loading
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
isLoading: false,
|
||||
YearInfo: {
|
||||
banks: {
|
||||
bs: 0,
|
||||
bd: 0
|
||||
showConfirmDialog: false,
|
||||
showSuccessDialog: false,
|
||||
snackbar: {
|
||||
show: false,
|
||||
text: '',
|
||||
color: 'success',
|
||||
timeout: 5000
|
||||
},
|
||||
cashdesks: {
|
||||
bs: 0,
|
||||
bd: 0
|
||||
currentYear: {
|
||||
id: null,
|
||||
label: '',
|
||||
start: '',
|
||||
end: ''
|
||||
},
|
||||
salarys: {
|
||||
bs: 0,
|
||||
bd: 0
|
||||
profitLoss: {
|
||||
totalIncome: 0,
|
||||
totalExpense: 0,
|
||||
netProfit: 0
|
||||
},
|
||||
persons: {
|
||||
bs: 0,
|
||||
bd: 0
|
||||
balanceSheet: {
|
||||
totalAssets: 0,
|
||||
totalLiabilities: 0,
|
||||
totalEquity: 0
|
||||
},
|
||||
year: {
|
||||
label: ''
|
||||
accountsTree: [], // New data property for accounts tree
|
||||
closeData: {
|
||||
taxPercent: 0,
|
||||
dividendPercent: 0,
|
||||
newYearLabel: '',
|
||||
newYearStart: '',
|
||||
newYearEnd: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
taxAmount() {
|
||||
return (this.profitLoss.netProfit * this.closeData.taxPercent) / 100;
|
||||
},
|
||||
netProfitAfterTax() {
|
||||
return this.profitLoss.netProfit - this.taxAmount;
|
||||
},
|
||||
dividendAmount() {
|
||||
return (this.netProfitAfterTax * this.closeData.dividendPercent) / 100;
|
||||
},
|
||||
retainedEarnings() {
|
||||
return this.netProfitAfterTax - this.dividendAmount;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
axios.post('/api/year/lastyear/info').then((Response) => {
|
||||
this.YearInfo = Response.data;
|
||||
})
|
||||
showSnackbar(text, color = 'success', timeout = 5000) {
|
||||
this.snackbar.text = text;
|
||||
this.snackbar.color = color;
|
||||
this.snackbar.timeout = timeout;
|
||||
this.snackbar.show = true;
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/year/close/prepare');
|
||||
if (response.data.result === 1) {
|
||||
this.currentYear = response.data.currentYear;
|
||||
this.profitLoss = response.data.profitLoss;
|
||||
this.balanceSheet = response.data.balanceSheet;
|
||||
this.accountsTree = response.data.accountsTree; // Assign accounts tree data
|
||||
|
||||
// تنظیم مقادیر پیشفرض سال مالی جدید
|
||||
await this.setupNewYearDefaults();
|
||||
} else {
|
||||
this.showSnackbar(response.data.msg || 'خطا در بارگذاری اطلاعات', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
|
||||
let errorMessage = 'خطا در بارگذاری اطلاعات';
|
||||
|
||||
try {
|
||||
if (error && error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage = error.response.data.msg;
|
||||
} else if (error && error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing error message:', e);
|
||||
}
|
||||
|
||||
this.showSnackbar(errorMessage, 'error');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async setupNewYearDefaults() {
|
||||
// محاسبه سال مالی جدید بر اساس سال مالی جاری
|
||||
if (this.currentYear.end) {
|
||||
let currentEndDate;
|
||||
|
||||
// بررسی اینکه آیا تاریخ timestamp است یا فرمت شمسی
|
||||
if (typeof this.currentYear.end === 'number' || !isNaN(this.currentYear.end)) {
|
||||
// اگر timestamp است، به تاریخ شمسی تبدیل کن
|
||||
const timestamp = parseInt(this.currentYear.end);
|
||||
const jDate = moment.unix(timestamp);
|
||||
currentEndDate = [
|
||||
jDate.jYear().toString(),
|
||||
jDate.jMonth().toString().padStart(2, '0'),
|
||||
jDate.jDate().toString().padStart(2, '0')
|
||||
];
|
||||
} else {
|
||||
// اگر فرمت شمسی است، مستقیماً استفاده کن
|
||||
currentEndDate = this.currentYear.end.split('/');
|
||||
}
|
||||
|
||||
const nextYear = parseInt(currentEndDate[0]) + 1;
|
||||
|
||||
// سال مالی جدید باید از روز بعد از پایان سال مالی جاری شروع شود
|
||||
// برای جلوگیری از تداخل زمانی
|
||||
const currentEndMoment = moment(`${currentEndDate[0]}/${currentEndDate[1]}/${currentEndDate[2]}`, 'jYYYY/jMM/jDD');
|
||||
const nextDay = currentEndMoment.add(1, 'day');
|
||||
|
||||
this.closeData.newYearStart = nextDay.format('jYYYY/jMM/jDD');
|
||||
this.closeData.newYearEnd = `${nextYear}/${currentEndDate[1]}/${currentEndDate[2]}`;
|
||||
this.closeData.newYearLabel = `سال مالی منتهی به ${this.closeData.newYearEnd}`;
|
||||
}
|
||||
},
|
||||
|
||||
async closeYear() {
|
||||
const { valid } = await this.$refs.form.validate();
|
||||
if (valid) {
|
||||
this.showConfirmDialog = true;
|
||||
}
|
||||
},
|
||||
|
||||
async confirmCloseYear() {
|
||||
this.showConfirmDialog = false;
|
||||
|
||||
// اعتبارسنجی تاریخها
|
||||
if (!(await this.validateDates())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/year/close/execute', this.closeData);
|
||||
if (response.data.result === 1) {
|
||||
this.showSnackbar(response.data.msg, 'success');
|
||||
this.showSuccessDialog = true;
|
||||
} else {
|
||||
this.showSnackbar(response.data.msg || 'خطا در بستن سال مالی', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error closing year:', error);
|
||||
|
||||
let errorMessage = 'خطا در بستن سال مالی';
|
||||
|
||||
try {
|
||||
if (error && error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage = error.response.data.msg;
|
||||
} else if (error && error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing error message:', e);
|
||||
}
|
||||
|
||||
this.showSnackbar(errorMessage, 'error');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async validateDates() {
|
||||
try {
|
||||
// بررسی وجود تاریخها
|
||||
if (!this.closeData.newYearStart || !this.closeData.newYearEnd) {
|
||||
this.showSnackbar('تاریخ شروع و پایان سال مالی الزامی است', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// اعتبارسنجی از طریق API
|
||||
const response = await axios.post('/api/year/close/validate', this.closeData);
|
||||
|
||||
if (response.data.result === 0) {
|
||||
this.showSnackbar(response.data.msg, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error);
|
||||
|
||||
// سادهسازی error handling
|
||||
let errorMessage = 'خطا در اعتبارسنجی تاریخها';
|
||||
|
||||
try {
|
||||
// Check for axios error response
|
||||
if (error && error.response && error.response.data && error.response.data.msg) {
|
||||
errorMessage = error.response.data.msg;
|
||||
}
|
||||
// Check for network error
|
||||
else if (error && error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
// Check for any other error type
|
||||
else if (error && typeof error === 'string') {
|
||||
errorMessage = error;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing error message:', parseError);
|
||||
errorMessage = 'خطا در اعتبارسنجی تاریخها';
|
||||
}
|
||||
|
||||
this.showSnackbar(errorMessage, 'error');
|
||||
return false;
|
||||
}
|
||||
} catch (outerError) {
|
||||
console.error('Unexpected error in validateDates:', outerError);
|
||||
this.showSnackbar('خطای غیرمنتظره در اعتبارسنجی تاریخها', 'error');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
convertPersianDateToTimestamp(persianDate) {
|
||||
// اگر تاریخ به صورت timestamp است، مستقیماً برگردان
|
||||
if (!isNaN(persianDate)) {
|
||||
return parseInt(persianDate);
|
||||
}
|
||||
|
||||
// تبدیل تاریخ شمسی به میلادی و سپس به timestamp
|
||||
const parts = persianDate.split('/');
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('فرمت تاریخ نامعتبر است');
|
||||
}
|
||||
|
||||
const year = parseInt(parts[0]);
|
||||
const month = parseInt(parts[1]);
|
||||
const day = parseInt(parts[2]);
|
||||
|
||||
// تبدیل سال شمسی به میلادی (تقریبی)
|
||||
const gregorianYear = year + 621;
|
||||
|
||||
// ایجاد تاریخ میلادی
|
||||
const date = new Date(gregorianYear, month - 1, day);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
return Math.floor(date.getTime() / 1000);
|
||||
},
|
||||
|
||||
getAccountTypeClass(type) {
|
||||
switch (type) {
|
||||
case 'asset':
|
||||
return 'bg-primary text-light';
|
||||
case 'liability':
|
||||
return 'bg-info text-light';
|
||||
case 'equity':
|
||||
return 'bg-success text-light';
|
||||
case 'income':
|
||||
return 'bg-warning text-dark';
|
||||
case 'expense':
|
||||
return 'bg-danger text-light';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
goToBusinessProfile() {
|
||||
this.showSuccessDialog = false;
|
||||
this.$router.push('/profile/business');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// Removed the problematic watcher that was overriding correct date calculations
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.alert {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.block {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 12px 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.account-tree {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.account-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.account-code {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.account-balance {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
min-width: 120px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.account-type-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.account-children {
|
||||
margin-left: 20px;
|
||||
border-left: 2px solid #e9ecef;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.account-children .account-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.account-children .account-children {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.child .account-header {
|
||||
font-size: 0.9rem;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.grandchild .account-header {
|
||||
font-size: 0.85rem;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* تقویم شمسی */
|
||||
.txt_calendar_start,
|
||||
.txt_calendar_end {
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.txt_calendar_start:focus,
|
||||
.txt_calendar_end:focus {
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
</style>
|
|
@ -131,7 +131,7 @@ export default {
|
|||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
||||
rowsOfPageSeparatorMessage="از" theme-color="#1d90ff" header-text-direction="center"
|
||||
body-text-direction="center" :loading="loading">
|
||||
<template #item-operation="props: any">
|
||||
<template #item-operation="props">
|
||||
<v-btn variant="text" @click="selected = props; dialog = true;" color="success" icon="mdi-file-edit"
|
||||
v-bind="props"></v-btn>
|
||||
</template>
|
||||
|
|
Loading…
Reference in a new issue