diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php index f73b042..b04d04d 100644 --- a/hesabixCore/src/Controller/BusinessController.php +++ b/hesabixCore/src/Controller/BusinessController.php @@ -543,6 +543,7 @@ class BusinessController extends AbstractController 'plugAccproPresell' => true, 'plugRepservice' => true, 'plugHrmDocs' => true, + 'plugGhestaManager' => true, ]; } elseif ($perm) { $result = [ @@ -585,6 +586,7 @@ class BusinessController extends AbstractController 'plugRepservice' => $perm->isPlugRepservice(), 'plugAccproPresell' => $perm->isPlugAccproPresell(), 'plugHrmDocs' => $perm->isPlugHrmDocs(), + 'plugGhestaManager' => $perm->isPlugGhestaManager(), ]; } return $this->json($result); @@ -653,6 +655,7 @@ class BusinessController extends AbstractController $perm->setPlugAccproAccounting($params['plugAccproAccounting']); $perm->setPlugRepservice($params['plugRepservice']); $perm->setPlugHrmDocs($params['plugHrmDocs']); + $perm->setPlugGhestaManager($params['plugGhestaManager']); $entityManager->persist($perm); $entityManager->flush(); $log->insert('تنظیمات پایه', 'ویرایش دسترسی‌های کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business); diff --git a/hesabixCore/src/Controller/Componenets/DocsearchController.php b/hesabixCore/src/Controller/Componenets/DocsearchController.php new file mode 100644 index 0000000..f3a78b9 --- /dev/null +++ b/hesabixCore/src/Controller/Componenets/DocsearchController.php @@ -0,0 +1,230 @@ +getContent()) { + $params = json_decode($content, true); + } + + // بررسی دسترسی کاربر + $acc = $access->hasRole('join'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + // ایجاد کوئری بیس + $qb = $entityManager->createQueryBuilder(); + $qb->select('d') + ->addSelect('p.name as personName') + ->addSelect('p.nikename as personNikename') + ->from(HesabdariDoc::class, 'd') + ->leftJoin('d.hesabdariRows', 'r') + ->leftJoin('r.person', 'p') + ->where('d.bid = :bid') + ->andWhere('d.year = :year') + ->andWhere('d.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + // اعمال فیلترهای جستجو + if (isset($params['search']) && !empty($params['search'])) { + $search = $params['search']; + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->like('d.code', ':search'), + $qb->expr()->like('d.des', ':search'), + $qb->expr()->like('p.name', ':search'), + $qb->expr()->like('p.nikename', ':search') + ) + ) + ->setParameter('search', '%' . $search . '%'); + } + + // فیلتر بر اساس نوع سند + if (isset($params['docType']) && !empty($params['docType'])) { + $qb->andWhere('d.type = :type') + ->setParameter('type', $params['docType']); + } + + // فیلتر بر اساس تاریخ + if (isset($params['dateFrom']) && !empty($params['dateFrom'])) { + $qb->andWhere('d.date >= :dateFrom') + ->setParameter('dateFrom', $params['dateFrom']); + } + if (isset($params['dateTo']) && !empty($params['dateTo'])) { + $qb->andWhere('d.date <= :dateTo') + ->setParameter('dateTo', $params['dateTo']); + } + + // مرتب‌سازی + $qb->orderBy('d.code', 'DESC'); + + // صفحه‌بندی + $page = isset($params['page']) ? (int)$params['page'] : 1; + $itemsPerPage = isset($params['itemsPerPage']) ? (int)$params['itemsPerPage'] : 10; + $qb->setFirstResult(($page - 1) * $itemsPerPage) + ->setMaxResults($itemsPerPage); + + // اجرای کوئری + $results = $qb->getQuery()->getResult(); + + // آماده‌سازی نتایج + $formattedResults = []; + foreach ($results as $result) { + $doc = $result[0]; // اولین عنصر آرایه، شیء HesabdariDoc است + $temp = [ + 'id' => $doc->getId(), + 'code' => $doc->getCode(), + 'date' => $doc->getDate(), + 'dateSubmit' => $doc->getDateSubmit(), + 'type' => $doc->getType(), + 'des' => $doc->getDes(), + 'amount' => $doc->getAmount(), + 'submitter' => $doc->getSubmitter()->getFullName(), + 'status' => 'بدون تراکنش دریافت/پرداخت', + 'personName' => $result['personName'] ?? null, + 'personNikename' => $result['personNikename'] ?? null, + 'relatedDocs' => [] + ]; + + // محاسبه وضعیت تسویه و اضافه کردن اسناد مرتبط + $pays = 0; + foreach ($doc->getRelatedDocs() as $relatedDoc) { + $pays += $relatedDoc->getAmount(); + $temp['relatedDocs'][] = [ + 'id' => $relatedDoc->getId(), + 'code' => $relatedDoc->getCode(), + 'date' => $relatedDoc->getDate(), + 'amount' => $relatedDoc->getAmount(), + 'type' => $relatedDoc->getType() + ]; + } + + if ($pays > 0) { + if ($doc->getAmount() <= $pays) { + $temp['status'] = 'تسویه شده'; + } else { + $temp['status'] = 'تسویه نشده'; + } + } + + // اضافه کردن اطلاعات مشتری یا کالا + $mainRow = $entityManager->getRepository(HesabdariRow::class)->getNotEqual($doc, 'person'); + if ($mainRow && $mainRow->getPerson()) { + $temp['person'] = Explore::ExplorePerson($mainRow->getPerson()); + } + + // اضافه کردن برچسب فاکتور + if ($doc->getInvoiceLabel()) { + $temp['label'] = [ + 'code' => $doc->getInvoiceLabel()->getCode(), + 'label' => $doc->getInvoiceLabel()->getLabel() + ]; + } + + $formattedResults[] = $temp; + } + + // محاسبه تعداد کل نتایج + $countQb = clone $qb; + $countQb->select('COUNT(d.id)'); + $totalItems = $countQb->getQuery()->getSingleScalarResult(); + + return $this->json([ + 'items' => $formattedResults, + 'total' => $totalItems, + 'page' => $page, + 'itemsPerPage' => $itemsPerPage + ]); + } + + #[Route('/api/componenets/doc/get/{code}', name: 'app_componenets_doc_get', methods: ['GET'])] + public function getDoc(string $code, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + // بررسی دسترسی کاربر + $acc = $access->hasRole('join'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + // دریافت سند + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'code' => $code, + 'bid' => $acc['bid'], + 'year' => $acc['year'], + 'money' => $acc['money'] + ]); + + if (!$doc) { + throw $this->createNotFoundException('سند مورد نظر یافت نشد'); + } + + // آماده‌سازی اطلاعات سند + $result = [ + 'id' => $doc->getId(), + 'code' => $doc->getCode(), + 'date' => $doc->getDate(), + 'dateSubmit' => $doc->getDateSubmit(), + 'type' => $doc->getType(), + 'des' => $doc->getDes(), + 'amount' => $doc->getAmount(), + 'submitter' => $doc->getSubmitter()->getFullName(), + 'status' => 'بدون تراکنش دریافت/پرداخت', + 'relatedDocs' => [] + ]; + + // محاسبه وضعیت تسویه و اضافه کردن اسناد مرتبط + $pays = 0; + foreach ($doc->getRelatedDocs() as $relatedDoc) { + $pays += $relatedDoc->getAmount(); + $result['relatedDocs'][] = [ + 'id' => $relatedDoc->getId(), + 'code' => $relatedDoc->getCode(), + 'date' => $relatedDoc->getDate(), + 'amount' => $relatedDoc->getAmount(), + 'type' => $relatedDoc->getType() + ]; + } + + if ($pays > 0) { + if ($doc->getAmount() <= $pays) { + $result['status'] = 'تسویه شده'; + } else { + $result['status'] = 'تسویه نشده'; + } + } + + // اضافه کردن اطلاعات مشتری یا کالا + $mainRow = $entityManager->getRepository(HesabdariRow::class)->getNotEqual($doc, 'person'); + if ($mainRow && $mainRow->getPerson()) { + $result['person'] = Explore::ExplorePerson($mainRow->getPerson()); + } + + // اضافه کردن برچسب فاکتور + if ($doc->getInvoiceLabel()) { + $result['label'] = [ + 'code' => $doc->getInvoiceLabel()->getCode(), + 'label' => $doc->getInvoiceLabel()->getLabel() + ]; + } + + return $this->json($result); + } +} diff --git a/hesabixCore/src/Controller/IncomeController.php b/hesabixCore/src/Controller/IncomeController.php index ea5427f..33a2e04 100644 --- a/hesabixCore/src/Controller/IncomeController.php +++ b/hesabixCore/src/Controller/IncomeController.php @@ -9,7 +9,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use App\Service\Provider; +use App\Entity\HesabdariDoc; +use App\Entity\HesabdariRow; +use App\Entity\HesabdariTable; +use App\Entity\BankAccount; +use App\Entity\Cashdesk; +use App\Entity\Salary; +use App\Entity\Person; +use App\Service\Log; use Doctrine\Common\Collections\ArrayCollection; +use App\Repository\HesabdariTableRepository; class IncomeController extends AbstractController { @@ -160,4 +171,476 @@ class IncomeController extends AbstractController 'series' => $series, ]); } + + #[Route('/api/income/list/search', name: 'app_income_list_search', methods: ['POST'])] + public function searchIncomeList( + Request $request, + Access $access, + EntityManagerInterface $entityManager, + HesabdariTableRepository $hesabdariTableRepository, + Jdate $jdate + ): JsonResponse { + $acc = $access->hasRole('income'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + + // Input parameters + $filters = $params['filters'] ?? []; + $pagination = $params['pagination'] ?? ['page' => 1, 'limit' => 10]; + $sort = $params['sort'] ?? ['sortBy' => 'id', 'sortDesc' => true]; + $type = $params['type'] ?? 'income'; + + // Set pagination parameters + $page = max(1, $pagination['page'] ?? 1); + $limit = max(1, min(100, $pagination['limit'] ?? 10)); + + // Build base query + $queryBuilder = $entityManager->createQueryBuilder() + ->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount') + ->addSelect('u.fullName as submitter') + ->from('App\Entity\HesabdariDoc', 'd') + ->leftJoin('d.submitter', 'u') + ->leftJoin('d.hesabdariRows', 'r') + ->leftJoin('r.ref', 't') + ->where('d.bid = :bid') + ->andWhere('d.year = :year') + ->andWhere('d.type = :type') + ->andWhere('d.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('year', $acc['year']) + ->setParameter('type', $type) + ->setParameter('money', $acc['money']); + + // Apply filters + if (!empty($filters)) { + // Text search + if (isset($filters['search'])) { + $searchValue = is_array($filters['search']) ? $filters['search']['value'] : $filters['search']; + $queryBuilder->leftJoin('r.person', 'p') + ->andWhere( + $queryBuilder->expr()->orX( + 'd.code LIKE :search', + 'd.des LIKE :search', + 'd.date LIKE :search', + 'd.amount LIKE :search', + 'p.nikename LIKE :search', + 't.name LIKE :search', + 't.code LIKE :search' + ) + ) + ->setParameter('search', "%{$searchValue}%"); + } + + // Income center filter + if (isset($filters['account']) && $filters['account'] !== '66') { + $accountCodes = $hesabdariTableRepository->findAllSubAccountCodes($filters['account'], $acc['bid']->getId()); + if (!empty($accountCodes)) { + $queryBuilder->andWhere('t.code IN (:accountCodes)') + ->setParameter('accountCodes', $accountCodes); + } else { + $queryBuilder->andWhere('1 = 0'); + } + } + + // Time filter + if (isset($filters['timeFilter'])) { + $today = $jdate->jdate('Y/m/d', time()); + switch ($filters['timeFilter']) { + case 'today': + $queryBuilder->andWhere('d.date = :today') + ->setParameter('today', $today); + break; + case 'week': + $weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days')); + $queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today') + ->setParameter('weekStart', $weekStart) + ->setParameter('today', $today); + break; + case 'month': + $monthStart = $jdate->jdate('Y/m/01', time()); + $queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today') + ->setParameter('monthStart', $monthStart) + ->setParameter('today', $today); + break; + case 'custom': + if (isset($filters['date']) && isset($filters['date']['from']) && isset($filters['date']['to'])) { + $queryBuilder->andWhere('d.date BETWEEN :dateFrom AND :dateTo') + ->setParameter('dateFrom', $filters['date']['from']) + ->setParameter('dateTo', $filters['date']['to']); + } + break; + } + } + + // Amount filter + if (isset($filters['amount'])) { + $queryBuilder->andWhere('d.amount = :amount') + ->setParameter('amount', $filters['amount']); + } + } + + // Apply sorting + $sortField = is_array($sort['sortBy']) ? ($sort['sortBy']['key'] ?? 'id') : ($sort['sortBy'] ?? 'id'); + $sortDirection = ($sort['sortDesc'] ?? true) ? 'DESC' : 'ASC'; + $queryBuilder->orderBy("d.$sortField", $sortDirection); + + // Calculate total items + $totalItemsQuery = clone $queryBuilder; + $totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)') + ->getQuery() + ->getSingleScalarResult(); + + // Apply pagination + $queryBuilder->setFirstResult(($page - 1) * $limit) + ->setMaxResults($limit); + + $docs = $queryBuilder->getQuery()->getArrayResult(); + + $dataTemp = []; + foreach ($docs as $doc) { + $item = [ + 'id' => $doc['id'], + 'dateSubmit' => $doc['dateSubmit'], + 'date' => $doc['date'], + 'type' => $doc['type'], + 'code' => $doc['code'], + 'des' => $doc['des'], + 'amount' => $doc['amount'], + 'submitter' => $doc['submitter'], + ]; + + // Get income center details + $incomeDetails = $entityManager->createQueryBuilder() + ->select('t.name as center_name, t.code as center_code, r.bs as amount, r.des as des') + ->from('App\Entity\HesabdariRow', 'r') + ->join('r.ref', 't') + ->where('r.doc = :docId') + ->andWhere('r.bs != 0') + ->setParameter('docId', $doc['id']) + ->getQuery() + ->getResult(); + + $item['incomeCenters'] = array_map(function ($detail) { + return [ + 'name' => $detail['center_name'], + 'code' => $detail['center_code'], + 'amount' => (int) $detail['amount'], + 'des' => $detail['des'], + ]; + }, $incomeDetails); + + // Get related person info + $personInfo = $entityManager->createQueryBuilder() + ->select('p.id, p.nikename, p.code') + ->from('App\Entity\HesabdariRow', 'r') + ->join('r.person', 'p') + ->where('r.doc = :docId') + ->andWhere('r.person IS NOT NULL') + ->setParameter('docId', $doc['id']) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + + $item['person'] = $personInfo ? [ + 'id' => $personInfo['id'], + 'nikename' => $personInfo['nikename'], + 'code' => $personInfo['code'], + ] : null; + + $dataTemp[] = $item; + } + + return $this->json([ + 'items' => $dataTemp, + 'total' => (int) $totalItems, + 'page' => $page, + 'limit' => $limit, + ]); + } + + #[Route('/api/incomes/list/print', name: 'app_incomes_list_print')] + public function app_incomes_list_print( + Provider $provider, + Request $request, + Access $access, + EntityManagerInterface $entityManager + ): JsonResponse { + $acc = $access->hasRole('income'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + + // دریافت آیتم‌های انتخاب شده یا همه آیتم‌ها + if (!isset($params['items'])) { + $items = $entityManager->getRepository(HesabdariDoc::class)->findBy([ + 'bid' => $acc['bid'], + 'type' => 'income', + 'year' => $acc['year'], + 'money' => $acc['money'] + ]); + } else { + $items = []; + foreach ($params['items'] as $param) { + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $param['id'], + 'bid' => $acc['bid'], + 'type' => 'income', + 'year' => $acc['year'], + 'money' => $acc['money'] + ]); + if ($doc) { + $items[] = $doc; + } + } + } + + $pid = $provider->createPrint( + $acc['bid'], + $this->getUser(), + $this->renderView('pdf/incomes.html.twig', [ + 'page_title' => 'فهرست درآمدها', + 'bid' => $acc['bid'], + 'items' => $items + ]) + ); + + return $this->json(['id' => $pid]); + } + + #[Route('/api/incomes/list/excel', name: 'app_incomes_list_excel')] + public function app_incomes_list_excel( + Provider $provider, + Request $request, + Access $access, + EntityManagerInterface $entityManager + ): BinaryFileResponse { + $acc = $access->hasRole('income'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + + // دریافت آیتم‌های انتخاب شده یا همه آیتم‌ها + if (!isset($params['items'])) { + $items = $entityManager->getRepository(HesabdariDoc::class)->findBy([ + 'bid' => $acc['bid'], + 'type' => 'income', + 'year' => $acc['year'], + 'money' => $acc['money'] + ]); + } else { + $items = []; + foreach ($params['items'] as $param) { + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $param['id'], + 'bid' => $acc['bid'], + 'type' => 'income', + 'year' => $acc['year'], + 'money' => $acc['money'] + ]); + if ($doc) { + $items[] = $doc; + } + } + } + + // ایجاد فایل اکسل + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setRightToLeft(true); + + // تنظیم هدرها + $sheet->setCellValue('A1', 'ردیف') + ->setCellValue('B1', 'شماره سند') + ->setCellValue('C1', 'تاریخ') + ->setCellValue('D1', 'شرح') + ->setCellValue('E1', 'مرکز درآمد') + ->setCellValue('F1', 'مرکز دریافت') + ->setCellValue('G1', 'مبلغ (ریال)'); + + // پر کردن داده‌ها + $rowNumber = 2; + foreach ($items as $index => $item) { + // محاسبه مراکز درآمد + $incomeCenters = []; + foreach ($item->getHesabdariRows() as $row) { + if ($row->getRef()) { + $incomeCenters[] = $row->getRef()->getName(); + } + } + $incomeCenterNames = implode('، ', array_unique($incomeCenters)); + + // محاسبه مرکز دریافت + $receiveCenter = null; + foreach ($item->getHesabdariRows() as $row) { + if (!$receiveCenter) { + if ($row->getBank()) { + $receiveCenter = $row->getBank()->getName(); + } elseif ($row->getCashdesk()) { + $receiveCenter = $row->getCashdesk()->getName(); + } elseif ($row->getSalary()) { + $receiveCenter = $row->getSalary()->getName(); + } elseif ($row->getPerson()) { + $receiveCenter = $row->getPerson()->getNikename(); + } + } + } + + $sheet->setCellValue('A' . $rowNumber, $index + 1) + ->setCellValue('B' . $rowNumber, $item->getCode()) + ->setCellValue('C' . $rowNumber, $item->getDate()) + ->setCellValue('D' . $rowNumber, $item->getDes()) + ->setCellValue('E' . $rowNumber, $incomeCenterNames) + ->setCellValue('F' . $rowNumber, $receiveCenter) + ->setCellValue('G' . $rowNumber, number_format($item->getAmount())); + $rowNumber++; + } + + // ذخیره فایل اکسل + $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); + $filePath = __DIR__ . '/../../var/' . uniqid() . '.xlsx'; + $writer->save($filePath); + + return new BinaryFileResponse($filePath); + } + + #[Route('/api/income/doc/insert', name: 'app_income_doc_insert', methods: ['POST'])] + public function insertIncomeDoc( + Request $request, + Access $access, + EntityManagerInterface $entityManager, + Provider $provider, + Log $log, + Jdate $jdate + ): JsonResponse { + $acc = $access->hasRole('income'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + + // بررسی پارامترهای ضروری + if (!isset($params['rows']) || count($params['rows']) < 2) { + return $this->json(['result' => 0, 'message' => 'حداقل دو ردیف برای سند درآمد الزامی است'], 400); + } + + if (!isset($params['date']) || !isset($params['des'])) { + return $this->json(['result' => 0, 'message' => 'تاریخ و شرح سند الزامی است'], 400); + } + + // تنظیم نوع سند به income + $params['type'] = 'income'; + + // بررسی وجود سند برای ویرایش + if (isset($params['update']) && $params['update'] != '') { + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'bid' => $acc['bid'], + 'year' => $acc['year'], + 'code' => $params['update'], + 'money' => $acc['money'] + ]); + if (!$doc) { + return $this->json(['result' => 0, 'message' => 'سند مورد نظر یافت نشد'], 404); + } + } + + // ایجاد سند جدید + $doc = new HesabdariDoc(); + $doc->setBid($acc['bid']); + $doc->setYear($acc['year']); + $doc->setDes($params['des']); + $doc->setDateSubmit(time()); + $doc->setType('income'); + $doc->setDate($params['date']); + $doc->setSubmitter($this->getUser()); + $doc->setMoney($acc['money']); + $doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting')); + + $entityManager->persist($doc); + $entityManager->flush(); + + // پردازش ردیف‌های سند + $amount = 0; + foreach ($params['rows'] as $row) { + $row['bs'] = str_replace(',', '', $row['bs']); + $row['bd'] = str_replace(',', '', $row['bd']); + + $hesabdariRow = new HesabdariRow(); + $hesabdariRow->setBid($acc['bid']); + $hesabdariRow->setYear($acc['year']); + $hesabdariRow->setDoc($doc); + $hesabdariRow->setBs($row['bs']); + $hesabdariRow->setBd($row['bd']); + + // تنظیم مرکز درآمد + $ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => $row['table'] + ]); + $hesabdariRow->setRef($ref); + + // تنظیم مرکز دریافت (بانک، صندوق، تنخواه، شخص) + if ($row['type'] == 'bank') { + $bank = $entityManager->getRepository(BankAccount::class)->findOneBy([ + 'id' => $row['id'], + 'bid' => $acc['bid'] + ]); + if (!$bank) { + return $this->json(['result' => 0, 'message' => 'حساب بانکی مورد نظر یافت نشد'], 404); + } + $hesabdariRow->setBank($bank); + } elseif ($row['type'] == 'cashdesk') { + $cashdesk = $entityManager->getRepository(Cashdesk::class)->find($row['id']); + if (!$cashdesk) { + return $this->json(['result' => 0, 'message' => 'صندوق مورد نظر یافت نشد'], 404); + } + $hesabdariRow->setCashdesk($cashdesk); + } elseif ($row['type'] == 'salary') { + $salary = $entityManager->getRepository(Salary::class)->find($row['id']); + if (!$salary) { + return $this->json(['result' => 0, 'message' => 'تنخواه مورد نظر یافت نشد'], 404); + } + $hesabdariRow->setSalary($salary); + } elseif ($row['type'] == 'person') { + $person = $entityManager->getRepository(Person::class)->findOneBy([ + 'id' => $row['id'], + 'bid' => $acc['bid'] + ]); + if (!$person) { + return $this->json(['result' => 0, 'message' => 'شخص مورد نظر یافت نشد'], 404); + } + $hesabdariRow->setPerson($person); + } + + if (isset($row['des'])) { + $hesabdariRow->setDes($row['des']); + } + + $entityManager->persist($hesabdariRow); + $amount += $row['bs']; + } + + $doc->setAmount($amount); + $entityManager->persist($doc); + $entityManager->flush(); + + $log->insert( + 'حسابداری', + 'سند درآمد شماره ' . $doc->getCode() . ' ثبت شد.', + $this->getUser(), + $acc['bid'], + $doc + ); + + return $this->json([ + 'result' => 1, + 'doc' => $provider->Entity2Array($doc, 0) + ]); + } } \ No newline at end of file diff --git a/hesabixCore/src/Controller/PersonsController.php b/hesabixCore/src/Controller/PersonsController.php index 05a353d..c4b1437 100644 --- a/hesabixCore/src/Controller/PersonsController.php +++ b/hesabixCore/src/Controller/PersonsController.php @@ -1280,119 +1280,267 @@ class PersonsController extends AbstractController $acc = $access->hasRole('getpay'); if (!$acc) throw $this->createAccessDeniedException(); + $params = []; if ($content = $request->getContent()) { $params = json_decode($content, true); } - if (!array_key_exists('items', $params)) { - $items = $entityManager->getRepository(HesabdariDoc::class)->findBy([ - 'bid' => $acc['bid'], - 'type' => 'person_send', - 'year' => $acc['year'], - 'money' => $acc['money'] - ]); - } else { - $items = []; - foreach ($params['items'] as $param) { - $prs = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ - 'id' => $param['id'], - 'bid' => $acc['bid'], - 'type' => 'person_send', - 'year' => $acc['year'], - 'money' => $acc['money'] - ]); - if ($prs) - $items[] = $prs; - } + + $queryBuilder = $entityManager->getRepository(HesabdariDoc::class)->createQueryBuilder('d') + ->where('d.bid = :bid') + ->andWhere('d.type = :type') + ->andWhere('d.year = :year') + ->andWhere('d.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('type', 'person_send') + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']); + + // اگر آیتم‌های خاصی درخواست شده‌اند + if (array_key_exists('items', $params)) { + $ids = array_map(function($item) { return $item['id']; }, $params['items']); + $queryBuilder->andWhere('d.id IN (:ids)') + ->setParameter('ids', $ids); } + + // دریافت تعداد کل رکوردها + $totalItems = $queryBuilder->select('COUNT(d.id)') + ->getQuery() + ->getSingleScalarResult(); + + // اگر درخواست با صفحه‌بندی است + if (array_key_exists('page', $params) && array_key_exists('limit', $params)) { + $page = $params['page']; + $limit = $params['limit']; + $offset = ($page - 1) * $limit; + + $items = $queryBuilder->select('d') + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } else { + // دریافت همه آیتم‌ها بدون صفحه‌بندی + $items = $queryBuilder->select('d') + ->getQuery() + ->getResult(); + } + + // اضافه کردن اطلاعات اشخاص به هر آیتم + foreach ($items as $item) { + $personNames = []; + foreach ($item->getHesabdariRows() as $row) { + if ($row->getPerson()) { + $personNames[] = $row->getPerson()->getNikename(); + } + } + $item->personNames = implode('، ', array_unique($personNames)); + } + $pid = $provider->createPrint( $acc['bid'], $this->getUser(), - $this->renderView('pdf/persons_receive.html.twig', [ + $this->renderView('pdf/persons_send.html.twig', [ 'page_title' => 'لیست پرداخت‌ها', 'bid' => $acc['bid'], - 'items' => $items + 'items' => $items, + 'totalItems' => $totalItems, + 'currentPage' => $params['page'] ?? 1, + 'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1 ]) ); - return $this->json(['id' => $pid]); + + return $this->json([ + 'id' => $pid, + 'totalItems' => $totalItems, + 'currentPage' => $params['page'] ?? 1, + 'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1 + ]); } - #[Route('/api/person/send/list/search', name: 'app_persons_send_list_search')] - public function app_persons_send_list_search(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse - { + #[Route('/api/person/send/list/search', name: 'app_persons_send_list_search', methods: ['POST'])] + public function app_persons_send_list_search( + Request $request, + Access $access, + EntityManagerInterface $entityManager, + Jdate $jdate + ): JsonResponse { $acc = $access->hasRole('getpay'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); } - $items = $entityManager->getRepository(HesabdariDoc::class)->findBy( - [ - 'bid' => $acc['bid'], - 'type' => 'person_send', - 'year' => $acc['year'], - 'money' => $acc['money'] - ], - ['id' => 'DESC'] - ); - $res = []; - foreach ($items as $item) { - $temp = [ - 'id' => $item->getId(), - 'date' => $item->getDate(), - 'code' => $item->getCode(), - 'des' => $item->getDes(), - 'amount' => $item->getAmount() - ]; - $persons = []; - foreach ($item->getHesabdariRows() as $row) { - if ($row->getPerson()) { - $persons[] = Explore::ExplorePerson($row->getPerson()); + // دریافت پارامترها + $params = json_decode($request->getContent(), true) ?? []; + $page = (int) ($params['page'] ?? 1); + $itemsPerPage = (int) ($params['itemsPerPage'] ?? 10); + $search = $params['search'] ?? ''; + $dateFilter = $params['dateFilter'] ?? 'all'; + + // کوئری پایه برای اسناد + $queryBuilder = $entityManager->getRepository(HesabdariDoc::class) + ->createQueryBuilder('d') + ->select('DISTINCT d.id, d.date, d.code, d.des, d.amount') + ->leftJoin('d.hesabdariRows', 'hr') + ->leftJoin('hr.person', 'p') + ->where('d.bid = :bid') + ->andWhere('d.type = :type') + ->andWhere('d.year = :year') + ->andWhere('d.money = :money') + ->setParameter('bid', $acc['bid']) + ->setParameter('type', 'person_send') + ->setParameter('year', $acc['year']) + ->setParameter('money', $acc['money']) + ->orderBy('d.id', 'DESC'); + + // جست‌وجو + if (!empty($search)) { + $queryBuilder->andWhere( + $queryBuilder->expr()->orX( + 'd.code LIKE :search', + 'd.des LIKE :search', + 'p.nikename LIKE :search' + ) + )->setParameter('search', "%$search%"); + } + + // فیلتر تاریخ + $today = $jdate->GetTodayDate(); + switch ($dateFilter) { + case 'today': + $queryBuilder->andWhere('d.date = :today') + ->setParameter('today', $today); + break; + case 'thisWeek': + $dayOfWeek = (int) $jdate->jdate('w', time()); + $startOfWeek = $jdate->shamsiDate(0, 0, -$dayOfWeek); + $endOfWeek = $jdate->shamsiDate(0, 0, 6 - $dayOfWeek); + $queryBuilder->andWhere('d.date BETWEEN :start AND :end') + ->setParameter('start', $startOfWeek) + ->setParameter('end', $endOfWeek); + break; + case 'thisMonth': + $currentYear = (int) $jdate->jdate('Y', time()); + $currentMonth = (int) $jdate->jdate('n', time()); + $daysInMonth = (int) $jdate->jdate('t', time()); + $startOfMonth = sprintf('%d/%02d/01', $currentYear, $currentMonth); + $endOfMonth = sprintf('%d/%02d/%02d', $currentYear, $currentMonth, $daysInMonth); + $queryBuilder->andWhere('d.date BETWEEN :start AND :end') + ->setParameter('start', $startOfMonth) + ->setParameter('end', $endOfMonth); + break; + case 'all': + default: + break; + } + + // محاسبه تعداد کل + $totalQuery = (clone $queryBuilder) + ->select('COUNT(DISTINCT d.id) as total') + ->getQuery() + ->getSingleResult(); + $total = (int) $totalQuery['total']; + + // گرفتن اسناد با صفحه‌بندی + $docs = $queryBuilder + ->setFirstResult(($page - 1) * $itemsPerPage) + ->setMaxResults($itemsPerPage) + ->getQuery() + ->getArrayResult(); + + // گرفتن اشخاص مرتبط + $docIds = array_column($docs, 'id'); + $persons = []; + if (!empty($docIds)) { + $personQuery = $entityManager->createQueryBuilder() + ->select('IDENTITY(hr.doc) as doc_id, p.code as person_code, p.nikename as person_nikename') + ->from('App\Entity\HesabdariRow', 'hr') + ->leftJoin('hr.person', 'p') + ->where('hr.doc IN (:docIds)') + ->setParameter('docIds', $docIds) + ->getQuery() + ->getArrayResult(); + + foreach ($personQuery as $row) { + if (!empty($row['person_code'])) { + $persons[$row['doc_id']][] = [ + 'code' => $row['person_code'], + 'nikename' => $row['person_nikename'], + ]; } } - $temp['persons'] = $persons; - $res[] = $temp; + } + + // ساختاردهی خروجی + $items = []; + foreach ($docs as $doc) { + $items[] = [ + 'id' => $doc['id'], + 'date' => $doc['date'], + 'code' => $doc['code'], + 'des' => $doc['des'], + 'amount' => $doc['amount'], + 'persons' => $persons[$doc['id']] ?? [], + ]; } - return $this->json($res); + return $this->json([ + 'items' => $items, + 'total' => $total, + ]); } /** * @throws Exception */ - #[Route('/api/person/send/list/excel', name: 'app_persons_send_list_excel')] - public function app_persons_send_list_excel(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): BinaryFileResponse|JsonResponse|StreamedResponse - { + #[Route('/api/person/send/list/excel', name: 'app_persons_send_list_excel', methods: ['POST'])] + public function app_persons_send_list_excel( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager + ): BinaryFileResponse { $acc = $access->hasRole('getpay'); - if (!$acc) + if (!$acc) { throw $this->createAccessDeniedException(); - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); } - if (!array_key_exists('items', $params)) { + + $params = json_decode($request->getContent(), true) ?? []; + if (!array_key_exists('items', $params) || empty($params['items'])) { $items = $entityManager->getRepository(HesabdariDoc::class)->findBy([ 'bid' => $acc['bid'], 'type' => 'person_send', 'year' => $acc['year'], - 'money' => $acc['money'] + 'money' => $acc['money'], ]); } else { $items = []; foreach ($params['items'] as $param) { - $prs = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + if (!is_array($param) || !isset($param['id'])) { + throw new \InvalidArgumentException('Invalid item format in request'); + } + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ 'id' => $param['id'], 'bid' => $acc['bid'], 'type' => 'person_send', 'year' => $acc['year'], - 'money' => $acc['money'] + 'money' => $acc['money'], ]); - if ($prs) - $items[] = $prs; + if ($doc) { + // اضافه کردن اطلاعات اشخاص + $personNames = []; + foreach ($doc->getHesabdariRows() as $row) { + if ($row->getPerson()) { + $personNames[] = $row->getPerson()->getNikename(); + } + } + $doc->personNames = implode('، ', array_unique($personNames)); + $items[] = $doc; + } } } + return new BinaryFileResponse($provider->createExcell($items, ['type', 'dateSubmit'])); } diff --git a/hesabixCore/src/Controller/Plugins/PlugGhestaController.php b/hesabixCore/src/Controller/Plugins/PlugGhestaController.php new file mode 100644 index 0000000..8387e52 --- /dev/null +++ b/hesabixCore/src/Controller/Plugins/PlugGhestaController.php @@ -0,0 +1,419 @@ +entityManager = $entityManager; + } + + #[Route('/api/plugins/ghesta/invoices', name: 'plugin_ghesta_invoices', methods: ['GET'])] + public function plugin_ghesta_invoices(EntityManagerInterface $entityManager, Access $access) : JsonResponse + { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + $invoices = $entityManager->getRepository(PlugGhestaDoc::class)->findBy(['bid' => $acc['bid']]); + $data = []; + foreach($invoices as $invoice){ + $data[] = [ + 'id' => $invoice->getId(), + 'code' => $invoice->getMainDoc() ? $invoice->getMainDoc()->getCode() : null, + 'dateSubmit' => $invoice->getDateSubmit(), + 'count' => $invoice->getCount(), + 'profitPercent' => $invoice->getProfitPercent(), + 'profitAmount' => $invoice->getProfitAmount(), + 'profitType' => $invoice->getProfitType(), + 'daysPay' => $invoice->getDaysPay(), + 'person' => [ + 'id' => $invoice->getPerson()->getId(), + 'name' => $invoice->getPerson()->getName(), + 'nikename' => $invoice->getPerson()->getNikename() + ] + ]; + } + return $this->json($data); + } + + #[Route('/api/plugins/ghesta/invoices/{id}', name: 'plugin_ghesta_invoice', methods: ['GET'])] + public function plugin_ghesta_invoice(EntityManagerInterface $entityManager, Access $access, $id) : JsonResponse + { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + + $invoice = $entityManager->getRepository(PlugGhestaDoc::class)->findOneBy([ + 'id' => $id, + 'bid' => $acc['bid'] + ]); + + if(!$invoice) + throw $this->createNotFoundException(); + + $data = [ + 'id' => $invoice->getId(), + 'code' => $invoice->getMainDoc() ? $invoice->getMainDoc()->getCode() : null, + 'dateSubmit' => $invoice->getDateSubmit(), + 'count' => $invoice->getCount(), + 'profitPercent' => $invoice->getProfitPercent(), + 'profitAmount' => $invoice->getProfitAmount(), + 'profitType' => $invoice->getProfitType(), + 'daysPay' => $invoice->getDaysPay(), + 'person' => [ + 'id' => $invoice->getPerson()->getId(), + 'name' => $invoice->getPerson()->getName(), + 'nikename' => $invoice->getPerson()->getNikename() + ], + 'items' => [] + ]; + + foreach($invoice->getPlugGhestaItems() as $item) { + $data['items'][] = [ + 'id' => $item->getId(), + 'date' => $item->getDate(), + 'amount' => $item->getAmount(), + 'num' => $item->getNum(), + 'hesabdariDoc' => $item->getHesabdariDoc() ? [ + 'id' => $item->getHesabdariDoc()->getId(), + 'code' => $item->getHesabdariDoc()->getCode() + ] : null + ]; + } + + return $this->json($data); + } + + #[Route('/api/plugins/ghesta/invoices/add', name: 'plugin_ghesta_invoice_add', methods: ['POST'])] + public function plugin_ghesta_invoice_add(Request $request, EntityManagerInterface $entityManager, Access $access, Provider $provider) : JsonResponse + { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + + $params = json_decode($request->getContent(), true); + if(!$params) + throw $this->createNotFoundException(); + + // دریافت سند حسابداری + $hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $params['hesabdariDocId'], + 'bid' => $acc['bid'] + ]); + if (!$hesabdariDoc) { + throw $this->createNotFoundException('HesabdariDoc not found'); + } + + // ایجاد سند اقساط + $doc = new PlugGhestaDoc(); + $doc->setBid($acc['bid']); + $doc->setSubmitter($this->getUser()); + $doc->setDateSubmit(time()); + $doc->setCount($params['count']); + $doc->setProfitPercent($params['profitPercent']); + $doc->setProfitAmount($params['profitAmount']); + $doc->setProfitType($params['profitType']); + $doc->setDaysPay(floatval($params['daysPay'])); + $doc->setMainDoc($hesabdariDoc); + + // دریافت اطلاعات شخص از فاکتور + $person = $entityManager->getRepository(Person::class)->findOneBy([ + 'id' => $params['personId'], + 'bid' => $acc['bid'] + ]); + if (!$person) { + throw $this->createNotFoundException('Person not found'); + } + $doc->setPerson($person); + + $entityManager->persist($doc); + $entityManager->flush(); + + // ایجاد اقساط + foreach($params['items'] as $item) { + $ghestaItem = new PlugGhestaItem(); + $ghestaItem->setDoc($doc); + $ghestaItem->setDate($item['date']); + $ghestaItem->setAmount($item['amount']); + $ghestaItem->setNum($item['num']); + + if(isset($item['hesabdariDocId'])) { + $hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $item['hesabdariDocId'], + 'bid' => $acc['bid'] + ]); + if($hesabdariDoc) { + $ghestaItem->setHesabdariDoc($hesabdariDoc); + } + } + + $entityManager->persist($ghestaItem); + } + + $entityManager->flush(); + + return $this->json(['result' => 1, 'id' => $doc->getId()]); + } + + #[Route('/api/plugins/ghesta/invoices/edit/{id}', name: 'plugin_ghesta_invoice_edit', methods: ['POST'])] + public function plugin_ghesta_invoice_edit(Request $request, EntityManagerInterface $entityManager, Access $access, $id) : JsonResponse + { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + + $doc = $entityManager->getRepository(PlugGhestaDoc::class)->findOneBy([ + 'id' => $id, + 'bid' => $acc['bid'] + ]); + + if(!$doc) + throw $this->createNotFoundException(); + + $params = json_decode($request->getContent(), true); + if(!$params) + throw $this->createNotFoundException(); + + // به‌روزرسانی اطلاعات سند + $doc->setCount($params['count']); + $doc->setProfitPercent($params['profitPercent']); + $doc->setProfitAmount($params['profitAmount']); + $doc->setProfitType($params['profitType']); + $doc->setDaysPay(floatval($params['daysPay'])); + + // دریافت اطلاعات شخص از فاکتور + $person = $entityManager->getRepository(Person::class)->find($params['personId']); + if (!$person) { + throw $this->createNotFoundException('Person not found'); + } + $doc->setPerson($person); + + // حذف اقساط قبلی + foreach($doc->getPlugGhestaItems() as $item) { + $entityManager->remove($item); + } + + // ایجاد اقساط جدید + foreach($params['items'] as $item) { + $ghestaItem = new PlugGhestaItem(); + $ghestaItem->setDoc($doc); + $ghestaItem->setDate($item['date']); + $ghestaItem->setAmount($item['amount']); + $ghestaItem->setNum($item['num']); + + if(isset($item['hesabdariDocId'])) { + $hesabdariDoc = $entityManager->getRepository(HesabdariDoc::class)->find($item['hesabdariDocId']); + if($hesabdariDoc) { + $ghestaItem->setHesabdariDoc($hesabdariDoc); + } + } + + $entityManager->persist($ghestaItem); + } + + $entityManager->flush(); + + return $this->json(['result' => 1]); + } + + #[Route('/api/plugins/ghesta/invoice/{id}', name: 'plugin_ghesta_invoice_delete', methods: ['DELETE'])] + public function plugin_ghesta_invoice_delete(EntityManagerInterface $entityManager, Access $access, $id) : JsonResponse + { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + + $doc = $entityManager->getRepository(PlugGhestaDoc::class)->findOneBy([ + 'id' => $id, + 'bid' => $acc['bid'] + ]); + + if(!$doc) + throw $this->createNotFoundException(); + + // حذف اقساط + foreach($doc->getPlugGhestaItems() as $item) { + $entityManager->remove($item); + } + + $entityManager->remove($doc); + $entityManager->flush(); + + return $this->json(['result' => 1]); + } + + #[Route('/api/plugins/ghesta/invoices/search', name: 'plugin_ghesta_invoice_search', methods: ['POST'])] + public function plugin_ghesta_invoice_search(Request $request, Access $access): JsonResponse + { + try { + $acc = $access->hasRole('plugGhestaManager'); + if(!$acc) + throw $this->createAccessDeniedException(); + + $params = json_decode($request->getContent(), true); + $search = $params['search'] ?? ''; + $page = (int)($params['page'] ?? 1); + $perPage = (int)($params['perPage'] ?? 10); + $dateFilter = $params['dateFilter'] ?? 'all'; + $statusFilter = $params['statusFilter'] ?? 'all'; + $sortBy = $params['sortBy'] ?? []; + + $qb = $this->entityManager->createQueryBuilder(); + $qb->select('d') + ->from(PlugGhestaDoc::class, 'd') + ->leftJoin('d.person', 'p') + ->leftJoin('d.plugGhestaItems', 'i') + ->where('d.bid = :bid') + ->setParameter('bid', $acc['bid']) + ->groupBy('d.id'); + + // اعمال فیلتر جستجو + if (!empty($search)) { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->like('d.id', ':search'), + $qb->expr()->like('p.name', ':search'), + $qb->expr()->like('p.nikename', ':search') + ) + )->setParameter('search', '%' . $search . '%'); + } + + // اعمال فیلتر تاریخ + if ($dateFilter !== 'all') { + $now = new \DateTime(); + switch ($dateFilter) { + case 'today': + $qb->andWhere('DATE(d.dateSubmit) = :today') + ->setParameter('today', $now->format('Y-m-d')); + break; + case 'week': + $qb->andWhere('d.dateSubmit >= :weekStart') + ->setParameter('weekStart', $now->modify('-7 days')->format('Y-m-d')); + break; + case 'month': + $qb->andWhere('d.dateSubmit >= :monthStart') + ->setParameter('monthStart', $now->modify('-30 days')->format('Y-m-d')); + break; + } + } + + // اعمال فیلتر وضعیت + if ($statusFilter !== 'all') { + switch ($statusFilter) { + case 'paid': + $qb->andWhere('i.hesabdariDoc IS NOT NULL'); + break; + case 'unpaid': + $qb->andWhere('i.hesabdariDoc IS NULL'); + break; + case 'partial': + $qb->andWhere('EXISTS (SELECT 1 FROM ' . PlugGhestaItem::class . ' i2 WHERE i2.doc = d.id AND i2.hesabdariDoc IS NOT NULL)') + ->andWhere('EXISTS (SELECT 1 FROM ' . PlugGhestaItem::class . ' i3 WHERE i3.doc = d.id AND i3.hesabdariDoc IS NULL)'); + break; + } + } + + // اعمال مرتب‌سازی + if (!empty($sortBy)) { + foreach ($sortBy as $sort) { + $field = $sort['key']; + $direction = $sort['order'] === 'desc' ? 'DESC' : 'ASC'; + + // تبدیل نام فیلد به نام ستون در دیتابیس + $columnMap = [ + 'id' => 'd.id', + 'code' => 'd.code', + 'dateSubmit' => 'd.dateSubmit', + 'amount' => 'd.amount', + 'profitAmount' => 'd.profitAmount', + 'profitPercent' => 'd.profitPercent', + 'count' => 'd.count', + 'profitType' => 'd.profitType' + ]; + + if (isset($columnMap[$field])) { + $qb->addOrderBy($columnMap[$field], $direction); + } + } + } else { + $qb->orderBy('d.dateSubmit', 'DESC'); + } + + // محاسبه تعداد کل رکوردها + $countQb = clone $qb; + $countQb->select('COUNT(DISTINCT d.id)'); + $total = (int)$countQb->getQuery()->getScalarResult(); + + // اگر هیچ نتیجه‌ای وجود نداشت، آرایه خالی برگردان + if ($total == 0) { + return $this->json([ + 'result' => 1, + 'items' => [], + 'total' => 0 + ]); + } + + // اعمال صفحه‌بندی + $qb->setFirstResult(($page - 1) * $perPage) + ->setMaxResults($perPage); + + $items = $qb->getQuery()->getResult(); + + // تبدیل نتایج به آرایه + $result = []; + foreach ($items as $item) { + $firstGhestaDate = null; + $ghestaItems = $item->getPlugGhestaItems(); + if (count($ghestaItems) > 0) { + $firstGhestaDate = $ghestaItems[0]->getDate(); + } + + $result[] = [ + 'id' => $item->getId(), + 'code' => $item->getMainDoc() ? $item->getMainDoc()->getCode() : null, + 'firstGhestaDate' => $firstGhestaDate, + 'amount' => $item->getProfitAmount(), // مبلغ کل شامل سود + 'profitAmount' => $item->getProfitAmount(), + 'profitPercent' => $item->getProfitPercent(), + 'profitType' => $item->getProfitType(), + 'count' => $item->getCount(), + 'person' => $item->getPerson() ? [ + 'id' => $item->getPerson()->getId(), + 'name' => $item->getPerson()->getName(), + 'nikename' => $item->getPerson()->getNikename() + ] : null, + 'mainDoc' => $item->getMainDoc() ? [ + 'id' => $item->getMainDoc()->getId(), + 'code' => $item->getMainDoc()->getCode() + ] : null + ]; + } + + return $this->json([ + 'result' => 1, + 'items' => $result, + 'total' => $total + ]); + } catch (\Exception $e) { + return $this->json([ + 'result' => 0, + 'message' => $e->getMessage() + ], 500); + } + } +} \ No newline at end of file diff --git a/hesabixCore/src/Entity/Business.php b/hesabixCore/src/Entity/Business.php index 87e0c2a..7a97ae0 100644 --- a/hesabixCore/src/Entity/Business.php +++ b/hesabixCore/src/Entity/Business.php @@ -291,6 +291,12 @@ class Business #[ORM\OneToMany(mappedBy: 'bid', targetEntity: AccountingPackageOrder::class, orphanRemoval: true)] private Collection $accountingPackageOrders; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'bid', orphanRemoval: true)] + private Collection $PlugGhestaDocs; + public function __construct() { $this->logs = new ArrayCollection(); @@ -332,6 +338,7 @@ class Business $this->dashboardSettings = new ArrayCollection(); $this->hesabdariTables = new ArrayCollection(); $this->accountingPackageOrders = new ArrayCollection(); + $this->PlugGhestaDocs = new ArrayCollection(); } public function getId(): ?int @@ -2018,4 +2025,34 @@ class Business return $this; } + + /** + * @return Collection + */ + public function getPlugGhestaDocs(): Collection + { + return $this->PlugGhestaDocs; + } + + public function addPlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if (!$this->PlugGhestaDocs->contains($PlugGhestaDoc)) { + $this->PlugGhestaDocs->add($PlugGhestaDoc); + $PlugGhestaDoc->setBid($this); + } + + return $this; + } + + public function removePlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if ($this->PlugGhestaDocs->removeElement($PlugGhestaDoc)) { + // set the owning side to null (unless already changed) + if ($PlugGhestaDoc->getBid() === $this) { + $PlugGhestaDoc->setBid(null); + } + } + + return $this; + } } diff --git a/hesabixCore/src/Entity/HesabdariDoc.php b/hesabixCore/src/Entity/HesabdariDoc.php index dc066e3..61b2977 100644 --- a/hesabixCore/src/Entity/HesabdariDoc.php +++ b/hesabixCore/src/Entity/HesabdariDoc.php @@ -128,6 +128,18 @@ class HesabdariDoc #[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2, nullable: true)] private ?float $discountPercent = null; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: PlugGhestaItem::class, mappedBy: 'hesabdariDoc')] + private Collection $plugGhestaItems; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'mainDoc', orphanRemoval: true)] + private Collection $plugGhestaDocs; + public function __construct() { $this->hesabdariRows = new ArrayCollection(); @@ -137,6 +149,8 @@ class HesabdariDoc $this->logs = new ArrayCollection(); $this->notes = new ArrayCollection(); $this->pairDoc = new ArrayCollection(); + $this->plugGhestaItems = new ArrayCollection(); + $this->plugGhestaDocs = new ArrayCollection(); } public function getId(): ?int @@ -612,6 +626,67 @@ class HesabdariDoc public function setDiscountPercent(?float $discountPercent): static { $this->discountPercent = $discountPercent; + + return $this; + } + + /** + * @return Collection + */ + public function getPlugGhestaItems(): Collection + { + return $this->plugGhestaItems; + } + + public function addPlugGhestaItem(PlugGhestaItem $plugGhestaItem): static + { + if (!$this->plugGhestaItems->contains($plugGhestaItem)) { + $this->plugGhestaItems->add($plugGhestaItem); + $plugGhestaItem->setHesabdariDoc($this); + } + + return $this; + } + + public function removePlugGhestaItem(PlugGhestaItem $plugGhestaItem): static + { + if ($this->plugGhestaItems->removeElement($plugGhestaItem)) { + // set the owning side to null (unless already changed) + if ($plugGhestaItem->getHesabdariDoc() === $this) { + $plugGhestaItem->setHesabdariDoc(null); + } + } + + return $this; + } + + /** + * @return Collection + */ + public function getPlugGhestaDocs(): Collection + { + return $this->plugGhestaDocs; + } + + public function addPlugGhestaDoc(PlugGhestaDoc $plugGhestaDoc): static + { + if (!$this->plugGhestaDocs->contains($plugGhestaDoc)) { + $this->plugGhestaDocs->add($plugGhestaDoc); + $plugGhestaDoc->setMainDoc($this); + } + + return $this; + } + + public function removePlugGhestaDoc(PlugGhestaDoc $plugGhestaDoc): static + { + if ($this->plugGhestaDocs->removeElement($plugGhestaDoc)) { + // set the owning side to null (unless already changed) + if ($plugGhestaDoc->getMainDoc() === $this) { + $plugGhestaDoc->setMainDoc(null); + } + } + return $this; } } \ No newline at end of file diff --git a/hesabixCore/src/Entity/Person.php b/hesabixCore/src/Entity/Person.php index 4e645f7..262c023 100644 --- a/hesabixCore/src/Entity/Person.php +++ b/hesabixCore/src/Entity/Person.php @@ -152,6 +152,12 @@ class Person #[ORM\ManyToOne(inversedBy: 'people')] private ?PersonPrelabel $prelabel = null; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'person', orphanRemoval: true)] + private Collection $PlugGhestaDocs; + public function __construct() { $this->hesabdariRows = new ArrayCollection(); @@ -166,6 +172,7 @@ class Person $this->preInvoiceDocs = new ArrayCollection(); $this->hesabdariDocs = new ArrayCollection(); $this->preinvoiceDocsSalemans = new ArrayCollection(); + $this->PlugGhestaDocs = new ArrayCollection(); } public function getId(): ?int @@ -862,4 +869,34 @@ class Person return $this; } + + /** + * @return Collection + */ + public function getPlugGhestaDocs(): Collection + { + return $this->PlugGhestaDocs; + } + + public function addPlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if (!$this->PlugGhestaDocs->contains($PlugGhestaDoc)) { + $this->PlugGhestaDocs->add($PlugGhestaDoc); + $PlugGhestaDoc->setPerson($this); + } + + return $this; + } + + public function removePlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if ($this->PlugGhestaDocs->removeElement($PlugGhestaDoc)) { + // set the owning side to null (unless already changed) + if ($PlugGhestaDoc->getPerson() === $this) { + $PlugGhestaDoc->setPerson(null); + } + } + + return $this; + } } diff --git a/hesabixCore/src/Entity/PlugGhestaDoc.php b/hesabixCore/src/Entity/PlugGhestaDoc.php new file mode 100644 index 0000000..fa27a76 --- /dev/null +++ b/hesabixCore/src/Entity/PlugGhestaDoc.php @@ -0,0 +1,217 @@ + + */ + #[ORM\OneToMany(targetEntity: PlugGhestaItem::class, mappedBy: 'doc', orphanRemoval: true)] + private Collection $plugGhestaItems; + + #[ORM\ManyToOne(inversedBy: 'plugGhestaDocs')] + #[ORM\JoinColumn(nullable: false)] + private ?HesabdariDoc $mainDoc = null; + + public function __construct() + { + $this->plugGhestaItems = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getBid(): ?Business + { + return $this->bid; + } + + public function setBid(?Business $bid): static + { + $this->bid = $bid; + + return $this; + } + + public function getSubmitter(): ?User + { + return $this->submitter; + } + + public function setSubmitter(?User $submitter): static + { + $this->submitter = $submitter; + + return $this; + } + + public function getDateSubmit(): ?string + { + return $this->dateSubmit; + } + + public function setDateSubmit(string $dateSubmit): static + { + $this->dateSubmit = $dateSubmit; + + return $this; + } + + public function getCount(): ?string + { + return $this->count; + } + + public function setCount(string $count): static + { + $this->count = $count; + + return $this; + } + + public function getProfitPercent(): ?string + { + return $this->profitPercent; + } + + public function setProfitPercent(string $profitPercent): static + { + $this->profitPercent = $profitPercent; + + return $this; + } + + public function getProfitAmount(): ?string + { + return $this->profitAmount; + } + + public function setProfitAmount(?string $profitAmount): static + { + $this->profitAmount = $profitAmount; + + return $this; + } + + public function getProfitType(): ?string + { + return $this->profitType; + } + + public function setProfitType(?string $profitType): static + { + $this->profitType = $profitType; + + return $this; + } + + public function getDaysPay(): ?float + { + return $this->daysPay; + } + + public function setDaysPay(?float $daysPay): static + { + $this->daysPay = $daysPay; + + return $this; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function setPerson(?Person $person): static + { + $this->person = $person; + + return $this; + } + + /** + * @return Collection + */ + public function getPlugGhestaItems(): Collection + { + return $this->plugGhestaItems; + } + + public function addPlugGhestaItem(PlugGhestaItem $plugGhestaItem): static + { + if (!$this->plugGhestaItems->contains($plugGhestaItem)) { + $this->plugGhestaItems->add($plugGhestaItem); + $plugGhestaItem->setDoc($this); + } + + return $this; + } + + public function removePlugGhestaItem(PlugGhestaItem $plugGhestaItem): static + { + if ($this->plugGhestaItems->removeElement($plugGhestaItem)) { + // set the owning side to null (unless already changed) + if ($plugGhestaItem->getDoc() === $this) { + $plugGhestaItem->setDoc(null); + } + } + + return $this; + } + + public function getMainDoc(): ?HesabdariDoc + { + return $this->mainDoc; + } + + public function setMainDoc(?HesabdariDoc $mainDoc): static + { + $this->mainDoc = $mainDoc; + + return $this; + } +} diff --git a/hesabixCore/src/Entity/PlugGhestaItem.php b/hesabixCore/src/Entity/PlugGhestaItem.php new file mode 100644 index 0000000..38ce692 --- /dev/null +++ b/hesabixCore/src/Entity/PlugGhestaItem.php @@ -0,0 +1,96 @@ +id; + } + + public function getDoc(): ?PlugGhestaDoc + { + return $this->doc; + } + + public function setDoc(?PlugGhestaDoc $doc): static + { + $this->doc = $doc; + + return $this; + } + + public function getDate(): ?string + { + return $this->date; + } + + public function setDate(string $date): static + { + $this->date = $date; + + return $this; + } + + public function getAmount(): ?string + { + return $this->amount; + } + + public function setAmount(string $amount): static + { + $this->amount = $amount; + + return $this; + } + + public function getNum(): ?int + { + return $this->num; + } + + public function setNum(int $num): static + { + $this->num = $num; + + return $this; + } + + public function getHesabdariDoc(): ?HesabdariDoc + { + return $this->hesabdariDoc; + } + + public function setHesabdariDoc(?HesabdariDoc $hesabdariDoc): static + { + $this->hesabdariDoc = $hesabdariDoc; + + return $this; + } +} diff --git a/hesabixCore/src/Entity/User.php b/hesabixCore/src/Entity/User.php index ac5acab..ccc7f5d 100644 --- a/hesabixCore/src/Entity/User.php +++ b/hesabixCore/src/Entity/User.php @@ -131,6 +131,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\OneToMany(targetEntity: BackBuiltModule::class, mappedBy: 'submitter', orphanRemoval: true)] private Collection $backBuiltModules; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: PlugGhestaDoc::class, mappedBy: 'submitter')] + private Collection $PlugGhestaDocs; + public function __construct() { $this->userTokens = new ArrayCollection(); @@ -155,6 +161,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->dashboardSettings = new ArrayCollection(); $this->accountingPackageOrders = new ArrayCollection(); $this->backBuiltModules = new ArrayCollection(); + $this->PlugGhestaDocs = new ArrayCollection(); } public function getId(): ?int @@ -960,4 +967,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + /** + * @return Collection + */ + public function getPlugGhestaDocs(): Collection + { + return $this->PlugGhestaDocs; + } + + public function addPlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if (!$this->PlugGhestaDocs->contains($PlugGhestaDoc)) { + $this->PlugGhestaDocs->add($PlugGhestaDoc); + $PlugGhestaDoc->setSubmitter($this); + } + + return $this; + } + + public function removePlugGhestaDoc(PlugGhestaDoc $PlugGhestaDoc): static + { + if ($this->PlugGhestaDocs->removeElement($PlugGhestaDoc)) { + // set the owning side to null (unless already changed) + if ($PlugGhestaDoc->getSubmitter() === $this) { + $PlugGhestaDoc->setSubmitter(null); + } + } + + return $this; + } } diff --git a/hesabixCore/src/Repository/PlugGhestaDocRepository.php b/hesabixCore/src/Repository/PlugGhestaDocRepository.php new file mode 100644 index 0000000..5b1a6bc --- /dev/null +++ b/hesabixCore/src/Repository/PlugGhestaDocRepository.php @@ -0,0 +1,43 @@ + + */ +class PlugGhestaDocRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PlugGhestaDoc::class); + } + + // /** + // * @return PlugGhestaDoc[] Returns an array of PlugGhestaDoc objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?PlugGhestaDoc + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/hesabixCore/src/Repository/PlugGhestaItemRepository.php b/hesabixCore/src/Repository/PlugGhestaItemRepository.php new file mode 100644 index 0000000..bd9f61e --- /dev/null +++ b/hesabixCore/src/Repository/PlugGhestaItemRepository.php @@ -0,0 +1,43 @@ + + */ +class PlugGhestaItemRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, PlugGhestaItem::class); + } + + // /** + // * @return PlugGhestaItem[] Returns an array of PlugGhestaItem objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?PlugGhestaItem + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/webUI/public/img/plugins/ghesta.png b/webUI/public/img/plugins/ghesta.png new file mode 100644 index 0000000..59486b6 Binary files /dev/null and b/webUI/public/img/plugins/ghesta.png differ diff --git a/webUI/src/App.vue b/webUI/src/App.vue index 2f55f9f..c887a0e 100644 --- a/webUI/src/App.vue +++ b/webUI/src/App.vue @@ -1,136 +1,136 @@ - - - - - + + + + + diff --git a/webUI/src/components/application/buttons/SecretDialog.vue b/webUI/src/components/application/buttons/SecretDialog.vue index c2d626b..78276e8 100644 --- a/webUI/src/components/application/buttons/SecretDialog.vue +++ b/webUI/src/components/application/buttons/SecretDialog.vue @@ -315,7 +315,7 @@ export default { if (Math.floor(this.score) > this.bestScore) { this.bestScore = Math.floor(this.score); localStorage.setItem('bestScore', this.bestScore); - lunch } + } }, resetGame() { this.gameOver(); diff --git a/webUI/src/components/application/buttons/ShortcutsButton.vue b/webUI/src/components/application/buttons/ShortcutsButton.vue index 585ec40..ac2347e 100644 --- a/webUI/src/components/application/buttons/ShortcutsButton.vue +++ b/webUI/src/components/application/buttons/ShortcutsButton.vue @@ -1,8 +1,8 @@ + + + + + {{ $t('drawer.ghesta_invoices') }} + {{ getShortcutKey('/acc/plugins/ghesta/list') }} + + + + diff --git a/webUI/src/views/acc/accounting/openBalance.vue b/webUI/src/views/acc/accounting/openBalance.vue index 386b116..36aeabb 100644 --- a/webUI/src/views/acc/accounting/openBalance.vue +++ b/webUI/src/views/acc/accounting/openBalance.vue @@ -450,10 +450,42 @@ export default { axios.post('/api/openbalance/get').then((Response) => { if (Response.data && Response.data.data) { - this.data = Response.data.data; - // اطمینان از وجود آرایه commodities - if (!this.data.commodities) { - this.data.commodities = []; + this.data = Response.data.data; + + // محاسبه مجموع بانک‌ها + if (this.data.banks) { + this.data.banks.forEach(item => { + if (item.openbalance) { + this.sums.banks += parseFloat(item.openbalance); + } + }); + } + + // محاسبه مجموع صندوق‌ها + if (this.data.cashdesks) { + this.data.cashdesks.forEach(item => { + if (item.openbalance) { + this.sums.cashdesks += parseFloat(item.openbalance); + } + }); + } + + // محاسبه مجموع تنخواه‌گردان‌ها + if (this.data.salarys) { + this.data.salarys.forEach(item => { + if (item.openbalance) { + this.sums.salarys += parseFloat(item.openbalance); + } + }); + } + + // محاسبه مجموع سهام‌داران + if (this.data.shareholders) { + this.data.shareholders.forEach(item => { + if (item.openbalance) { + this.sums.shareholders += parseFloat(item.openbalance); + } + }); } // محاسبه مجموع موجودی کالا @@ -465,8 +497,8 @@ export default { }); } - this.sums.degSum = parseFloat(this.sums.banks) + parseFloat(this.sums.cashdesks) + parseFloat(this.sums.salarys) + parseFloat(this.sums.inventory); - this.sums.shareSum = parseFloat(this.sums.shareholders); + this.sums.degSum = this.sums.banks + this.sums.cashdesks + this.sums.salarys + this.sums.inventory; + this.sums.shareSum = this.sums.shareholders; } }).catch(error => { console.error('Error loading data:', error); diff --git a/webUI/src/views/acc/incomes/list.vue b/webUI/src/views/acc/incomes/list.vue index 05fce1d..6702652 100644 --- a/webUI/src/views/acc/incomes/list.vue +++ b/webUI/src/views/acc/incomes/list.vue @@ -1,177 +1,747 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/webUI/src/views/acc/persons/card.vue b/webUI/src/views/acc/persons/card.vue index 624b1e4..351e611 100644 --- a/webUI/src/views/acc/persons/card.vue +++ b/webUI/src/views/acc/persons/card.vue @@ -92,12 +92,13 @@ - + + :loading="loading" @update:search="debouncedSearchPerson" @update:model-value="updateRoute" + class="rounded-lg elevation-2"> @@ -128,8 +129,8 @@ - - + + {{ $t('pages.person_card.account_card') }} {{ selectedPerson.nikename }} @@ -154,8 +155,8 @@ - - + + {{ $t('pages.person_card.account_status') }} {{ selectedPerson.nikename }} @@ -187,9 +188,9 @@ + show-select dense :items-per-page="25" class="elevation-2 rounded-lg" :header-props="{ class: 'custom-header' }"> + + @@ -404,5 +411,39 @@ export default { \ No newline at end of file diff --git a/webUI/src/views/acc/persons/send/list.vue b/webUI/src/views/acc/persons/send/list.vue index 4f3bdb9..ea58529 100644 --- a/webUI/src/views/acc/persons/send/list.vue +++ b/webUI/src/views/acc/persons/send/list.vue @@ -1,270 +1,531 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/ghesta/intro.vue b/webUI/src/views/acc/plugins/ghesta/intro.vue new file mode 100644 index 0000000..f7fdd1b --- /dev/null +++ b/webUI/src/views/acc/plugins/ghesta/intro.vue @@ -0,0 +1,134 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/ghesta/list.vue b/webUI/src/views/acc/plugins/ghesta/list.vue new file mode 100644 index 0000000..d052737 --- /dev/null +++ b/webUI/src/views/acc/plugins/ghesta/list.vue @@ -0,0 +1,396 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/ghesta/mod.vue b/webUI/src/views/acc/plugins/ghesta/mod.vue new file mode 100644 index 0000000..beb06d0 --- /dev/null +++ b/webUI/src/views/acc/plugins/ghesta/mod.vue @@ -0,0 +1,1184 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/ghesta/view.vue b/webUI/src/views/acc/plugins/ghesta/view.vue new file mode 100644 index 0000000..5bbf960 --- /dev/null +++ b/webUI/src/views/acc/plugins/ghesta/view.vue @@ -0,0 +1,768 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/views/acc/sell/mod.vue b/webUI/src/views/acc/sell/mod.vue index 638db08..26e4ba0 100644 --- a/webUI/src/views/acc/sell/mod.vue +++ b/webUI/src/views/acc/sell/mod.vue @@ -71,7 +71,7 @@
- +