diff --git a/hesabixCore/config/services.yaml b/hesabixCore/config/services.yaml index e7cdd8f..28583e1 100644 --- a/hesabixCore/config/services.yaml +++ b/hesabixCore/config/services.yaml @@ -4,6 +4,22 @@ parameters: avatarDir: '%kernel.project_dir%/../hesabixArchive/avatars' 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: @@ -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' diff --git a/hesabixCore/src/Cog/AccountingDocService.php b/hesabixCore/src/Cog/AccountingDocService.php index 0fd0d54..97a6bcf 100644 --- a/hesabixCore/src/Cog/AccountingDocService.php +++ b/hesabixCore/src/Cog/AccountingDocService.php @@ -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' ]); diff --git a/hesabixCore/src/Controller/CloseYearController.php b/hesabixCore/src/Controller/CloseYearController.php new file mode 100644 index 0000000..b6acef2 --- /dev/null +++ b/hesabixCore/src/Controller/CloseYearController.php @@ -0,0 +1,433 @@ +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; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Controller/SellController.php b/hesabixCore/src/Controller/SellController.php index 83a8045..6bf2a26 100644 --- a/hesabixCore/src/Controller/SellController.php +++ b/hesabixCore/src/Controller/SellController.php @@ -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 { diff --git a/hesabixCore/src/Service/CloseYearService.php b/hesabixCore/src/Service/CloseYearService.php new file mode 100644 index 0000000..255754f --- /dev/null +++ b/hesabixCore/src/Service/CloseYearService.php @@ -0,0 +1,1085 @@ +entityManager = $entityManager; + $this->logService = $logService; + $this->provider = $provider; + $this->params = $params; + } + + /** + * محاسبه سود و زیان سال مالی + */ + public function calculateProfitLoss(Year $year): array + { + $totalIncome = 0; + $totalExpense = 0; + + // محاسبه درآمدها - حساب‌های انتهایی درآمد (کدهای 56-66) + $incomeAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 56, 66); + foreach ($incomeAccounts as $account) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalIncome += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + + // محاسبه فروش - حساب‌های انتهایی فروش (کدهای 52-55) + $salesAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 52, 55); + foreach ($salesAccounts as $account) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalIncome += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + + // محاسبه هزینه‌ها - حساب‌های انتهایی هزینه (کدهای 67-111) + $expenseAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 67, 111); + foreach ($expenseAccounts as $account) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalExpense += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + + return [ + 'totalIncome' => $totalIncome, + 'totalExpense' => $totalExpense, + 'netProfit' => $totalIncome - $totalExpense + ]; + } + + /** + * محاسبه ترازنامه + */ + public function calculateBalanceSheet(Year $year): array + { + $totalAssets = 0; + $totalLiabilities = 0; + $totalEquity = 0; + + // محاسبه دارایی‌ها - حساب‌های انتهایی دارایی (کدهای 2-19، به جز 8 و 9) + $assetAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 2, 19); + foreach ($assetAccounts as $account) { + $codeInt = (int)$account->getCode(); + if ($codeInt != 8 && $codeInt != 9) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalAssets += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + } + + // محاسبه بدهی‌ها - حساب‌های انتهایی بدهی (کدهای 6-39) + $liabilityAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 6, 39); + foreach ($liabilityAccounts as $account) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalLiabilities += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + + // محاسبه سرمایه - حساب‌های انتهایی سرمایه (کدهای 40-47) + $equityAccounts = $this->getLeafAccountsByCodeRange($year->getBid(), 40, 47); + foreach ($equityAccounts as $account) { + $accountBalance = $this->calculateAccountBalance($account, $year); + $totalEquity += $this->calculateBalanceWithType($account->getCode(), $accountBalance); + } + + return [ + 'totalAssets' => $totalAssets, + 'totalLiabilities' => $totalLiabilities, + 'totalEquity' => $totalEquity + ]; + } + + /** + * بستن حساب‌های موقت + */ + public function closeTemporaryAccounts(Year $year, array $params, ?UserInterface $user = null): void + { + // اطمینان از وجود حساب‌های مورد نیاز + $this->ensureCloseYearAccountsExist(); + + // دریافت حساب سود و زیان + $profitLossAccount = $this->getOrCreateAccount($year->getBid(), '999999', 'حساب سود و زیان', 'income'); + + // محاسبه سود و زیان + $profitLoss = $this->calculateProfitLoss($year); + $netProfit = $profitLoss['netProfit']; + + // محاسبه مالیات + $taxPercent = $params['taxPercent'] ?? 0; + $taxAmount = $netProfit * ($taxPercent / 100); + + // محاسبه سود سهام + $dividendPercent = $params['dividendPercent'] ?? 0; + $dividendAmount = $netProfit * ($dividendPercent / 100); + + // محاسبه سود انباشته + $retainedEarnings = $netProfit - $taxAmount - $dividendAmount; + + // بستن حساب‌های موقت (درآمد و هزینه) + $temporaryAccounts = $this->getAccountsByCodeRange($year->getBid(), 4000, 999998); + + foreach ($temporaryAccounts as $account) { + $balance = $this->calculateAccountBalance($account, $year); + if ($balance != 0) { + $this->createClosingEntry($year, $account, $profitLossAccount, $balance, $user); + } + } + + // ثبت مالیات + if ($taxAmount > 0) { + $taxAccount = $this->getOrCreateAccount($year->getBid(), '999997', 'مالیات بر درآمد', 'expense'); + $this->createClosingEntry($year, $taxAccount, $profitLossAccount, $taxAmount, $user); + } + + // ثبت سود سهام + if ($dividendAmount > 0) { + $dividendAccount = $this->getOrCreateAccount($year->getBid(), '999996', 'سود سهام', 'expense'); + $this->createClosingEntry($year, $dividendAccount, $profitLossAccount, $dividendAmount, $user); + } + + // ثبت سود انباشته + if ($retainedEarnings != 0) { + $retainedEarningsAccount = $this->getOrCreateAccount($year->getBid(), '999998', 'سود انباشته', 'equity'); + $this->createClosingEntry($year, $profitLossAccount, $retainedEarningsAccount, $retainedEarnings, $user); + } + } + + /** + * انتقال حساب‌های دائمی + */ + public function transferPermanentAccounts(Year $oldYear, Year $newYear, ?UserInterface $user = null): void + { + // انتقال دارایی‌ها (کدهای 2 تا 19) - عمومی و اختصاصی + $assetAccounts = $this->getAccountsByCodeRange($oldYear->getBid(), 2, 19); + foreach ($assetAccounts as $account) { + $accountBalance = $this->calculateAccountTreeBalance($account, $oldYear, 'asset'); + if ($accountBalance != 0) { + $this->createOpeningEntry($newYear, $account, $accountBalance, 'asset', $user); + } + } + + // انتقال بدهی‌ها (کدهای 6 تا 39) - عمومی و اختصاصی + $liabilityAccounts = $this->getAccountsByCodeRange($oldYear->getBid(), 6, 39); + foreach ($liabilityAccounts as $account) { + $accountBalance = $this->calculateAccountTreeBalance($account, $oldYear, 'liability'); + if ($accountBalance != 0) { + $this->createOpeningEntry($newYear, $account, $accountBalance, 'liability', $user); + } + } + + // انتقال سرمایه (کدهای 40 تا 47) - عمومی و اختصاصی + $equityAccounts = $this->getAccountsByCodeRange($oldYear->getBid(), 40, 47); + foreach ($equityAccounts as $account) { + $accountBalance = $this->calculateAccountTreeBalance($account, $oldYear, 'equity'); + if ($accountBalance != 0) { + $this->createOpeningEntry($newYear, $account, $accountBalance, 'equity', $user); + } + } + + // انتقال تراز اشخاص + $this->transferPersonBalances($oldYear, $newYear, $user); + + // انتقال تراز حساب‌ها از hesabdari_row + $this->transferAccountBalancesFromRows($oldYear, $newYear, $user); + + // ایجاد سند افتتاحیه برای حساب‌های مهم (حتی با تراز صفر) + $this->createOpeningEntriesForImportantAccounts($newYear, $user); + } + + /** + * انتقال تراز اشخاص + */ + private function transferPersonBalances(Year $oldYear, Year $newYear, ?UserInterface $user = null): void + { + // دریافت تمام اشخاص که در سال مالی قبلی تراکنش داشته‌اند + $personBalances = $this->entityManager->createQueryBuilder() + ->select('p.id, p.nikename, p.code, SUM(r.bs) as totalBs, SUM(r.bd) as totalBd') + ->from('App\Entity\HesabdariRow', 'r') + ->join('r.person', 'p') + ->where('r.year = :oldYear') + ->andWhere('r.person IS NOT NULL') + ->andWhere('r.bid = :business') + ->setParameter('oldYear', $oldYear) + ->setParameter('business', $oldYear->getBid()) + ->groupBy('p.id, p.nikename, p.code') + ->getQuery() + ->getResult(); + + foreach ($personBalances as $personBalance) { + $balance = $personBalance['totalBs'] - $personBalance['totalBd']; + + // فقط اگر تراز صفر نباشد، سند افتتاحیه ایجاد کن + if ($balance != 0) { + $this->createPersonOpeningEntry($newYear, $personBalance['id'], $balance, $user); + } + } + } + + /** + * انتقال تراز حساب‌ها مستقیماً از hesabdari_row + */ + public function transferAccountBalancesFromRows(Year $oldYear, Year $newYear, ?UserInterface $user = null): void + { + // دریافت تمام حساب‌هایی که در سال مالی قبلی تراکنش داشته‌اند + $accountBalances = $this->entityManager->createQueryBuilder() + ->select('IDENTITY(r.ref) as accountId, SUM(r.bs) as totalBs, SUM(r.bd) as totalBd, IDENTITY(r.bank) as bankId, IDENTITY(r.commodity) as commodityId, IDENTITY(r.cashdesk) as cashdeskId') + ->from('App\Entity\HesabdariRow', 'r') + ->where('r.year = :oldYear') + ->andWhere('r.ref IS NOT NULL') + ->andWhere('r.bid = :business') + ->setParameter('oldYear', $oldYear) + ->setParameter('business', $oldYear->getBid()) + ->groupBy('r.ref, r.bank, r.commodity, r.cashdesk') + ->getQuery() + ->getResult(); + + foreach ($accountBalances as $accountBalance) { + $balance = $accountBalance['totalBs'] - $accountBalance['totalBd']; + + // فقط اگر تراز صفر نباشد، سند افتتاحیه ایجاد کن + if ($balance != 0) { + $account = $this->entityManager->getRepository(HesabdariTable::class)->find($accountBalance['accountId']); + if ($account) { + $this->createAccountOpeningEntry( + $newYear, + $account, + $balance, + $user, + $accountBalance['bankId'], + $accountBalance['commodityId'], + $accountBalance['cashdeskId'] + ); + } + } + } + } + + /** + * ثبت سود انباشته + */ + public function recordRetainedEarnings(Year $oldYear, Year $newYear, array $params, ?UserInterface $user = null): void + { + $profitLoss = $this->calculateProfitLoss($oldYear); + $netProfit = $profitLoss['netProfit']; + + // اعمال مالیات + $taxPercent = $params['taxPercent'] ?? $this->params->get('close_year.defaults.tax_percent'); + $taxAmount = ($netProfit * $taxPercent) / 100; + $netProfitAfterTax = $netProfit - $taxAmount; + + // تقسیم سود + $dividendPercent = $params['dividendPercent'] ?? $this->params->get('close_year.defaults.dividend_percent'); + $dividendAmount = ($netProfitAfterTax * $dividendPercent) / 100; + $retainedEarnings = $netProfitAfterTax - $dividendAmount; + + if ($retainedEarnings != 0) { + $retainedEarningsAccount = $this->getOrCreateAccount( + $newYear->getBid(), + $this->params->get('close_year.accounts.retained_earnings'), + 'سود انباشته', + 'equity' + ); + + $this->createRetainedEarningsEntry($newYear, $retainedEarningsAccount, $retainedEarnings, $user); + } + } + + /** + * ایجاد سال مالی جدید + */ + public function createNewYear(Business $business, array $params): Year + { + // اعتبارسنجی تاریخ‌های سال مالی جدید + $this->validateNewYearDates($business, $params); + + $newYear = new Year(); + $newYear->setBid($business); + $newYear->setHead(false); + $newYear->setLabel($params['newYearLabel'] ?? 'سال مالی جدید'); + $newYear->setStart($params['newYearStart'] ?? time()); + $newYear->setEnd($params['newYearEnd'] ?? time() + $this->params->get('close_year.defaults.new_year_duration')); + + $this->entityManager->persist($newYear); + $this->entityManager->flush(); + + return $newYear; + } + + /** + * اعتبارسنجی تاریخ‌های سال مالی جدید + */ + public function validateNewYearDates(Business $business, array $params): void + { + $newYearStart = $params['newYearStart'] ?? null; + $newYearEnd = $params['newYearEnd'] ?? null; + + if (!$newYearStart || !$newYearEnd) { + throw new \InvalidArgumentException('تاریخ شروع و پایان سال مالی الزامی است'); + } + + // تبدیل تاریخ‌های شمسی به timestamp برای مقایسه + $startTimestamp = $this->convertPersianDateToTimestamp($newYearStart); + $endTimestamp = $this->convertPersianDateToTimestamp($newYearEnd); + + // بررسی اینکه تاریخ پایان از شروع کمتر نباشد + if ($endTimestamp <= $startTimestamp) { + throw new \InvalidArgumentException('تاریخ پایان سال مالی نمی‌تواند از تاریخ شروع کمتر یا مساوی باشد'); + } + + // بررسی تداخل با سال‌های مالی موجود + $existingYears = $this->entityManager->getRepository(Year::class)->findBy(['bid' => $business]); + + foreach ($existingYears as $existingYear) { + $existingStart = $this->convertPersianDateToTimestamp($existingYear->getStart()); + $existingEnd = $this->convertPersianDateToTimestamp($existingYear->getEnd()); + + // بررسی تداخل زمانی + if (($startTimestamp >= $existingStart && $startTimestamp <= $existingEnd) || + ($endTimestamp >= $existingStart && $endTimestamp <= $existingEnd) || + ($startTimestamp <= $existingStart && $endTimestamp >= $existingEnd)) { + throw new \InvalidArgumentException( + 'سال مالی جدید با سال مالی موجود "' . $existingYear->getLabel() . '" تداخل زمانی دارد' + ); + } + } + } + + /** + * تبدیل تاریخ شمسی به timestamp + */ + private function convertPersianDateToTimestamp(string $persianDate): int + { + // اگر تاریخ به صورت timestamp است، مستقیماً برگردان + if (is_numeric($persianDate)) { + return (int)$persianDate; + } + + // تبدیل تاریخ شمسی به میلادی و سپس به timestamp + $parts = explode('/', $persianDate); + if (count($parts) !== 3) { + throw new \InvalidArgumentException('فرمت تاریخ نامعتبر است'); + } + + $year = (int)$parts[0]; + $month = (int)$parts[1]; + $day = (int)$parts[2]; + + // تبدیل سال شمسی به میلادی (تقریبی) + $gregorianYear = $year + 621; + + // ایجاد تاریخ میلادی + $date = new \DateTime(); + $date->setDate($gregorianYear, $month, $day); + $date->setTime(0, 0, 0); + + return $date->getTimestamp(); + } + + /** + * محاسبه مانده حساب + */ + private function calculateAccountTreeBalance(HesabdariTable $account, Year $year, string $type): float + { + $totalBalance = 0; + + // محاسبه مانده خود حساب (فقط برای حساب‌های انتهایی) + $rows = $this->entityManager->getRepository(HesabdariRow::class)->findBy([ + 'ref' => $account, + 'year' => $year + ]); + + foreach ($rows as $row) { + $totalBalance += (float)$row->getBd() - (float)$row->getBs(); + } + + // محاسبه مانده تمام زیرمجموعه‌ها (عمومی و اختصاصی) + $childAccounts = $this->getChildAccounts($account, $year->getBid()); + + foreach ($childAccounts as $childAccount) { + $totalBalance += $this->calculateAccountTreeBalance($childAccount, $year, $type); + } + + return $totalBalance; + } + + /** + * محاسبه مانده با در نظر گرفتن نوع حساب + */ + private function calculateBalanceWithType(string $code, float $balance): float + { + if ($this->isDebitAccount($code)) { + return $balance; // بدهکار: بدهی - بستانکاری + } else { + return -$balance; // بستانکار: بستانکاری - بدهی + } + } + + /** + * بررسی اینکه آیا حساب بدهکار است یا بستانکار + */ + public 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 getAllAccountsByType(Business $business, string $type): array + { + $accounts = []; + + // دریافت حساب‌های اصلی (بدون parent) + $mainAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => $business, + 'type' => $type, + 'upper' => null + ]); + + foreach ($mainAccounts as $mainAccount) { + $accounts[] = $this->buildAccountTree($mainAccount); + } + + return $accounts; + } + + /** + * ساخت درخت حساب + */ + private function buildAccountTree(HesabdariTable $account): array + { + $tree = [ + 'id' => $account->getId(), + 'name' => $account->getName(), + 'code' => $account->getCode(), + 'type' => $account->getType(), + 'children' => [] + ]; + + // دریافت زیرمجموعه‌ها + $childAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'upper' => $account, + 'bid' => $account->getBid() + ]); + + foreach ($childAccounts as $childAccount) { + $tree['children'][] = $this->buildAccountTree($childAccount); + } + + return $tree; + } + + /** + * دریافت حساب‌های انتهایی (بدون زیرمجموعه) + */ + private function getLeafAccounts(Business $business, string $type): array + { + $leafAccounts = []; + + // دریافت تمام حساب‌های نوع مورد نظر + $allAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => $business, + 'type' => $type + ]); + + foreach ($allAccounts as $account) { + // بررسی اینکه آیا این حساب زیرمجموعه دارد یا نه + $hasChildren = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'upper' => $account, + 'bid' => $business + ]); + + if (!$hasChildren) { + $leafAccounts[] = $account; + } + } + + return $leafAccounts; + } + + /** + * اطمینان از وجود حساب‌های مورد نیاز بستن سال مالی + */ + private function ensureCloseYearAccountsExist(): void + { + $requiredAccounts = [ + [ + 'code' => $this->params->get('close_year.accounts.profit_loss'), + 'name' => 'سود و زیان', + 'type' => 'calc' + ], + [ + 'code' => $this->params->get('close_year.accounts.retained_earnings'), + 'name' => 'سود انباشته', + 'type' => 'calc' + ] + ]; + + foreach ($requiredAccounts as $accountData) { + // بررسی وجود حساب عمومی + $existingAccount = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => $accountData['code'], + 'bid' => null + ]); + + // اگر حساب وجود نداشت، ایجاد کن + if (!$existingAccount) { + $account = new HesabdariTable(); + $account->setBid(null); // حساب عمومی + $account->setName($accountData['name']); + $account->setCode($accountData['code']); + $account->setType($accountData['type']); + $account->setUpper(null); // حساب اصلی + $this->entityManager->persist($account); + } + } + + // ذخیره تغییرات + $this->entityManager->flush(); + } + + /** + * دریافت یا ایجاد حساب + */ + private function getOrCreateAccount(Business $business, string $code, string $name, string $type): HesabdariTable + { + // ابتدا حساب اختصاصی را جستجو کن + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'bid' => $business, + 'code' => $code + ]); + + // اگر حساب اختصاصی وجود نداشت، حساب عمومی را جستجو کن + if (!$account) { + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'bid' => null, + 'code' => $code + ]); + } + + // اگر هیچ کدام وجود نداشت، حساب عمومی جدید ایجاد کن (برای حساب‌های بستن سال مالی) + if (!$account) { + $account = new HesabdariTable(); + $account->setBid(null); // حساب عمومی + $account->setName($name); + $account->setCode($code); + $account->setType($type); + $account->setUpper(null); // حساب اصلی + $this->entityManager->persist($account); + $this->entityManager->flush(); + } + + return $account; + } + + /** + * ایجاد سند بستن حساب + */ + private function createClosingEntry(Year $year, HesabdariTable $account, HesabdariTable $counterAccount, float $balance, ?UserInterface $user = null): void + { + $doc = new HesabdariDoc(); + $doc->setBid($year->getBid()); + $doc->setYear($year); + $doc->setDes('بستن حساب ' . $account->getName()); + $doc->setDateSubmit(time()); + $doc->setType('close_year'); + $doc->setDate($year->getEnd()); + $doc->setMoney($year->getBid()->getMoney()); + $doc->setCode($this->provider->getAccountingCode($year->getBid(), 'accounting')); + if ($user) { + $doc->setSubmitter($user); + } + $this->entityManager->persist($doc); + + // ردیف بستن حساب + $row1 = new HesabdariRow(); + $row1->setBid($year->getBid()); + $row1->setYear($year); + $row1->setDoc($doc); + $row1->setRef($account); + if ($balance > 0) { + $row1->setBd($balance); + $row1->setBs('0'); + } else { + $row1->setBd('0'); + $row1->setBs(abs($balance)); + } + $row1->setDes('بستن حساب ' . $account->getName()); + $this->entityManager->persist($row1); + + // ردیف مقابل + $row2 = new HesabdariRow(); + $row2->setBid($year->getBid()); + $row2->setYear($year); + $row2->setDoc($doc); + $row2->setRef($counterAccount); + if ($balance > 0) { + $row2->setBd('0'); + $row2->setBs($balance); + } else { + $row2->setBd(abs($balance)); + $row2->setBs('0'); + } + $row2->setDes('بستن حساب ' . $account->getName()); + $this->entityManager->persist($row2); + } + + /** + * ایجاد سند افتتاحیه + */ + private function createOpeningEntry(Year $year, HesabdariTable $account, float $balance, string $type, ?UserInterface $user = null): void + { + $doc = new HesabdariDoc(); + $doc->setBid($year->getBid()); + $doc->setYear($year); + $doc->setDes('افتتاح حساب ' . $account->getName()); + $doc->setDateSubmit(time()); + $doc->setType('opening_balance'); + $doc->setDate($year->getStart()); + $doc->setMoney($year->getBid()->getMoney()); + $doc->setCode($this->provider->getAccountingCode($year->getBid(), 'accounting')); + if ($user) { + $doc->setSubmitter($user); + } + $this->entityManager->persist($doc); + + $row = new HesabdariRow(); + $row->setBid($year->getBid()); + $row->setYear($year); + $row->setDoc($doc); + $row->setRef($account); + if ($balance > 0) { + if ($type === 'asset') { + $row->setBd($balance); + $row->setBs('0'); + } else { + $row->setBd('0'); + $row->setBs($balance); + } + } else { + if ($type === 'asset') { + $row->setBd('0'); + $row->setBs(abs($balance)); + } else { + $row->setBd(abs($balance)); + $row->setBs('0'); + } + } + $row->setDes('افتتاح حساب ' . $account->getName()); + $this->entityManager->persist($row); + } + + /** + * ایجاد سند سود انباشته + */ + private function createRetainedEarningsEntry(Year $year, HesabdariTable $account, float $amount, ?UserInterface $user = null): void + { + $doc = new HesabdariDoc(); + $doc->setBid($year->getBid()); + $doc->setYear($year); + $doc->setDes('سود انباشته از سال قبل'); + $doc->setDateSubmit(time()); + $doc->setType('retained_earnings'); + $doc->setDate($year->getStart()); + $doc->setMoney($year->getBid()->getMoney()); + $doc->setCode($this->provider->getAccountingCode($year->getBid(), 'accounting')); + if ($user) { + $doc->setSubmitter($user); + } + $this->entityManager->persist($doc); + + $row = new HesabdariRow(); + $row->setBid($year->getBid()); + $row->setYear($year); + $row->setDoc($doc); + $row->setRef($account); + if ($amount > 0) { + $row->setBd('0'); + $row->setBs($amount); + } else { + $row->setBd(abs($amount)); + $row->setBs('0'); + } + $row->setDes('سود انباشته از سال قبل'); + $this->entityManager->persist($row); + } + + /** + * دریافت حساب‌ها بر اساس محدوده کد (عمومی و اختصاصی) + */ + private function getAccountsByCodeRange(Business $business, int $startCode, int $endCode): array + { + $accounts = []; + + // دریافت حساب‌های عمومی (bid = NULL) در محدوده کد + $publicAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => null, + 'upper' => null + ]); + + foreach ($publicAccounts as $account) { + $code = (int)$account->getCode(); + if ($code >= $startCode && $code <= $endCode) { + $accounts[] = $account; + } + } + + // دریافت حساب‌های اختصاصی (bid = business) در محدوده کد + $privateAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => $business, + 'upper' => null + ]); + + foreach ($privateAccounts as $account) { + $code = (int)$account->getCode(); + if ($code >= $startCode && $code <= $endCode) { + $accounts[] = $account; + } + } + + return $accounts; + } + + /** + * دریافت زیرمجموعه‌های یک حساب (عمومی و اختصاصی) + */ + private function getChildAccounts(HesabdariTable $parentAccount, Business $business): array + { + $childAccounts = []; + + // دریافت زیرمجموعه‌های عمومی + $publicChildren = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'upper' => $parentAccount, + 'bid' => null + ]); + + foreach ($publicChildren as $child) { + $childAccounts[] = $child; + } + + // دریافت زیرمجموعه‌های اختصاصی + $privateChildren = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'upper' => $parentAccount, + 'bid' => $business + ]); + + foreach ($privateChildren as $child) { + $childAccounts[] = $child; + } + + return $childAccounts; + } + + /** + * محاسبه مانده یک حساب (بدون زیرمجموعه) + */ + private function calculateAccountBalance(HesabdariTable $account, Year $year): float + { + $totalBalance = 0; + + $rows = $this->entityManager->getRepository(HesabdariRow::class)->findBy([ + 'ref' => $account, + 'year' => $year + ]); + + foreach ($rows as $row) { + $totalBalance += (float)$row->getBd() - (float)$row->getBs(); + } + + return $totalBalance; + } + + /** + * دریافت حساب‌های انتهایی بر اساس محدوده کد + */ + private function getLeafAccountsByCodeRange(Business $business, int $startCode, int $endCode): array + { + $accounts = []; + + // دریافت حساب‌های عمومی در محدوده کد + $publicAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => null + ]); + + foreach ($publicAccounts as $account) { + $code = (int)$account->getCode(); + if ($code >= $startCode && $code <= $endCode) { + // بررسی اینکه آیا این حساب زیرمجموعه دارد یا نه + $hasChildren = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'upper' => $account, + 'bid' => null + ]); + + if (!$hasChildren) { + $accounts[] = $account; + } + } + } + + // دریافت حساب‌های اختصاصی در محدوده کد + $privateAccounts = $this->entityManager->getRepository(HesabdariTable::class)->findBy([ + 'bid' => $business + ]); + + foreach ($privateAccounts as $account) { + $code = (int)$account->getCode(); + if ($code >= $startCode && $code <= $endCode) { + // بررسی اینکه آیا این حساب زیرمجموعه دارد یا نه + $hasChildren = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'upper' => $account, + 'bid' => $business + ]); + + if (!$hasChildren) { + $accounts[] = $account; + } + } + } + + return $accounts; + } + + /** + * ایجاد سند افتتاحیه برای شخص + */ + public function createPersonOpeningEntry(Year $year, int $personId, float $balance, ?UserInterface $user = null): void + { + // دریافت شخص + $person = $this->entityManager->getRepository(\App\Entity\Person::class)->find($personId); + if (!$person) { + return; + } + + // دریافت حساب مطالبات یا بدهی‌ها (بسته به نوع تراز) + if ($balance > 0) { + // شخص بستانکار است - از حساب‌های دریافتی استفاده کن + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => '3', // حساب‌های دریافتی + 'bid' => null + ]); + } else { + // شخص بدهکار است - از حساب‌های پرداختنی استفاده کن + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => '9', // حساب‌های پرداختنی + 'bid' => null + ]); + } + + if (!$account) { + // اگر حساب اصلی وجود نداشت، از حساب‌های دریافتی استفاده کن + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => '3', // حساب‌های دریافتی + 'bid' => null + ]); + } + + if (!$account) { + return; + } + + $doc = new HesabdariDoc(); + $doc->setBid($year->getBid()); + $doc->setYear($year); + $doc->setDes('افتتاح تراز شخص: ' . $person->getNikename()); + $doc->setDateSubmit(time()); + $doc->setType('person_opening_balance'); + $doc->setDate($year->getStart()); + $doc->setMoney($year->getBid()->getMoney()); + $doc->setCode($this->provider->getAccountingCode($year->getBid(), 'accounting')); + if ($user) { + $doc->setSubmitter($user); + } + $this->entityManager->persist($doc); + + $row = new HesabdariRow(); + $row->setBid($year->getBid()); + $row->setYear($year); + $row->setDoc($doc); + $row->setRef($account); + $row->setPerson($person); + + if ($balance > 0) { + // شخص بستانکار است (bs > bd) - در سند افتتاحیه هم بستانکار باقی بماند + $row->setBs($balance); + $row->setBd('0'); + } else { + // شخص بدهکار است (bs < bd) - در سند افتتاحیه هم بدهکار باقی بماند + $row->setBs('0'); + $row->setBd(abs($balance)); + } + + $row->setDes('افتتاح تراز شخص: ' . $person->getNikename()); + $this->entityManager->persist($row); + } + + /** + * ایجاد سند افتتاحیه برای حساب + */ + private function createAccountOpeningEntry(Year $year, HesabdariTable $account, float $balance, ?UserInterface $user = null, ?int $bankId = null, ?int $commodityId = null, ?int $cashdeskId = null): void + { + $doc = new HesabdariDoc(); + $doc->setBid($year->getBid()); + $doc->setYear($year); + $doc->setDes('افتتاح تراز حساب: ' . $account->getName()); + $doc->setDateSubmit(time()); + $doc->setType('account_opening_balance'); + $doc->setDate($year->getStart()); + $doc->setMoney($year->getBid()->getMoney()); + $doc->setCode($this->provider->getAccountingCode($year->getBid(), 'accounting')); + if ($user) { + $doc->setSubmitter($user); + } + $this->entityManager->persist($doc); + + $row = new HesabdariRow(); + $row->setBid($year->getBid()); + $row->setYear($year); + $row->setDoc($doc); + $row->setRef($account); + // تنظیم bank، commodity، و cashdesk + if ($bankId) { + $bank = $this->entityManager->getRepository(\App\Entity\BankAccount::class)->find($bankId); + if ($bank) { + $row->setBank($bank); + } + } + if ($commodityId) { + $commodity = $this->entityManager->getRepository(\App\Entity\Commodity::class)->find($commodityId); + if ($commodity) { + $row->setCommodity($commodity); + } + } + if ($cashdeskId) { + $cashdesk = $this->entityManager->getRepository(\App\Entity\Cashdesk::class)->find($cashdeskId); + if ($cashdesk) { + $row->setCashdesk($cashdesk); + } + } + + if ($balance > 0) { + // حساب بستانکار است (bs > bd) - در سند افتتاحیه هم بستانکار باقی بماند + $row->setBs($balance); + $row->setBd('0'); + } else { + // حساب بدهکار است (bs < bd) - در سند افتتاحیه هم بدهکار باقی بماند + $row->setBs('0'); + $row->setBd(abs($balance)); + } + + $row->setDes('افتتاح تراز حساب: ' . $account->getName()); + $this->entityManager->persist($row); + } + + /** + * ایجاد سند افتتاحیه برای حساب‌های مهم (حتی با تراز صفر) + */ + private function createOpeningEntriesForImportantAccounts(Year $year, ?UserInterface $user = null): void + { + // حساب‌های مهم که باید سند افتتاحیه داشته باشند + $importantAccountCodes = [121, 122, 123, 124, 138, 139]; // صندوق و تنخواه گردان + + foreach ($importantAccountCodes as $accountCode) { + $account = $this->entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => $accountCode, + 'bid' => null + ]); + + if ($account) { + // بررسی اینکه آیا قبلاً سند افتتاحیه ایجاد شده یا نه + $existingDoc = $this->entityManager->createQueryBuilder() + ->select('COUNT(d.id)') + ->from('App\Entity\HesabdariDoc', 'd') + ->join('App\Entity\HesabdariRow', 'r', 'WITH', 'r.doc = d') + ->where('d.type = :type') + ->andWhere('d.year = :year') + ->andWhere('r.ref = :account') + ->setParameter('type', 'account_opening_balance') + ->setParameter('year', $year) + ->setParameter('account', $account) + ->getQuery() + ->getSingleScalarResult(); + + if ($existingDoc == 0) { + // تعیین cashdesk_id مناسب بر اساس نوع حساب + $cashdeskId = null; + if (in_array($accountCode, [121, 123])) { // صندوق + $cashdeskId = 2; // صندوق اصلی + } elseif (in_array($accountCode, [122, 124])) { // تنخواه گردان + $cashdeskId = 2; // صندوق اصلی + } + + // ایجاد سند افتتاحیه با تراز صفر + $this->createAccountOpeningEntry($year, $account, 0, $user, null, null, $cashdeskId); + } + } + } + } +} \ No newline at end of file diff --git a/hesabixCore/tests/CloseYearAccountsTest.php b/hesabixCore/tests/CloseYearAccountsTest.php new file mode 100644 index 0000000..b7ee96a --- /dev/null +++ b/hesabixCore/tests/CloseYearAccountsTest.php @@ -0,0 +1,99 @@ +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(); + } +} \ No newline at end of file diff --git a/hesabixCore/tests/CloseYearTest.php b/hesabixCore/tests/CloseYearTest.php new file mode 100644 index 0000000..3aa21bc --- /dev/null +++ b/hesabixCore/tests/CloseYearTest.php @@ -0,0 +1,159 @@ +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')); + } +} \ No newline at end of file diff --git a/hesabixCore/tests/Controller/CloseYearControllerTest.php b/hesabixCore/tests/Controller/CloseYearControllerTest.php new file mode 100644 index 0000000..b47a538 --- /dev/null +++ b/hesabixCore/tests/Controller/CloseYearControllerTest.php @@ -0,0 +1,207 @@ +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; + } +} \ No newline at end of file diff --git a/webUI/src/views/acc/accounting/closeyear.vue b/webUI/src/views/acc/accounting/closeyear.vue index 4145afa..aac93c2 100755 --- a/webUI/src/views/acc/accounting/closeyear.vue +++ b/webUI/src/views/acc/accounting/closeyear.vue @@ -21,181 +21,730 @@ -
سال مالی : - {{ YearInfo.year.label }} -
-
-
-
-
-

دارائی‌ها

-
+ +
+
سال مالی جاری: + {{ currentYear.label }} +
+
+ +
+
+
+

سود و زیان

+
+
+
+
+ کل درآمد: +
+
+ {{ $filters.formatNumber(profitLoss.totalIncome) }} ریال
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
آیتمبستانکاربدهکارترازوضعیت
- بانک‌ها - - {{ $filters.formatNumber(YearInfo.banks.bd) }} - - {{ $filters.formatNumber(YearInfo.banks.bs) }} - - {{ $filters.formatNumber(Math.abs(YearInfo.banks.bs - - YearInfo.banks.bd)) }} - - بدهکار - بستانکار - تسویه -
- صندوق‌ها - - {{ $filters.formatNumber(YearInfo.cashdesks.bd) }} - - {{ $filters.formatNumber(YearInfo.cashdesks.bs) }} - - {{ $filters.formatNumber(Math.abs(YearInfo.cashdesks.bd - - YearInfo.cashdesks.bs)) }} - - بدهکار - بستانکار - تسویه -
- تنخواه گردان‌ها - - {{ $filters.formatNumber(YearInfo.salarys.bd) }} - - {{ $filters.formatNumber(YearInfo.salarys.bs) }} - - {{ $filters.formatNumber(Math.abs(YearInfo.salarys.bd - - YearInfo.salarys.bs)) }} - - بدهکار - بستانکار - تسویه -
- بدهکاران - - {{ $filters.formatNumber(YearInfo.persons.bd) }} - - {{ $filters.formatNumber(YearInfo.persons.bs) }} - - {{ $filters.formatNumber(Math.abs(YearInfo.persons.bs - - YearInfo.persons.bd)) }} - - بدهکار - بستانکار - تسویه -
+
+
+ کل هزینه: +
+
+ {{ $filters.formatNumber(profitLoss.totalExpense) }} ریال +
+
+
+
+
+ سود خالص: +
+
+ {{ $filters.formatNumber(profitLoss.netProfit) }} ریال +
+ +
+
+
+

ترازنامه

+
+
+
+
+ کل دارایی‌ها: +
+
+ {{ $filters.formatNumber(balanceSheet.totalAssets) }} ریال +
+
+
+
+ کل بدهی‌ها: +
+
+ {{ $filters.formatNumber(balanceSheet.totalLiabilities) }} ریال +
+
+
+
+
+ کل سرمایه: +
+
+ {{ $filters.formatNumber(balanceSheet.totalEquity) }} ریال +
+
+
+
+
+ + +
+
+
+

ساختار حساب‌های حسابداری

+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+

تنظیمات بستن سال مالی

+
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ + +

اطلاعات سال مالی جدید

+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + {{ $t('drawer.close_year') }} + +
+
+
+
+
+
+ + + + + + mdi-alert-circle + تایید بستن سال مالی + + +

آیا از بستن سال مالی اطمینان دارید؟

+

این عملیات غیرقابل بازگشت است.

+
+ + + + انصراف + + + تایید + + +
+
+ + + + + + mdi-check-circle + سال مالی جدید ایجاد شد + + +

سال مالی جدید با موفقیت ایجاد شد.

+

حالا می‌توانید سال مالی جدید را انتخاب کنید.

+
+ + + + تایید + + +
+
+ + + + {{ snackbar.text }} + + + - \ No newline at end of file + \ No newline at end of file diff --git a/webUI/src/views/user/manager/settings/plugins.vue b/webUI/src/views/user/manager/settings/plugins.vue index 2253bc6..b95ff72 100755 --- a/webUI/src/views/user/manager/settings/plugins.vue +++ b/webUI/src/views/user/manager/settings/plugins.vue @@ -131,7 +131,7 @@ export default { rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد" rowsOfPageSeparatorMessage="از" theme-color="#1d90ff" header-text-direction="center" body-text-direction="center" :loading="loading"> -