diff --git a/hesabixCore/src/Cog/AccountingDocService.php b/hesabixCore/src/Cog/AccountingDocService.php index fdf6e59..f8c8c28 100644 --- a/hesabixCore/src/Cog/AccountingDocService.php +++ b/hesabixCore/src/Cog/AccountingDocService.php @@ -167,7 +167,7 @@ class AccountingDocService 'code' => $params['id'], ]); if (!$salary) - return ['error' => 'حقوق یافت نشد']; + return ['error' => 'تنخواه یافت نشد']; // Check if we should include preview documents $includePreview = $params['includePreview'] ?? false; diff --git a/hesabixCore/src/Controller/BankController.php b/hesabixCore/src/Controller/BankController.php index 0a4ef1b..52e4972 100644 --- a/hesabixCore/src/Controller/BankController.php +++ b/hesabixCore/src/Controller/BankController.php @@ -441,18 +441,23 @@ class BankController extends AbstractController 'توضیحات', 'شرح سند', 'تفصیل', + 'طرف حساب‌ها', 'بستانکار', 'بدهکار', 'سال مالی', ] ]; foreach ($transactions as $transaction) { + // استخراج طرف حساب‌ها برای این تراکنش + $counterpartAccounts = $this->getCounterpartAccountsForTransaction($transaction, $bank, $entityManager); + $arrayEntity[] = [ $transaction->getId(), $transaction->getDoc()->getDate(), $transaction->getDes(), $transaction->getDoc()->getDes(), $transaction->getRef()->getName(), + $counterpartAccounts, $transaction->getBs(), $transaction->getBd(), $transaction->getYear()->getlabel() @@ -466,6 +471,57 @@ class BankController extends AbstractController return new BinaryFileResponse($filePath); } + /** + * استخراج طرف حساب‌های مربوط به یک تراکنش + */ + private function getCounterpartAccountsForTransaction($transaction, $bank, EntityManagerInterface $entityManager): string + { + $doc = $transaction->getDoc(); + $bankCode = $bank->getCode(); + + // دریافت تمام ردیف‌های مربوط به این سند + $docRows = $entityManager->getRepository(HesabdariRow::class) + ->createQueryBuilder('hr') + ->leftJoin('hr.bank', 'ba') + ->leftJoin('hr.cashdesk', 'cd') + ->leftJoin('hr.salary', 's') + ->leftJoin('hr.person', 'p') + ->where('hr.doc = :doc') + ->setParameter('doc', $doc) + ->getQuery() + ->getResult(); + + $accounts = []; + foreach ($docRows as $docRow) { + // بررسی اینکه آیا این ردیف طرف حساب است (نه بانک انتخابی) + $isCounterpart = false; + $accountName = ''; + + if ($docRow->getBank()) { + if ($docRow->getBank()->getCode() != $bankCode) { + $isCounterpart = true; + $accountName = 'بانک: ' . $docRow->getBank()->getName(); + } + } elseif ($docRow->getCashdesk()) { + $isCounterpart = true; + $accountName = 'صندوق: ' . $docRow->getCashdesk()->getName(); + } elseif ($docRow->getSalary()) { + $isCounterpart = true; + $accountName = 'تنخواه: ' . $docRow->getSalary()->getName(); + } elseif ($docRow->getPerson()) { + $isCounterpart = true; + $accountName = 'شخص: ' . $docRow->getPerson()->getNikename(); + } + + if ($isCounterpart) { + $amount = $docRow->getBd() > 0 ? $docRow->getBd() : $docRow->getBs(); + $accounts[] = $accountName . ' (' . number_format($amount, 0, '.', ',') . ')'; + } + } + + return implode(' | ', $accounts); + } + #[Route('/api/bank/card/list/print', name: 'app_bank_card_list_print')] public function app_bank_card_list_print(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse { @@ -521,6 +577,12 @@ class BankController extends AbstractController } } } + + // اضافه کردن طرف حساب‌ها به هر تراکنش + foreach ($transactions as $transaction) { + $transaction->counterpartAccounts = $this->getCounterpartAccountsForTransaction($transaction, $bank, $entityManager); + } + $pid = $provider->createPrint( $acc['bid'], $this->getUser(), diff --git a/hesabixCore/src/Controller/HesabdariController.php b/hesabixCore/src/Controller/HesabdariController.php index 29d968c..c9fa1ef 100644 --- a/hesabixCore/src/Controller/HesabdariController.php +++ b/hesabixCore/src/Controller/HesabdariController.php @@ -491,18 +491,28 @@ class HesabdariController extends AbstractController ]); } - // Set approval status based on business settings + // وضعیت تایید: اگر autoApprove=true ارسال شده باشد، اجباری تایید شود + $autoApprove = isset($params['autoApprove']) ? (bool)$params['autoApprove'] : null; $business = $acc['bid']; - if ($business->isRequireTwoStepApproval()) { - // Two-step approval is enabled + if ($autoApprove === true) { + $doc->setIsPreview(false); + $doc->setIsApproved(true); + $doc->setApprovedBy($this->getUser()); + } elseif ($autoApprove === false) { $doc->setIsPreview(true); $doc->setIsApproved(false); $doc->setApprovedBy(null); } else { - // Two-step approval is disabled - auto approve - $doc->setIsPreview(false); - $doc->setIsApproved(true); - $doc->setApprovedBy($this->getUser()); + // پیش‌فرض مطابق تنظیمات کسب‌وکار + if ($business->isRequireTwoStepApproval()) { + $doc->setIsPreview(true); + $doc->setIsApproved(false); + $doc->setApprovedBy(null); + } else { + $doc->setIsPreview(false); + $doc->setIsApproved(true); + $doc->setApprovedBy($this->getUser()); + } } if (array_key_exists('refData', $params)) diff --git a/hesabixCore/src/Controller/PersonsController.php b/hesabixCore/src/Controller/PersonsController.php index 347a4f0..dcc7b37 100644 --- a/hesabixCore/src/Controller/PersonsController.php +++ b/hesabixCore/src/Controller/PersonsController.php @@ -18,6 +18,7 @@ use App\Entity\Storeroom; use App\Entity\StoreroomItem; use App\Entity\StoreroomTicket; use App\Service\Explore; +use App\Cog\AccountingDocService; use App\Cog\PersonService; use Doctrine\ORM\EntityManagerInterface; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -2926,4 +2927,123 @@ class PersonsController extends AbstractController return $this->json(['success' => true, 'message' => "$successCount سند پرداخت تایید شد"]); } + /** + * دریافت طرف حساب‌های مربوط به حساب بانکی + */ + #[Route('/api/person/bank/accounts/list', name: 'app_person_bank_accounts_list')] + public function app_bank_accounts_list( + Request $request, + Access $access, + EntityManagerInterface $entityManager, + AccountingDocService $accountingDocService + ): JsonResponse { + $acc = $access->hasRole('getpay'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + $bankCode = $params['bankCode'] ?? null; + + if (!$bankCode) { + return $this->json(['error' => 'کد بانک الزامی است'], 400); + } + + // استفاده از AccountingDocService برای دریافت ردیف‌های حسابداری + $searchParams = [ + 'type' => 'bank', + 'id' => $bankCode, + 'includePreview' => true // شامل اسناد پیش‌نمایش هم می‌شود + ]; + + $rows = $accountingDocService->searchRows($searchParams, $acc); + + if (isset($rows['error'])) { + return $this->json(['error' => $rows['error']], 400); + } + + // استخراج طرف حساب‌ها از ردیف‌ها + $accounts = []; + $processedDocs = []; + + foreach ($rows as $row) { + $docId = $row['code']; // کد سند + + // اگر این سند قبلاً پردازش شده، رد کن + if (in_array($docId, $processedDocs)) { + continue; + } + + $processedDocs[] = $docId; + + // دریافت سند و ردیف‌های مربوط به آن + $doc = $entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findOneBy(['code' => $docId]); + if (!$doc) { + continue; + } + + $docRows = $entityManager->getRepository(\App\Entity\HesabdariRow::class) + ->createQueryBuilder('hr') + ->leftJoin('hr.bank', 'ba') + ->leftJoin('hr.cashdesk', 'cd') + ->leftJoin('hr.salary', 's') + ->leftJoin('hr.person', 'p') + ->where('hr.doc = :doc') + ->setParameter('doc', $doc) + ->getQuery() + ->getResult(); + + $docAccounts = []; + foreach ($docRows as $docRow) { + // بررسی اینکه آیا این ردیف طرف حساب است (نه بانک انتخابی) + $isCounterpart = false; + $accountName = ''; + $accountType = ''; + $amount = 0; + + if ($docRow->getBank()) { + if ($docRow->getBank()->getCode() != $bankCode) { + $isCounterpart = true; + $accountName = 'بانک: ' . $docRow->getBank()->getName(); + $accountType = 'bank'; + } + } elseif ($docRow->getCashdesk()) { + $isCounterpart = true; + $accountName = 'صندوق: ' . $docRow->getCashdesk()->getName(); + $accountType = 'cashdesk'; + } elseif ($docRow->getSalary()) { + $isCounterpart = true; + $accountName = 'تنخواه: ' . $docRow->getSalary()->getName(); + $accountType = 'salary'; + } elseif ($docRow->getPerson()) { + $isCounterpart = true; + $accountName = 'شخص: ' . $docRow->getPerson()->getNikename(); + $accountType = 'person'; + } + + if ($isCounterpart) { + $amount = $docRow->getBd() > 0 ? $docRow->getBd() : $docRow->getBs(); + $docAccounts[] = [ + 'name' => $accountName, + 'type' => $accountType, + 'amount' => $amount, + 'formattedAmount' => number_format($amount, 0, '.', ','), + ]; + } + } + + if (!empty($docAccounts)) { + $accounts[] = [ + 'docId' => $docId, + 'date' => $row['date'], + 'type' => $row['type'], + 'des' => $row['des'], + 'accounts' => $docAccounts + ]; + } + } + + return $this->json($accounts); + } + } diff --git a/hesabixCore/src/Controller/SellController.php b/hesabixCore/src/Controller/SellController.php index 4f71f68..bf98e4e 100644 --- a/hesabixCore/src/Controller/SellController.php +++ b/hesabixCore/src/Controller/SellController.php @@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use App\Entity\BankAccount; use App\Entity\Cashdesk; @@ -800,11 +801,56 @@ class SellController extends AbstractController // اولویت با پارامترهای ارسالی است $printOptions = array_merge($defaultOptions, $params['printOptions'] ?? []); - $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + $docRepo = $entityManager->getRepository(HesabdariDoc::class); + $doc = $docRepo->findOneBy([ 'bid' => $acc['bid'], 'code' => $params['code'], 'money' => $acc['money'] ]); + // Fallback در صورت ناهمخوانی activeMoney + if (!$doc) { + $doc = $docRepo->findOneBy([ + 'bid' => $acc['bid'], + 'code' => $params['code'], + 'year' => $acc['year'] + ]); + } + // Fallback با QueryBuilder بر اساس شناسه BID (عدم تکیه بر شیء) + if (!$doc) { + try { + $doc = $entityManager->createQueryBuilder() + ->select('d') + ->from(\App\Entity\HesabdariDoc::class, 'd') + ->where('d.code = :code') + ->andWhere('IDENTITY(d.bid) = :bidId') + ->setParameter('code', (string)($params['code'] ?? '')) + ->setParameter('bidId', $acc['bid']->getId()) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } catch (\Exception $e) { + $doc = null; + } + } + // Fallback نهایی فقط بر اساس کد (در صورت وجود چند کسب‌وکار، کنترل امنیت بعدی برقرار است) + if (!$doc) { + try { + $doc = $entityManager->createQueryBuilder() + ->select('d') + ->from(\App\Entity\HesabdariDoc::class, 'd') + ->where('d.code = :code') + ->setParameter('code', (string)($params['code'] ?? '')) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + if ($doc && $doc->getBid()->getId() !== $acc['bid']->getId()) { + // سند پیدا شد ولی متعلق به کسب‌وکار دیگری است + throw $this->createAccessDeniedException(); + } + } catch (\Exception $e) { + $doc = null; + } + } if (!$doc) throw $this->createNotFoundException(); $person = null; @@ -953,6 +999,10 @@ class SellController extends AbstractController false, $printOptions['paper'] ); + // اگر چاپ ابری انتخاب شده، فایل را به صف پرینترها اضافه کن + if (!empty($params['printers'])) { + $printers->addFile($pdfPid, $acc, "fastSellInvoice"); + } } if ($params['posPrint'] == true) { $pid = $provider->createPrint( @@ -977,9 +1027,23 @@ class SellController extends AbstractController 'showPercentDiscount' => $doc->getDiscountType() === 'percent', 'discountPercent' => $doc->getDiscountPercent() ]), - false + true ); - $printers->addFile($pid, $acc, "fastSellInvoice"); + $printers->addFile($pid, $acc, "fastSellPosInvoice"); + } + // چاپ قبض صندوق در صورت نیاز + if (!empty($params['posPrintRecp'])) { + $pid = $provider->createPrint( + $acc['bid'], + $this->getUser(), + $this->renderView('pdf/posPrinters/cashdesk.html.twig', [ + 'bid' => $acc['bid'], + 'doc' => $doc, + 'rows' => $doc->getHesabdariRows(), + ]), + true + ); + $printers->addFile($pid, $acc, "fastSellCashdesk"); } return $this->json(['id' => $pdfPid]); } @@ -1587,4 +1651,452 @@ class SellController extends AbstractController ]); } } + + #[Route('/api/sell/list/excel', name: 'app_sell_list_excel', methods: ['POST'])] + public function app_sell_list_excel( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager, + Jdate $jdate + ): BinaryFileResponse|JsonResponse|StreamedResponse { + $acc = $access->hasRole('sell'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + $searchTerm = $params['search'] ?? ''; + $types = $params['types'] ?? []; + $dateFilter = $params['dateFilter'] ?? 'all'; + $sortBy = $params['sortBy'] ?? []; + + $queryBuilder = $entityManager->createQueryBuilder() + ->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount') + ->addSelect('d.isPreview, d.isApproved') + ->addSelect('u.fullName as submitter') + ->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail') + ->addSelect('l.code as labelCode, l.label as labelLabel') + ->from(HesabdariDoc::class, 'd') + ->leftJoin('d.submitter', 'u') + ->leftJoin('d.approvedBy', 'approver') + ->leftJoin('d.InvoiceLabel', 'l') + ->leftJoin('d.hesabdariRows', 'r') + ->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', 'sell') + ->setParameter('money', $acc['money']); + + // اعمال فیلترهای تاریخ + $today = $jdate->jdate('Y/m/d', time()); + if ($dateFilter === 'today') { + $queryBuilder->andWhere('d.date = :today') + ->setParameter('today', $today); + } elseif ($dateFilter === 'week') { + $weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days')); + $queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today') + ->setParameter('weekStart', $weekStart) + ->setParameter('today', $today); + } elseif ($dateFilter === 'month') { + $monthStart = $jdate->jdate('Y/m/01', time()); + $queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today') + ->setParameter('monthStart', $monthStart) + ->setParameter('today', $today); + } + + if ($searchTerm) { + $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', + 'p.mobile LIKE :search' + ) + ) + ->setParameter('search', "%$searchTerm%"); + } + + if (!empty($types)) { + $queryBuilder->andWhere('l.code IN (:types)') + ->setParameter('types', $types); + } + + // اگر آیتم‌های خاصی درخواست شده‌اند + if (array_key_exists('items', $params)) { + $codes = array_map(function($item) { return $item['code']; }, $params['items']); + $queryBuilder->andWhere('d.code IN (:codes)') + ->setParameter('codes', $codes); + } + + // اعمال مرتب‌سازی + if (!empty($sortBy)) { + foreach ($sortBy as $sort) { + $key = $sort['key'] ?? 'id'; + $direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? 'DESC' : 'ASC'; + if ($key === 'profit' || $key === 'receivedAmount') { + continue; // این‌ها توی PHP مرتب می‌شن + } elseif (in_array($key, ['id', 'dateSubmit', 'date', 'type', 'code', 'des', 'amount', 'isPreview', 'isApproved'])) { + $queryBuilder->addOrderBy('d.' . $key, $direction); + } elseif ($key === 'submitter') { + $queryBuilder->addOrderBy('u.fullName', $direction); + } elseif ($key === 'label') { + $queryBuilder->addOrderBy('l.label', $direction); + } + } + } else { + $queryBuilder->orderBy('d.id', 'DESC'); + } + + $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'], + 'label' => $doc['labelCode'] ? [ + 'code' => $doc['labelCode'], + 'label' => $doc['labelLabel'] + ] : null, + 'isPreview' => $doc['isPreview'], + 'isApproved' => $doc['isApproved'], + 'approvedBy' => $doc['approvedByName'] ? [ + 'fullName' => $doc['approvedByName'], + 'id' => $doc['approvedById'], + 'email' => $doc['approvedByEmail'] + ] : null, + ]; + + $mainRow = $entityManager->getRepository(HesabdariRow::class) + ->createQueryBuilder('r') + ->where('r.doc = :docId') + ->andWhere('r.person IS NOT NULL') + ->setParameter('docId', $doc['id']) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + $item['person'] = $mainRow && $mainRow->getPerson() ? [ + 'id' => $mainRow->getPerson()->getId(), + 'nikename' => $mainRow->getPerson()->getNikename(), + 'code' => $mainRow->getPerson()->getCode() + ] : null; + + // استفاده از SQL خام برای محاسبه پرداختی‌ها + $sql = " + SELECT SUM(rd.amount) as total_pays, COUNT(rd.id) as count_docs + FROM hesabdari_doc rd + JOIN hesabdari_doc_hesabdari_doc rel ON rel.hesabdari_doc_target = rd.id + WHERE rel.hesabdari_doc_source = :sourceDocId + AND rd.bid_id = :bidId + "; + $stmt = $entityManager->getConnection()->prepare($sql); + $stmt->bindValue('sourceDocId', $doc['id']); + $stmt->bindValue('bidId', $acc['bid']->getId()); + $result = $stmt->executeQuery()->fetchAssociative(); + + $relatedDocsPays = $result['total_pays'] ?? 0; + $relatedDocsCount = $result['count_docs'] ?? 0; + + $item['relatedDocsCount'] = (int) $relatedDocsCount; + $item['relatedDocsPays'] = $relatedDocsPays; + $item['profit'] = $this->calculateProfit($doc['id'], $acc, $entityManager); + $item['discountAll'] = 0; + $item['transferCost'] = 0; + + $rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $doc]); + foreach ($rows as $row) { + if ($row->getRef()->getCode() == '104') { + $item['discountAll'] = $row->getBd(); + } elseif ($row->getRef()->getCode() == '61') { + $item['transferCost'] = $row->getBs(); + } + } + + $dataTemp[] = $item; + } + + // مرتب‌سازی توی PHP برای profit و receivedAmount + if (!empty($sortBy)) { + foreach ($sortBy as $sort) { + $key = $sort['key'] ?? 'id'; + $direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? SORT_DESC : SORT_ASC; + if ($key === 'profit') { + usort($dataTemp, function ($a, $b) use ($direction) { + return $direction === SORT_ASC ? $a['profit'] - $b['profit'] : $b['profit'] - $a['profit']; + }); + } elseif ($key === 'receivedAmount') { + usort($dataTemp, function ($a, $b) use ($direction) { + return $direction === SORT_ASC ? $a['relatedDocsPays'] - $b['relatedDocsPays'] : $b['relatedDocsPays'] - $a['relatedDocsPays']; + }); + } + } + } + + // آماده‌سازی داده‌ها برای Excel + $excelData = []; + $headers = [ + 'ردیف', + 'کد فاکتور', + 'تاریخ', + 'خریدار', + 'وضعیت تایید', + 'تاییدکننده', + 'تخفیف', + 'حمل و نقل', + 'مبلغ', + 'سود فاکتور', + 'پرداختی', + 'برچسب', + 'شرح' + ]; + $excelData[] = $headers; + + foreach ($dataTemp as $index => $item) { + $excelData[] = [ + $index + 1, + $item['code'], + $item['date'], + $item['person'] ? $item['person']['nikename'] : '-', + $item['isApproved'] ? 'تایید شده' : ($item['isPreview'] ? 'در انتظار تایید' : 'تایید شده'), + $item['approvedBy'] ? $item['approvedBy']['fullName'] : '-', + number_format($item['discountAll']), + number_format($item['transferCost']), + number_format($item['amount']), + number_format($item['profit']), + number_format($item['relatedDocsPays']), + $item['label'] ? $item['label']['label'] : '-', + $item['des'] + ]; + } + + return new BinaryFileResponse($provider->createExcellFromArray($excelData)); + } + + #[Route('/api/sell/list/pdf', name: 'app_sell_list_pdf', methods: ['POST'])] + public function app_sell_list_pdf( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager, + Jdate $jdate + ): JsonResponse { + $acc = $access->hasRole('sell'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true) ?? []; + $searchTerm = $params['search'] ?? ''; + $types = $params['types'] ?? []; + $dateFilter = $params['dateFilter'] ?? 'all'; + $sortBy = $params['sortBy'] ?? []; + + $queryBuilder = $entityManager->createQueryBuilder() + ->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount') + ->addSelect('d.isPreview, d.isApproved') + ->addSelect('u.fullName as submitter') + ->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail') + ->addSelect('l.code as labelCode, l.label as labelLabel') + ->from(HesabdariDoc::class, 'd') + ->leftJoin('d.submitter', 'u') + ->leftJoin('d.approvedBy', 'approver') + ->leftJoin('d.InvoiceLabel', 'l') + ->leftJoin('d.hesabdariRows', 'r') + ->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', 'sell') + ->setParameter('money', $acc['money']); + + // اعمال فیلترهای تاریخ + $today = $jdate->jdate('Y/m/d', time()); + if ($dateFilter === 'today') { + $queryBuilder->andWhere('d.date = :today') + ->setParameter('today', $today); + } elseif ($dateFilter === 'week') { + $weekStart = $jdate->jdate('Y/m/d', strtotime('-6 days')); + $queryBuilder->andWhere('d.date BETWEEN :weekStart AND :today') + ->setParameter('weekStart', $weekStart) + ->setParameter('today', $today); + } elseif ($dateFilter === 'month') { + $monthStart = $jdate->jdate('Y/m/01', time()); + $queryBuilder->andWhere('d.date BETWEEN :monthStart AND :today') + ->setParameter('monthStart', $monthStart) + ->setParameter('today', $today); + } + + if ($searchTerm) { + $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', + 'p.mobile LIKE :search' + ) + ) + ->setParameter('search', "%$searchTerm%"); + } + + if (!empty($types)) { + $queryBuilder->andWhere('l.code IN (:types)') + ->setParameter('types', $types); + } + + // اگر آیتم‌های خاصی درخواست شده‌اند + if (array_key_exists('items', $params)) { + $codes = array_map(function($item) { return $item['code']; }, $params['items']); + $queryBuilder->andWhere('d.code IN (:codes)') + ->setParameter('codes', $codes); + } + + // اعمال مرتب‌سازی + if (!empty($sortBy)) { + foreach ($sortBy as $sort) { + $key = $sort['key'] ?? 'id'; + $direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? 'DESC' : 'ASC'; + if ($key === 'profit' || $key === 'receivedAmount') { + continue; // این‌ها توی PHP مرتب می‌شن + } elseif (in_array($key, ['id', 'dateSubmit', 'date', 'type', 'code', 'des', 'amount', 'isPreview', 'isApproved'])) { + $queryBuilder->addOrderBy('d.' . $key, $direction); + } elseif ($key === 'submitter') { + $queryBuilder->addOrderBy('u.fullName', $direction); + } elseif ($key === 'label') { + $queryBuilder->addOrderBy('l.label', $direction); + } + } + } else { + $queryBuilder->orderBy('d.id', 'DESC'); + } + + $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'], + 'label' => $doc['labelCode'] ? [ + 'code' => $doc['labelCode'], + 'label' => $doc['labelLabel'] + ] : null, + 'isPreview' => $doc['isPreview'], + 'isApproved' => $doc['isApproved'], + 'approvedBy' => $doc['approvedByName'] ? [ + 'fullName' => $doc['approvedByName'], + 'id' => $doc['approvedById'], + 'email' => $doc['approvedByEmail'] + ] : null, + ]; + + $mainRow = $entityManager->getRepository(HesabdariRow::class) + ->createQueryBuilder('r') + ->where('r.doc = :docId') + ->andWhere('r.person IS NOT NULL') + ->setParameter('docId', $doc['id']) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + $item['person'] = $mainRow && $mainRow->getPerson() ? [ + 'id' => $mainRow->getPerson()->getId(), + 'nikename' => $mainRow->getPerson()->getNikename(), + 'code' => $mainRow->getPerson()->getCode() + ] : null; + + // استفاده از SQL خام برای محاسبه پرداختی‌ها + $sql = " + SELECT SUM(rd.amount) as total_pays, COUNT(rd.id) as count_docs + FROM hesabdari_doc rd + JOIN hesabdari_doc_hesabdari_doc rel ON rel.hesabdari_doc_target = rd.id + WHERE rel.hesabdari_doc_source = :sourceDocId + AND rd.bid_id = :bidId + "; + $stmt = $entityManager->getConnection()->prepare($sql); + $stmt->bindValue('sourceDocId', $doc['id']); + $stmt->bindValue('bidId', $acc['bid']->getId()); + $result = $stmt->executeQuery()->fetchAssociative(); + + $relatedDocsPays = $result['total_pays'] ?? 0; + $relatedDocsCount = $result['count_docs'] ?? 0; + + $item['relatedDocsCount'] = (int) $relatedDocsCount; + $item['relatedDocsPays'] = $relatedDocsPays; + $item['profit'] = $this->calculateProfit($doc['id'], $acc, $entityManager); + $item['discountAll'] = 0; + $item['transferCost'] = 0; + + $rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $doc]); + foreach ($rows as $row) { + if ($row->getRef()->getCode() == '104') { + $item['discountAll'] = $row->getBd(); + } elseif ($row->getRef()->getCode() == '61') { + $item['transferCost'] = $row->getBs(); + } + } + + $dataTemp[] = $item; + } + + // مرتب‌سازی توی PHP برای profit و receivedAmount + if (!empty($sortBy)) { + foreach ($sortBy as $sort) { + $key = $sort['key'] ?? 'id'; + $direction = isset($sort['order']) && strtoupper($sort['order']) === 'DESC' ? SORT_DESC : SORT_ASC; + if ($key === 'profit') { + usort($dataTemp, function ($a, $b) use ($direction) { + return $direction === SORT_ASC ? $a['profit'] - $b['profit'] : $b['profit'] - $a['profit']; + }); + } elseif ($key === 'receivedAmount') { + usort($dataTemp, function ($a, $b) use ($direction) { + return $direction === SORT_ASC ? $a['relatedDocsPays'] - $b['relatedDocsPays'] : $b['relatedDocsPays'] - $a['relatedDocsPays']; + }); + } + } + } + + $html = $this->renderView('pdf/sell_list.html.twig', [ + 'items' => $dataTemp, + 'bid' => $acc['bid'], + 'currentPage' => 1, + 'totalPages' => 1, + 'totalItems' => count($dataTemp), + 'page_title' => 'لیست فاکتورهای فروش' + ]); + + $pdfPid = $provider->createPrint( + $acc['bid'], + $this->getUser(), + $html, + false, + 'A4-L' + ); + + return $this->json(['id' => $pdfPid]); + } } \ No newline at end of file diff --git a/hesabixCore/templates/pdf/bank_card.html.twig b/hesabixCore/templates/pdf/bank_card.html.twig index 395a8e3..a999b3d 100644 --- a/hesabixCore/templates/pdf/bank_card.html.twig +++ b/hesabixCore/templates/pdf/bank_card.html.twig @@ -50,6 +50,7 @@ توضیحات شرح سند تفصیل + طرف حساب‌ها بدهکار بستانکار سال مالی @@ -66,6 +67,7 @@ {{ item.des }} {{ item.doc.des }} {{ item.ref.name }} + {{ item.counterpartAccounts }} {{ item.bd | number_format }} {{ item.bs | number_format }} {{ item.year.label }} diff --git a/hesabixCore/templates/pdf/sell_list.html.twig b/hesabixCore/templates/pdf/sell_list.html.twig new file mode 100644 index 0000000..5111ef1 --- /dev/null +++ b/hesabixCore/templates/pdf/sell_list.html.twig @@ -0,0 +1,53 @@ +{% extends "pdf/base.html.twig" %} +{% block body %} +
+ + + + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + + + + + + {% endfor %} + +
ردیفکد فاکتورتاریخخریداروضعیت تاییدتاییدکنندهتخفیفحمل و نقلمبلغسود فاکتورپرداختیبرچسبشرح
{{ loop.index + ((currentPage - 1) * 10) }}{{ item.code }}{{ item.date }}{{ item.person ? item.person.nikename : '-' }} + {% if item.isApproved %} + تایید شده + {% elseif item.isPreview %} + در انتظار تایید + {% else %} + تایید شده + {% endif %} + {{ item.approvedBy ? item.approvedBy.fullName : '-' }}{{ item.discountAll | number_format }}{{ item.transferCost | number_format }}{{ item.amount | number_format }}{{ item.profit | number_format }}{{ item.relatedDocsPays | number_format }}{{ item.label ? item.label.label : '-' }}{{ item.des }}
+ +
+ صفحه {{ currentPage }} از {{ totalPages }} - تعداد کل: {{ totalItems }} +
+
+{% endblock %} diff --git a/webUI/src/views/acc/bank/card.vue b/webUI/src/views/acc/bank/card.vue index 03fdf73..1ecc408 100755 --- a/webUI/src/views/acc/bank/card.vue +++ b/webUI/src/views/acc/bank/card.vue @@ -191,6 +191,19 @@ {{ item.settlement }} + + + + + + +