diff --git a/hesabixCore/src/Controller/CommodityController.php b/hesabixCore/src/Controller/CommodityController.php index 19c0907..cb9e1ec 100644 --- a/hesabixCore/src/Controller/CommodityController.php +++ b/hesabixCore/src/Controller/CommodityController.php @@ -21,14 +21,135 @@ use App\Entity\StoreroomItem; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Psr\Log\LoggerInterface; class CommodityController extends AbstractController { + + #[Route('/api/commodities/search', name: 'search_commodities')] + public function searchCommodities( + Access $access, + EntityManagerInterface $entityManager, + Request $request, + Explore $explore + ): JsonResponse { + $acc = $access->hasRole('commodity'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + // دریافت داده‌ها از بدنه درخواست POST + $payload = json_decode($request->getContent(), true) ?? []; + $filters = $payload['filters'] ?? []; + $pagination = $payload['pagination'] ?? []; + $sort = $payload['sort'] ?? []; + + // پارامترهای صفحه‌بندی و مرتب‌سازی + $page = max(1, $pagination['page'] ?? 1); + $limit = max(1, $pagination['limit'] ?? 10); + $sortBy = $sort['sortBy'] ?? 'code'; + $sortDesc = $sort['sortDesc'] ?? true; + + // فیلدهای معتبر برای مرتب‌سازی + $validSortFields = ['id', 'name', 'code', 'des', 'priceBuy', 'priceSell', 'orderPoint', 'minOrderCount', 'dayLoading']; + $sortBy = in_array($sortBy, $validSortFields) ? $sortBy : 'code'; + + // ساخت کوئری پایه + $qb = $entityManager->getRepository(Commodity::class)->createQueryBuilder('c') + ->andWhere('c.bid = :bid') + ->setParameter('bid', $acc['bid']); + + // اعمال فیلتر دسته‌بندی + if (!empty($filters['cat']) && !empty($filters['cat']['value'])) { + $qb->andWhere('c.cat IN (:cats)') + ->setParameter('cats', (array) $filters['cat']['value']); + } + + // جستجوی جامع در تمام فیلدها + if (!empty($filters['search']) && !empty($filters['search']['value'])) { + $searchValue = trim($filters['search']['value']); + $searchConditions = []; + $searchParams = []; + + // فیلدهای رشته‌ای با LOWER + $stringFields = ['name', 'des', 'barcodes']; + foreach ($stringFields as $index => $field) { + $paramName = "search_$index"; + $searchConditions[] = "LOWER(c.$field) LIKE :$paramName"; + $searchParams[$paramName] = "%$searchValue%"; + } + + // کد کالا بدون LOWER + $searchConditions[] = "c.code LIKE :search_code"; + $searchParams['search_code'] = "%$searchValue%"; + + // فیلدهای عددی + $numericFields = ['priceBuy', 'priceSell', 'orderPoint', 'minOrderCount', 'dayLoading']; + foreach ($numericFields as $index => $field) { + $paramName = "search_" . (count($stringFields) + $index + 1); + $searchConditions[] = "CAST(c.$field AS CHAR) LIKE :$paramName"; + $searchParams[$paramName] = "%$searchValue%"; + } + + // جستجو در نام واحد شمارش + $qb->leftJoin('c.unit', 'u'); + $searchConditions[] = "LOWER(u.name) LIKE :search_unit"; + $searchParams['search_unit'] = "%$searchValue%"; + + $qb->andWhere('(' . implode(' OR ', $searchConditions) . ')'); + foreach ($searchParams as $param => $value) { + $qb->setParameter($param, $value); + } + } + + // مرتب‌سازی + $qb->orderBy("c.$sortBy", $sortDesc ? 'DESC' : 'ASC'); + + // شمارش کل نتایج + $countQb = clone $qb; + $totalItems = $countQb->select('COUNT(c.id)')->getQuery()->getSingleScalarResult(); + + // اعمال صفحه‌بندی + $qb->setFirstResult(($page - 1) * $limit) + ->setMaxResults($limit); + + // اجرای کوئری + $results = $qb->getQuery()->getResult(); + + // تبدیل نتایج + $data = array_map(function (Commodity $item) use ($entityManager, $acc, $explore) { + $temp = $explore::ExploreCommodity($item); + if (!$item->isKhadamat()) { + $rows = $entityManager->getRepository('App\Entity\HesabdariRow')->findBy([ + 'bid' => $acc['bid'], + 'commodity' => $item + ]); + $count = 0; + foreach ($rows as $row) { + $count += $row->getDoc()->getType() === 'buy' ? $row->getCommdityCount() : -$row->getCommdityCount(); + } + $temp['count'] = $count; + } + return $temp; + }, $results); + + return new JsonResponse([ + 'results' => $data, + 'pagination' => [ + 'current_page' => $page, + 'per_page' => $limit, + 'total_items' => (int) $totalItems, + 'total_pages' => ceil($totalItems / $limit), + ], + ], 200); + } + #[Route('/api/commodity/search/extra', name: 'app_commodity_search_extra')] public function app_commodity_search_extra(Provider $provider, Extractor $extractor, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse { @@ -338,63 +459,93 @@ class CommodityController extends AbstractController return $this->json($extractor->operationSuccess($temp)); } - /** - * @throws Exception - */ - #[Route('/api/commodity/list/excel', name: 'app_commodity_list_excel')] - public function app_commodity_list_excel(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): BinaryFileResponse|JsonResponse|StreamedResponse - { + #[Route('/api/commodity/list/excel', name: 'app_commodity_list_excel', methods: ['POST'])] + public function app_commodity_list_excel( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager + ): BinaryFileResponse { $acc = $access->hasRole('commodity'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); } - if (!array_key_exists('items', $params)) { - $items = $entityManager->getRepository(Commodity::class)->findBy([ - 'bid' => $acc['bid'] - ]); + + $params = json_decode($request->getContent(), true) ?? []; + + if (isset($params['all']) && $params['all'] === true) { + // دریافت همه کالاها بدون محدودیت + $items = $entityManager->getRepository(Commodity::class)->findBy(['bid' => $acc['bid']]); } else { + if (!isset($params['items']) || empty($params['items'])) { + throw new \Exception('هیچ کالایی برای خروجی انتخاب نشده است'); + } $items = []; foreach ($params['items'] as $param) { - $prs = $entityManager->getRepository(Commodity::class)->findOneBy([ + $item = $entityManager->getRepository(Commodity::class)->findOneBy([ 'id' => $param['id'], 'bid' => $acc['bid'] ]); - if ($prs) - $items[] = $prs; + if ($item) { + $items[] = $item; + } } } - return new BinaryFileResponse($provider->createExcell($items)); + + if (empty($items)) { + throw new \Exception('هیچ کالایی برای خروجی یافت نشد'); + } + + $filePath = $provider->createExcell($items); + $response = new BinaryFileResponse($filePath); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'commodities.xlsx'); + $response->deleteFileAfterSend(true); + + $log->insert('کالا/خدمات', 'خروجی اکسل برای ' . count($items) . ' کالا تولید شد.', $this->getUser(), $acc['bid']->getId()); + return $response; } - #[Route('/api/commodity/list/print', name: 'app_commodity_list_print')] - public function app_commodity_list_print(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse - { + + + #[Route('/api/commodity/list/print', name: 'app_commodity_list_print', methods: ['POST'])] + public function app_commodity_list_print( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager + ): JsonResponse { $acc = $access->hasRole('commodity'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); } - if (!array_key_exists('items', $params)) { - $items = $entityManager->getRepository(Commodity::class)->findBy([ - 'bid' => $request->headers->get('activeBid') - ]); + + $params = json_decode($request->getContent(), true) ?? []; + + if (isset($params['all']) && $params['all'] === true) { + // دریافت همه کالاها بدون محدودیت + $items = $entityManager->getRepository(Commodity::class)->findBy(['bid' => $acc['bid']]); } else { + if (!isset($params['items']) || empty($params['items'])) { + return $this->json(['Success' => false, 'message' => 'هیچ کالایی برای چاپ انتخاب نشده است'], 400); + } $items = []; foreach ($params['items'] as $param) { - $prs = $entityManager->getRepository(Commodity::class)->findOneBy([ + $item = $entityManager->getRepository(Commodity::class)->findOneBy([ 'id' => $param['id'], 'bid' => $acc['bid'] ]); - if ($prs) - $items[] = $prs; + if ($item) { + $items[] = $item; + } } } + if (empty($items)) { + return $this->json(['Success' => false, 'message' => 'هیچ کالایی برای چاپ یافت نشد'], 400); + } + $pid = $provider->createPrint( $acc['bid'], $this->getUser(), @@ -404,8 +555,11 @@ class CommodityController extends AbstractController 'persons' => $items ]) ); + + $log->insert('کالا/خدمات', 'خروجی PDF برای ' . count($items) . ' کالا تولید شد.', $this->getUser(), $acc['bid']->getId()); return $this->json(['id' => $pid]); } + #[Route('/api/commodity/info/{code}', name: 'app_commodity_info')] public function app_commodity_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse { @@ -1079,70 +1233,100 @@ class CommodityController extends AbstractController return $this->json(['result' => 1]); } - #[Route('/api/commodity/delete/{code}', name: 'app_commodity_delete')] - public function app_commodity_delete(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, $code = 0): JsonResponse - { + #[Route('/api/commodities/{code}', name: 'app_commodity_delete', methods: ['DELETE'])] + public function app_commodity_delete( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager, + $code = 0 + ): JsonResponse { $acc = $access->hasRole('commodity'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); + } $commodity = $entityManager->getRepository(Commodity::class)->findOneBy(['bid' => $acc['bid'], 'code' => $code]); - if (!$commodity) - throw $this->createNotFoundException(); - //check accounting docs - $docs = $entityManager->getRepository(HesabdariRow::class)->findby(['bid' => $acc['bid'], 'commodity' => $commodity]); - if (count($docs) > 0) - return $this->json(['result' => 2]); - //check for storeroom docs - $storeDocs = $entityManager->getRepository(StoreroomItem::class)->findby(['bid' => $acc['bid'], 'commodity' => $commodity]); - if (count($storeDocs) > 0) - return $this->json(['result' => 2]); + if (!$commodity) { + throw $this->createNotFoundException('کالا یافت نشد'); + } + + // بررسی اسناد حسابداری + $docs = $entityManager->getRepository(HesabdariRow::class)->findBy(['bid' => $acc['bid'], 'commodity' => $commodity]); + if (count($docs) > 0) { + return $this->json(['result' => 2, 'message' => 'این کالا در اسناد حسابداری استفاده شده و قابل حذف نیست']); + } + + // بررسی اسناد انبار + $storeDocs = $entityManager->getRepository(StoreroomItem::class)->findBy(['bid' => $acc['bid'], 'commodity' => $commodity]); + if (count($storeDocs) > 0) { + return $this->json(['result' => 2, 'message' => 'این کالا در اسناد انبار استفاده شده و قابل حذف نیست']); + } $comName = $commodity->getName(); $entityManager->remove($commodity); - $log->insert('کالا/خدمات', ' کالا / خدمات با نام ' . $comName . ' حذف شد. ', $this->getUser(), $acc['bid']->getId()); - return $this->json(['result' => 1]); + $entityManager->flush(); + $log->insert('کالا/خدمات', 'کالا/خدمات با نام ' . $comName . ' حذف شد.', $this->getUser(), $acc['bid']->getId()); + return $this->json(['result' => 1, 'message' => 'کالا با موفقیت حذف شد']); } - #[Route('/api/commodity/deletegroup', name: 'app_commodity_delete_group')] - public function app_commodity_delete_group(Extractor $extractor, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, $code = 0): JsonResponse - { + #[Route('/api/commodity/deletegroup', name: 'app_commodity_delete_group', methods: ['POST'])] + public function app_commodity_delete_group( + Extractor $extractor, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager + ): JsonResponse { $acc = $access->hasRole('commodity'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); - $params = $request->getPayload()->all(); + } + + $params = json_decode($request->getContent(), true); + if (!isset($params['codes']) || !is_array($params['codes'])) { + return $this->json(['Success' => false, 'message' => 'لیست کدهای کالا ارسال نشده است'], 400); + } + $hasIgnored = false; - foreach ($params as $param) { + $deletedCount = 0; + + foreach ($params['codes'] as $code) { $commodity = $entityManager->getRepository(Commodity::class)->findOneBy([ 'bid' => $acc['bid'], - 'code' => $param['code'] + 'code' => $code ]); + if (!$commodity) { $hasIgnored = true; continue; } - //check accounting docs - $docs = $entityManager->getRepository(HesabdariRow::class)->findby(['bid' => $acc['bid'], 'commodity' => $commodity]); - if (count($docs) > 0) { - $hasIgnored = true; - continue; - } + $docs = $entityManager->getRepository(HesabdariRow::class)->findBy(['bid' => $acc['bid'], 'commodity' => $commodity]); + $storeDocs = $entityManager->getRepository(StoreroomItem::class)->findBy(['bid' => $acc['bid'], 'commodity' => $commodity]); - //check for storeroom docs - $storeDocs = $entityManager->getRepository(StoreroomItem::class)->findby(['bid' => $acc['bid'], 'commodity' => $commodity]); - if (count($storeDocs) > 0) { + if (count($docs) > 0 || count($storeDocs) > 0) { $hasIgnored = true; continue; } $comName = $commodity->getName(); - $entityManager->getRepository(Commodity::class)->remove($commodity, false); - $log->insert('کالا/خدمات', ' کالا / خدمات با نام ' . $comName . ' حذف شد. ', $this->getUser(), $acc['bid']->getId()); + $entityManager->remove($commodity); + $log->insert('کالا/خدمات', 'کالا/خدمات با نام ' . $comName . ' حذف شد.', $this->getUser(), $acc['bid']->getId()); + $deletedCount++; } - $entityManager->flush(); - return $this->json($extractor->operationSuccess(['ignored' => $hasIgnored])); + $entityManager->flush(); + + return $this->json([ + 'Success' => true, + 'result' => [ + 'ignored' => $hasIgnored, + 'deletedCount' => $deletedCount, + 'message' => $hasIgnored ? 'برخی کالاها به دلیل استفاده در اسناد حذف نشدند' : 'همه کالاها با موفقیت حذف شدند' + ] + ]); } #[Route('/api/commodity/pricelist/list', name: 'app_commodity_pricelist_list')] @@ -1316,149 +1500,4 @@ class CommodityController extends AbstractController return $this->json($extractor->operationSuccess()); } - /** - * @Route("/api/commodities/search", name="search_commodities", methods={"POST"}) - */ - public function searchCommodities(Access $access, EntityManagerInterface $entityManagerInterface, Request $request): JsonResponse - { - $acc = $access->hasRole('commodity'); - if (!$acc) { - throw $this->createAccessDeniedException(); - } - - // لیست فیلدهای ممنوعه - $forbiddenFields = ['id', 'bid', 'submitter']; - - // پارامترهای صفحه‌بندی - $page = max(1, $request->query->getInt('page', 1)); - $limit = max(1, $request->query->getInt('limit', 10)); - - // دریافت فیلترهای ارسالی - $filters = json_decode($request->getContent(), true); - if (!is_array($filters)) { - $filters = []; - } - - // ساخت کوئری - $qb = $entityManagerInterface->getRepository(Commodity::class)->createQueryBuilder('c'); - - // شرط ثابت: bid.id همیشه برابر با $acc['bid'] - $qb->andWhere('c.bid = :bid') - ->setParameter('bid', $acc['bid']); - - // اعمال فیلترهای کاربر - foreach ($filters as $field => $condition) { - if (in_array($field, $forbiddenFields)) { - continue; - } - - if (!isset($condition['operator']) || !isset($condition['value'])) { - continue; - } - - $operator = $condition['operator']; - $value = $condition['value']; - $paramName = str_replace('.', '_', $field) . '_param'; - - switch ($operator) { - case '=': - $qb->andWhere("c.$field = :$paramName") - ->setParameter($paramName, $value); - break; - case '>': - $qb->andWhere("c.$field > :$paramName") - ->setParameter($paramName, $value); - break; - case '<': - $qb->andWhere("c.$field < :$paramName") - ->setParameter($paramName, $value); - break; - case '%': - $qb->andWhere("c.$field LIKE :$paramName") - ->setParameter($paramName, "%$value%"); - break; - } - } - - // مرتب‌سازی پیش‌فرض بر اساس code (نزولی) - $qb->orderBy('c.code', 'DESC'); - - // شمارش کل نتایج - $countQb = clone $qb; - $totalItems = $countQb->select('COUNT(c.id)')->getQuery()->getSingleScalarResult(); - - // اعمال صفحه‌بندی - $qb->setFirstResult(($page - 1) * $limit) - ->setMaxResults($limit); - - // اجرای کوئری - $results = $qb->getQuery()->getResult(); - - // تبدیل نتایج به آرایه - $data = array_map(function (Commodity $item) use ($entityManagerInterface, $acc): array { - $temp = []; - $temp['id'] = $item->getId(); - $temp['name'] = $item->getName(); - $temp['unit'] = $item->getUnit()->getName(); - $temp['des'] = $item->getDes(); - $temp['priceBuy'] = $item->getPriceBuy(); - $temp['speedAccess'] = $item->isSpeedAccess(); - $temp['priceSell'] = $item->getPriceSell(); - $temp['code'] = $item->getCode(); - $temp['cat'] = null; - if ($item->getCat()) { - $temp['cat'] = $item->getCat()->getName(); - $temp['catData'] = Explore::ExploreCommodityCat($item->getCat()); - } - $temp['khadamat'] = false; - if ($item->isKhadamat()) { - $temp['khadamat'] = true; - } - $temp['withoutTax'] = false; - if ($item->isWithoutTax()) { - $temp['withoutTax'] = true; - } - $temp['commodityCountCheck'] = $item->isCommodityCountCheck(); - $temp['minOrderCount'] = $item->getMinOrderCount(); - $temp['dayLoading'] = $item->getDayLoading(); - $temp['orderPoint'] = $item->getOrderPoint(); - $temp['unitData'] = [ - 'name' => $item->getUnit()->getName(), - 'floatNumber' => $item->getUnit()->getFloatNumber(), - ]; - $temp['barcodes'] = $item->getBarcodes(); - // محاسبه موجودی - if ($item->isKhadamat()) { - $temp['count'] = 0; - } else { - $rows = $entityManagerInterface->getRepository(HesabdariRow::class)->findBy([ - 'bid' => $acc['bid'], - 'commodity' => $item - ]); - $count = 0; - foreach ($rows as $row) { - if ($row->getDoc()->getType() == 'buy') { - $count += $row->getCommdityCount(); - } elseif ($row->getDoc()->getType() == 'sell') { - $count -= $row->getCommdityCount(); - } - } - $temp['count'] = $count; - } - return $temp; - }, $results); - - // اطلاعات صفحه‌بندی - $totalPages = ceil($totalItems / $limit); - - return new JsonResponse([ - 'results' => $data, - 'pagination' => [ - 'current_page' => $page, - 'per_page' => $limit, - 'total_items' => $totalItems, - 'total_pages' => $totalPages, - ], - ], 200); - } } diff --git a/hesabixCore/src/Controller/ExploreAccountsController.php b/hesabixCore/src/Controller/ExploreAccountsController.php index b27683e..19af10b 100644 --- a/hesabixCore/src/Controller/ExploreAccountsController.php +++ b/hesabixCore/src/Controller/ExploreAccountsController.php @@ -4,6 +4,7 @@ namespace App\Controller; use App\Entity\BankAccount; use App\Entity\Cashdesk; +use App\Entity\Cheque; use App\Entity\Commodity; use App\Entity\HesabdariRow; use App\Entity\HesabdariTable; @@ -18,376 +19,996 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use Symfony\Component\HttpFoundation\StreamedResponse; -final class ExploreAccountsController extends AbstractController +class ExploreAccountsController extends AbstractController { private $em; private $provider; - function __construct(Provider $provider, EntityManagerInterface $entityManager) + + public function __construct(EntityManagerInterface $entityManager, Provider $provider) { $this->em = $entityManager; $this->provider = $provider; } - #[Route('/api/report/acc/get_details', name: 'app_report_acc_get_details')] - public function app_report_acc_get_details(Provider $provider, Jdate $jdate, Access $access, Request $request, EntityManagerInterface $entityManagerInterface): JsonResponse + #[Route('/api/report/acc/explore_accounts_det', name: 'app_explore_accounts_det', methods: ['POST'])] + public function app_explore_accounts_det(Access $access, Request $request, Extractor $extractor): JsonResponse { $acc = $access->hasRole('report'); if (!$acc) { throw $this->createAccessDeniedException(); } - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); - } - if (!array_key_exists('node', $params)) - throw $this->createNotFoundException(); - - if ($params['isObject'] == false) { - $node = $entityManagerInterface->getRepository(HesabdariTable::class)->findNode($params['node'], $acc['bid']->getId()); - if (!$node) - throw $this->createNotFoundException(); - $rows = $this->tree2flat($node, $acc); - } else { - $node = $entityManagerInterface->getRepository(HesabdariTable::class)->findNode($params['upperID'], $acc['bid']->getId()); - if (!$node) - throw $this->createNotFoundException(); - if ($node->getType() == 'bank') { - $item = $entityManagerInterface->getRepository(BankAccount::class)->findOneBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - 'id' => $params['node'] - ]); - $rows = $this->getAllBanksRows($node, $acc, [$item]); - } elseif ($node->getType() == 'cashdesk') { - $item = $entityManagerInterface->getRepository(Cashdesk::class)->findOneBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - 'id' => $params['node'] - ]); - $rows = $this->getAllCashdeskRows($node, $acc, [$item]); - } elseif ($node->getType() == 'salary') { - $item = $entityManagerInterface->getRepository(Salary::class)->findOneBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - 'id' => $params['node'] - ]); - $rows = $this->getAllSalarysRows($node, $acc, [$item]); - } elseif ($node->getType() == 'person') { - $item = $entityManagerInterface->getRepository(Person::class)->findOneBy([ - 'bid' => $acc['bid'], - 'id' => $params['node'] - ]); - $rows = $this->getAllPersonsRows($node, $acc, [$item]); - - } elseif ($node->getType() == 'commodity') { - $item = $entityManagerInterface->getRepository(Commodity::class)->findOneBy([ - 'bid' => $acc['bid'], - 'id' => $params['node'] - ]); - $rows = $this->getAllCommoditiesRows($node, $acc, [$item]); - } - } - return $this->json(Explore::ExploreHesabdariRows($rows)); - } - - #[Route('/api/report/acc/explore_accounts_det', name: 'app_explore_accounts_det')] - public function app_explore_accounts_det(Access $access, Request $request, EntityManagerInterface $entityManagerInterface, Extractor $extractor): JsonResponse - { - $acc = $access->hasRole('report'); - if (!$acc) { - throw $this->createAccessDeniedException(); + $params = json_decode($request->getContent(), true) ?? []; + if (!isset($params['node']) || !isset($params['type'])) { + throw $this->createNotFoundException('Node or type parameter is missing'); } - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); - } - if (!array_key_exists('node', $params)) - throw $this->createNotFoundException(); + $page = $params['page'] ?? 1; + $perPage = $params['perPage'] ?? 10; + $offset = ($page - 1) * $perPage; - if ($params['node'] == 'root') { - $params['node'] = $entityManagerInterface - ->getRepository(HesabdariTable::class) - ->findOneBy(['upper' => null]) - ->getId(); + $nodeId = $params['node'] === 'root' + ? $this->em->getRepository(HesabdariTable::class) + ->findOneBy(['upper' => null, 'bid' => [$acc['bid']->getId(), null]])->getId() + : $params['node']; + + $node = $this->em->getRepository(HesabdariTable::class) + ->findNode($nodeId, $acc['bid']->getId()); + if (!$node) { + throw $this->createNotFoundException('Node not found'); } - $node = $entityManagerInterface->getRepository(HesabdariTable::class)->findNode($params['node'], $acc['bid']->getId()); - if (!$node) - throw $this->createNotFoundException(); + $output = []; + $totalItems = 0; - if ($node->getType() == 'calc') { - $childs = $this->getChilds($node, $acc); - $output = []; - foreach ($childs as $child) { - $childRows = $this->tree2flat($child, $acc); - $output[] = $this->calculateOutput($childRows, $child, $acc); - } - } elseif ($node->getType() == 'bank') { - $items = $entityManagerInterface->getRepository(BankAccount::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - foreach ($items as $item) { - $rows = $this->getAllBanksRows($node, $acc, [$item]); - $output[] = $this->calculateOutputObject($rows, $node, $acc, $item); - } - } elseif ($node->getType() == 'cashdesk') { - $items = $entityManagerInterface->getRepository(Cashdesk::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - foreach ($items as $item) { - $rows = $this->getAllCashdeskRows($node, $acc, [$item]); - $output[] = $this->calculateOutputObject($rows, $node, $acc, $item); - } - } elseif ($node->getType() == 'salary') { - $items = $entityManagerInterface->getRepository(Salary::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - foreach ($items as $item) { - $rows = $this->getAllSalarysRows($node, $acc, [$item]); - $output[] = $this->calculateOutputObject($rows, $node, $acc, $item); - } - } elseif ($node->getType() == 'person') { - $items = $entityManagerInterface->getRepository(Person::class)->findBy([ - 'bid' => $acc['bid'], - ]); - foreach ($items as $item) { - $rows = $this->getAllPersonsRows($node, $acc, [$item]); - $output[] = $this->calculateOutputObject($rows, $node, $acc, $item); - } - } elseif ($node->getType() == 'commodity') { - $items = $entityManagerInterface->getRepository(Commodity::class)->findBy([ - 'bid' => $acc['bid'], - ]); - foreach ($items as $item) { - $rows = $this->getAllCommoditiesRows($node, $acc, [$item]); - $output[] = $this->calculateOutputObject($rows, $node, $acc, $item); - } - } - - $data = []; - $data['itemData'] = $output; - $data['tree'] = $this->getTree($node); - return $this->json($data); - } - private function tree2flat(Person|HesabdariTable|BankAccount|Cashdesk|Salary $item, array $acc): array - { - $res = []; - if ($this->getEntityName($item) == 'App\Entity\HesabdariTable') { - if ($this->hasChild($item)) { - foreach ($this->getChilds($item) as $child) { - $res = array_merge($res, $this->tree2flat($child, $acc)); + switch ($params['type']) { + case 'calc': + $query = $this->em->getRepository(HesabdariTable::class)->createQueryBuilder('ht') + ->where('ht.upper = :node') + ->andWhere('ht.bid = :bid OR ht.bid IS NULL') + ->setParameter('node', $node) + ->setParameter('bid', $acc['bid']->getId()) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $children = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(ht.id)')->getQuery()->getSingleScalarResult(); + foreach ($children as $child) { + $allNodes = $this->getAllDescendants($child, $acc); + $allNodes[] = $child; + $rows = $this->getRowsForNodes($allNodes, $acc); + $output[] = $this->calculateTotals($rows, $child, $acc); } - return array_merge($res, $res); - } else { - $items = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'bid' => $acc['bid'], - 'ref' => $item - ], $acc['money']); - $res = array_merge($res, $items); - } - } elseif ($this->getEntityName($item) == 'App\Entity\BankAccount') { - $res = $this->getAllBanksRows($item, $acc); - } elseif ($this->getEntityName($item) == 'App\Entity\Cashdesk') { - $res = $this->getAllCashdeskRows($item, $acc); - } elseif ($this->getEntityName($item) == 'App\Entity\Salary') { - $res = $this->getAllSalarysRows($item, $acc); - } elseif ($this->getEntityName($item) == 'App\Entity\Person') { - $res = $this->getAllPersonsRows($item, $acc); - } elseif ($this->getEntityName($item) == 'App\Entity\Commodity') { - $res = $this->getAllCommoditiesRows($item, $acc); + break; + + case 'bank': + $query = $this->em->getRepository(BankAccount::class)->createQueryBuilder('b') + ->where('b.bid = :bid') + ->andWhere('b.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('money', $acc['money']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $bankAccounts = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(b.id)')->getQuery()->getSingleScalarResult(); + foreach ($bankAccounts as $bankAccount) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'bank' => $bankAccount, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculateBankTotals($rows, $bankAccount, $node); + } + break; + + case 'cashdesk': + $query = $this->em->getRepository(Cashdesk::class)->createQueryBuilder('c') + ->where('c.bid = :bid') + ->andWhere('c.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('money', $acc['money']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $cashdesks = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(c.id)')->getQuery()->getSingleScalarResult(); + foreach ($cashdesks as $cashdesk) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'cashdesk' => $cashdesk, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculateCashdeskTotals($rows, $cashdesk, $node); + } + break; + + case 'salary': + $query = $this->em->getRepository(Salary::class)->createQueryBuilder('s') + ->where('s.bid = :bid') + ->andWhere('s.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('money', $acc['money']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $salaries = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(s.id)')->getQuery()->getSingleScalarResult(); + foreach ($salaries as $salary) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'salary' => $salary, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculateSalaryTotals($rows, $salary, $node); + } + break; + + case 'person': + $query = $this->em->getRepository(Person::class)->createQueryBuilder('p') + ->where('p.bid = :bid') + ->setParameter('bid', $acc['bid']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $persons = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(p.id)')->getQuery()->getSingleScalarResult(); + foreach ($persons as $person) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'person' => $person, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculatePersonTotals($rows, $person, $node); + } + break; + + case 'commodity': + $query = $this->em->getRepository(Commodity::class)->createQueryBuilder('c') + ->where('c.bid = :bid') + ->setParameter('bid', $acc['bid']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $commodities = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(c.id)')->getQuery()->getSingleScalarResult(); + foreach ($commodities as $commodity) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'commodity' => $commodity, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculateCommodityTotals($rows, $commodity, $node); + } + break; + + case 'cheque': + $query = $this->em->getRepository(Cheque::class)->createQueryBuilder('c') + ->where('c.bid = :bid') + ->setParameter('bid', $acc['bid']) + ->setMaxResults($perPage) + ->setFirstResult($offset); + $cheques = $query->getQuery()->getResult(); + $totalItems = $query->select('COUNT(c.id)')->getQuery()->getSingleScalarResult(); + foreach ($cheques as $cheque) { + $rows = $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'cheque' => $cheque, + 'ref' => $node, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + $output[] = $this->calculateChequeTotals($rows, $cheque, $node); + } + break; + + default: + throw $this->createNotFoundException('Unsupported type'); } - return $res; + + return $this->json([ + 'itemData' => $output, + 'tree' => $this->getTree($node, $acc), + 'pagination' => [ + 'totalItems' => $totalItems, + 'totalPages' => ceil($totalItems / $perPage), + 'currentPage' => $page, + 'perPage' => $perPage + ] + ]); + } + + #[Route('/api/report/acc/get_details', name: 'app_report_acc_get_details', methods: ['POST'])] + public function app_report_acc_get_details( + Access $access, + Request $request, + Jdate $jdate, + Explore $explore + ): JsonResponse { + $acc = $access->hasRole('report'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + if (!isset($params['node']) || !isset($params['type']) || !isset($params['isObject'])) { + throw $this->createNotFoundException('Required parameters (node, type, isObject) are missing'); + } + + $page = max(1, (int) ($params['page'] ?? 1)); + $perPage = max(1, (int) ($params['perPage'] ?? 10)); + $offset = ($page - 1) * $perPage; + + $rows = []; + $totalItems = 0; + + $node = $this->em->getRepository(HesabdariTable::class) + ->findNode($params['upperID'] ?? $params['node'], $acc['bid']->getId()); + if (!$node) { + throw $this->createNotFoundException('Node not found'); + } + + if ($params['isObject'] === false) { + $allNodes = $this->getAllDescendants($node, $acc); + $allNodes[] = $node; + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.ref IN (:nodeIds)') + ->andWhere('r.bid = :bid OR r.bid IS NULL') + ->andWhere('d.money = :money') + ->andWhere('r.year = :year') + ->setParameter('nodeIds', array_map(fn($n) => $n->getId(), $allNodes)) + ->setParameter('bid', $acc['bid']) + ->setParameter('money', $acc['money']) + ->setParameter('year', $acc['year']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + } else { + switch ($params['type']) { + case 'bank': + $item = $this->em->getRepository(BankAccount::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Bank account not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.bank = :bank') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('bank', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + case 'cashdesk': + $item = $this->em->getRepository(Cashdesk::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Cashdesk not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.cashdesk = :cashdesk') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('cashdesk', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + case 'salary': + $item = $this->em->getRepository(Salary::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Salary not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.salary = :salary') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('salary', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + case 'person': + $item = $this->em->getRepository(Person::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Person not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.person = :person') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('person', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + case 'commodity': + $item = $this->em->getRepository(Commodity::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Commodity not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.commodity = :commodity') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('commodity', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + case 'cheque': + $item = $this->em->getRepository(Cheque::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Cheque not found'); + } + $qb = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.cheque = :cheque') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('cheque', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + $totalItems = (int) $qb->select('COUNT(r.id)') + ->getQuery() + ->getSingleScalarResult(); + + $rows = $qb->select('r') + ->setMaxResults($perPage) + ->setFirstResult($offset) + ->getQuery() + ->getResult(); + break; + + default: + throw $this->createNotFoundException('Unsupported type'); + } + } + + return $this->json([ + 'items' => $explore::ExploreHesabdariRows($rows), + 'pagination' => [ + 'totalItems' => $totalItems, + 'totalPages' => ceil($totalItems / $perPage), + 'currentPage' => $page, + 'perPage' => $perPage + ] + ]); + } + + #[Route('/api/report/acc/export_details_excel', name: 'app_report_acc_export_details_excel', methods: ['POST'])] + public function exportDetailsExcel(Access $access, Request $request, Explore $explore): StreamedResponse + { + $acc = $access->hasRole('report'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + if (!isset($params['node']) || !isset($params['type']) || !isset($params['isObject'])) { + throw $this->createNotFoundException('Required parameters (node, type, isObject) are missing'); + } + + $node = $this->em->getRepository(HesabdariTable::class) + ->findNode($params['upperID'] ?? $params['node'], $acc['bid']->getId()); + if (!$node) { + throw $this->createNotFoundException('Node not found'); + } + + $rows = []; + if ($params['isObject'] === false) { + $allNodes = $this->getAllDescendants($node, $acc); + $allNodes[] = $node; + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.ref IN (:nodeIds)') + ->andWhere('r.bid = :bid OR r.bid IS NULL') + ->andWhere('d.money = :money') + ->andWhere('r.year = :year') + ->setParameter('nodeIds', array_map(fn($n) => $n->getId(), $allNodes)) + ->setParameter('bid', $acc['bid']) + ->setParameter('money', $acc['money']) + ->setParameter('year', $acc['year']) + ->getQuery() + ->getResult(); + } else { + switch ($params['type']) { + case 'bank': + $item = $this->em->getRepository(BankAccount::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Bank account not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.bank = :bank') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('bank', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + case 'cashdesk': + $item = $this->em->getRepository(Cashdesk::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Cashdesk not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.cashdesk = :cashdesk') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('cashdesk', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + case 'salary': + $item = $this->em->getRepository(Salary::class)->findOneBy([ + 'bid' => $acc['bid'], + 'money' => $acc['money'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Salary not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.salary = :salary') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('salary', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + case 'person': + $item = $this->em->getRepository(Person::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Person not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.person = :person') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('person', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + case 'commodity': + $item = $this->em->getRepository(Commodity::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Commodity not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.commodity = :commodity') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('commodity', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + case 'cheque': + $item = $this->em->getRepository(Cheque::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['node'] + ]); + if (!$item) { + throw $this->createNotFoundException('Cheque not found'); + } + $rows = $this->em->getRepository(HesabdariRow::class)->createQueryBuilder('r') + ->innerJoin('r.doc', 'd') + ->where('r.cheque = :cheque') + ->andWhere('r.ref = :ref') + ->andWhere('r.bid = :bid') + ->andWhere('r.year = :year') + ->andWhere('d.money = :money') + ->setParameter('cheque', $item) + ->setParameter('ref', $node) + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->getQuery() + ->getResult(); + break; + + default: + throw $this->createNotFoundException('Unsupported type'); + } + } + + $data = $explore::ExploreHesabdariRows($rows); + + // تولید فایل اکسل + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // ستون‌ها + $headers = ['تاریخ', 'شماره', 'شرح', 'بدهکار', 'بستانکار', 'تعداد']; + $sheet->fromArray($headers, null, 'A1'); + + // داده‌ها + $rowData = []; + foreach ($data as $index => $item) { + $rowData[] = [ + $item['date'] ?? '', + $item['doc_code'] ?? '', + $item['des'] ?? '', + $item['bd'] ?? 0, + $item['bs'] ?? 0, + $item['commodity_count'] ?? 0, + ]; + } + $sheet->fromArray($rowData, null, 'A2'); + + // تنظیم پاسخ برای دانلود + $response = new StreamedResponse(function () use ($spreadsheet) { + $writer = new Xlsx($spreadsheet); + $writer->save('php://output'); + }); + + $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $response->headers->set('Content-Disposition', 'attachment;filename="details_export.xlsx"'); + $response->headers->set('Cache-Control', 'max-age=0'); + + return $response; } /** - * Returns Doctrine entity name - * - * @param mixed $entity - * - * @return string - * @throws \Exception + * پیدا کردن زیرمجموعه‌های مستقیم یک نود */ - private function getEntityName($entity): string + private function getDirectChildren(HesabdariTable $node, array $acc): array { - $entityName = $this->em->getMetadataFactory()->getMetadataFor(get_class($entity))->getName(); - return $entityName; + return $this->em->getRepository(HesabdariTable::class) + ->findBy(['upper' => $node, 'bid' => [$acc['bid']->getId(), null]]); } - - private function calculateOutput(array $items, HesabdariTable $item, array $acc): array + /** + * پیدا کردن تمام زیرمجموعه‌های یک نود به صورت بازگشتی + */ + private function getAllDescendants(HesabdariTable $node, array $acc): array { - $res = [ - 'his_bd' => 0, - 'his_bs' => 0, - 'bal_bd' => 0, - 'bal_bs' => 0, - 'id' => $item->getId(), - 'account' => $item->getName(), - 'type' => $item->getType(), - 'code' => $item->getCode(), - 'name' => $item->getName(), - 'isObject' => false, - 'hasChild' => $this->hasChild($item, $acc), - 'upperID' => $item->getUpper()->getId() - ]; - foreach ($items as $item) { - $res['his_bs'] += $item->getBs(); - $res['his_bd'] += $item->getBd(); + $descendants = []; + $children = $this->getDirectChildren($node, $acc); + + foreach ($children as $child) { + $descendants[] = $child; + $descendants = array_merge($descendants, $this->getAllDescendants($child, $acc)); } - if ($res['his_bd'] > $res['his_bs']) { - $res['bal_bd'] = $res['his_bd'] - $res['his_bs']; - $res['bal_bs'] = 0; - } else { - $res['bal_bs'] = $res['his_bs'] - $res['his_bd']; - $res['bal_bd'] = 0; - } - return $res; + + return $descendants; } - private function calculateOutputObject(array $items, HesabdariTable $node, array $acc, BankAccount|Person|Commodity|Cashdesk|Salary $obj): array + /** + * پیدا کردن ردیف‌های مرتبط با نودها (برای type=calc) + */ + private function getRowsForNodes(array $nodes, array $acc): array { - $res = [ - 'his_bd' => 0, - 'his_bs' => 0, - 'bal_bd' => 0, - 'bal_bs' => 0, - 'id' => $obj->getId(), - 'account' => $obj->getName(), + $nodeIds = array_unique(array_map(fn($node) => $node->getId(), $nodes)); + return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ + 'ref' => $nodeIds, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + ], $acc['money']); + } + + /** + * محاسبه جمع‌ها برای نودهای calc + */ + private function calculateTotals(array $rows, HesabdariTable $node, array $acc): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $node->getId(), + 'account' => $node->getName(), 'type' => $node->getType(), - 'isObject' => true, - 'code' => $obj->getCode(), - 'name' => $obj->getName(), - 'hasChild' => false, - 'upperID' => $node->getId() + 'code' => $node->getCode(), + 'name' => $node->getName(), + 'isObject' => false, + 'hasChild' => $this->hasChild($node, $acc), + 'upperID' => $node->getUpper() ? $node->getUpper()->getId() : null, + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, ]; - foreach ($items as $item) { - $res['his_bs'] += $item->getBs(); - $res['his_bd'] += $item->getBd(); - } - if ($res['his_bd'] > $res['his_bs']) { - $res['bal_bd'] = $res['his_bd'] - $res['his_bs']; - $res['bal_bs'] = 0; - } else { - $res['bal_bs'] = $res['his_bs'] - $res['his_bd']; - $res['bal_bd'] = 0; - } - if ($node->getType() == 'person') { - $res['name'] = $obj->getNikename(); - $res['account'] = $obj->getNikename(); - } - return $res; } - private function getTree(HesabdariTable $table): array + /** + * محاسبه جمع‌ها برای حساب‌های بانکی + */ + private function calculateBankTotals(array $rows, BankAccount $bankAccount, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $bankAccount->getId(), + 'account' => $bankAccount->getName(), + 'type' => 'bank', + 'code' => $bankAccount->getCode(), + 'name' => $bankAccount->getName(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * محاسبه جمع‌ها برای صندوق‌ها + */ + private function calculateCashdeskTotals(array $rows, Cashdesk $cashdesk, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $cashdesk->getId(), + 'account' => $cashdesk->getName(), + 'type' => 'cashdesk', + 'code' => $cashdesk->getCode(), + 'name' => $cashdesk->getName(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * محاسبه جمع‌ها برای تنخواه‌گردان‌ها + */ + private function calculateSalaryTotals(array $rows, Salary $salary, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $salary->getId(), + 'account' => $salary->getName(), + 'type' => 'salary', + 'code' => $salary->getCode(), + 'name' => $salary->getName(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * محاسبه جمع‌ها برای اشخاص + */ + private function calculatePersonTotals(array $rows, Person $person, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $person->getId(), + 'account' => $person->getNikename(), + 'type' => 'person', + 'code' => $person->getCode(), + 'name' => $person->getNikename(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * محاسبه جمع‌ها برای کالاها + */ + private function calculateCommodityTotals(array $rows, Commodity $commodity, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $commodity->getId(), + 'account' => $commodity->getName(), + 'type' => 'commodity', + 'code' => $commodity->getCode(), + 'name' => $commodity->getName(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * محاسبه جمع‌ها برای چک‌ها + */ + private function calculateChequeTotals(array $rows, Cheque $cheque, HesabdariTable $node): array + { + $his_bd = 0; + $his_bs = 0; + + foreach ($rows as $row) { + $his_bd += (int) $row->getBd(); + $his_bs += (int) $row->getBs(); + } + + $bal_bd = $his_bd > $his_bs ? $his_bd - $his_bs : 0; + $bal_bs = $his_bs > $his_bd ? $his_bs - $his_bd : 0; + + return [ + 'id' => $cheque->getId(), + 'account' => $cheque->getNumber(), + 'type' => 'cheque', + 'code' => $cheque->getNumber(), + 'name' => $cheque->getNumber(), + 'isObject' => true, + 'hasChild' => false, + 'upperID' => $node->getId(), + 'his_bd' => $his_bd, + 'his_bs' => $his_bs, + 'bal_bd' => $bal_bd, + 'bal_bs' => $bal_bs, + ]; + } + + /** + * بررسی وجود زیرمجموعه (فقط برای type=calc استفاده می‌شود) + */ + private function hasChild(HesabdariTable $table, array $acc): bool + { + return (bool) $this->em->getRepository(HesabdariTable::class) + ->findOneBy(['upper' => $table, 'bid' => [$acc['bid']->getId(), null]]); + } + + /** + * تولید درخت مسیر (breadcrumbs) + */ + private function getTree(HesabdariTable $table, array $acc): array { $tree = []; - while ($table->getUpper() != null) { + $current = $table; + + while ($current) { $tree[] = [ - 'id' => $table->getId(), - 'code' => $table->getCode(), - 'name' => $table->getName(), + 'id' => $current->getId(), + 'code' => $current->getCode(), + 'name' => $current->getName(), + 'hasChild' => $this->hasChild($current, $acc), ]; - $table = $table->getUpper(); + $current = $current->getUpper(); } + $tree[] = [ - 'id' => 1, + 'id' => 'root', 'code' => 'root', 'name' => 'جدول حساب‌ها', + 'hasChild' => true, ]; + return array_reverse($tree); } - - private function hasChild(HesabdariTable $table, array $acc = []): bool - { - if ($this->em->getRepository(HesabdariTable::class)->findOneBy(['upper' => $table])) - return true; - return false; - } - - private function getChilds(HesabdariTable $table, array $acc = []): array - { - return $this->em->getRepository(HesabdariTable::class)->findBy(['upper' => $table]); - } - - private function getAllBanksRows(HesabdariTable $table, array $acc, array $items = []): array - { - if (count($items) == 0) { - $items = $this->em->getRepository(BankAccount::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - } - return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'bank' => $items, - 'year' => $acc['year'], - 'ref' => $table - ], $acc['money']); - } - private function getAllCashdeskRows(HesabdariTable $table, array $acc, array $items = []): array - { - if (count($items) == 0) { - $items = $this->em->getRepository(Cashdesk::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - } - return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'cashdesk' => $items, - 'year' => $acc['year'], - 'ref' => $table - ], $acc['money']); - } - - private function getAllSalarysRows(HesabdariTable $table, array $acc, array $items = []): array - { - if (count($items) == 0) { - $items = $this->em->getRepository(Salary::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - } - return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'salary' => $items, - 'year' => $acc['year'], - 'ref' => $table - ], $acc['money']); - } - private function getAllPersonsRows(HesabdariTable $table, array $acc, array $items = []): array - { - if (count($items) == 0) { - $items = $this->em->getRepository(Person::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - } - return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'person' => $items, - 'year' => $acc['year'], - 'ref' => $table - ], $acc['money']); - } - - private function getAllCommoditiesRows(HesabdariTable $table, array $acc, array $items = []): array - { - if (count($items) == 0) { - $items = $this->em->getRepository(Commodity::class)->findBy([ - 'bid' => $acc['bid'], - 'money' => $acc['money'], - ]); - } - return $this->em->getRepository(HesabdariRow::class)->findByJoinMoney([ - 'commodity' => $items, - 'year' => $acc['year'], - 'ref' => $table - ], $acc['money']); - } - -} +} \ No newline at end of file diff --git a/hesabixCore/src/Controller/OpenbalanceController.php b/hesabixCore/src/Controller/OpenbalanceController.php index 419f599..33dfeca 100644 --- a/hesabixCore/src/Controller/OpenbalanceController.php +++ b/hesabixCore/src/Controller/OpenbalanceController.php @@ -15,7 +15,7 @@ use App\Service\Extractor; use App\Service\Jdate; use App\Service\Provider; use Doctrine\ORM\EntityManagerInterface; -use Proxies\__CG__\App\Entity\HesabdariRow; +use App\Entity\HesabdariRow; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/hesabixCore/src/Controller/PluginController.php b/hesabixCore/src/Controller/PluginController.php index 8e542e3..cb35475 100644 --- a/hesabixCore/src/Controller/PluginController.php +++ b/hesabixCore/src/Controller/PluginController.php @@ -21,7 +21,7 @@ use OpenApi\Annotations as OA; class PluginController extends AbstractController { - private const PRICE_MULTIPLIER = 1.09; // ضریب قیمت به صورت ثابت برای محاسبه + private const PRICE_MULTIPLIER = 10.1; // ضریب قیمت به صورت ثابت برای محاسبه /** * بررسی دسترسی کاربر با نقش مشخص @@ -117,7 +117,7 @@ class PluginController extends AbstractController $result = $payMGR->createRequest( $plugin->getPrice(), $this->generateUrl('api_plugin_buy_verify', ['id' => $plugin->getId()], UrlGeneratorInterface::ABSOLUTE_URL), - 'خرید فضای ابری' + 'خرید ' . $pp->getName() ); if ($result['Success'] ?? false) { @@ -149,7 +149,7 @@ class PluginController extends AbstractController * @OA\Response(response=404, description="افزونه یافت نشد") * ) */ - #[Route('/api/plugin/buy/verify/{id}', name: 'api_plugin_buy_verify', methods: ["POST"])] + #[Route('/api/plugin/buy/verify/{id}', name: 'api_plugin_buy_verify',requirements: ['id' => '.+'], methods: ["POST"])] public function api_plugin_buy_verify(string $id, twigFunctions $twigFunctions, PayMGR $payMGR, Request $request, EntityManagerInterface $entityManager, Log $log): Response { $req = $entityManager->getRepository(Plugin::class)->find($id) diff --git a/hesabixCore/src/Repository/HesabdariRowRepository.php b/hesabixCore/src/Repository/HesabdariRowRepository.php index 22d591c..8c23356 100644 --- a/hesabixCore/src/Repository/HesabdariRowRepository.php +++ b/hesabixCore/src/Repository/HesabdariRowRepository.php @@ -2,21 +2,11 @@ namespace App\Repository; -use App\Entity\Commodity; use App\Entity\HesabdariRow; use App\Entity\Money; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\ORM\NonUniqueResultException; use Doctrine\Persistence\ManagerRegistry; -/** - * @extends ServiceEntityRepository - * - * @method HesabdariRow|null find($id, $lockMode = null, $lockVersion = null) - * @method HesabdariRow|null findOneBy(array $criteria, array $orderBy = null) - * @method HesabdariRow[] findAll() - * @method HesabdariRow[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) - */ class HesabdariRowRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) @@ -27,7 +17,6 @@ class HesabdariRowRepository extends ServiceEntityRepository public function save(HesabdariRow $entity, bool $flush = false): void { $this->getEntityManager()->persist($entity); - if ($flush) { $this->getEntityManager()->flush(); } @@ -36,15 +25,42 @@ class HesabdariRowRepository extends ServiceEntityRepository public function remove(HesabdariRow $entity, bool $flush = false): void { $this->getEntityManager()->remove($entity); - if ($flush) { $this->getEntityManager()->flush(); } } - /** - * @throws NonUniqueResultException + * پیدا کردن ردیف‌ها با جوین روی سند و فیلتر پول، با حذف تکرارها + */ + public function findByJoinMoney(array $params, Money $money): array + { + $query = $this->createQueryBuilder('t') + ->select('DISTINCT t') // حذف تکرارها با DISTINCT + ->innerJoin('t.doc', 'd') + ->where('d.money = :money') + ->andWhere('t.bid = :bid OR t.bid IS NULL') // فقط کسب‌وکار فعلی یا عمومی + ->setParameter('money', $money) + ->setParameter('bid', $params['bid']); // bid از پارامترها دریافت می‌شود + + unset($params['bid']); // حذف bid از params برای جلوگیری از تداخل + + foreach ($params as $key => $value) { + if (is_array($value)) { + $temp = array_map(fn($v) => is_object($v) ? $v->getId() : $v, $value); + $query->andWhere('t.' . $key . ' IN (:' . $key . ')') + ->setParameter($key, $temp); + } else { + $query->andWhere('t.' . $key . ' = :' . $key) + ->setParameter($key, $value); + } + } + + return $query->getQuery()->getResult(); + } + + /** + * @throws \Doctrine\ORM\NonUniqueResultException */ public function getNotEqual($doc, $notField): ?HesabdariRow { @@ -56,28 +72,4 @@ class HesabdariRowRepository extends ServiceEntityRepository ->getQuery() ->getOneOrNullResult(); } - - public function findByJoinMoney(array $params, Money $money): array - { - $query = $this->createQueryBuilder('t') - ->select('t') - ->innerJoin('t.doc', 'd') - ->where('d.money = :money') - ->setParameter('money', $money); - foreach ($params as $key => $value) { - if (is_array($value)) { - $temp = []; - foreach ($value as $k => $v) { - $temp[] = $v->getId(); - } - $query->andWhere('t.' . $key . ' IN (:' . $key . ')'); - $query->setParameter($key, $temp); - } else { - $query->andWhere('t.' . $key . '= :' . $key); - $query->setParameter($key, $value); - } - - } - return $query->getQuery()->getResult(); - } -} +} \ No newline at end of file diff --git a/hesabixCore/src/Repository/HesabdariTableRepository.php b/hesabixCore/src/Repository/HesabdariTableRepository.php index 96bba8b..aaf525a 100644 --- a/hesabixCore/src/Repository/HesabdariTableRepository.php +++ b/hesabixCore/src/Repository/HesabdariTableRepository.php @@ -54,13 +54,17 @@ class HesabdariTableRepository extends ServiceEntityRepository // ; // } - public function findNode($value, $bid): ?HesabdariTable + /** + * پیدا کردن یک نود با فیلتر bid + */ + public function findNode($nodeId, $bid): ?HesabdariTable { - return $this->createQueryBuilder('h') - ->Where('h.id = :val AND (h.bid= :bid OR h.bid IS NULL)') - ->setParameters(['val' => $value, 'bid' => $bid]) + return $this->createQueryBuilder('ht') + ->where('ht.id = :nodeId') + ->andWhere('ht.bid = :bid OR ht.bid IS NULL') // فقط کسب‌وکار فعلی یا عمومی + ->setParameter('nodeId', $nodeId) + ->setParameter('bid', $bid) ->getQuery() - ->getOneOrNullResult() - ; + ->getOneOrNullResult(); } }