From c8b2c53e2bce78fe1f4b1b4b3d910676c4b471fb Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Sat, 3 May 2025 21:37:39 +0000 Subject: [PATCH] bug fix in progress in AI --- .../src/Controller/BusinessController.php | 4 +- .../src/Controller/CommodityController.php | 97 +- hesabixCore/src/Controller/CostController.php | 53 +- .../src/Controller/HesabdariController.php | 34 +- .../src/Controller/PersonsController.php | 39 +- .../src/Controller/PreinvoiceController.php | 20 +- .../src/Controller/PrintersController.php | 4 + hesabixCore/src/Controller/SellController.php | 472 ++++----- hesabixCore/src/Controller/UserController.php | 1 + hesabixCore/src/Entity/BackBuiltModule.php | 112 +++ hesabixCore/src/Entity/HesabdariRow.php | 9 +- hesabixCore/src/Entity/Permission.php | 15 + hesabixCore/src/Entity/PrintOptions.php | 28 + hesabixCore/src/Entity/User.php | 37 + .../Repository/BackBuiltModuleRepository.php | 43 + .../Repository/HesabdariTableRepository.php | 55 ++ .../pdf/printers/preinvoice.html.twig | 8 +- .../templates/pdf/printers/sell.html.twig | 16 +- webUI/src/components/PrintDialog.vue | 147 +++ .../components/forms/HesabdariTreeView.vue | 377 +++++++ webUI/src/components/forms/TreeNode.vue | 60 +- webUI/src/i18n/fa_lang.ts | 4 +- webUI/src/views/acc/App.vue | 10 +- webUI/src/views/acc/commodity/cat/list.vue | 619 ++++++++---- webUI/src/views/acc/costs/list.vue | 66 +- webUI/src/views/acc/persons/receive/list.vue | 4 +- webUI/src/views/acc/presell/list.vue | 51 +- webUI/src/views/acc/presell/view.vue | 58 +- webUI/src/views/acc/presell/viewInvoice.vue | 67 +- webUI/src/views/acc/sell/list.vue | 91 +- webUI/src/views/acc/sell/mod.vue | 4 +- webUI/src/views/acc/settings/print.vue | 8 + .../src/views/acc/settings/user_perm_edit.vue | 929 +++++++++++++----- webUI/src/views/acc/settings/user_rolls.vue | 328 ++++--- webUI/src/views/acc/storeroom/io/buy.vue | 6 +- webUI/src/views/acc/storeroom/io/rfbuy.vue | 55 +- webUI/src/views/acc/storeroom/io/rfsell.vue | 6 +- webUI/src/views/acc/storeroom/io/sell.vue | 6 +- webUI/src/views/wizard/home.vue | 529 ++++++++-- 39 files changed, 3369 insertions(+), 1103 deletions(-) create mode 100644 hesabixCore/src/Entity/BackBuiltModule.php create mode 100644 hesabixCore/src/Repository/BackBuiltModuleRepository.php create mode 100644 webUI/src/components/PrintDialog.vue create mode 100644 webUI/src/components/forms/HesabdariTreeView.vue diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php index 2451186..ebe3b55 100644 --- a/hesabixCore/src/Controller/BusinessController.php +++ b/hesabixCore/src/Controller/BusinessController.php @@ -540,6 +540,7 @@ class BusinessController extends AbstractController 'plugAccproRfsell' => true, 'plugAccproRfbuy' => true, 'plugAccproCloseYear' => true, + 'plugAccproPresell' => true, 'plugRepservice' => true, ]; } elseif ($perm) { @@ -581,10 +582,10 @@ class BusinessController extends AbstractController 'plugAccproRfbuy' => $perm->isPlugAccproRfbuy(), 'plugAccproCloseYear' => $perm->isPlugAccproCloseYear(), 'plugRepservice' => $perm->isPlugRepservice(), + 'plugAccproPresell' => $perm->isPlugAccproPresell(), ]; } return $this->json($result); - return $this->json(['result' => -1]); } #[Route('/api/business/save/user/permissions', name: 'api_business_save_user_permission')] @@ -646,6 +647,7 @@ class BusinessController extends AbstractController $perm->setPlugAccproCloseYear($params['plugAccproCloseYear']); $perm->setPlugAccproRfbuy($params['plugAccproRfbuy']); $perm->setPlugAccproRfsell($params['plugAccproRfsell']); + $perm->setPlugAccproPresell($params['plugAccproPresell']); $perm->setPlugAccproAccounting($params['plugAccproAccounting']); $perm->setPlugRepservice($params['plugRepservice']); $entityManager->persist($perm); diff --git a/hesabixCore/src/Controller/CommodityController.php b/hesabixCore/src/Controller/CommodityController.php index 43896e2..0a3a4e7 100644 --- a/hesabixCore/src/Controller/CommodityController.php +++ b/hesabixCore/src/Controller/CommodityController.php @@ -31,6 +31,13 @@ use Psr\Log\LoggerInterface; class CommodityController extends AbstractController { + private const DEFAULT_ROOT_CATEGORY = 'دسته بندی ها'; + private const DEFAULT_NO_CATEGORY = 'بدون دسته‌بندی'; + + private function isDefaultCategoryName(string $name): bool + { + return $name === self::DEFAULT_ROOT_CATEGORY || $name === self::DEFAULT_NO_CATEGORY; + } #[Route('/api/commodities/search', name: 'search_commodities')] public function searchCommodities( @@ -1063,6 +1070,15 @@ class CommodityController extends AbstractController } if (!array_key_exists('upper', $params) || !array_key_exists('text', $params)) return $this->json(['result' => -1]); + + if ($this->isDefaultCategoryName($params['text'])) { + return $this->json([ + 'result' => 4, + 'message' => 'این نام برای دسته‌بندی مجاز نیست', + 'errorCode' => 'DEFAULT_CATEGORY_NAME' + ]); + } + $upper = $entityManager->getRepository(CommodityCat::class)->find($params['upper']); if ($upper) { if ($upper->getBid() == $acc['bid']) { @@ -1090,9 +1106,26 @@ class CommodityController extends AbstractController } if (!array_key_exists('id', $params) || !array_key_exists('text', $params)) return $this->json(['result' => -1]); + + if ($this->isDefaultCategoryName($params['text'])) { + return $this->json([ + 'result' => 4, + 'message' => 'این نام برای دسته‌بندی مجاز نیست', + 'errorCode' => 'DEFAULT_CATEGORY_NAME' + ]); + } + $node = $entityManager->getRepository(CommodityCat::class)->find($params['id']); if ($node) { if ($node->getBid() == $acc['bid']) { + // بررسی دسته‌بندی پیش‌فرض + if ($this->isDefaultCategoryName($node->getName())) { + return $this->json([ + 'result' => 4, + 'message' => 'ویرایش دسته‌بندی پیش‌فرض مجاز نیست', + 'errorCode' => 'DEFAULT_CATEGORY_EDIT' + ]); + } $node->setName($params['text']); $entityManager->persist($node); $entityManager->flush(); @@ -1150,7 +1183,7 @@ class CommodityController extends AbstractController public function createDefaultCat(Business $bid, EntityManagerInterface $en): array { $item = new CommodityCat(); - $item->setName('دسته بندی ها'); + $item->setName(self::DEFAULT_ROOT_CATEGORY); $item->setUpper(null); $item->setBid($bid); $item->setRoot(true); @@ -1160,7 +1193,7 @@ class CommodityController extends AbstractController $child = new CommodityCat(); $child->setUpper($item->getId()); $child->setBid($bid); - $child->setName('بدون دسته‌بندی'); + $child->setName(self::DEFAULT_NO_CATEGORY); $en->persist($child); $en->flush(); return [$item, $child]; @@ -1522,4 +1555,64 @@ class CommodityController extends AbstractController $log->insert('کالا/خدمات', 'قیمت تعدادی از کالا‌ها به صورت گروهی ویرایش شد.', $this->getUser(), $acc['bid']->getId()); return $this->json($extractor->operationSuccess()); } + + #[Route('/api/commodity/cat/delete', name: 'app_commodity_cat_delete', methods: ['POST'])] + public function app_commodity_cat_delete( + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager + ): JsonResponse { + $acc = $access->hasRole('commodity'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent(), true); + if (!isset($params['id'])) { + return $this->json(['Success' => false, 'message' => 'شناسه دسته‌بندی ارسال نشده است'], 400); + } + + $category = $entityManager->getRepository(CommodityCat::class)->findOneBy([ + 'bid' => $acc['bid'], + 'id' => $params['id'] + ]); + + if (!$category) { + return $this->json(['Success' => false, 'message' => 'دسته‌بندی یافت نشد'], 404); + } + + // بررسی دسته‌بندی پیش‌فرض + if ($this->isDefaultCategoryName($category->getName())) { + return $this->json([ + 'Success' => false, + 'message' => 'حذف دسته‌بندی پیش‌فرض مجاز نیست', + 'errorCode' => 'DEFAULT_CATEGORY_DELETE' + ], 400); + } + + // بررسی دسته‌بندی ریشه + if ($category->isRoot()) { + return $this->json(['Success' => false, 'message' => 'دسته‌بندی ریشه قابل حذف نیست'], 400); + } + + // بررسی وجود زیرمجموعه + $hasChildren = $this->hasChild($entityManager, $category); + if ($hasChildren) { + return $this->json(['Success' => false, 'message' => 'این دسته‌بندی دارای زیرمجموعه است و قابل حذف نیست'], 400); + } + + // بررسی وجود کالا در این دسته‌بندی + $hasCommodities = $entityManager->getRepository(Commodity::class)->findOneBy(['cat' => $category]); + if ($hasCommodities) { + return $this->json(['Success' => false, 'message' => 'این دسته‌بندی دارای کالا است و قابل حذف نیست'], 400); + } + + $catName = $category->getName(); + $entityManager->remove($category); + $entityManager->flush(); + + $log->insert('کالا/خدمات', 'دسته‌بندی با نام ' . $catName . ' حذف شد.', $this->getUser(), $acc['bid']->getId()); + return $this->json(['Success' => true, 'message' => 'دسته‌بندی با موفقیت حذف شد']); + } } diff --git a/hesabixCore/src/Controller/CostController.php b/hesabixCore/src/Controller/CostController.php index be3ed66..379f19b 100644 --- a/hesabixCore/src/Controller/CostController.php +++ b/hesabixCore/src/Controller/CostController.php @@ -20,7 +20,7 @@ use App\Entity\Salary; use App\Entity\Person; use App\Service\Log; use Doctrine\Common\Collections\ArrayCollection; - +use App\Repository\HesabdariTableRepository; class CostController extends AbstractController { #[Route('/api/cost/dashboard/data', name: 'app_cost_dashboard_data', methods: ['GET'])] @@ -180,6 +180,7 @@ class CostController extends AbstractController Request $request, Access $access, EntityManagerInterface $entityManager, + HesabdariTableRepository $hesabdariTableRepository, Jdate $jdate ): JsonResponse { $acc = $access->hasRole('cost'); @@ -189,22 +190,24 @@ class CostController extends AbstractController $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'] ?? 'cost'; - // تنظیم پارامترهای صفحه‌بندی + // 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') @@ -214,14 +217,12 @@ class CostController extends AbstractController ->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('d.hesabdariRows', 'r') - ->leftJoin('r.person', 'p') - ->leftJoin('r.ref', 't') + $queryBuilder->leftJoin('r.person', 'p') ->andWhere( $queryBuilder->expr()->orX( 'd.code LIKE :search', @@ -229,13 +230,25 @@ class CostController extends AbstractController 'd.date LIKE :search', 'd.amount LIKE :search', 'p.nikename LIKE :search', - 't.name LIKE :search' + 't.name LIKE :search', + 't.code LIKE :search' ) ) ->setParameter('search', "%{$searchValue}%"); } - // فیلتر زمانی + // Cost center filter + if (isset($filters['account']) && $filters['account'] !== '67') { + $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']) { @@ -262,31 +275,28 @@ class CostController extends AbstractController ->setParameter('dateTo', $filters['dateTo']); } break; - case 'all': - default: - // بدون فیلتر زمانی اضافه - 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); @@ -305,9 +315,9 @@ class CostController extends AbstractController 'submitter' => $doc['submitter'], ]; - // دریافت اطلاعات مرکز هزینه و مبلغ + // Get cost center details $costDetails = $entityManager->createQueryBuilder() - ->select('t.name as center_name, r.bd as amount, r.des as des') + ->select('t.name as center_name, t.code as center_code, r.bd as amount, r.des as des') ->from('App\Entity\HesabdariRow', 'r') ->join('r.ref', 't') ->where('r.doc = :docId') @@ -319,12 +329,13 @@ class CostController extends AbstractController $item['costCenters'] = array_map(function ($detail) { return [ 'name' => $detail['center_name'], + 'code' => $detail['center_code'], 'amount' => (int) $detail['amount'], 'des' => $detail['des'], ]; }, $costDetails); - // دریافت اطلاعات شخص مرتبط + // Get related person info $personInfo = $entityManager->createQueryBuilder() ->select('p.id, p.nikename, p.code') ->from('App\Entity\HesabdariRow', 'r') diff --git a/hesabixCore/src/Controller/HesabdariController.php b/hesabixCore/src/Controller/HesabdariController.php index 7605c78..e6542b6 100644 --- a/hesabixCore/src/Controller/HesabdariController.php +++ b/hesabixCore/src/Controller/HesabdariController.php @@ -1143,32 +1143,36 @@ class HesabdariController extends AbstractController throw $this->createAccessDeniedException(); } - $depth = (int) $request->query->get('depth', 2); // عمق پیش‌فرض 2 - $rootId = (int) $request->query->get('rootId', 1); // گره ریشه پیش‌فرض + $rootCode = $request->query->get('rootCode'); + + if (!$rootCode) { + return $this->json(['Success' => false, 'message' => 'کد ریشه مشخص نشده است'], 400); + } + + // جستجوی ریشه بر اساس code + $root = $entityManager->getRepository(HesabdariTable::class)->findOneBy([ + 'code' => $rootCode, + 'bid' => [$acc['bid']->getId(), null] + ]); - $root = $entityManager->getRepository(HesabdariTable::class)->find($rootId); if (!$root) { return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404); } - $buildTree = function ($node, $depth, $currentDepth = 0) use ($entityManager, $acc, &$buildTree) { - if ($currentDepth >= $depth) { - return null; - } - + // تابع بازگشتی برای ساخت درخت + $buildTree = function ($node) use ($entityManager, $acc, &$buildTree) { $children = $entityManager->getRepository(HesabdariTable::class)->findBy([ 'upper' => $node, 'bid' => [$acc['bid']->getId(), null], - ]); + ], ['code' => 'ASC']); // مرتب‌سازی بر اساس کد $result = []; foreach ($children as $child) { $childData = [ - 'id' => $child->getId(), - 'name' => $child->getName(), 'code' => $child->getCode(), + 'name' => $child->getName(), 'type' => $child->getType(), - 'children' => $buildTree($child, $depth, $currentDepth + 1), + 'children' => $buildTree($child) ]; $result[] = $childData; } @@ -1176,12 +1180,12 @@ class HesabdariController extends AbstractController return $result; }; + // ساخت درخت کامل $tree = [ - 'id' => $root->getId(), - 'name' => $root->getName(), 'code' => $root->getCode(), + 'name' => $root->getName(), 'type' => $root->getType(), - 'children' => $buildTree($root, $depth), + 'children' => $buildTree($root) ]; return $this->json(['Success' => true, 'data' => $tree]); diff --git a/hesabixCore/src/Controller/PersonsController.php b/hesabixCore/src/Controller/PersonsController.php index 48ae409..c5c8ef5 100644 --- a/hesabixCore/src/Controller/PersonsController.php +++ b/hesabixCore/src/Controller/PersonsController.php @@ -317,6 +317,9 @@ class PersonsController extends AbstractController $person->setPrelabel($prelabel); } } + elseif ($params['prelabel'] == null) { + $person->setPrelabel(null); + } } //inset cards if (array_key_exists('accounts', $params)) { @@ -995,11 +998,6 @@ class PersonsController extends AbstractController $params = json_decode($content, true); } - // پارامترهای صفحه‌بندی - $page = $params['page'] ?? 1; - $limit = $params['limit'] ?? 10; - $offset = ($page - 1) * $limit; - $queryBuilder = $entityManager->getRepository(HesabdariDoc::class)->createQueryBuilder('d') ->where('d.bid = :bid') ->andWhere('d.type = :type') @@ -1022,12 +1020,23 @@ class PersonsController extends AbstractController ->getQuery() ->getSingleScalarResult(); - // دریافت داده‌های صفحه فعلی - $items = $queryBuilder->select('d') - ->setFirstResult($offset) - ->setMaxResults($limit) - ->getQuery() - ->getResult(); + // اگر درخواست با صفحه‌بندی است + 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) { @@ -1048,16 +1057,16 @@ class PersonsController extends AbstractController 'bid' => $acc['bid'], 'items' => $items, 'totalItems' => $totalItems, - 'currentPage' => $page, - 'totalPages' => ceil($totalItems / $limit) + 'currentPage' => $params['page'] ?? 1, + 'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1 ]) ); return $this->json([ 'id' => $pid, 'totalItems' => $totalItems, - 'currentPage' => $page, - 'totalPages' => ceil($totalItems / $limit) + 'currentPage' => $params['page'] ?? 1, + 'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1 ]); } diff --git a/hesabixCore/src/Controller/PreinvoiceController.php b/hesabixCore/src/Controller/PreinvoiceController.php index da190f1..5021363 100644 --- a/hesabixCore/src/Controller/PreinvoiceController.php +++ b/hesabixCore/src/Controller/PreinvoiceController.php @@ -88,7 +88,7 @@ class PreinvoiceController extends AbstractController #[Route('/api/preinvoice/save', name: 'app_preinvoice_save')] public function savePreinvoice(Access $access,Provider $provider, Log $log, EntityManagerInterface $entityManager, Request $request): JsonResponse { - $acc = $access->hasRole('preinvoice'); + $acc = $access->hasRole('plugAccproPresell'); if (!$acc) { return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403); } @@ -170,7 +170,7 @@ class PreinvoiceController extends AbstractController #[Route('/api/preinvoice/delete/{id}', name: 'app_preinvoice_delete')] public function deletePreinvoice(Access $access, Log $log, EntityManagerInterface $entityManager, int $id): JsonResponse { - $acc = $access->hasRole('preinvoice'); + $acc = $access->hasRole('plugAccproPresell'); if (!$acc) { return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403); } @@ -207,7 +207,7 @@ class PreinvoiceController extends AbstractController #[Route('/api/preinvoice/docs/search', name: 'app_presell_search')] public function searchPreinvoices(Access $access, EntityManagerInterface $entityManager): JsonResponse { - $acc = $access->hasRole('preinvoice'); + $acc = $access->hasRole('plugAccproPresell'); if (!$acc) { return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403); } @@ -244,7 +244,7 @@ class PreinvoiceController extends AbstractController #[Route('/api/preinvoice/remove/group', name: 'app_presell_delete_group')] public function deletePreinvoiceGroup(Log $log, Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse { - $acc = $access->hasRole('preinvoice'); + $acc = $access->hasRole('plugAccproPresell'); if (!$acc) { return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403); } @@ -289,7 +289,7 @@ class PreinvoiceController extends AbstractController $params = json_decode($content, true); } - $acc = $access->hasRole('preinvoice'); + $acc = $access->hasRole('plugAccproPresell'); if (!$acc) { throw $this->createAccessDeniedException(); } @@ -312,7 +312,9 @@ class PreinvoiceController extends AbstractController 'taxInfo' => true, 'discountInfo' => true, 'note' => true, - 'paper' => 'A4-L' + 'paper' => 'A4-L', + 'invoiceIndex' => false, + 'businessStamp' => false ]; if (array_key_exists('printOptions', $params)) { @@ -334,6 +336,12 @@ class PreinvoiceController extends AbstractController if (array_key_exists('paper', $params['printOptions'])) { $printOptions['paper'] = $params['printOptions']['paper']; } + if (array_key_exists('invoiceIndex', $params['printOptions'])) { + $printOptions['invoiceIndex'] = $params['printOptions']['invoiceIndex']; + } + if (array_key_exists('businessStamp', $params['printOptions'])) { + $printOptions['businessStamp'] = $params['printOptions']['businessStamp']; + } } $note = ''; diff --git a/hesabixCore/src/Controller/PrintersController.php b/hesabixCore/src/Controller/PrintersController.php index 5fe1223..ef40892 100644 --- a/hesabixCore/src/Controller/PrintersController.php +++ b/hesabixCore/src/Controller/PrintersController.php @@ -57,6 +57,8 @@ class PrintersController extends AbstractController $temp['sell']['noteString'] = $settings->getSellNoteString(); $temp['sell']['pays'] = $settings->isSellPays(); $temp['sell']['paper'] = $settings->getSellPaper(); + $temp['sell']['businessStamp'] = $settings->isSellBusinessStamp(); + $temp['sell']['invoiceIndex'] = $settings->isSellInvoiceIndex(); if (!$temp['sell']['paper']) { $temp['sell']['paper'] = 'A4-L'; } @@ -135,6 +137,8 @@ class PrintersController extends AbstractController $settings->setSellNoteString($params['sell']['noteString']); $settings->setSellPays($params['sell']['pays']); $settings->setSellPaper($params['sell']['paper']); + $settings->setSellBusinessStamp($params['sell']['businessStamp']); + $settings->setSellInvoiceIndex($params['sell']['invoiceIndex']); if ($params['buy']['bidInfo'] == null) { $settings->setBuyBidInfo(false); } else { diff --git a/hesabixCore/src/Controller/SellController.php b/hesabixCore/src/Controller/SellController.php index a198e14..d1809d1 100644 --- a/hesabixCore/src/Controller/SellController.php +++ b/hesabixCore/src/Controller/SellController.php @@ -368,262 +368,262 @@ class SellController extends AbstractController } #[Route('/api/sell/docs/search', name: 'app_sell_docs_search', methods: ['POST'])] -public function searchSellDocs( - 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'] ?? ''; - $page = max(1, $params['page'] ?? 1); - $perPage = max(1, min(100, $params['perPage'] ?? 10)); - $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('u.fullName as submitter') - ->addSelect('l.code as labelCode, l.label as labelLabel') - ->from(HesabdariDoc::class, 'd') - ->leftJoin('d.submitter', 'u') - ->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); - } - - // فیلدهای معتبر برای مرتب‌سازی توی دیتابیس - $validDbFields = [ - 'id' => 'd.id', - 'dateSubmit' => 'd.dateSubmit', - 'date' => 'd.date', - 'type' => 'd.type', - 'code' => 'd.code', - 'des' => 'd.des', - 'amount' => 'd.amount', - 'mdate' => 'd.mdate', - 'plugin' => 'd.plugin', - 'refData' => 'd.refData', - 'shortlink' => 'd.shortlink', - 'status' => 'd.status', - 'submitter' => 'u.fullName', - 'label' => 'l.label', // از InvoiceLabel - ]; - - // اعمال مرتب‌سازی توی دیتابیس - 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 (isset($validDbFields[$key])) { - $queryBuilder->addOrderBy($validDbFields[$key], $direction); - } - // اگه کلید معتبر نبود، نادیده گرفته می‌شه + public function searchSellDocs( + Provider $provider, + Request $request, + Access $access, + Log $log, + EntityManagerInterface $entityManager, + Jdate $jdate + ): JsonResponse { + $acc = $access->hasRole('sell'); + if (!$acc) { + throw $this->createAccessDeniedException(); } - } else { - $queryBuilder->orderBy('d.id', 'DESC'); - } - $totalItemsQuery = clone $queryBuilder; - $totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)') - ->getQuery() - ->getSingleScalarResult(); + $params = json_decode($request->getContent(), true) ?? []; + $searchTerm = $params['search'] ?? ''; + $page = max(1, $params['page'] ?? 1); + $perPage = max(1, min(100, $params['perPage'] ?? 10)); + $types = $params['types'] ?? []; + $dateFilter = $params['dateFilter'] ?? 'all'; + $sortBy = $params['sortBy'] ?? []; - $queryBuilder->setFirstResult(($page - 1) * $perPage) - ->setMaxResults($perPage); + $queryBuilder = $entityManager->createQueryBuilder() + ->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount') + ->addSelect('u.fullName as submitter') + ->addSelect('l.code as labelCode, l.label as labelLabel') + ->from(HesabdariDoc::class, 'd') + ->leftJoin('d.submitter', 'u') + ->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']); - $docs = $queryBuilder->getQuery()->getArrayResult(); + // اعمال فیلترهای تاریخ + $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); + } - $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, + 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); + } + + // فیلدهای معتبر برای مرتب‌سازی توی دیتابیس + $validDbFields = [ + 'id' => 'd.id', + 'dateSubmit' => 'd.dateSubmit', + 'date' => 'd.date', + 'type' => 'd.type', + 'code' => 'd.code', + 'des' => 'd.des', + 'amount' => 'd.amount', + 'mdate' => 'd.mdate', + 'plugin' => 'd.plugin', + 'refData' => 'd.refData', + 'shortlink' => 'd.shortlink', + 'status' => 'd.status', + 'submitter' => 'u.fullName', + 'label' => 'l.label', // از InvoiceLabel ]; - $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; + // اعمال مرتب‌سازی توی دیتابیس + 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 (isset($validDbFields[$key])) { + $queryBuilder->addOrderBy($validDbFields[$key], $direction); + } + // اگه کلید معتبر نبود، نادیده گرفته می‌شه + } + } else { + $queryBuilder->orderBy('d.id', 'DESC'); + } - // استفاده از SQL خام برای محاسبه پرداختی‌ها - $sql = " + $totalItemsQuery = clone $queryBuilder; + $totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)') + ->getQuery() + ->getSingleScalarResult(); + + $queryBuilder->setFirstResult(($page - 1) * $perPage) + ->setMaxResults($perPage); + + $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, + ]; + + $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(); + $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; + $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; + $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(); + $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']; + }); + } } } - $dataTemp[] = $item; + return $this->json([ + 'items' => $dataTemp, + 'total' => (int) $totalItems, + 'page' => $page, + 'perPage' => $perPage, + ]); } - // مرتب‌سازی توی 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']; - }); - } - } - } - - return $this->json([ - 'items' => $dataTemp, - 'total' => (int) $totalItems, - 'page' => $page, - 'perPage' => $perPage, - ]); -} - -// متد calculateProfit بدون تغییر -private function calculateProfit(int $docId, array $acc, EntityManagerInterface $entityManager): int -{ - $profit = 0; - $rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $docId]); - foreach ($rows as $item) { - if ($item->getCommdityCount() && $item->getBs()) { - $commodityId = $item->getCommodity() ? $item->getCommodity()->getId() : null; - if ($acc['bid']->getProfitCalctype() === 'lis') { - if ($commodityId) { - $last = $entityManager->getRepository(HesabdariRow::class) - ->findOneBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']); - if ($last) { - $price = $last->getBd() / $last->getCommdityCount(); - $profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount()); + // متد calculateProfit بدون تغییر + private function calculateProfit(int $docId, array $acc, EntityManagerInterface $entityManager): int + { + $profit = 0; + $rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $docId]); + foreach ($rows as $item) { + if ($item->getCommdityCount() && $item->getBs()) { + $commodityId = $item->getCommodity() ? $item->getCommodity()->getId() : null; + if ($acc['bid']->getProfitCalctype() === 'lis') { + if ($commodityId) { + $last = $entityManager->getRepository(HesabdariRow::class) + ->findOneBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']); + if ($last) { + $price = $last->getBd() / $last->getCommdityCount(); + $profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount()); + } else { + $profit += $item->getBs(); + } + } else { + $profit += $item->getBs(); + } + } elseif ($acc['bid']->getProfitCalctype() === 'simple') { + if ($item->getCommodity() && $item->getCommodity()->getPriceSell() !== null && $item->getCommodity()->getPriceBuy() !== null) { + $profit += (($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceBuy()) * $item->getCommdityCount()); } else { $profit += $item->getBs(); } } else { - $profit += $item->getBs(); - } - } elseif ($acc['bid']->getProfitCalctype() === 'simple') { - if ($item->getCommodity() && $item->getCommodity()->getPriceSell() !== null && $item->getCommodity()->getPriceBuy() !== null) { - $profit += (($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceBuy()) * $item->getCommdityCount()); - } else { - $profit += $item->getBs(); - } - } else { - if ($commodityId) { - $lasts = $entityManager->getRepository(HesabdariRow::class) - ->findBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']); - $avg = array_sum(array_map(fn($last) => $last->getBd(), $lasts)); - $count = array_sum(array_map(fn($last) => $last->getCommdityCount(), $lasts)); - if ($count != 0) { - $price = $avg / $count; - $profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount()); + if ($commodityId) { + $lasts = $entityManager->getRepository(HesabdariRow::class) + ->findBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']); + $avg = array_sum(array_map(fn($last) => $last->getBd(), $lasts)); + $count = array_sum(array_map(fn($last) => $last->getCommdityCount(), $lasts)); + if ($count != 0) { + $price = $avg / $count; + $profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount()); + } else { + $profit += $item->getBs(); + } } else { $profit += $item->getBs(); } - } else { - $profit += $item->getBs(); } } } + return round($profit); } - return round($profit); -} #[Route('/api/sell/rows/{code}', name: 'app_sell_rows', methods: ['GET'])] public function getSellRows( @@ -678,15 +678,25 @@ private function calculateProfit(int $docId, array $acc, EntityManagerInterface #[Route('/api/sell/print/invoice', name: 'app_sell_print_invoice')] public function app_sell_print_invoice(Printers $printers, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse { - $params = []; - if ($content = $request->getContent()) { - $params = json_decode($content, true); - } - $acc = $access->hasRole('sell'); if (!$acc) throw $this->createAccessDeniedException(); + $params = json_decode($request->getContent(), true); + $printOptions = $params['printOptions'] ?? []; + + // اضافه کردن کلیدهای پیش‌فرض + $printOptions = array_merge([ + 'note' => true, + 'bidInfo' => true, + 'taxInfo' => true, + 'discountInfo' => true, + 'pays' => false, + 'paper' => 'A4-L', + 'invoiceIndex' => false, + 'businessStamp' => false + ], $printOptions); + $doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ 'bid' => $acc['bid'], 'code' => $params['code'], @@ -735,6 +745,12 @@ private function calculateProfit(int $docId, array $acc, EntityManagerInterface if (array_key_exists('paper', $params['printOptions'])) { $printOptions['paper'] = $params['printOptions']['paper']; } + if (array_key_exists('invoiceIndex', $params['printOptions'])) { + $printOptions['invoiceIndex'] = $params['printOptions']['invoiceIndex']; + } + if (array_key_exists('businessStamp', $params['printOptions'])) { + $printOptions['businessStamp'] = $params['printOptions']['businessStamp']; + } } $note = ''; $printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]); diff --git a/hesabixCore/src/Controller/UserController.php b/hesabixCore/src/Controller/UserController.php index 94f7d50..0a7da72 100644 --- a/hesabixCore/src/Controller/UserController.php +++ b/hesabixCore/src/Controller/UserController.php @@ -199,6 +199,7 @@ class UserController extends AbstractController $temp = []; $temp['name'] = $perm->getUser()->getFullName(); $temp['email'] = $perm->getUser()->getEmail(); + $temp['mobile'] = $perm->getUser()->getMobile(); $temp['owner'] = $perm->isOwner(); $out[] = $temp; } diff --git a/hesabixCore/src/Entity/BackBuiltModule.php b/hesabixCore/src/Entity/BackBuiltModule.php new file mode 100644 index 0000000..8f17e16 --- /dev/null +++ b/hesabixCore/src/Entity/BackBuiltModule.php @@ -0,0 +1,112 @@ +id; + } + + 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 getCode(): ?string + { + return $this->code; + } + + public function setCode(?string $code): static + { + $this->code = $code; + + return $this; + } + + public function isLocked(): ?bool + { + return $this->locked; + } + + public function setLocked(?bool $locked): static + { + $this->locked = $locked; + + return $this; + } + + public function isPublic(): ?bool + { + return $this->public; + } + + public function setPublic(?bool $public): static + { + $this->public = $public; + + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(string $type): static + { + $this->type = $type; + + return $this; + } +} diff --git a/hesabixCore/src/Entity/HesabdariRow.php b/hesabixCore/src/Entity/HesabdariRow.php index 885bd80..3044604 100644 --- a/hesabixCore/src/Entity/HesabdariRow.php +++ b/hesabixCore/src/Entity/HesabdariRow.php @@ -59,8 +59,8 @@ class HesabdariRow #[Ignore] private ?Commodity $commodity = null; - #[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 0, nullable: true)] - private ?int $commdityCount = null; + #[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 4, nullable: true)] + private ?float $commdityCount = null; #[ORM\ManyToOne(inversedBy: 'hesabdariRows')] #[Ignore] @@ -220,15 +220,14 @@ class HesabdariRow return $this; } - public function getCommdityCount(): ?int + public function getCommdityCount(): ?float { return $this->commdityCount; } - public function setCommdityCount(?int $commdityCount): self + public function setCommdityCount(?float $commdityCount): self { $this->commdityCount = $commdityCount; - return $this; } diff --git a/hesabixCore/src/Entity/Permission.php b/hesabixCore/src/Entity/Permission.php index a0439ff..96ab27a 100644 --- a/hesabixCore/src/Entity/Permission.php +++ b/hesabixCore/src/Entity/Permission.php @@ -120,6 +120,9 @@ class Permission #[ORM\Column(nullable: true)] private ?bool $plugRepservice = null; + #[ORM\Column(nullable: true)] + private ?bool $plugAccproPresell = null; + public function getId(): ?int { return $this->id; @@ -545,4 +548,16 @@ class Permission return $this; } + public function isPlugAccproPresell(): ?bool + { + return $this->plugAccproPresell; + } + + public function setPlugAccproPresell(?bool $plugAccproPresell): static + { + $this->plugAccproPresell = $plugAccproPresell; + + return $this; + } + } diff --git a/hesabixCore/src/Entity/PrintOptions.php b/hesabixCore/src/Entity/PrintOptions.php index 790cc1c..826c108 100644 --- a/hesabixCore/src/Entity/PrintOptions.php +++ b/hesabixCore/src/Entity/PrintOptions.php @@ -126,6 +126,12 @@ class PrintOptions #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $rightFooter = null; + #[ORM\Column(nullable: true)] + private ?bool $sellInvoiceIndex = null; + + #[ORM\Column(nullable: true)] + private ?bool $sellBusinessStamp = null; + public function getId(): ?int { return $this->id; @@ -574,4 +580,26 @@ class PrintOptions return $this; } + + public function isSellInvoiceIndex(): ?bool + { + return $this->sellInvoiceIndex; + } + + public function setSellInvoiceIndex(bool $sellInvoiceIndex): self + { + $this->sellInvoiceIndex = $sellInvoiceIndex; + return $this; + } + + public function isSellBusinessStamp(): ?bool + { + return $this->sellBusinessStamp; + } + + public function setSellBusinessStamp(bool $sellBusinessStamp): self + { + $this->sellBusinessStamp = $sellBusinessStamp; + return $this; + } } diff --git a/hesabixCore/src/Entity/User.php b/hesabixCore/src/Entity/User.php index cf5a760..ac5acab 100644 --- a/hesabixCore/src/Entity/User.php +++ b/hesabixCore/src/Entity/User.php @@ -125,6 +125,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\OneToMany(mappedBy: 'submitter', targetEntity: AccountingPackageOrder::class, orphanRemoval: true)] private Collection $accountingPackageOrders; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: BackBuiltModule::class, mappedBy: 'submitter', orphanRemoval: true)] + private Collection $backBuiltModules; + public function __construct() { $this->userTokens = new ArrayCollection(); @@ -148,6 +154,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface $this->preInvoiceDocs = new ArrayCollection(); $this->dashboardSettings = new ArrayCollection(); $this->accountingPackageOrders = new ArrayCollection(); + $this->backBuiltModules = new ArrayCollection(); } public function getId(): ?int @@ -923,4 +930,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + /** + * @return Collection + */ + public function getBackBuiltModules(): Collection + { + return $this->backBuiltModules; + } + + public function addBackBuiltModule(BackBuiltModule $backBuiltModule): static + { + if (!$this->backBuiltModules->contains($backBuiltModule)) { + $this->backBuiltModules->add($backBuiltModule); + $backBuiltModule->setSubmitter($this); + } + + return $this; + } + + public function removeBackBuiltModule(BackBuiltModule $backBuiltModule): static + { + if ($this->backBuiltModules->removeElement($backBuiltModule)) { + // set the owning side to null (unless already changed) + if ($backBuiltModule->getSubmitter() === $this) { + $backBuiltModule->setSubmitter(null); + } + } + + return $this; + } } diff --git a/hesabixCore/src/Repository/BackBuiltModuleRepository.php b/hesabixCore/src/Repository/BackBuiltModuleRepository.php new file mode 100644 index 0000000..c8bdb56 --- /dev/null +++ b/hesabixCore/src/Repository/BackBuiltModuleRepository.php @@ -0,0 +1,43 @@ + + */ +class BackBuiltModuleRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, BackBuiltModule::class); + } + + // /** + // * @return BackBuiltModule[] Returns an array of BackBuiltModule objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('b') + // ->andWhere('b.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('b.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?BackBuiltModule + // { + // return $this->createQueryBuilder('b') + // ->andWhere('b.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/hesabixCore/src/Repository/HesabdariTableRepository.php b/hesabixCore/src/Repository/HesabdariTableRepository.php index aaf525a..cb4b4b1 100644 --- a/hesabixCore/src/Repository/HesabdariTableRepository.php +++ b/hesabixCore/src/Repository/HesabdariTableRepository.php @@ -3,6 +3,7 @@ namespace App\Repository; use App\Entity\HesabdariTable; +use App\Entity\Business; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -67,4 +68,58 @@ class HesabdariTableRepository extends ServiceEntityRepository ->getQuery() ->getOneOrNullResult(); } + + /** + * Find all sub-account codes recursively, filtered by business ID. + * + * @param string $accountCode The account code to start with + * @param int|Business|null $business The business ID or Business entity (or null for no business filter) + * @return array List of unique account codes + */ + public function findAllSubAccountCodes(string $accountCode, $business): array + { + $businessId = $business instanceof Business ? $business->getId() : $business; + $result = [$accountCode]; + + // Find the parent node by account code and business filter + $qb = $this->createQueryBuilder('t') + ->select('t.id') + ->where('t.code = :code') + ->setParameter('code', $accountCode); + + // Apply business filter + if ($businessId !== null) { + $qb->andWhere('t.bid IS NULL OR t.bid = :businessId') + ->setParameter('businessId', $businessId); + } + + $parent = $qb->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + + if (!$parent) { + return array_unique($result); + } + + // Find direct children using the upper relationship and business filter + $qb = $this->createQueryBuilder('t') + ->select('t.code') + ->where('t.upper = :parentId') + ->setParameter('parentId', $parent['id']); + + // Apply business filter for children + if ($businessId !== null) { + $qb->andWhere('t.bid IS NULL OR t.bid = :businessId') + ->setParameter('businessId', $businessId); + } + + $subAccounts = $qb->getQuery()->getResult(); + + // Recursively find sub-accounts for each child + foreach ($subAccounts as $subAccount) { + $result = array_merge($result, $this->findAllSubAccountCodes($subAccount['code'], $businessId)); + } + + return array_unique($result); + } } diff --git a/hesabixCore/templates/pdf/printers/preinvoice.html.twig b/hesabixCore/templates/pdf/printers/preinvoice.html.twig index 29a81fa..540bafb 100644 --- a/hesabixCore/templates/pdf/printers/preinvoice.html.twig +++ b/hesabixCore/templates/pdf/printers/preinvoice.html.twig @@ -26,7 +26,9 @@ - + {% if printOptions.invoiceIndex is defined and printOptions.invoiceIndex %} + + {% endif %}

پیش فاکتور فروش کالا و خدمات

@@ -266,7 +268,9 @@

مهر و امضا فروشنده:


- + {% if printOptions.businessStamp is defined and printOptions.businessStamp %} + + {% endif %} diff --git a/hesabixCore/templates/pdf/printers/sell.html.twig b/hesabixCore/templates/pdf/printers/sell.html.twig index 9d24b0e..05bee97 100644 --- a/hesabixCore/templates/pdf/printers/sell.html.twig +++ b/hesabixCore/templates/pdf/printers/sell.html.twig @@ -26,7 +26,9 @@ - + {% if printOptions.invoiceIndex %} + + {% endif %}

صورتحساب فروش کالا و خدمات

@@ -214,7 +216,13 @@ {{ item.commdityCount }} {{ item.commodity.unit.name }} - {{ ((item.bs - item.tax + item.discount) / item.commdityCount) | number_format }} + + {% if item.commdityCount > 0 %} + {{ ((item.bs|number_format(0, '.', '') - item.tax|number_format(0, '.', '') + item.discount|number_format(0, '.', '')) / item.commdityCount) | number_format }} + {% else %} + 0 + {% endif %} + {% if printOptions.discountInfo %} {{ item.discount | number_format }} {% endif %} @@ -304,7 +312,9 @@ مهر و امضا فروشنده:
- + {% if printOptions.businessStamp %} + + {% endif %} diff --git a/webUI/src/components/PrintDialog.vue b/webUI/src/components/PrintDialog.vue new file mode 100644 index 0000000..40c4373 --- /dev/null +++ b/webUI/src/components/PrintDialog.vue @@ -0,0 +1,147 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/components/forms/HesabdariTreeView.vue b/webUI/src/components/forms/HesabdariTreeView.vue new file mode 100644 index 0000000..d869cc0 --- /dev/null +++ b/webUI/src/components/forms/HesabdariTreeView.vue @@ -0,0 +1,377 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/components/forms/TreeNode.vue b/webUI/src/components/forms/TreeNode.vue index c666475..7d87b7e 100644 --- a/webUI/src/components/forms/TreeNode.vue +++ b/webUI/src/components/forms/TreeNode.vue @@ -1,25 +1,36 @@ - + {{ $t('drawer.presells') }} {{ getShortcutKey('/acc/presell/list') }} @@ -883,8 +883,8 @@ export default { diff --git a/webUI/src/views/acc/commodity/cat/list.vue b/webUI/src/views/acc/commodity/cat/list.vue index 4ff4746..c96f457 100644 --- a/webUI/src/views/acc/commodity/cat/list.vue +++ b/webUI/src/views/acc/commodity/cat/list.vue @@ -1,115 +1,213 @@