bug fix in progress in AI
This commit is contained in:
parent
1537bf238f
commit
c8b2c53e2b
|
@ -540,6 +540,7 @@ class BusinessController extends AbstractController
|
||||||
'plugAccproRfsell' => true,
|
'plugAccproRfsell' => true,
|
||||||
'plugAccproRfbuy' => true,
|
'plugAccproRfbuy' => true,
|
||||||
'plugAccproCloseYear' => true,
|
'plugAccproCloseYear' => true,
|
||||||
|
'plugAccproPresell' => true,
|
||||||
'plugRepservice' => true,
|
'plugRepservice' => true,
|
||||||
];
|
];
|
||||||
} elseif ($perm) {
|
} elseif ($perm) {
|
||||||
|
@ -581,10 +582,10 @@ class BusinessController extends AbstractController
|
||||||
'plugAccproRfbuy' => $perm->isPlugAccproRfbuy(),
|
'plugAccproRfbuy' => $perm->isPlugAccproRfbuy(),
|
||||||
'plugAccproCloseYear' => $perm->isPlugAccproCloseYear(),
|
'plugAccproCloseYear' => $perm->isPlugAccproCloseYear(),
|
||||||
'plugRepservice' => $perm->isPlugRepservice(),
|
'plugRepservice' => $perm->isPlugRepservice(),
|
||||||
|
'plugAccproPresell' => $perm->isPlugAccproPresell(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return $this->json($result);
|
return $this->json($result);
|
||||||
return $this->json(['result' => -1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/business/save/user/permissions', name: 'api_business_save_user_permission')]
|
#[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->setPlugAccproCloseYear($params['plugAccproCloseYear']);
|
||||||
$perm->setPlugAccproRfbuy($params['plugAccproRfbuy']);
|
$perm->setPlugAccproRfbuy($params['plugAccproRfbuy']);
|
||||||
$perm->setPlugAccproRfsell($params['plugAccproRfsell']);
|
$perm->setPlugAccproRfsell($params['plugAccproRfsell']);
|
||||||
|
$perm->setPlugAccproPresell($params['plugAccproPresell']);
|
||||||
$perm->setPlugAccproAccounting($params['plugAccproAccounting']);
|
$perm->setPlugAccproAccounting($params['plugAccproAccounting']);
|
||||||
$perm->setPlugRepservice($params['plugRepservice']);
|
$perm->setPlugRepservice($params['plugRepservice']);
|
||||||
$entityManager->persist($perm);
|
$entityManager->persist($perm);
|
||||||
|
|
|
@ -31,6 +31,13 @@ use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class CommodityController extends AbstractController
|
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')]
|
#[Route('/api/commodities/search', name: 'search_commodities')]
|
||||||
public function searchCommodities(
|
public function searchCommodities(
|
||||||
|
@ -1063,6 +1070,15 @@ class CommodityController extends AbstractController
|
||||||
}
|
}
|
||||||
if (!array_key_exists('upper', $params) || !array_key_exists('text', $params))
|
if (!array_key_exists('upper', $params) || !array_key_exists('text', $params))
|
||||||
return $this->json(['result' => -1]);
|
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']);
|
$upper = $entityManager->getRepository(CommodityCat::class)->find($params['upper']);
|
||||||
if ($upper) {
|
if ($upper) {
|
||||||
if ($upper->getBid() == $acc['bid']) {
|
if ($upper->getBid() == $acc['bid']) {
|
||||||
|
@ -1090,9 +1106,26 @@ class CommodityController extends AbstractController
|
||||||
}
|
}
|
||||||
if (!array_key_exists('id', $params) || !array_key_exists('text', $params))
|
if (!array_key_exists('id', $params) || !array_key_exists('text', $params))
|
||||||
return $this->json(['result' => -1]);
|
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']);
|
$node = $entityManager->getRepository(CommodityCat::class)->find($params['id']);
|
||||||
if ($node) {
|
if ($node) {
|
||||||
if ($node->getBid() == $acc['bid']) {
|
if ($node->getBid() == $acc['bid']) {
|
||||||
|
// بررسی دستهبندی پیشفرض
|
||||||
|
if ($this->isDefaultCategoryName($node->getName())) {
|
||||||
|
return $this->json([
|
||||||
|
'result' => 4,
|
||||||
|
'message' => 'ویرایش دستهبندی پیشفرض مجاز نیست',
|
||||||
|
'errorCode' => 'DEFAULT_CATEGORY_EDIT'
|
||||||
|
]);
|
||||||
|
}
|
||||||
$node->setName($params['text']);
|
$node->setName($params['text']);
|
||||||
$entityManager->persist($node);
|
$entityManager->persist($node);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
@ -1150,7 +1183,7 @@ class CommodityController extends AbstractController
|
||||||
public function createDefaultCat(Business $bid, EntityManagerInterface $en): array
|
public function createDefaultCat(Business $bid, EntityManagerInterface $en): array
|
||||||
{
|
{
|
||||||
$item = new CommodityCat();
|
$item = new CommodityCat();
|
||||||
$item->setName('دسته بندی ها');
|
$item->setName(self::DEFAULT_ROOT_CATEGORY);
|
||||||
$item->setUpper(null);
|
$item->setUpper(null);
|
||||||
$item->setBid($bid);
|
$item->setBid($bid);
|
||||||
$item->setRoot(true);
|
$item->setRoot(true);
|
||||||
|
@ -1160,7 +1193,7 @@ class CommodityController extends AbstractController
|
||||||
$child = new CommodityCat();
|
$child = new CommodityCat();
|
||||||
$child->setUpper($item->getId());
|
$child->setUpper($item->getId());
|
||||||
$child->setBid($bid);
|
$child->setBid($bid);
|
||||||
$child->setName('بدون دستهبندی');
|
$child->setName(self::DEFAULT_NO_CATEGORY);
|
||||||
$en->persist($child);
|
$en->persist($child);
|
||||||
$en->flush();
|
$en->flush();
|
||||||
return [$item, $child];
|
return [$item, $child];
|
||||||
|
@ -1522,4 +1555,64 @@ class CommodityController extends AbstractController
|
||||||
$log->insert('کالا/خدمات', 'قیمت تعدادی از کالاها به صورت گروهی ویرایش شد.', $this->getUser(), $acc['bid']->getId());
|
$log->insert('کالا/خدمات', 'قیمت تعدادی از کالاها به صورت گروهی ویرایش شد.', $this->getUser(), $acc['bid']->getId());
|
||||||
return $this->json($extractor->operationSuccess());
|
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' => 'دستهبندی با موفقیت حذف شد']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use App\Entity\Salary;
|
||||||
use App\Entity\Person;
|
use App\Entity\Person;
|
||||||
use App\Service\Log;
|
use App\Service\Log;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use App\Repository\HesabdariTableRepository;
|
||||||
class CostController extends AbstractController
|
class CostController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/api/cost/dashboard/data', name: 'app_cost_dashboard_data', methods: ['GET'])]
|
#[Route('/api/cost/dashboard/data', name: 'app_cost_dashboard_data', methods: ['GET'])]
|
||||||
|
@ -180,6 +180,7 @@ class CostController extends AbstractController
|
||||||
Request $request,
|
Request $request,
|
||||||
Access $access,
|
Access $access,
|
||||||
EntityManagerInterface $entityManager,
|
EntityManagerInterface $entityManager,
|
||||||
|
HesabdariTableRepository $hesabdariTableRepository,
|
||||||
Jdate $jdate
|
Jdate $jdate
|
||||||
): JsonResponse {
|
): JsonResponse {
|
||||||
$acc = $access->hasRole('cost');
|
$acc = $access->hasRole('cost');
|
||||||
|
@ -189,22 +190,24 @@ class CostController extends AbstractController
|
||||||
|
|
||||||
$params = json_decode($request->getContent(), true) ?? [];
|
$params = json_decode($request->getContent(), true) ?? [];
|
||||||
|
|
||||||
// پارامترهای ورودی
|
// Input parameters
|
||||||
$filters = $params['filters'] ?? [];
|
$filters = $params['filters'] ?? [];
|
||||||
$pagination = $params['pagination'] ?? ['page' => 1, 'limit' => 10];
|
$pagination = $params['pagination'] ?? ['page' => 1, 'limit' => 10];
|
||||||
$sort = $params['sort'] ?? ['sortBy' => 'id', 'sortDesc' => true];
|
$sort = $params['sort'] ?? ['sortBy' => 'id', 'sortDesc' => true];
|
||||||
$type = $params['type'] ?? 'cost';
|
$type = $params['type'] ?? 'cost';
|
||||||
|
|
||||||
// تنظیم پارامترهای صفحهبندی
|
// Set pagination parameters
|
||||||
$page = max(1, $pagination['page'] ?? 1);
|
$page = max(1, $pagination['page'] ?? 1);
|
||||||
$limit = max(1, min(100, $pagination['limit'] ?? 10));
|
$limit = max(1, min(100, $pagination['limit'] ?? 10));
|
||||||
|
|
||||||
// ساخت کوئری پایه
|
// Build base query
|
||||||
$queryBuilder = $entityManager->createQueryBuilder()
|
$queryBuilder = $entityManager->createQueryBuilder()
|
||||||
->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount')
|
->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount')
|
||||||
->addSelect('u.fullName as submitter')
|
->addSelect('u.fullName as submitter')
|
||||||
->from('App\Entity\HesabdariDoc', 'd')
|
->from('App\Entity\HesabdariDoc', 'd')
|
||||||
->leftJoin('d.submitter', 'u')
|
->leftJoin('d.submitter', 'u')
|
||||||
|
->leftJoin('d.hesabdariRows', 'r')
|
||||||
|
->leftJoin('r.ref', 't')
|
||||||
->where('d.bid = :bid')
|
->where('d.bid = :bid')
|
||||||
->andWhere('d.year = :year')
|
->andWhere('d.year = :year')
|
||||||
->andWhere('d.type = :type')
|
->andWhere('d.type = :type')
|
||||||
|
@ -214,14 +217,12 @@ class CostController extends AbstractController
|
||||||
->setParameter('type', $type)
|
->setParameter('type', $type)
|
||||||
->setParameter('money', $acc['money']);
|
->setParameter('money', $acc['money']);
|
||||||
|
|
||||||
// اعمال فیلترها
|
// Apply filters
|
||||||
if (!empty($filters)) {
|
if (!empty($filters)) {
|
||||||
// جستجوی متنی
|
// Text search
|
||||||
if (isset($filters['search'])) {
|
if (isset($filters['search'])) {
|
||||||
$searchValue = is_array($filters['search']) ? $filters['search']['value'] : $filters['search'];
|
$searchValue = is_array($filters['search']) ? $filters['search']['value'] : $filters['search'];
|
||||||
$queryBuilder->leftJoin('d.hesabdariRows', 'r')
|
$queryBuilder->leftJoin('r.person', 'p')
|
||||||
->leftJoin('r.person', 'p')
|
|
||||||
->leftJoin('r.ref', 't')
|
|
||||||
->andWhere(
|
->andWhere(
|
||||||
$queryBuilder->expr()->orX(
|
$queryBuilder->expr()->orX(
|
||||||
'd.code LIKE :search',
|
'd.code LIKE :search',
|
||||||
|
@ -229,13 +230,25 @@ class CostController extends AbstractController
|
||||||
'd.date LIKE :search',
|
'd.date LIKE :search',
|
||||||
'd.amount LIKE :search',
|
'd.amount LIKE :search',
|
||||||
'p.nikename LIKE :search',
|
'p.nikename LIKE :search',
|
||||||
't.name LIKE :search'
|
't.name LIKE :search',
|
||||||
|
't.code LIKE :search'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
->setParameter('search', "%{$searchValue}%");
|
->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'])) {
|
if (isset($filters['timeFilter'])) {
|
||||||
$today = $jdate->jdate('Y/m/d', time());
|
$today = $jdate->jdate('Y/m/d', time());
|
||||||
switch ($filters['timeFilter']) {
|
switch ($filters['timeFilter']) {
|
||||||
|
@ -262,31 +275,28 @@ class CostController extends AbstractController
|
||||||
->setParameter('dateTo', $filters['dateTo']);
|
->setParameter('dateTo', $filters['dateTo']);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'all':
|
|
||||||
default:
|
|
||||||
// بدون فیلتر زمانی اضافه
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Amount filter
|
||||||
if (isset($filters['amount'])) {
|
if (isset($filters['amount'])) {
|
||||||
$queryBuilder->andWhere('d.amount = :amount')
|
$queryBuilder->andWhere('d.amount = :amount')
|
||||||
->setParameter('amount', $filters['amount']);
|
->setParameter('amount', $filters['amount']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// اعمال مرتبسازی
|
// Apply sorting
|
||||||
$sortField = is_array($sort['sortBy']) ? ($sort['sortBy']['key'] ?? 'id') : ($sort['sortBy'] ?? 'id');
|
$sortField = is_array($sort['sortBy']) ? ($sort['sortBy']['key'] ?? 'id') : ($sort['sortBy'] ?? 'id');
|
||||||
$sortDirection = ($sort['sortDesc'] ?? true) ? 'DESC' : 'ASC';
|
$sortDirection = ($sort['sortDesc'] ?? true) ? 'DESC' : 'ASC';
|
||||||
$queryBuilder->orderBy("d.$sortField", $sortDirection);
|
$queryBuilder->orderBy("d.$sortField", $sortDirection);
|
||||||
|
|
||||||
// محاسبه تعداد کل نتایج
|
// Calculate total items
|
||||||
$totalItemsQuery = clone $queryBuilder;
|
$totalItemsQuery = clone $queryBuilder;
|
||||||
$totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)')
|
$totalItems = $totalItemsQuery->select('COUNT(DISTINCT d.id)')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getSingleScalarResult();
|
->getSingleScalarResult();
|
||||||
|
|
||||||
// اعمال صفحهبندی
|
// Apply pagination
|
||||||
$queryBuilder->setFirstResult(($page - 1) * $limit)
|
$queryBuilder->setFirstResult(($page - 1) * $limit)
|
||||||
->setMaxResults($limit);
|
->setMaxResults($limit);
|
||||||
|
|
||||||
|
@ -305,9 +315,9 @@ class CostController extends AbstractController
|
||||||
'submitter' => $doc['submitter'],
|
'submitter' => $doc['submitter'],
|
||||||
];
|
];
|
||||||
|
|
||||||
// دریافت اطلاعات مرکز هزینه و مبلغ
|
// Get cost center details
|
||||||
$costDetails = $entityManager->createQueryBuilder()
|
$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')
|
->from('App\Entity\HesabdariRow', 'r')
|
||||||
->join('r.ref', 't')
|
->join('r.ref', 't')
|
||||||
->where('r.doc = :docId')
|
->where('r.doc = :docId')
|
||||||
|
@ -319,12 +329,13 @@ class CostController extends AbstractController
|
||||||
$item['costCenters'] = array_map(function ($detail) {
|
$item['costCenters'] = array_map(function ($detail) {
|
||||||
return [
|
return [
|
||||||
'name' => $detail['center_name'],
|
'name' => $detail['center_name'],
|
||||||
|
'code' => $detail['center_code'],
|
||||||
'amount' => (int) $detail['amount'],
|
'amount' => (int) $detail['amount'],
|
||||||
'des' => $detail['des'],
|
'des' => $detail['des'],
|
||||||
];
|
];
|
||||||
}, $costDetails);
|
}, $costDetails);
|
||||||
|
|
||||||
// دریافت اطلاعات شخص مرتبط
|
// Get related person info
|
||||||
$personInfo = $entityManager->createQueryBuilder()
|
$personInfo = $entityManager->createQueryBuilder()
|
||||||
->select('p.id, p.nikename, p.code')
|
->select('p.id, p.nikename, p.code')
|
||||||
->from('App\Entity\HesabdariRow', 'r')
|
->from('App\Entity\HesabdariRow', 'r')
|
||||||
|
|
|
@ -1143,32 +1143,36 @@ class HesabdariController extends AbstractController
|
||||||
throw $this->createAccessDeniedException();
|
throw $this->createAccessDeniedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$depth = (int) $request->query->get('depth', 2); // عمق پیشفرض 2
|
$rootCode = $request->query->get('rootCode');
|
||||||
$rootId = (int) $request->query->get('rootId', 1); // گره ریشه پیشفرض
|
|
||||||
|
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) {
|
if (!$root) {
|
||||||
return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404);
|
return $this->json(['Success' => false, 'message' => 'نود ریشه یافت نشد'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$buildTree = function ($node, $depth, $currentDepth = 0) use ($entityManager, $acc, &$buildTree) {
|
// تابع بازگشتی برای ساخت درخت
|
||||||
if ($currentDepth >= $depth) {
|
$buildTree = function ($node) use ($entityManager, $acc, &$buildTree) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
$children = $entityManager->getRepository(HesabdariTable::class)->findBy([
|
||||||
'upper' => $node,
|
'upper' => $node,
|
||||||
'bid' => [$acc['bid']->getId(), null],
|
'bid' => [$acc['bid']->getId(), null],
|
||||||
]);
|
], ['code' => 'ASC']); // مرتبسازی بر اساس کد
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($children as $child) {
|
foreach ($children as $child) {
|
||||||
$childData = [
|
$childData = [
|
||||||
'id' => $child->getId(),
|
|
||||||
'name' => $child->getName(),
|
|
||||||
'code' => $child->getCode(),
|
'code' => $child->getCode(),
|
||||||
|
'name' => $child->getName(),
|
||||||
'type' => $child->getType(),
|
'type' => $child->getType(),
|
||||||
'children' => $buildTree($child, $depth, $currentDepth + 1),
|
'children' => $buildTree($child)
|
||||||
];
|
];
|
||||||
$result[] = $childData;
|
$result[] = $childData;
|
||||||
}
|
}
|
||||||
|
@ -1176,12 +1180,12 @@ class HesabdariController extends AbstractController
|
||||||
return $result;
|
return $result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ساخت درخت کامل
|
||||||
$tree = [
|
$tree = [
|
||||||
'id' => $root->getId(),
|
|
||||||
'name' => $root->getName(),
|
|
||||||
'code' => $root->getCode(),
|
'code' => $root->getCode(),
|
||||||
|
'name' => $root->getName(),
|
||||||
'type' => $root->getType(),
|
'type' => $root->getType(),
|
||||||
'children' => $buildTree($root, $depth),
|
'children' => $buildTree($root)
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->json(['Success' => true, 'data' => $tree]);
|
return $this->json(['Success' => true, 'data' => $tree]);
|
||||||
|
|
|
@ -317,6 +317,9 @@ class PersonsController extends AbstractController
|
||||||
$person->setPrelabel($prelabel);
|
$person->setPrelabel($prelabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elseif ($params['prelabel'] == null) {
|
||||||
|
$person->setPrelabel(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//inset cards
|
//inset cards
|
||||||
if (array_key_exists('accounts', $params)) {
|
if (array_key_exists('accounts', $params)) {
|
||||||
|
@ -995,11 +998,6 @@ class PersonsController extends AbstractController
|
||||||
$params = json_decode($content, true);
|
$params = json_decode($content, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// پارامترهای صفحهبندی
|
|
||||||
$page = $params['page'] ?? 1;
|
|
||||||
$limit = $params['limit'] ?? 10;
|
|
||||||
$offset = ($page - 1) * $limit;
|
|
||||||
|
|
||||||
$queryBuilder = $entityManager->getRepository(HesabdariDoc::class)->createQueryBuilder('d')
|
$queryBuilder = $entityManager->getRepository(HesabdariDoc::class)->createQueryBuilder('d')
|
||||||
->where('d.bid = :bid')
|
->where('d.bid = :bid')
|
||||||
->andWhere('d.type = :type')
|
->andWhere('d.type = :type')
|
||||||
|
@ -1022,12 +1020,23 @@ class PersonsController extends AbstractController
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getSingleScalarResult();
|
->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')
|
$items = $queryBuilder->select('d')
|
||||||
->setFirstResult($offset)
|
->setFirstResult($offset)
|
||||||
->setMaxResults($limit)
|
->setMaxResults($limit)
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult();
|
||||||
|
} else {
|
||||||
|
// دریافت همه آیتمها بدون صفحهبندی
|
||||||
|
$items = $queryBuilder->select('d')
|
||||||
|
->getQuery()
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
// اضافه کردن اطلاعات اشخاص به هر آیتم
|
// اضافه کردن اطلاعات اشخاص به هر آیتم
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
|
@ -1048,16 +1057,16 @@ class PersonsController extends AbstractController
|
||||||
'bid' => $acc['bid'],
|
'bid' => $acc['bid'],
|
||||||
'items' => $items,
|
'items' => $items,
|
||||||
'totalItems' => $totalItems,
|
'totalItems' => $totalItems,
|
||||||
'currentPage' => $page,
|
'currentPage' => $params['page'] ?? 1,
|
||||||
'totalPages' => ceil($totalItems / $limit)
|
'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'id' => $pid,
|
'id' => $pid,
|
||||||
'totalItems' => $totalItems,
|
'totalItems' => $totalItems,
|
||||||
'currentPage' => $page,
|
'currentPage' => $params['page'] ?? 1,
|
||||||
'totalPages' => ceil($totalItems / $limit)
|
'totalPages' => array_key_exists('limit', $params) ? ceil($totalItems / $params['limit']) : 1
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ class PreinvoiceController extends AbstractController
|
||||||
#[Route('/api/preinvoice/save', name: 'app_preinvoice_save')]
|
#[Route('/api/preinvoice/save', name: 'app_preinvoice_save')]
|
||||||
public function savePreinvoice(Access $access,Provider $provider, Log $log, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
public function savePreinvoice(Access $access,Provider $provider, Log $log, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$acc = $access->hasRole('preinvoice');
|
$acc = $access->hasRole('plugAccproPresell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ class PreinvoiceController extends AbstractController
|
||||||
#[Route('/api/preinvoice/delete/{id}', name: 'app_preinvoice_delete')]
|
#[Route('/api/preinvoice/delete/{id}', name: 'app_preinvoice_delete')]
|
||||||
public function deletePreinvoice(Access $access, Log $log, EntityManagerInterface $entityManager, int $id): JsonResponse
|
public function deletePreinvoice(Access $access, Log $log, EntityManagerInterface $entityManager, int $id): JsonResponse
|
||||||
{
|
{
|
||||||
$acc = $access->hasRole('preinvoice');
|
$acc = $access->hasRole('plugAccproPresell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ class PreinvoiceController extends AbstractController
|
||||||
#[Route('/api/preinvoice/docs/search', name: 'app_presell_search')]
|
#[Route('/api/preinvoice/docs/search', name: 'app_presell_search')]
|
||||||
public function searchPreinvoices(Access $access, EntityManagerInterface $entityManager): JsonResponse
|
public function searchPreinvoices(Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||||
{
|
{
|
||||||
$acc = $access->hasRole('preinvoice');
|
$acc = $access->hasRole('plugAccproPresell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
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')]
|
#[Route('/api/preinvoice/remove/group', name: 'app_presell_delete_group')]
|
||||||
public function deletePreinvoiceGroup(Log $log, Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
public function deletePreinvoiceGroup(Log $log, Access $access, EntityManagerInterface $entityManager, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$acc = $access->hasRole('preinvoice');
|
$acc = $access->hasRole('plugAccproPresell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
return new JsonResponse($this->extractor->operationFail('دسترسی ندارید'), 403);
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,7 @@ class PreinvoiceController extends AbstractController
|
||||||
$params = json_decode($content, true);
|
$params = json_decode($content, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$acc = $access->hasRole('preinvoice');
|
$acc = $access->hasRole('plugAccproPresell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
throw $this->createAccessDeniedException();
|
throw $this->createAccessDeniedException();
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,9 @@ class PreinvoiceController extends AbstractController
|
||||||
'taxInfo' => true,
|
'taxInfo' => true,
|
||||||
'discountInfo' => true,
|
'discountInfo' => true,
|
||||||
'note' => true,
|
'note' => true,
|
||||||
'paper' => 'A4-L'
|
'paper' => 'A4-L',
|
||||||
|
'invoiceIndex' => false,
|
||||||
|
'businessStamp' => false
|
||||||
];
|
];
|
||||||
|
|
||||||
if (array_key_exists('printOptions', $params)) {
|
if (array_key_exists('printOptions', $params)) {
|
||||||
|
@ -334,6 +336,12 @@ class PreinvoiceController extends AbstractController
|
||||||
if (array_key_exists('paper', $params['printOptions'])) {
|
if (array_key_exists('paper', $params['printOptions'])) {
|
||||||
$printOptions['paper'] = $params['printOptions']['paper'];
|
$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 = '';
|
$note = '';
|
||||||
|
|
|
@ -57,6 +57,8 @@ class PrintersController extends AbstractController
|
||||||
$temp['sell']['noteString'] = $settings->getSellNoteString();
|
$temp['sell']['noteString'] = $settings->getSellNoteString();
|
||||||
$temp['sell']['pays'] = $settings->isSellPays();
|
$temp['sell']['pays'] = $settings->isSellPays();
|
||||||
$temp['sell']['paper'] = $settings->getSellPaper();
|
$temp['sell']['paper'] = $settings->getSellPaper();
|
||||||
|
$temp['sell']['businessStamp'] = $settings->isSellBusinessStamp();
|
||||||
|
$temp['sell']['invoiceIndex'] = $settings->isSellInvoiceIndex();
|
||||||
if (!$temp['sell']['paper']) {
|
if (!$temp['sell']['paper']) {
|
||||||
$temp['sell']['paper'] = 'A4-L';
|
$temp['sell']['paper'] = 'A4-L';
|
||||||
}
|
}
|
||||||
|
@ -135,6 +137,8 @@ class PrintersController extends AbstractController
|
||||||
$settings->setSellNoteString($params['sell']['noteString']);
|
$settings->setSellNoteString($params['sell']['noteString']);
|
||||||
$settings->setSellPays($params['sell']['pays']);
|
$settings->setSellPays($params['sell']['pays']);
|
||||||
$settings->setSellPaper($params['sell']['paper']);
|
$settings->setSellPaper($params['sell']['paper']);
|
||||||
|
$settings->setSellBusinessStamp($params['sell']['businessStamp']);
|
||||||
|
$settings->setSellInvoiceIndex($params['sell']['invoiceIndex']);
|
||||||
if ($params['buy']['bidInfo'] == null) {
|
if ($params['buy']['bidInfo'] == null) {
|
||||||
$settings->setBuyBidInfo(false);
|
$settings->setBuyBidInfo(false);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -368,14 +368,14 @@ class SellController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/sell/docs/search', name: 'app_sell_docs_search', methods: ['POST'])]
|
#[Route('/api/sell/docs/search', name: 'app_sell_docs_search', methods: ['POST'])]
|
||||||
public function searchSellDocs(
|
public function searchSellDocs(
|
||||||
Provider $provider,
|
Provider $provider,
|
||||||
Request $request,
|
Request $request,
|
||||||
Access $access,
|
Access $access,
|
||||||
Log $log,
|
Log $log,
|
||||||
EntityManagerInterface $entityManager,
|
EntityManagerInterface $entityManager,
|
||||||
Jdate $jdate
|
Jdate $jdate
|
||||||
): JsonResponse {
|
): JsonResponse {
|
||||||
$acc = $access->hasRole('sell');
|
$acc = $access->hasRole('sell');
|
||||||
if (!$acc) {
|
if (!$acc) {
|
||||||
throw $this->createAccessDeniedException();
|
throw $this->createAccessDeniedException();
|
||||||
|
@ -575,11 +575,11 @@ public function searchSellDocs(
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
'perPage' => $perPage,
|
'perPage' => $perPage,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// متد calculateProfit بدون تغییر
|
// متد calculateProfit بدون تغییر
|
||||||
private function calculateProfit(int $docId, array $acc, EntityManagerInterface $entityManager): int
|
private function calculateProfit(int $docId, array $acc, EntityManagerInterface $entityManager): int
|
||||||
{
|
{
|
||||||
$profit = 0;
|
$profit = 0;
|
||||||
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $docId]);
|
$rows = $entityManager->getRepository(HesabdariRow::class)->findBy(['doc' => $docId]);
|
||||||
foreach ($rows as $item) {
|
foreach ($rows as $item) {
|
||||||
|
@ -623,7 +623,7 @@ private function calculateProfit(int $docId, array $acc, EntityManagerInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return round($profit);
|
return round($profit);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/sell/rows/{code}', name: 'app_sell_rows', methods: ['GET'])]
|
#[Route('/api/sell/rows/{code}', name: 'app_sell_rows', methods: ['GET'])]
|
||||||
public function getSellRows(
|
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')]
|
#[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
|
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');
|
$acc = $access->hasRole('sell');
|
||||||
if (!$acc)
|
if (!$acc)
|
||||||
throw $this->createAccessDeniedException();
|
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([
|
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||||
'bid' => $acc['bid'],
|
'bid' => $acc['bid'],
|
||||||
'code' => $params['code'],
|
'code' => $params['code'],
|
||||||
|
@ -735,6 +745,12 @@ private function calculateProfit(int $docId, array $acc, EntityManagerInterface
|
||||||
if (array_key_exists('paper', $params['printOptions'])) {
|
if (array_key_exists('paper', $params['printOptions'])) {
|
||||||
$printOptions['paper'] = $params['printOptions']['paper'];
|
$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 = '';
|
$note = '';
|
||||||
$printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]);
|
$printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]);
|
||||||
|
|
|
@ -199,6 +199,7 @@ class UserController extends AbstractController
|
||||||
$temp = [];
|
$temp = [];
|
||||||
$temp['name'] = $perm->getUser()->getFullName();
|
$temp['name'] = $perm->getUser()->getFullName();
|
||||||
$temp['email'] = $perm->getUser()->getEmail();
|
$temp['email'] = $perm->getUser()->getEmail();
|
||||||
|
$temp['mobile'] = $perm->getUser()->getMobile();
|
||||||
$temp['owner'] = $perm->isOwner();
|
$temp['owner'] = $perm->isOwner();
|
||||||
$out[] = $temp;
|
$out[] = $temp;
|
||||||
}
|
}
|
||||||
|
|
112
hesabixCore/src/Entity/BackBuiltModule.php
Normal file
112
hesabixCore/src/Entity/BackBuiltModule.php
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\BackBuiltModuleRepository;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: BackBuiltModuleRepository::class)]
|
||||||
|
class BackBuiltModule
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'backBuiltModules')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $submitter = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 40)]
|
||||||
|
private ?string $dateSubmit = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
|
private ?string $code = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $locked = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $public = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 120)]
|
||||||
|
private ?string $type = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,8 +59,8 @@ class HesabdariRow
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
private ?Commodity $commodity = null;
|
private ?Commodity $commodity = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 0, nullable: true)]
|
#[ORM\Column(type: Types::DECIMAL, precision: 20, scale: 4, nullable: true)]
|
||||||
private ?int $commdityCount = null;
|
private ?float $commdityCount = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
#[ORM\ManyToOne(inversedBy: 'hesabdariRows')]
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
|
@ -220,15 +220,14 @@ class HesabdariRow
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCommdityCount(): ?int
|
public function getCommdityCount(): ?float
|
||||||
{
|
{
|
||||||
return $this->commdityCount;
|
return $this->commdityCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCommdityCount(?int $commdityCount): self
|
public function setCommdityCount(?float $commdityCount): self
|
||||||
{
|
{
|
||||||
$this->commdityCount = $commdityCount;
|
$this->commdityCount = $commdityCount;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,9 @@ class Permission
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?bool $plugRepservice = null;
|
private ?bool $plugRepservice = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $plugAccproPresell = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -545,4 +548,16 @@ class Permission
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPlugAccproPresell(): ?bool
|
||||||
|
{
|
||||||
|
return $this->plugAccproPresell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlugAccproPresell(?bool $plugAccproPresell): static
|
||||||
|
{
|
||||||
|
$this->plugAccproPresell = $plugAccproPresell;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,12 @@ class PrintOptions
|
||||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||||
private ?string $rightFooter = null;
|
private ?string $rightFooter = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $sellInvoiceIndex = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?bool $sellBusinessStamp = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -574,4 +580,26 @@ class PrintOptions
|
||||||
|
|
||||||
return $this;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
#[ORM\OneToMany(mappedBy: 'submitter', targetEntity: AccountingPackageOrder::class, orphanRemoval: true)]
|
#[ORM\OneToMany(mappedBy: 'submitter', targetEntity: AccountingPackageOrder::class, orphanRemoval: true)]
|
||||||
private Collection $accountingPackageOrders;
|
private Collection $accountingPackageOrders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, BackBuiltModule>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: BackBuiltModule::class, mappedBy: 'submitter', orphanRemoval: true)]
|
||||||
|
private Collection $backBuiltModules;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->userTokens = new ArrayCollection();
|
$this->userTokens = new ArrayCollection();
|
||||||
|
@ -148,6 +154,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
$this->preInvoiceDocs = new ArrayCollection();
|
$this->preInvoiceDocs = new ArrayCollection();
|
||||||
$this->dashboardSettings = new ArrayCollection();
|
$this->dashboardSettings = new ArrayCollection();
|
||||||
$this->accountingPackageOrders = new ArrayCollection();
|
$this->accountingPackageOrders = new ArrayCollection();
|
||||||
|
$this->backBuiltModules = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
|
@ -923,4 +930,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, BackBuiltModule>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
43
hesabixCore/src/Repository/BackBuiltModuleRepository.php
Normal file
43
hesabixCore/src/Repository/BackBuiltModuleRepository.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\BackBuiltModule;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<BackBuiltModule>
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\HesabdariTable;
|
use App\Entity\HesabdariTable;
|
||||||
|
use App\Entity\Business;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
@ -67,4 +68,58 @@ class HesabdariTableRepository extends ServiceEntityRepository
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getOneOrNullResult();
|
->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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,9 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:20%">
|
<td style="width:20%">
|
||||||
|
{% if printOptions.invoiceIndex is defined and printOptions.invoiceIndex %}
|
||||||
<img src="{{ url('front_avatar_file_get', {id: bid.id}) }}" width="65"/>
|
<img src="{{ url('front_avatar_file_get', {id: bid.id}) }}" width="65"/>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td style="width:60%; text-align:center">
|
<td style="width:60%; text-align:center">
|
||||||
<h3 class="">پیش فاکتور فروش کالا و خدمات</h3>
|
<h3 class="">پیش فاکتور فروش کالا و خدمات</h3>
|
||||||
|
@ -266,7 +268,9 @@
|
||||||
<td class="center" style="height:90px">
|
<td class="center" style="height:90px">
|
||||||
<h4>مهر و امضا فروشنده:</h4>
|
<h4>مهر و امضا فروشنده:</h4>
|
||||||
<br>
|
<br>
|
||||||
|
{% if printOptions.businessStamp is defined and printOptions.businessStamp %}
|
||||||
<img src="{{ url('front_seal_file_get', {id: bid.id}) }}" width="160"/>
|
<img src="{{ url('front_seal_file_get', {id: bid.id}) }}" width="160"/>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -26,7 +26,9 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:20%">
|
<td style="width:20%">
|
||||||
|
{% if printOptions.invoiceIndex %}
|
||||||
<img src="{{ url('front_avatar_file_get', {id: bid.id},)}}" width="65"/>
|
<img src="{{ url('front_avatar_file_get', {id: bid.id},)}}" width="65"/>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td style="width:60%; text-align:center">
|
<td style="width:60%; text-align:center">
|
||||||
<h3 class="">صورتحساب فروش کالا و خدمات</h3>
|
<h3 class="">صورتحساب فروش کالا و خدمات</h3>
|
||||||
|
@ -214,7 +216,13 @@
|
||||||
{{ item.commdityCount }}
|
{{ item.commdityCount }}
|
||||||
{{ item.commodity.unit.name }}
|
{{ item.commodity.unit.name }}
|
||||||
</td>
|
</td>
|
||||||
<td class="center item">{{ ((item.bs - item.tax + item.discount) / item.commdityCount) | number_format }}</td>
|
<td class="center item">
|
||||||
|
{% 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 %}
|
||||||
|
</td>
|
||||||
{% if printOptions.discountInfo %}
|
{% if printOptions.discountInfo %}
|
||||||
<td class="center item">{{ item.discount | number_format }}</td>
|
<td class="center item">{{ item.discount | number_format }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -304,7 +312,9 @@
|
||||||
مهر و امضا فروشنده:
|
مهر و امضا فروشنده:
|
||||||
</h4>
|
</h4>
|
||||||
<br>
|
<br>
|
||||||
|
{% if printOptions.businessStamp %}
|
||||||
<img src="{{ url('front_seal_file_get', {id: bid.id},)}}" width="160"/>
|
<img src="{{ url('front_seal_file_get', {id: bid.id},)}}" width="160"/>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
147
webUI/src/components/PrintDialog.vue
Normal file
147
webUI/src/components/PrintDialog.vue
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" width="auto">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="primary">
|
||||||
|
<v-toolbar-title class="text-white">
|
||||||
|
<v-icon icon="mdi-file-pdf-box" class="ml-2"></v-icon>
|
||||||
|
{{ $t('dialog.export_pdf') }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip :text="$t('dialog.print')" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-printer" color="white" @click="handlePrint"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.cancel')" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-close" color="white" @click="handleCancel"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card-text>
|
||||||
|
<v-select class="mb-2" v-model="printOptions.paper" :items="paperSizes" :label="$t('dialog.paper_size')">
|
||||||
|
</v-select>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-tooltip :text="$t('dialog.bid_info_label')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.invoice_pays')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.pays" color="primary" :label="$t('dialog.invoice_pays')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.invoice_footer_note')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-tooltip :text="$t('dialog.tax_dexpo')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.discount_dexpo')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.business_stamp')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.businessStamp" color="primary" :label="$t('dialog.business_stamp')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.invoice_index')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.invoiceIndex" color="primary" :label="$t('dialog.invoice_index')" hide-details density="compact"></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PrintDialog',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'print', 'cancel'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const dialog = ref(props.modelValue);
|
||||||
|
const printOptions = ref({
|
||||||
|
note: true,
|
||||||
|
bidInfo: true,
|
||||||
|
taxInfo: true,
|
||||||
|
discountInfo: true,
|
||||||
|
pays: false,
|
||||||
|
paper: 'A4-L',
|
||||||
|
businessStamp: false,
|
||||||
|
invoiceIndex: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const paperSizes = [
|
||||||
|
{ title: 'A4 عمودی', value: 'A4' },
|
||||||
|
{ title: 'A4 افقی', value: 'A4-L' },
|
||||||
|
{ title: 'A5 عمودی', value: 'A5' },
|
||||||
|
{ title: 'A5 افقی', value: 'A5-L' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const isPluginActive = (pluginCode) => {
|
||||||
|
return props.plugins && props.plugins[pluginCode];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrint = () => {
|
||||||
|
emit('print', printOptions.value);
|
||||||
|
dialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('cancel');
|
||||||
|
dialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
dialog,
|
||||||
|
printOptions,
|
||||||
|
paperSizes,
|
||||||
|
isPluginActive,
|
||||||
|
handlePrint,
|
||||||
|
handleCancel
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(newVal) {
|
||||||
|
this.dialog = newVal;
|
||||||
|
},
|
||||||
|
dialog(newVal) {
|
||||||
|
this.$emit('update:modelValue', newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.v-dialog {
|
||||||
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
</style>
|
377
webUI/src/components/forms/HesabdariTreeView.vue
Normal file
377
webUI/src/components/forms/HesabdariTreeView.vue
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
<template>
|
||||||
|
<v-menu
|
||||||
|
v-model="menu"
|
||||||
|
:close-on-content-click="false"
|
||||||
|
location="bottom"
|
||||||
|
width="400"
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-text-field
|
||||||
|
v-bind="props"
|
||||||
|
:model-value="selectedAccountName"
|
||||||
|
:label="label"
|
||||||
|
:rules="rules"
|
||||||
|
readonly
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
@click="openMenu"
|
||||||
|
>
|
||||||
|
<template v-slot:append-inner>
|
||||||
|
<v-icon>{{ menu ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-progress-linear v-if="isLoading" indeterminate color="primary" />
|
||||||
|
<v-card-text v-if="accountData.length > 0" class="pa-0">
|
||||||
|
<div class="tree-container">
|
||||||
|
<tree-node
|
||||||
|
v-for="node in accountData"
|
||||||
|
:key="node.code"
|
||||||
|
:node="node"
|
||||||
|
:selected-code="selectedAccount?.code"
|
||||||
|
:selectable-only="selectableOnly"
|
||||||
|
@select="handleNodeSelect"
|
||||||
|
@toggle="toggleNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-else>
|
||||||
|
{{ isLoading ? 'در حال بارگذاری...' : 'هیچ حسابی یافت نشد' }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import TreeNode from '@/components/forms/TreeNode.vue';
|
||||||
|
|
||||||
|
// اضافه کردن کش سراسری
|
||||||
|
const globalCache = {
|
||||||
|
data: null as AccountNode[] | null,
|
||||||
|
isLoading: false,
|
||||||
|
promise: null as Promise<void> | null,
|
||||||
|
lastUpdate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
type ValidationRule = (value: any) => boolean | string;
|
||||||
|
|
||||||
|
interface AccountNode {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
children?: AccountNode[];
|
||||||
|
isOpen?: boolean;
|
||||||
|
tableType?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: 'حساب',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array as () => ValidationRule[],
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
initialAccount: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({ code: '', name: '' }),
|
||||||
|
},
|
||||||
|
showSubTree: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
selectableOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'select', 'tableType', 'accountSelected']);
|
||||||
|
|
||||||
|
const menu = ref(false);
|
||||||
|
const accountData = ref<AccountNode[]>([]);
|
||||||
|
const selectedAccount = ref<AccountNode | null>(null);
|
||||||
|
const cache = ref(new Map<string, AccountNode[]>());
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const snackbar = ref({
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedAccountName = computed(() => {
|
||||||
|
return selectedAccount.value?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// نمایش پیام خطا یا موفقیت
|
||||||
|
const showMessage = (text: string, color = 'success') => {
|
||||||
|
snackbar.value.text = text;
|
||||||
|
snackbar.value.color = color;
|
||||||
|
snackbar.value.show = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// رمزگشایی کاراکترهای یونیکد
|
||||||
|
const decodeUnicode = (str: string): string => {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(
|
||||||
|
str.replace(/\\u([\dA-F]{4})/gi, (match, grp) =>
|
||||||
|
String.fromCharCode(parseInt(grp, 16))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('خطا در رمزگشایی یونیکد:', e);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// پردازش دادهها
|
||||||
|
const processTreeData = (items: any[]): any[] => {
|
||||||
|
return items.map((item) => {
|
||||||
|
if (cache.value.has(`processed-${item.code}`)) {
|
||||||
|
return cache.value.get(`processed-${item.code}`);
|
||||||
|
}
|
||||||
|
const processedItem = {
|
||||||
|
...item,
|
||||||
|
name: decodeUnicode(item.name),
|
||||||
|
children: item.children ? processTreeData(item.children) : [],
|
||||||
|
isOpen: false,
|
||||||
|
tableType: item.type,
|
||||||
|
};
|
||||||
|
cache.value.set(`processed-${item.code}`, processedItem);
|
||||||
|
return processedItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// بارگذاری دادهها
|
||||||
|
const fetchHesabdariTables = async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const CACHE_DURATION = 5 * 60 * 1000; // 5 دقیقه
|
||||||
|
|
||||||
|
// اگر کش معتبر است و در حالت showSubTree نیستیم، از کش استفاده میکنیم
|
||||||
|
if (globalCache.data && !props.showSubTree && (now - globalCache.lastUpdate) < CACHE_DURATION) {
|
||||||
|
accountData.value = globalCache.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر در حال بارگذاری هستیم، صبر میکنیم
|
||||||
|
if (globalCache.isLoading && globalCache.promise) {
|
||||||
|
await globalCache.promise;
|
||||||
|
if (!props.showSubTree) {
|
||||||
|
accountData.value = globalCache.data || [];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCache.isLoading = true;
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
globalCache.promise = new Promise(async (resolve) => {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
// اگر initialAccount وجود داشت، از آن برای فیلتر کردن استفاده میکنیم
|
||||||
|
if (props.initialAccount?.code) {
|
||||||
|
response = await axios.get(`/api/hesabdari/tables/tree?rootCode=${props.initialAccount.code}`);
|
||||||
|
} else if (props.showSubTree && selectedAccount.value) {
|
||||||
|
response = await axios.get(`/api/hesabdari/tables/tree?rootCode=${selectedAccount.value.code}`);
|
||||||
|
} else {
|
||||||
|
response = await axios.get('/api/hesabdari/tables/all');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.Success && response.data.data) {
|
||||||
|
let data;
|
||||||
|
if (props.initialAccount?.code) {
|
||||||
|
// در حالت initialAccount فقط زیرمجموعهها را نمایش میدهیم
|
||||||
|
data = response.data.data.children || [];
|
||||||
|
} else if (props.showSubTree) {
|
||||||
|
data = [response.data.data];
|
||||||
|
} else {
|
||||||
|
data = response.data.data.children || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedData = processTreeData(data);
|
||||||
|
|
||||||
|
if (!props.showSubTree && !props.initialAccount?.code) {
|
||||||
|
globalCache.data = processedData;
|
||||||
|
globalCache.lastUpdate = now;
|
||||||
|
}
|
||||||
|
accountData.value = processedData;
|
||||||
|
} else {
|
||||||
|
showMessage('هیچ حسابی یافت نشد', 'warning');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری حسابها:', error);
|
||||||
|
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||||
|
} finally {
|
||||||
|
globalCache.isLoading = false;
|
||||||
|
isLoading.value = false;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await globalCache.promise;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری حسابها:', error);
|
||||||
|
showMessage('خطا در بارگذاری حسابها', 'error');
|
||||||
|
globalCache.isLoading = false;
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// جستجوی حساب در دادههای موجود
|
||||||
|
const findAccount = (nodes: any[]): any => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.code === props.modelValue) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.children && node.children.length) {
|
||||||
|
const found = findAccount(node.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// تنظیم مقدار اولیه حساب
|
||||||
|
const initializeAccount = async () => {
|
||||||
|
if (props.modelValue) {
|
||||||
|
if (props.initialAccount && !selectedAccount.value) {
|
||||||
|
selectedAccount.value = {
|
||||||
|
code: props.modelValue,
|
||||||
|
name: props.initialAccount.name || '',
|
||||||
|
tableType: props.initialAccount.tableType || '',
|
||||||
|
};
|
||||||
|
emit('select', selectedAccount.value);
|
||||||
|
emit('accountSelected', selectedAccount.value);
|
||||||
|
if (selectedAccount.value.tableType) {
|
||||||
|
emit('tableType', selectedAccount.value.tableType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر initialAccount وجود داشت، دادهها را بارگذاری میکنیم
|
||||||
|
if (props.initialAccount?.code || !accountData.value.length) {
|
||||||
|
await fetchHesabdariTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = findAccount(accountData.value);
|
||||||
|
if (account && (!selectedAccount.value || selectedAccount.value.code !== account.code)) {
|
||||||
|
selectedAccount.value = account;
|
||||||
|
emit('select', account);
|
||||||
|
emit('accountSelected', account);
|
||||||
|
if (account.tableType && account.tableType !== selectedAccount.value?.tableType) {
|
||||||
|
emit('tableType', account.tableType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedAccount.value) {
|
||||||
|
selectedAccount.value = null;
|
||||||
|
emit('select', null);
|
||||||
|
emit('accountSelected', null);
|
||||||
|
emit('tableType', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// تغییر وضعیت گره
|
||||||
|
const toggleNode = (node: any) => {
|
||||||
|
node.isOpen = !node.isOpen;
|
||||||
|
};
|
||||||
|
|
||||||
|
// مدیریت انتخاب گره
|
||||||
|
const handleNodeSelect = async (node: any) => {
|
||||||
|
if (props.selectableOnly && node.children && node.children.length > 0) {
|
||||||
|
showMessage('این گزینه قابل انتخاب نیست زیرا دارای زیرمجموعه است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedAccount.value = node;
|
||||||
|
emit('select', node);
|
||||||
|
emit('update:modelValue', node.code);
|
||||||
|
emit('accountSelected', node);
|
||||||
|
menu.value = false;
|
||||||
|
|
||||||
|
if (props.showSubTree) {
|
||||||
|
await fetchHesabdariTables();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// باز کردن منو
|
||||||
|
const openMenu = async () => {
|
||||||
|
menu.value = true;
|
||||||
|
// اگر initialAccount وجود داشت یا کش منقضی شده بود، دادهها را بارگذاری میکنیم
|
||||||
|
if (props.initialAccount?.code || !accountData.value.length || (Date.now() - globalCache.lastUpdate) > 5 * 60 * 1000) {
|
||||||
|
await fetchHesabdariTables();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// اصلاح watch برای modelValue
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
async (newVal) => {
|
||||||
|
if (newVal === selectedAccount.value?.code) return;
|
||||||
|
await initializeAccount();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// اضافه کردن watch برای initialAccount
|
||||||
|
watch(
|
||||||
|
() => props.initialAccount,
|
||||||
|
async (newVal) => {
|
||||||
|
if (newVal && props.modelValue) {
|
||||||
|
await initializeAccount();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// اضافه کردن watch برای showSubTree
|
||||||
|
watch(
|
||||||
|
() => props.showSubTree,
|
||||||
|
async () => {
|
||||||
|
if (menu.value) {
|
||||||
|
await fetchHesabdariTables();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.modelValue || props.initialAccount) {
|
||||||
|
initializeAccount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-container {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,25 +1,36 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tree-node" :class="{ 'has-children': node.children && node.children.length > 0 }">
|
<div class="tree-node" :class="{ 'has-children': node.children && node.children.length > 0 }">
|
||||||
<div class="tree-node-content" @click="handleNodeClick">
|
<div
|
||||||
<div class="tree-node-toggle" @click.stop="toggleNode">
|
class="node-content"
|
||||||
<v-icon v-if="node.children && node.children.length > 0">
|
:class="{
|
||||||
{{ node.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}
|
'selected': node.code === selectedCode,
|
||||||
|
'has-children': node.children && node.children.length > 0,
|
||||||
|
'not-selectable': node.children && node.children.length > 0
|
||||||
|
}"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
v-if="node.children && node.children.length > 0"
|
||||||
|
@click.stop="toggleNode"
|
||||||
|
:class="{ 'rotated': node.isOpen }"
|
||||||
|
>
|
||||||
|
mdi-chevron-right
|
||||||
</v-icon>
|
</v-icon>
|
||||||
</div>
|
|
||||||
<div class="tree-node-icon">
|
<div class="tree-node-icon">
|
||||||
<v-icon v-if="node.children && node.children.length > 0">mdi-folder</v-icon>
|
<v-icon v-if="node.children && node.children.length > 0">mdi-folder</v-icon>
|
||||||
<v-icon v-else>mdi-file-document</v-icon>
|
<v-icon v-else>mdi-file-document</v-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="tree-node-label" :class="{ 'selected': selectedId === node.id }">
|
<div class="tree-node-label" :class="{ 'selected': node.code === selectedCode }">
|
||||||
{{ node.name }}
|
{{ node.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="node.isOpen && node.children && node.children.length > 0" class="tree-children">
|
<div v-if="node.isOpen && node.children && node.children.length > 0" class="tree-children">
|
||||||
<tree-node
|
<tree-node
|
||||||
v-for="child in node.children"
|
v-for="child in node.children"
|
||||||
:key="child.id"
|
:key="child.code"
|
||||||
:node="child"
|
:node="child"
|
||||||
:selected-id="selectedId"
|
:selected-code="selectedCode"
|
||||||
|
:selectable-only="selectableOnly"
|
||||||
@select="$emit('select', $event)"
|
@select="$emit('select', $event)"
|
||||||
@toggle="$emit('toggle', $event)"
|
@toggle="$emit('toggle', $event)"
|
||||||
/>
|
/>
|
||||||
|
@ -35,15 +46,19 @@
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
selectedId: {
|
selectedCode: {
|
||||||
type: Number,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
selectableOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['select', 'toggle']);
|
const emit = defineEmits(['select', 'toggle']);
|
||||||
|
|
||||||
const handleNodeClick = () => {
|
const handleClick = () => {
|
||||||
emit('select', props.node);
|
emit('select', props.node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,7 +72,7 @@
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-content {
|
.node-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
@ -66,15 +81,18 @@
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-content:hover {
|
.node-content:hover {
|
||||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-toggle {
|
.node-content.selected {
|
||||||
width: 24px;
|
background-color: var(--v-primary-base);
|
||||||
display: flex;
|
color: white;
|
||||||
justify-content: center;
|
}
|
||||||
align-items: center;
|
|
||||||
|
.node-content.not-selectable {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-icon {
|
.tree-node-icon {
|
||||||
|
@ -101,4 +119,8 @@
|
||||||
border-right: 2px solid rgba(var(--v-theme-primary), 0.1);
|
border-right: 2px solid rgba(var(--v-theme-primary), 0.1);
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rotated {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -408,6 +408,8 @@ const fa_lang = {
|
||||||
search_txt: "جست و جو ...",
|
search_txt: "جست و جو ...",
|
||||||
prev_page: "صفحه قبل",
|
prev_page: "صفحه قبل",
|
||||||
next_page: "صفحه بعد",
|
next_page: "صفحه بعد",
|
||||||
|
business_stamp: "مهر کسب و کار",
|
||||||
|
invoice_index: "نمایه فاکتور",
|
||||||
change_password: {
|
change_password: {
|
||||||
title: "تغییر کلمه عبور",
|
title: "تغییر کلمه عبور",
|
||||||
new_password: "کلمه عبور جدید",
|
new_password: "کلمه عبور جدید",
|
||||||
|
@ -443,7 +445,7 @@ const fa_lang = {
|
||||||
import_excel: "درون ریزی از اکسل",
|
import_excel: "درون ریزی از اکسل",
|
||||||
selected: "انتخاب شدهها",
|
selected: "انتخاب شدهها",
|
||||||
selected_all: "همهی موارد",
|
selected_all: "همهی موارد",
|
||||||
export_pdf: "خروجی پی دی اف",
|
export_pdf: "خروجی PDF",
|
||||||
export_excel: "خروجی اکسل",
|
export_excel: "خروجی اکسل",
|
||||||
filter_results: "فیلتر نتایج",
|
filter_results: "فیلتر نتایج",
|
||||||
account: "حساب کاربری",
|
account: "حساب کاربری",
|
||||||
|
|
|
@ -125,7 +125,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isPluginActive(plugName) {
|
isPluginActive(plugName) {
|
||||||
return this.plugins[plugName] !== undefined;
|
return this.plugins && this.plugins[plugName] !== undefined;
|
||||||
},
|
},
|
||||||
getDefaultShortcuts() {
|
getDefaultShortcuts() {
|
||||||
return [
|
return [
|
||||||
|
@ -152,7 +152,7 @@ export default {
|
||||||
{ path: '/acc/costs/list', key: 'G', label: this.$t('drawer.costs'), ctrl: true, shift: true, permission: () => this.permissions.cost },
|
{ path: '/acc/costs/list', key: 'G', label: this.$t('drawer.costs'), ctrl: true, shift: true, permission: () => this.permissions.cost },
|
||||||
{ path: '/acc/sell/fast-mod', key: 'J', label: this.$t('drawer.fast_sell'), ctrl: true, shift: true, permission: () => this.permissions.sell },
|
{ path: '/acc/sell/fast-mod', key: 'J', label: this.$t('drawer.fast_sell'), ctrl: true, shift: true, permission: () => this.permissions.sell },
|
||||||
{ path: '/acc/sell/list', key: 'V', label: this.$t('drawer.sell_invoices'), ctrl: true, shift: true, permission: () => this.permissions.sell },
|
{ path: '/acc/sell/list', key: 'V', label: this.$t('drawer.sell_invoices'), ctrl: true, shift: true, permission: () => this.permissions.sell },
|
||||||
{ path: '/acc/presell/list', key: 'X', label: this.$t('drawer.presells'), ctrl: true, shift: true, permission: () => this.permissions.sell && this.isPluginActive('accpro') },
|
{ path: '/acc/presell/list', key: 'X', label: this.$t('drawer.presells'), ctrl: true, shift: true, permission: () => this.permissions.plugAccproPresell && this.isPluginActive('accpro') },
|
||||||
{ path: '/acc/rfsell/list', key: 'Z', label: this.$t('drawer.rfsell_invoices'), ctrl: true, shift: true, permission: () => this.permissions.plugAccproRfsell && this.isPluginActive('accpro') },
|
{ path: '/acc/rfsell/list', key: 'Z', label: this.$t('drawer.rfsell_invoices'), ctrl: true, shift: true, permission: () => this.permissions.plugAccproRfsell && this.isPluginActive('accpro') },
|
||||||
{ path: '/acc/incomes/list', key: 'A', label: this.$t('drawer.incomes'), ctrl: true, shift: true, permission: () => this.permissions.income },
|
{ path: '/acc/incomes/list', key: 'A', label: this.$t('drawer.incomes'), ctrl: true, shift: true, permission: () => this.permissions.income },
|
||||||
{ path: '/acc/accounting/list', key: '1', label: this.$t('drawer.accounting_docs'), ctrl: true, shift: true, permission: () => this.permissions.accounting },
|
{ path: '/acc/accounting/list', key: '1', label: this.$t('drawer.accounting_docs'), ctrl: true, shift: true, permission: () => this.permissions.accounting },
|
||||||
|
@ -621,7 +621,7 @@ export default {
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item v-if="this.isPluginActive('accpro') && permissions.sell" to="/acc/presell/list">
|
<v-list-item v-if="this.isPluginActive('accpro') && permissions.plugAccproPresell" to="/acc/presell/list">
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
{{ $t('drawer.presells') }}
|
{{ $t('drawer.presells') }}
|
||||||
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/presell/list') }}</span>
|
<span v-if="isCtrlShiftPressed" class="shortcut-key">{{ getShortcutKey('/acc/presell/list') }}</span>
|
||||||
|
@ -883,8 +883,8 @@ export default {
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-tooltip text="جادوگر" location="bottom">
|
<v-tooltip text="جادوگر" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn class="d-none d-sm-flex" stacked v-bind="props" to="/acc/wizard/home">
|
<v-btn class="" stacked v-bind="props" to="/acc/wizard/home">
|
||||||
<v-icon>mdi-wizard-hat</v-icon>
|
<v-icon>mdi-robot</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
|
|
|
@ -1,115 +1,213 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full ">
|
<v-toolbar color="grey-lighten-4" density="compact" title="دستهبندیهای کالا و خدمات">
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
<template v-slot:prepend>
|
||||||
<h3 class="block-title text-primary-dark">
|
<v-btn icon variant="text" color="warning" class="d-none d-sm-none d-md-block" @click="$router.back()">
|
||||||
<button @click="$router.back()" type="button" class="float-start d-none d-sm-none d-md-block btn btn-sm btn-link text-warning">
|
<v-icon>mdi-arrow-right</v-icon>
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
</v-btn>
|
||||||
</button>
|
</template>
|
||||||
<i class="mx-2 fa fa-tree"></i>
|
</v-toolbar>
|
||||||
دستهبندیهای کالا و خدمات </h3>
|
<v-container>
|
||||||
<div class="block-options">
|
<v-card variant="outlined" class="pa-2">
|
||||||
</div>
|
<v-skeleton-loader
|
||||||
</div>
|
v-if="isLoading"
|
||||||
<div class="block-content pt-1 pb-3">
|
type="table-heading"
|
||||||
<loading color="blue" loader="dots" v-model:active="isLoading" :is-full-page="false"/>
|
class="mb-2"
|
||||||
<div class="alert alert-info">
|
></v-skeleton-loader>
|
||||||
<div class="alert-heading">
|
<Tree v-else :nodes="tree" :config="config">
|
||||||
<i class="fa fa-warning"></i>
|
|
||||||
هشدار</div>
|
|
||||||
<div class="">در انتخاب نوع دسته بندی دقت کنید . تا انتشار به روز رسانی بعدی حذف دسته بندی تنها از طریق تیکت پشتیبانی قابل انجام است.</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12 m-0 py-2 px-1 border border-1 rounded-2">
|
|
||||||
<Tree
|
|
||||||
:nodes="tree"
|
|
||||||
:config="config"
|
|
||||||
>
|
|
||||||
<template #after-input="props">
|
<template #after-input="props">
|
||||||
<button class="btn btn-link" type="button" @click="addChild(props.node.id)">
|
<v-tooltip location="top">
|
||||||
<i class="fa fa-plus-circle"></i>
|
<template v-slot:activator="{ props: tooltipProps }">
|
||||||
</button>
|
<v-btn v-bind="tooltipProps" icon variant="text" size="small" @click="addChild(props.node.id)">
|
||||||
<button class="btn btn-link text-warning" type="button" @click="editeNode(props.node)">
|
<v-icon size="small">mdi-plus-circle</v-icon>
|
||||||
<i class="fa fa-edit"></i>
|
</v-btn>
|
||||||
</button>
|
</template>
|
||||||
|
افزودن زیرمجموعه
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
<v-tooltip location="top">
|
||||||
|
<template v-slot:activator="{ props: tooltipProps }">
|
||||||
|
<v-btn v-bind="tooltipProps" icon variant="text" color="warning" size="small"
|
||||||
|
@click="editeNode(props.node)">
|
||||||
|
<v-icon size="small">mdi-pencil</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
ویرایش دستهبندی
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
<v-tooltip v-if="!props.node.children || props.node.children.length === 0" location="top">
|
||||||
|
<template v-slot:activator="{ props: tooltipProps }">
|
||||||
|
<v-btn v-bind="tooltipProps" icon variant="text" color="error" size="small"
|
||||||
|
@click="deleteNode(props.node)">
|
||||||
|
<v-icon size="small">mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
حذف دستهبندی
|
||||||
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</Tree>
|
</Tree>
|
||||||
</div>
|
</v-card>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Modal add child -->
|
<!-- Modal add child -->
|
||||||
<div class="modal fade" id="ModalAdd" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
<v-dialog v-model="showAddModal" persistent max-width="400">
|
||||||
<div class="modal-dialog">
|
<v-card class="rounded-lg">
|
||||||
<div class="modal-content">
|
<v-card-title class="text-h6 bg-grey-lighten-4 py-4">
|
||||||
<div class="modal-header">
|
<v-icon color="primary" class="me-2">mdi-plus-circle</v-icon>
|
||||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">افزودن دستهبندی</h1>
|
افزودن دستهبندی
|
||||||
<div class="block-options">
|
</v-card-title>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<v-card-text class="pt-6">
|
||||||
</div>
|
<v-text-field
|
||||||
</div>
|
v-model="addCatText"
|
||||||
<div class="modal-body">
|
label="نام دسته بندی"
|
||||||
<div class="form-floating mb-4">
|
required
|
||||||
<input v-model="addCatText" class="form-control" type="text">
|
variant="outlined"
|
||||||
<label class="form-label"><span class="text-danger">(لازم)</span> نام دسته بندی</label>
|
density="comfortable"
|
||||||
</div>
|
hide-details="auto"
|
||||||
</div>
|
class="mb-2"
|
||||||
<div class="modal-footer">
|
:rules="[v => !!v || 'نام دستهبندی الزامی است']"
|
||||||
<button @click="this.submitChild" type="button" class="btn btn-primary">
|
:disabled="isLoading"
|
||||||
<i class="fa fa-save"></i>
|
>
|
||||||
ثبت</button>
|
<template v-slot:label>
|
||||||
<button id="modalAddClose" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<span class="text-danger">(لازم)</span> نام دسته بندی
|
||||||
<i class="fa fa-close"></i>
|
</template>
|
||||||
انصراف</button>
|
</v-text-field>
|
||||||
</div>
|
</v-card-text>
|
||||||
</div>
|
<v-divider></v-divider>
|
||||||
</div>
|
<v-card-actions class="pa-4">
|
||||||
</div>
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="secondary" variant="text" @click="showAddModal = false" class="me-2" min-width="100" :disabled="isLoading">
|
||||||
|
<v-icon start>mdi-close</v-icon>
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" @click="submitChild" min-width="100" :loading="isLoading">
|
||||||
|
<v-icon start>mdi-content-save</v-icon>
|
||||||
|
ثبت
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
<!-- Modal edit node -->
|
<!-- Modal edit node -->
|
||||||
<div class="modal fade" id="ModalEdit" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
<v-dialog v-model="showEditModal" persistent max-width="400">
|
||||||
<div class="modal-dialog">
|
<v-card class="rounded-lg">
|
||||||
<div class="modal-content">
|
<v-card-title class="text-h6 bg-grey-lighten-4 py-4">
|
||||||
<div class="modal-header">
|
<v-icon color="warning" class="me-2">mdi-pencil</v-icon>
|
||||||
<h1 class="modal-title fs-5" id="staticBackdropLabel">ویرایش دستهبندی</h1>
|
ویرایش دستهبندی
|
||||||
<div class="block-options">
|
</v-card-title>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<v-card-text class="pt-6">
|
||||||
</div>
|
<v-text-field
|
||||||
</div>
|
v-model="editCatText"
|
||||||
<div class="modal-body">
|
label="نام دسته بندی"
|
||||||
<div class="form-floating mb-4">
|
required
|
||||||
<input v-model="editCatText" class="form-control" type="text">
|
variant="outlined"
|
||||||
<label class="form-label"><span class="text-danger">(لازم)</span> نام دسته بندی</label>
|
density="comfortable"
|
||||||
</div>
|
hide-details="auto"
|
||||||
</div>
|
class="mb-2"
|
||||||
<div class="modal-footer">
|
:rules="[v => !!v || 'نام دستهبندی الزامی است']"
|
||||||
<button @click="this.submitEditChild" type="button" class="btn btn-primary">
|
:disabled="isLoading"
|
||||||
<i class="fa fa-save"></i>
|
>
|
||||||
ثبت</button>
|
<template v-slot:label>
|
||||||
<button id="modalEditClose" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<span class="text-danger">(لازم)</span> نام دسته بندی
|
||||||
<i class="fa fa-close"></i>
|
</template>
|
||||||
انصراف</button>
|
</v-text-field>
|
||||||
</div>
|
</v-card-text>
|
||||||
</div>
|
<v-divider></v-divider>
|
||||||
</div>
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="secondary" variant="text" @click="showEditModal = false" class="me-2" min-width="100" :disabled="isLoading">
|
||||||
|
<v-icon start>mdi-close</v-icon>
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" @click="submitEditChild" min-width="100" :loading="isLoading">
|
||||||
|
<v-icon start>mdi-content-save</v-icon>
|
||||||
|
ثبت
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Dialog delete confirmation -->
|
||||||
|
<v-dialog v-model="showDeleteDialog" persistent max-width="400">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title class="text-h6 bg-error-lighten-5 py-4">
|
||||||
|
<v-icon color="error" class="me-2">mdi-alert-circle</v-icon>
|
||||||
|
حذف دستهبندی
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="pt-6">
|
||||||
|
<v-alert
|
||||||
|
type="warning"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
border="start"
|
||||||
|
>
|
||||||
|
<div class="text-subtitle-2 font-weight-bold mb-2">هشدار</div>
|
||||||
|
آیا از حذف این دستهبندی اطمینان دارید؟
|
||||||
|
<div class="text-caption mt-2">این عملیات غیرقابل بازگشت است.</div>
|
||||||
|
</v-alert>
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="secondary" variant="text" @click="showDeleteDialog = false" class="me-2" min-width="100" :disabled="isLoading">
|
||||||
|
<v-icon start>mdi-close</v-icon>
|
||||||
|
خیر
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="error" @click="confirmDelete" min-width="100" :loading="isLoading">
|
||||||
|
<v-icon start>mdi-delete</v-icon>
|
||||||
|
بله، حذف شود
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Snackbar for notifications -->
|
||||||
|
<v-snackbar
|
||||||
|
v-model="showSnackbar"
|
||||||
|
:color="snackbarColor"
|
||||||
|
:timeout="3000"
|
||||||
|
location="bottom"
|
||||||
|
class="rounded-lg"
|
||||||
|
elevation="2"
|
||||||
|
>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon :color="snackbarColor" class="me-2">
|
||||||
|
{{ snackbarColor === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle' }}
|
||||||
|
</v-icon>
|
||||||
|
{{ snackbarText }}
|
||||||
</div>
|
</div>
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn icon variant="text" @click="showSnackbar = false">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Swal from "sweetalert2";
|
import { ref, onMounted } from "vue";
|
||||||
import {ref} from "vue";
|
|
||||||
import Loading from "vue-loading-overlay";
|
|
||||||
import treeview from "vue3-treeview";
|
import treeview from "vue3-treeview";
|
||||||
import "vue3-treeview/dist/style.css";
|
import "vue3-treeview/dist/style.css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "list",
|
name: "list",
|
||||||
components:{
|
components: {
|
||||||
Tree : treeview,
|
Tree: treeview
|
||||||
Loading
|
|
||||||
},
|
},
|
||||||
data: ()=>{return {
|
setup() {
|
||||||
isLoading: true,
|
const isLoading = ref(true);
|
||||||
config: {
|
const showAddModal = ref(false);
|
||||||
|
const showEditModal = ref(false);
|
||||||
|
const showDeleteDialog = ref(false);
|
||||||
|
const showSnackbar = ref(false);
|
||||||
|
const snackbarText = ref('');
|
||||||
|
const snackbarColor = ref('success');
|
||||||
|
const selectedNode = ref(0);
|
||||||
|
const nodeToDelete = ref(null);
|
||||||
|
const addCatText = ref('');
|
||||||
|
const editCatText = ref('');
|
||||||
|
const tree = ref([]);
|
||||||
|
const config = ref({
|
||||||
roots: [],
|
roots: [],
|
||||||
opened: true,
|
opened: true,
|
||||||
openedIcon: {
|
openedIcon: {
|
||||||
|
@ -126,89 +224,258 @@ export default {
|
||||||
viewBox: "0 0 24 24",
|
viewBox: "0 0 24 24",
|
||||||
draw: `M 12 2 L 12 22 M 2 12 L 22 12`,
|
draw: `M 12 2 L 12 22 M 2 12 L 22 12`,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
tree: [],
|
|
||||||
selectedNode:0,
|
|
||||||
addCatText:'',
|
|
||||||
editCatText:''
|
|
||||||
}},
|
|
||||||
mounted() {
|
|
||||||
this.loadData();
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
loadData(){
|
|
||||||
axios.post('/api/commodity/cat/get').then((response)=>{
|
|
||||||
this.tree = response.data.items;
|
|
||||||
this.config.roots.push(response.data.root.toString());
|
|
||||||
this.isLoading =false;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
addChild(id){
|
|
||||||
this.selectedNode = id;
|
|
||||||
const myModalAlternative = new bootstrap.Modal('#ModalAdd', {})
|
|
||||||
myModalAlternative.show();
|
|
||||||
},
|
|
||||||
submitChild(node){
|
|
||||||
if(this.addCatText.trim().length === 0 ){
|
|
||||||
Swal.fire({
|
|
||||||
text: 'نام دستهبندی الزامی است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
else {
|
|
||||||
axios.post('/api/commodity/cat/insert',{
|
|
||||||
upper:this.selectedNode,
|
|
||||||
text:this.addCatText
|
|
||||||
}).then((response)=>{
|
|
||||||
Swal.fire({
|
|
||||||
text: 'دسته بندی افزوده شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((res)=>{
|
|
||||||
document.getElementById('modalAddClose').click();
|
|
||||||
window.location.reload();
|
|
||||||
} );
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editeNode(node){
|
|
||||||
this.selectedNode = node.id;
|
|
||||||
this.editCatText = node.text;
|
|
||||||
const myModalAlternative = new bootstrap.Modal('#ModalEdit', {})
|
|
||||||
myModalAlternative.show();
|
|
||||||
},
|
|
||||||
submitEditChild(node){
|
|
||||||
if(this.editCatText.trim().length === 0 ){
|
|
||||||
Swal.fire({
|
|
||||||
text: 'نام دستهبندی الزامی است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
axios.post('/api/commodity/cat/edit',{
|
|
||||||
id:this.selectedNode,
|
|
||||||
text:this.editCatText
|
|
||||||
}).then((response)=>{
|
|
||||||
Swal.fire({
|
|
||||||
text: 'دسته بندی ویرایش شد.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
}).then((res)=>{
|
|
||||||
document.getElementById('modalEditClose').click();
|
|
||||||
window.location.reload();
|
|
||||||
} );
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
const showNotification = (text, color = 'success') => {
|
||||||
|
snackbarText.value = text;
|
||||||
|
snackbarColor.value = color;
|
||||||
|
showSnackbar.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/commodity/cat/get');
|
||||||
|
const convertIdsToString = (items) => {
|
||||||
|
return Object.entries(items).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = {
|
||||||
|
...value,
|
||||||
|
id: String(value.id),
|
||||||
|
parent: value.parent ? String(value.parent) : null,
|
||||||
|
children: value.children ? value.children.map(child => String(child)) : [],
|
||||||
|
state: {
|
||||||
|
...value.state,
|
||||||
|
disabled: false,
|
||||||
|
opened: true,
|
||||||
|
selected: false,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.value = convertIdsToString(response.data.items);
|
||||||
|
config.value = {
|
||||||
|
...config.value,
|
||||||
|
roots: [String(response.data.root)],
|
||||||
|
opened: true,
|
||||||
|
editing: null,
|
||||||
|
disabled: false
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading data:', error);
|
||||||
|
showNotification('خطا در بارگذاری اطلاعات', 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addChild = (id) => {
|
||||||
|
selectedNode.value = String(id);
|
||||||
|
showAddModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitChild = async () => {
|
||||||
|
if (addCatText.value.trim().length === 0) {
|
||||||
|
showNotification('نام دستهبندی الزامی است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/commodity/cat/insert', {
|
||||||
|
upper: selectedNode.value,
|
||||||
|
text: addCatText.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.id) {
|
||||||
|
const newNode = {
|
||||||
|
id: String(response.data.id),
|
||||||
|
text: addCatText.value,
|
||||||
|
parent: selectedNode.value,
|
||||||
|
children: [],
|
||||||
|
state: {
|
||||||
|
disabled: false,
|
||||||
|
opened: true,
|
||||||
|
selected: false,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tree.value[selectedNode.value]) {
|
||||||
|
if (!tree.value[selectedNode.value].children) {
|
||||||
|
tree.value[selectedNode.value].children = [];
|
||||||
|
}
|
||||||
|
tree.value[selectedNode.value].children.push(String(response.data.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.value[String(response.data.id)] = newNode;
|
||||||
|
showNotification('دستهبندی با موفقیت افزوده شد');
|
||||||
|
showAddModal.value = false;
|
||||||
|
addCatText.value = '';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding category:', error);
|
||||||
|
showNotification('خطا در افزودن دستهبندی', 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editeNode = (node) => {
|
||||||
|
selectedNode.value = String(node.id);
|
||||||
|
editCatText.value = node.text;
|
||||||
|
showEditModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitEditChild = async () => {
|
||||||
|
if (editCatText.value.trim().length === 0) {
|
||||||
|
showNotification('نام دستهبندی الزامی است', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await axios.post('/api/commodity/cat/edit', {
|
||||||
|
id: selectedNode.value,
|
||||||
|
text: editCatText.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tree.value[selectedNode.value]) {
|
||||||
|
tree.value[selectedNode.value].text = editCatText.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification('دستهبندی با موفقیت ویرایش شد');
|
||||||
|
showEditModal.value = false;
|
||||||
|
editCatText.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error editing category:', error);
|
||||||
|
showNotification('خطا در ویرایش دستهبندی', 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteNode = (node) => {
|
||||||
|
nodeToDelete.value = node;
|
||||||
|
showDeleteDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!nodeToDelete.value) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await axios.post('/api/commodity/cat/delete', {
|
||||||
|
id: nodeToDelete.value.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tree.value[nodeToDelete.value.parent]) {
|
||||||
|
const parentNode = tree.value[nodeToDelete.value.parent];
|
||||||
|
parentNode.children = parentNode.children.filter(child => child !== nodeToDelete.value.id);
|
||||||
|
}
|
||||||
|
delete tree.value[nodeToDelete.value.id];
|
||||||
|
|
||||||
|
showNotification('دستهبندی با موفقیت حذف شد');
|
||||||
|
showDeleteDialog.value = false;
|
||||||
|
nodeToDelete.value = null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting category:', error);
|
||||||
|
showNotification('خطا در حذف دستهبندی', 'error');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
showAddModal,
|
||||||
|
showEditModal,
|
||||||
|
showDeleteDialog,
|
||||||
|
showSnackbar,
|
||||||
|
snackbarText,
|
||||||
|
snackbarColor,
|
||||||
|
selectedNode,
|
||||||
|
addCatText,
|
||||||
|
editCatText,
|
||||||
|
tree,
|
||||||
|
config,
|
||||||
|
loadData,
|
||||||
|
addChild,
|
||||||
|
submitChild,
|
||||||
|
editeNode,
|
||||||
|
submitEditChild,
|
||||||
|
deleteNode,
|
||||||
|
confirmDelete
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.node-input, .node-text, .tree{
|
.node-input,
|
||||||
|
.node-text,
|
||||||
|
.tree {
|
||||||
font-family: 'vazir', sans-serif;
|
font-family: 'vazir', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.v-btn) {
|
||||||
|
min-width: 24px !important;
|
||||||
|
width: 24px !important;
|
||||||
|
height: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-btn:not(.v-btn--icon)) {
|
||||||
|
min-width: 100px !important;
|
||||||
|
width: auto !important;
|
||||||
|
height: 36px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-icon) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-tooltip__content) {
|
||||||
|
font-family: 'vazir', sans-serif !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-card-title) {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-card-text) {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-dialog) {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-alert) {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-snackbar) {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
margin-bottom: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-snackbar__content) {
|
||||||
|
padding: 12px !important;
|
||||||
|
font-family: 'vazir', sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-snackbar__wrapper) {
|
||||||
|
min-width: 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-skeleton-loader) {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -91,6 +91,36 @@
|
||||||
<v-icon>mdi-filter</v-icon>
|
<v-icon>mdi-filter</v-icon>
|
||||||
{{ $t('dialog.filters') }}
|
{{ $t('dialog.filters') }}
|
||||||
</v-list-subheader>
|
</v-list-subheader>
|
||||||
|
|
||||||
|
<!-- فیلتر درختی حسابها -->
|
||||||
|
<v-list-item>
|
||||||
|
<v-list-item-title class="text-dark mb-2">
|
||||||
|
فیلتر مرکز هزینه:
|
||||||
|
<v-btn
|
||||||
|
v-if="selectedAccountId !== '67'"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
variant="text"
|
||||||
|
class="ms-2"
|
||||||
|
@click="resetAccountFilter"
|
||||||
|
>
|
||||||
|
<v-icon size="small" class="me-1">mdi-refresh</v-icon>
|
||||||
|
بازنشانی
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item-title>
|
||||||
|
<hesabdari-tree-view
|
||||||
|
v-model="selectedAccountId"
|
||||||
|
:show-sub-tree="true"
|
||||||
|
:selectable-only="false"
|
||||||
|
:initial-account="{ code: '67', name: 'هزینهها' }"
|
||||||
|
@select="handleAccountSelect"
|
||||||
|
@account-selected="handleAccountSelected"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-divider class="my-2"></v-divider>
|
||||||
|
|
||||||
|
<!-- فیلترهای زمانی -->
|
||||||
<v-list-item v-for="(filter, index) in timeFilters" :key="index" class="text-dark">
|
<v-list-item v-for="(filter, index) in timeFilters" :key="index" class="text-dark">
|
||||||
<template v-slot:title>
|
<template v-slot:title>
|
||||||
<v-checkbox v-model="filter.checked" :label="filter.label" @change="applyTimeFilter(filter.value)"
|
<v-checkbox v-model="filter.checked" :label="filter.label" @change="applyTimeFilter(filter.value)"
|
||||||
|
@ -152,7 +182,7 @@
|
||||||
<v-list density="compact" class="pa-0 ma-0">
|
<v-list density="compact" class="pa-0 ma-0">
|
||||||
<v-list-item :border="true" v-for="(center, index) in item.costCenters" :key="index">
|
<v-list-item :border="true" v-for="(center, index) in item.costCenters" :key="index">
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
{{ center.name }}
|
{{ center.name }} ({{ center.code }})
|
||||||
{{ $t('dialog.acc_price') }} : {{ $filters.formatNumber(center.amount) }}
|
{{ $t('dialog.acc_price') }} : {{ $filters.formatNumber(center.amount) }}
|
||||||
{{ $t('dialog.des') }} : {{ center.des }}
|
{{ $t('dialog.des') }} : {{ center.des }}
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
|
@ -211,6 +241,7 @@ import Swal from 'sweetalert2';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getApiUrl } from '/src/hesabixConfig';
|
import { getApiUrl } from '/src/hesabixConfig';
|
||||||
import moment from 'jalali-moment';
|
import moment from 'jalali-moment';
|
||||||
|
import HesabdariTreeView from '@/components/forms/HesabdariTreeView.vue';
|
||||||
|
|
||||||
const apiUrl = getApiUrl();
|
const apiUrl = getApiUrl();
|
||||||
axios.defaults.baseURL = apiUrl;
|
axios.defaults.baseURL = apiUrl;
|
||||||
|
@ -222,7 +253,8 @@ const selectedItems = ref(new Set());
|
||||||
const totalItems = ref(0);
|
const totalItems = ref(0);
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
const timeFilter = ref('all');
|
const timeFilter = ref('all');
|
||||||
const expanded = ref([]); // برای مدیریت ردیفهای گسترشیافته
|
const expanded = ref([]);
|
||||||
|
const selectedAccountId = ref('67');
|
||||||
|
|
||||||
// فیلترهای زمانی
|
// فیلترهای زمانی
|
||||||
const timeFilters = ref([
|
const timeFilters = ref([
|
||||||
|
@ -232,7 +264,7 @@ const timeFilters = ref([
|
||||||
{ label: 'همه', value: 'all', checked: true },
|
{ label: 'همه', value: 'all', checked: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// تعریف ستونهای جدول (ستون costCenter از هدرها حذف شده)
|
// تعریف ستونهای جدول
|
||||||
const headers = ref([
|
const headers = ref([
|
||||||
{ title: '', key: 'checkbox', sortable: false, width: '50', align: 'center' },
|
{ title: '', key: 'checkbox', sortable: false, width: '50', align: 'center' },
|
||||||
{ title: 'ردیف', key: 'index', align: 'center', sortable: false, width: '70' },
|
{ title: 'ردیف', key: 'index', align: 'center', sortable: false, width: '70' },
|
||||||
|
@ -265,6 +297,26 @@ const selectedCost = computed(() => {
|
||||||
.reduce((sum, item) => sum + Number(item.amount || 0), 0);
|
.reduce((sum, item) => sum + Number(item.amount || 0), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// متدهای مدیریت فیلتر حساب
|
||||||
|
const handleAccountSelect = (account) => {
|
||||||
|
if (account) {
|
||||||
|
selectedAccountId.value = account.code;
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAccountSelected = (account) => {
|
||||||
|
if (account) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// متد ریست کردن فیلتر حساب
|
||||||
|
const resetAccountFilter = () => {
|
||||||
|
selectedAccountId.value = '67';
|
||||||
|
fetchData();
|
||||||
|
};
|
||||||
|
|
||||||
// فچ کردن دادهها از سرور
|
// فچ کردن دادهها از سرور
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -291,12 +343,14 @@ const fetchData = async () => {
|
||||||
filters.dateFrom = moment().locale('fa').startOf('jMonth').format('YYYY/MM/DD');
|
filters.dateFrom = moment().locale('fa').startOf('jMonth').format('YYYY/MM/DD');
|
||||||
filters.dateTo = today;
|
filters.dateTo = today;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// اضافه کردن فیلتر حساب
|
||||||
|
if (selectedAccountId.value) {
|
||||||
|
filters.account = selectedAccountId.value;
|
||||||
|
}
|
||||||
|
|
||||||
const sortByArray = Array.isArray(serverOptions.value.sortBy) ? serverOptions.value.sortBy : [];
|
const sortByArray = Array.isArray(serverOptions.value.sortBy) ? serverOptions.value.sortBy : [];
|
||||||
const sortDescArray = Array.isArray(serverOptions.value.sortDesc) ? serverOptions.value.sortDesc : [];
|
const sortDescArray = Array.isArray(serverOptions.value.sortDesc) ? serverOptions.value.sortDesc : [];
|
||||||
const sortBy = sortByArray.length > 0 ? sortByArray[0].key : 'code';
|
const sortBy = sortByArray.length > 0 ? sortByArray[0].key : 'code';
|
||||||
|
|
|
@ -400,7 +400,9 @@ const print = async (allItems = true) => {
|
||||||
if (allItems) {
|
if (allItems) {
|
||||||
response = await axios.post('/api/person/receive/list/print');
|
response = await axios.post('/api/person/receive/list/print');
|
||||||
} else {
|
} else {
|
||||||
response = await axios.post('/api/person/receive/list/print', { items: selectedItems.value });
|
response = await axios.post('/api/person/receive/list/print', {
|
||||||
|
items: selectedItems.value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const printID = response.data.id;
|
const printID = response.data.id;
|
||||||
|
|
|
@ -118,29 +118,12 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<!-- Print Modal -->
|
<!-- Print Modal -->
|
||||||
<v-dialog v-model="modal" width="auto">
|
<PrintDialog
|
||||||
<v-card :subtitle="$t('dialog.print_info_des')" prepend-icon="mdi-file-pdf-box" :title="$t('dialog.export_pdf')">
|
v-model="modal"
|
||||||
<template v-slot:text>
|
:plugins="plugins"
|
||||||
<v-select class=mb-2 v-model="printOptions.paper" :items="paperSizes" :label="$t('dialog.paper_size')">
|
@print="printInvoice"
|
||||||
</v-select>
|
@cancel="modal = false"
|
||||||
<v-switch inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')"
|
/>
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<template v-slot:actions>
|
|
||||||
<v-btn variant="tonal" class="" prepend-icon="mdi-printer" color="primary" :text="$t('dialog.print')"
|
|
||||||
@click="modal = false; printInvoice()"></v-btn>
|
|
||||||
<v-btn variant="tonal" class="" prepend-icon="mdi-undo" color="secondary" :text="$t('dialog.cancel')"
|
|
||||||
@click="modal = false"></v-btn>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
<!-- End Print Modal -->
|
<!-- End Print Modal -->
|
||||||
<!-- Delete Dialog -->
|
<!-- Delete Dialog -->
|
||||||
<v-dialog v-model="deleteDialog" width="auto">
|
<v-dialog v-model="deleteDialog" width="auto">
|
||||||
|
@ -205,9 +188,13 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ref, defineComponent } from "vue";
|
import { ref, defineComponent } from "vue";
|
||||||
|
import PrintDialog from '@/components/PrintDialog.vue';
|
||||||
|
|
||||||
export default defineComponent ({
|
export default defineComponent ({
|
||||||
name: "list",
|
name: "list",
|
||||||
|
components: {
|
||||||
|
PrintDialog
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
let self = this;
|
let self = this;
|
||||||
return {
|
return {
|
||||||
|
@ -236,8 +223,11 @@ export default defineComponent ({
|
||||||
taxInfo: true,
|
taxInfo: true,
|
||||||
discountInfo: true,
|
discountInfo: true,
|
||||||
selectedPrintCode: 0,
|
selectedPrintCode: 0,
|
||||||
paper: 'A4-L'
|
paper: 'A4-L',
|
||||||
|
businessStamp: true,
|
||||||
|
invoiceIndex: true
|
||||||
},
|
},
|
||||||
|
plugins: {},
|
||||||
sumSelected: 0,
|
sumSelected: 0,
|
||||||
sumTotal: 0,
|
sumTotal: 0,
|
||||||
itemsSelected: [],
|
itemsSelected: [],
|
||||||
|
@ -267,7 +257,20 @@ export default defineComponent ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isPluginActive(pluginCode) {
|
||||||
|
return this.plugins && this.plugins[pluginCode] !== undefined;
|
||||||
|
},
|
||||||
|
async loadPlugins() {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/plugin/get/actives');
|
||||||
|
this.plugins = response.data || {};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading plugins:', error);
|
||||||
|
this.plugins = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
loadData() {
|
loadData() {
|
||||||
|
this.loadPlugins();
|
||||||
axios.post("/api/printers/options/info").then((response) => {
|
axios.post("/api/printers/options/info").then((response) => {
|
||||||
this.printOptions = response.data.sell;
|
this.printOptions = response.data.sell;
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-tooltip :text="$t('dialog.export_pdf')" location="bottom">
|
<v-tooltip :text="$t('dialog.export_pdf')" location="bottom">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" icon="mdi-file-pdf-box" color="primary" @click="printInvoice()"></v-btn>
|
<v-btn v-bind="props" icon="mdi-file-pdf-box" color="primary" @click="modal = true"></v-btn>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
@ -94,15 +94,28 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Print Dialog -->
|
||||||
|
<PrintDialog
|
||||||
|
v-model="modal"
|
||||||
|
:plugins="plugins"
|
||||||
|
@print="handlePrint"
|
||||||
|
@cancel="modal = false"
|
||||||
|
/>
|
||||||
|
<!-- End Print Dialog -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ref, defineComponent } from "vue";
|
import { ref, defineComponent } from "vue";
|
||||||
|
import PrintDialog from '@/components/PrintDialog.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "PresellView",
|
name: "PresellView",
|
||||||
|
components: {
|
||||||
|
PrintDialog
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
code: {
|
code: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
|
@ -116,7 +129,9 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialog: this.modelValue,
|
dialog: this.modelValue,
|
||||||
|
modal: false,
|
||||||
presellData: {},
|
presellData: {},
|
||||||
|
plugins: {},
|
||||||
itemsHeaders: [
|
itemsHeaders: [
|
||||||
{ text: "کد کالا", value: "itemCode" },
|
{ text: "کد کالا", value: "itemCode" },
|
||||||
{ text: "نام کالا", value: "itemName" },
|
{ text: "نام کالا", value: "itemName" },
|
||||||
|
@ -124,14 +139,7 @@ export default defineComponent({
|
||||||
{ text: "قیمت", value: "price" },
|
{ text: "قیمت", value: "price" },
|
||||||
{ text: "مبلغ", value: "amount" },
|
{ text: "مبلغ", value: "amount" },
|
||||||
{ text: "جمع", value: "total" }
|
{ text: "جمع", value: "total" }
|
||||||
],
|
]
|
||||||
printOptions: {
|
|
||||||
note: true,
|
|
||||||
bidInfo: true,
|
|
||||||
taxInfo: true,
|
|
||||||
discountInfo: true,
|
|
||||||
paper: 'A4-L'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -140,25 +148,37 @@ export default defineComponent({
|
||||||
this.$emit('update:modelValue', value);
|
this.$emit('update:modelValue', value);
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
|
async loadPlugins() {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/plugin/get/actives');
|
||||||
|
this.plugins = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری افزونهها:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlePrint(printOptions) {
|
||||||
|
this.printInvoice(true, true, printOptions);
|
||||||
|
},
|
||||||
|
printInvoice(pdf = true, cloudePrinters = true, printOptions = null) {
|
||||||
|
axios.post('/api/preinvoice/print/invoice', {
|
||||||
|
'code': this.code,
|
||||||
|
'pdf': pdf,
|
||||||
|
'printers': cloudePrinters,
|
||||||
|
'printOptions': printOptions
|
||||||
|
}).then((response) => {
|
||||||
|
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
|
||||||
|
});
|
||||||
|
},
|
||||||
loadData() {
|
loadData() {
|
||||||
axios.post('/api/preinvoice/get/' + this.code)
|
axios.post('/api/preinvoice/get/' + this.code)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.presellData = response.data;
|
this.presellData = response.data;
|
||||||
});
|
});
|
||||||
},
|
|
||||||
printInvoice() {
|
|
||||||
axios.post('/api/preinvoice/print/invoice', {
|
|
||||||
'code': this.code,
|
|
||||||
'pdf': true,
|
|
||||||
'printers': true,
|
|
||||||
'printOptions': this.printOptions
|
|
||||||
}).then((response) => {
|
|
||||||
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadData();
|
this.loadData();
|
||||||
|
this.loadPlugins();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -193,28 +193,12 @@
|
||||||
<v-progress-circular color="primary" indeterminate></v-progress-circular>
|
<v-progress-circular color="primary" indeterminate></v-progress-circular>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<!-- Print Modal -->
|
<!-- Print Modal -->
|
||||||
<v-dialog v-model="modal" width="auto">
|
<PrintDialog
|
||||||
<v-card :subtitle="$t('dialog.print_info_des')" prepend-icon="mdi-file-pdf-box" :title="$t('dialog.export_pdf')">
|
v-model="modal"
|
||||||
<template v-slot:text>
|
:plugins="plugins"
|
||||||
<v-select class="mb-2" v-model="printOptions.paper" :items="paperSizes" :label="$t('dialog.paper_size')">
|
@print="handlePrint"
|
||||||
</v-select>
|
@cancel="modal = false"
|
||||||
<v-switch inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')"
|
/>
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
<v-switch inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
</template>
|
|
||||||
<template v-slot:actions>
|
|
||||||
<v-btn variant="tonal" class="" prepend-icon="mdi-printer" color="primary" :text="$t('dialog.print')"
|
|
||||||
@click="modal = false; printInvoice()"></v-btn>
|
|
||||||
<v-btn variant="tonal" class="" prepend-icon="mdi-undo" color="secondary" :text="$t('dialog.cancel')"
|
|
||||||
@click="modal = false"></v-btn>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
<!-- End Print Modal -->
|
<!-- End Print Modal -->
|
||||||
<!-- Delete Dialog -->
|
<!-- Delete Dialog -->
|
||||||
<v-dialog v-model="deleteDialog" max-width="500">
|
<v-dialog v-model="deleteDialog" max-width="500">
|
||||||
|
@ -265,9 +249,13 @@
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import PrintDialog from '@/components/PrintDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "viewInvoice",
|
name: "viewInvoice",
|
||||||
|
components: {
|
||||||
|
PrintDialog
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const items = ref([]);
|
const items = ref([]);
|
||||||
const totalInvoice = ref(0);
|
const totalInvoice = ref(0);
|
||||||
|
@ -287,24 +275,12 @@ export default {
|
||||||
const preinvoiceCode = ref('');
|
const preinvoiceCode = ref('');
|
||||||
const modal = ref(false);
|
const modal = ref(false);
|
||||||
const deleteDialog = ref(false);
|
const deleteDialog = ref(false);
|
||||||
|
const plugins = ref({});
|
||||||
const snackbar = ref({
|
const snackbar = ref({
|
||||||
show: false,
|
show: false,
|
||||||
text: '',
|
text: '',
|
||||||
color: 'success'
|
color: 'success'
|
||||||
});
|
});
|
||||||
const printOptions = ref({
|
|
||||||
note: true,
|
|
||||||
bidInfo: true,
|
|
||||||
taxInfo: true,
|
|
||||||
discountInfo: true,
|
|
||||||
paper: 'A4-L'
|
|
||||||
});
|
|
||||||
const paperSizes = ref([
|
|
||||||
{ title: 'A4 عمودی', value: 'A4' },
|
|
||||||
{ title: 'A4 افقی', value: 'A4-L' },
|
|
||||||
{ title: 'A5 عمودی', value: 'A5' },
|
|
||||||
{ title: 'A5 افقی', value: 'A5-L' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
const customerName = computed(() => {
|
const customerName = computed(() => {
|
||||||
return customer.value?.nikename || customer.value?.name || 'نامشخص';
|
return customer.value?.nikename || customer.value?.name || 'نامشخص';
|
||||||
|
@ -330,15 +306,15 @@ export default {
|
||||||
preinvoiceCode,
|
preinvoiceCode,
|
||||||
modal,
|
modal,
|
||||||
deleteDialog,
|
deleteDialog,
|
||||||
snackbar,
|
plugins,
|
||||||
printOptions,
|
snackbar
|
||||||
paperSizes
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const id = this.$route.params.id;
|
const id = this.$route.params.id;
|
||||||
if (id) {
|
if (id) {
|
||||||
this.loadPreinvoice(id);
|
this.loadPreinvoice(id);
|
||||||
|
this.loadPlugins();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -420,13 +396,24 @@ export default {
|
||||||
|
|
||||||
this.finalTotal = total + this.taxAmount - calculatedTotalDiscount + (Number(this.shippingCost) || 0);
|
this.finalTotal = total + this.taxAmount - calculatedTotalDiscount + (Number(this.shippingCost) || 0);
|
||||||
},
|
},
|
||||||
printInvoice(pdf = true, cloudePrinters = true) {
|
async loadPlugins() {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/plugin/get/actives');
|
||||||
|
this.plugins = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری افزونهها:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlePrint(printOptions) {
|
||||||
|
this.printInvoice(true, true, printOptions);
|
||||||
|
},
|
||||||
|
printInvoice(pdf = true, cloudePrinters = true, printOptions = null) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
axios.post('/api/preinvoice/print/invoice', {
|
axios.post('/api/preinvoice/print/invoice', {
|
||||||
'code': this.preinvoiceCode,
|
'code': this.preinvoiceCode,
|
||||||
'pdf': pdf,
|
'pdf': pdf,
|
||||||
'printers': cloudePrinters,
|
'printers': cloudePrinters,
|
||||||
'printOptions': this.printOptions
|
'printOptions': printOptions
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
|
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
|
||||||
|
|
|
@ -236,20 +236,69 @@
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<v-dialog v-model="modal" width="auto">
|
<v-dialog v-model="modal" width="auto">
|
||||||
<v-card :subtitle="$t('dialog.print_info_des')" prepend-icon="mdi-file-pdf-box" :title="$t('dialog.export_pdf')">
|
<v-card>
|
||||||
<template v-slot:text>
|
<v-toolbar color="primary">
|
||||||
|
<v-toolbar-title class="text-white">
|
||||||
|
<v-icon icon="mdi-file-pdf-box" class="ml-2"></v-icon>
|
||||||
|
{{ $t('dialog.export_pdf') }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-tooltip :text="$t('dialog.print')" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-printer" color="white" @click="modal = false; printInvoice()"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.cancel')" location="bottom">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-close" color="white" @click="modal = false"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-card-text>
|
||||||
<v-select class="mb-2" v-model="printOptions.paper" :items="paperSizes" :label="$t('dialog.paper_size')">
|
<v-select class="mb-2" v-model="printOptions.paper" :items="paperSizes" :label="$t('dialog.paper_size')">
|
||||||
</v-select>
|
</v-select>
|
||||||
<v-switch inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')" hide-details></v-switch>
|
<v-row>
|
||||||
<v-switch inset v-model="printOptions.pays" color="primary" :label="$t('dialog.invoice_pays')" hide-details></v-switch>
|
<v-col cols="12" sm="6">
|
||||||
<v-switch inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')" hide-details></v-switch>
|
<v-tooltip :text="$t('dialog.bid_info_label')" location="right">
|
||||||
<v-switch inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')" hide-details></v-switch>
|
<template v-slot:activator="{ props }">
|
||||||
<v-switch inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')" hide-details></v-switch>
|
<v-switch v-bind="props" inset v-model="printOptions.bidInfo" color="primary" :label="$t('dialog.bid_info_label')" hide-details></v-switch>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:actions>
|
</v-tooltip>
|
||||||
<v-btn variant="tonal" prepend-icon="mdi-printer" color="primary" :text="$t('dialog.print')" @click="modal = false; printInvoice()"></v-btn>
|
<v-tooltip :text="$t('dialog.invoice_pays')" location="right">
|
||||||
<v-btn variant="tonal" prepend-icon="mdi-undo" color="secondary" :text="$t('dialog.cancel')" @click="modal = false"></v-btn>
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.pays" color="primary" :label="$t('dialog.invoice_pays')" hide-details></v-switch>
|
||||||
</template>
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.invoice_footer_note')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.note" color="primary" :label="$t('dialog.invoice_footer_note')" hide-details></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-tooltip :text="$t('dialog.tax_dexpo')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.taxInfo" color="primary" :label="$t('dialog.tax_dexpo')" hide-details></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.discount_dexpo')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-bind="props" inset v-model="printOptions.discountInfo" color="primary" :label="$t('dialog.discount_dexpo')" hide-details></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.business_stamp')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.businessStamp" color="primary" :label="$t('dialog.business_stamp')" hide-details></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip :text="$t('dialog.invoice_index')" location="right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-switch v-if="isPluginActive('accpro')" v-bind="props" inset v-model="printOptions.invoiceIndex" color="primary" :label="$t('dialog.invoice_index')" hide-details></v-switch>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
@ -280,8 +329,11 @@ export default defineComponent({
|
||||||
taxInfo: true,
|
taxInfo: true,
|
||||||
discountInfo: true,
|
discountInfo: true,
|
||||||
selectedPrintCode: 0,
|
selectedPrintCode: 0,
|
||||||
paper: 'A4-L'
|
paper: 'A4-L',
|
||||||
|
businessStamp: true,
|
||||||
|
invoiceIndex: true
|
||||||
},
|
},
|
||||||
|
plugins: {},
|
||||||
sumSelected: 0,
|
sumSelected: 0,
|
||||||
sumTotal: 0,
|
sumTotal: 0,
|
||||||
itemsSelected: [],
|
itemsSelected: [],
|
||||||
|
@ -330,6 +382,18 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isPluginActive(pluginCode) {
|
||||||
|
return this.plugins && this.plugins[pluginCode] !== undefined;
|
||||||
|
},
|
||||||
|
async loadPlugins() {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/plugin/get/actives');
|
||||||
|
this.plugins = response.data || {};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading plugins:', error);
|
||||||
|
this.plugins = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
changeLabel(label) {
|
changeLabel(label) {
|
||||||
if (this.itemsSelected.length === 0) {
|
if (this.itemsSelected.length === 0) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
|
@ -606,6 +670,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadColumnSettings();
|
this.loadColumnSettings();
|
||||||
|
this.loadPlugins();
|
||||||
this.loadData();
|
this.loadData();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -1083,7 +1083,7 @@
|
||||||
this.items.push({
|
this.items.push({
|
||||||
commodity: item.commodity,
|
commodity: item.commodity,
|
||||||
count: item.commodity_count,
|
count: item.commodity_count,
|
||||||
price: parseInt((parseInt(item.bs) - parseInt(item.tax) + parseInt(item.discount)) / parseInt(item.commodity_count)),
|
price: parseFloat((parseFloat(item.bs) - parseFloat(item.tax) + parseFloat(item.discount)) / parseFloat(item.commodity_count)),
|
||||||
bs: item.bs,
|
bs: item.bs,
|
||||||
bd: item.bd,
|
bd: item.bd,
|
||||||
type: 'commodity',
|
type: 'commodity',
|
||||||
|
@ -1091,7 +1091,7 @@
|
||||||
des: item.des,
|
des: item.des,
|
||||||
discount: item.discount,
|
discount: item.discount,
|
||||||
tax: item.tax,
|
tax: item.tax,
|
||||||
sumWithoutTax: item.bs - item.tax,
|
sumWithoutTax: parseFloat(item.bs) - parseFloat(item.tax),
|
||||||
sumTotal: item.bs,
|
sumTotal: item.bs,
|
||||||
table: 53
|
table: 53
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,6 +83,12 @@
|
||||||
<v-col cols="12" sm="6" md="4" lg="3">
|
<v-col cols="12" sm="6" md="4" lg="3">
|
||||||
<v-switch v-model="settings.sell.discountInfo" label="تخفیف به تفکیک اقلام" color="primary" hide-details density="compact"></v-switch>
|
<v-switch v-model="settings.sell.discountInfo" label="تخفیف به تفکیک اقلام" color="primary" hide-details density="compact"></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" lg="3">
|
||||||
|
<v-switch v-model="settings.sell.businessStamp" label="مهر کسب و کار" color="primary" hide-details density="compact"></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" lg="3">
|
||||||
|
<v-switch v-model="settings.sell.invoiceIndex" label="نمایه فاکتور" color="primary" hide-details density="compact"></v-switch>
|
||||||
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
@ -283,6 +289,8 @@ export default {
|
||||||
taxInfo: true,
|
taxInfo: true,
|
||||||
discountInfo: true,
|
discountInfo: true,
|
||||||
paper: 'A4-L',
|
paper: 'A4-L',
|
||||||
|
businessStamp: true,
|
||||||
|
invoiceIndex: true
|
||||||
},
|
},
|
||||||
buy: {
|
buy: {
|
||||||
pays: true,
|
pays: true,
|
||||||
|
|
|
@ -1,256 +1,574 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="block block-content-full">
|
<div>
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light pt-2 pb-1">
|
<v-toolbar color="toolbar" title="تنظیمات دسترسی">
|
||||||
<h3 class="block-title text-primary-dark">
|
<template v-slot:prepend>
|
||||||
<router-link class="text-warning mx-2 px-2" to="/acc/business/users">
|
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||||
<i class="fa fw-bold fa-arrow-right"></i>
|
<template v-slot:activator="{ props }">
|
||||||
</router-link>
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
||||||
تنظیمات دسترسی
|
icon="mdi-arrow-right" />
|
||||||
</h3>
|
</template>
|
||||||
<div class="block-options">
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-container>
|
||||||
|
<v-card class="mb-4" elevation="2">
|
||||||
|
<v-card-text>
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4 rounded-lg custom-alert"
|
||||||
|
border="start"
|
||||||
|
border-color="primary"
|
||||||
|
elevation="2"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar color="primary" class="me-3">
|
||||||
|
<v-icon color="white">mdi-shield-account</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<div class="text-h6 font-weight-bold mb-3 text-indigo-darken-2">
|
||||||
|
تنظیم دسترسیهای کاربر
|
||||||
|
</div>
|
||||||
|
<v-card variant="outlined" class="mb-3 pa-3 custom-card">
|
||||||
|
<div class="d-flex align-center mb-2">
|
||||||
|
<v-icon color="indigo-darken-2" class="me-2">mdi-account-circle</v-icon>
|
||||||
|
<span class="text-body-1 text-grey-darken-1">نام کاربر:</span>
|
||||||
|
<strong class="text-body-1 me-2 text-indigo-darken-2">{{ info.user }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="indigo-darken-2" class="me-2">mdi-email-outline</v-icon>
|
||||||
|
<span class="text-body-1 text-grey-darken-1">پست الکترونیکی:</span>
|
||||||
|
<strong class="text-body-1 text-indigo-darken-2">{{ info.email }}</strong>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
class="mt-2 custom-warning"
|
||||||
|
density="compact"
|
||||||
|
border="start"
|
||||||
|
border-color="indigo-darken-2"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon color="indigo-darken-2">mdi-shield-alert</v-icon>
|
||||||
|
</template>
|
||||||
|
<div class="text-body-2 text-indigo-darken-2">
|
||||||
|
لطفاً در تنظیم دسترسیها دقت کنید. دسترسیهای نامناسب میتواند امنیت سیستم را به خطر بیندازد.
|
||||||
|
</div>
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
</div>
|
<v-row>
|
||||||
</div>
|
<v-col cols="12" md="4">
|
||||||
<div class="block-content p-0">
|
<v-card variant="outlined" class="h-100">
|
||||||
<div class="alert alert-primary">
|
<v-card-text>
|
||||||
ویرایش دسترسیهای کاربر با نام
|
<v-list>
|
||||||
<span class="fw-bold">{{ this.info.user }}</span>
|
<v-list-item>
|
||||||
با پست الکترونیکی
|
<v-switch
|
||||||
<span class="fw-bold">{{ this.info.email }}</span>
|
v-model="info.persons"
|
||||||
</div>
|
label="اشخاص"
|
||||||
<div class="container-fluid">
|
@change="savePerms('persons')"
|
||||||
<div class="row">
|
hide-details
|
||||||
<label class="form-label">دسترسی ها</label>
|
color="success"
|
||||||
<div class="col-sm-12 col-md-4">
|
density="comfortable"
|
||||||
<div class="mb-2">
|
:loading="loadingSwitches.persons"
|
||||||
<div class="space-y-2">
|
:disabled="loadingSwitches.persons"
|
||||||
<div class="form-check form-switch">
|
></v-switch>
|
||||||
<input v-model="info.persons" @change="savePerms()" class="form-check-input" type="checkbox">
|
</v-list-item>
|
||||||
<label class="form-check-label">اشخاص</label>
|
<v-list-item>
|
||||||
</div>
|
<v-switch
|
||||||
</div>
|
v-model="info.getpay"
|
||||||
<div class="space-y-2">
|
label="دریافت و پرداخت"
|
||||||
<div class="form-check form-switch">
|
@change="savePerms('getpay')"
|
||||||
<input v-model="info.getpay" @change="savePerms()" class="form-check-input" type="checkbox">
|
hide-details
|
||||||
<label class="form-check-label">دریافت و پرداخت</label>
|
color="success"
|
||||||
</div>
|
density="comfortable"
|
||||||
</div>
|
:loading="loadingSwitches.getpay"
|
||||||
<div class="space-y-2">
|
:disabled="loadingSwitches.getpay"
|
||||||
<div class="form-check form-switch">
|
></v-switch>
|
||||||
<input v-model="info.commodity" @change="savePerms()" class="form-check-input" type="checkbox">
|
</v-list-item>
|
||||||
<label class="form-check-label">کالا و خدمات</label>
|
<v-list-item>
|
||||||
</div>
|
<v-switch
|
||||||
</div>
|
v-model="info.commodity"
|
||||||
<div class="space-y-2">
|
label="کالا و خدمات"
|
||||||
<div class="form-check form-switch">
|
@change="savePerms('commodity')"
|
||||||
<input v-model="info.bank" @change="savePerms()" class="form-check-input" type="checkbox">
|
hide-details
|
||||||
<label class="form-check-label">حسابهای بانکی</label>
|
color="success"
|
||||||
</div>
|
density="comfortable"
|
||||||
</div>
|
:loading="loadingSwitches.commodity"
|
||||||
<div class="space-y-2">
|
:disabled="loadingSwitches.commodity"
|
||||||
<div class="form-check form-switch">
|
></v-switch>
|
||||||
<input v-model="info.salary" @change="savePerms()" class="form-check-input" type="checkbox">
|
</v-list-item>
|
||||||
<label class="form-check-label">تنخواه گردانها</label>
|
<v-list-item>
|
||||||
</div>
|
<v-switch
|
||||||
</div>
|
v-model="info.bank"
|
||||||
<div class="space-y-2">
|
label="حسابهای بانکی"
|
||||||
<div class="form-check form-switch">
|
@change="savePerms('bank')"
|
||||||
<input v-model="info.bankTransfer" @change="savePerms()" class="form-check-input" type="checkbox">
|
hide-details
|
||||||
<label class="form-check-label">انتقال بین بانکی</label>
|
color="success"
|
||||||
</div>
|
density="comfortable"
|
||||||
</div>
|
:loading="loadingSwitches.bank"
|
||||||
<div class="space-y-2">
|
:disabled="loadingSwitches.bank"
|
||||||
<div class="form-check form-switch">
|
></v-switch>
|
||||||
<input v-model="info.archiveUpload" @change="savePerms()" class="form-check-input" type="checkbox">
|
</v-list-item>
|
||||||
<label class="form-check-label">افزودن فایل به آرشیو</label>
|
<v-list-item>
|
||||||
</div>
|
<v-switch
|
||||||
</div>
|
v-model="info.salary"
|
||||||
<div class="space-y-2">
|
label="تنخواه گردانها"
|
||||||
<div class="form-check form-switch">
|
@change="savePerms('salary')"
|
||||||
<input v-model="info.archiveView" @change="savePerms()" class="form-check-input" type="checkbox">
|
hide-details
|
||||||
<label class="form-check-label">مشاهده فایل های آرشیو</label>
|
color="success"
|
||||||
</div>
|
density="comfortable"
|
||||||
</div>
|
:loading="loadingSwitches.salary"
|
||||||
</div>
|
:disabled="loadingSwitches.salary"
|
||||||
</div>
|
></v-switch>
|
||||||
<div class="col-sm-12 col-md-4">
|
</v-list-item>
|
||||||
<div class="space-y-2">
|
<v-list-item>
|
||||||
<div class="form-check form-switch">
|
<v-switch
|
||||||
<input v-model="info.buy" @change="savePerms()" class="form-check-input" type="checkbox">
|
v-model="info.bankTransfer"
|
||||||
<label class="form-check-label">فاکتورهای خرید</label>
|
label="انتقال بین بانکی"
|
||||||
</div>
|
@change="savePerms('bankTransfer')"
|
||||||
</div>
|
hide-details
|
||||||
<div class="space-y-2">
|
color="success"
|
||||||
<div class="form-check form-switch">
|
density="comfortable"
|
||||||
<input v-model="info.sell" @change="savePerms()" class="form-check-input" type="checkbox">
|
:loading="loadingSwitches.bankTransfer"
|
||||||
<label class="form-check-label">فاکتورهای فروش</label>
|
:disabled="loadingSwitches.bankTransfer"
|
||||||
</div>
|
></v-switch>
|
||||||
</div>
|
</v-list-item>
|
||||||
<div class="space-y-2">
|
<v-list-item>
|
||||||
<div class="form-check form-switch">
|
<v-switch
|
||||||
<input v-model="info.cost" @change="savePerms()" class="form-check-input" type="checkbox">
|
v-model="info.archiveUpload"
|
||||||
<label class="form-check-label">هزینهها</label>
|
label="افزودن فایل به آرشیو"
|
||||||
</div>
|
@change="savePerms('archiveUpload')"
|
||||||
</div>
|
hide-details
|
||||||
<div class="space-y-2">
|
color="success"
|
||||||
<div class="form-check form-switch">
|
density="comfortable"
|
||||||
<input v-model="info.income" @change="savePerms()" class="form-check-input" type="checkbox">
|
:loading="loadingSwitches.archiveUpload"
|
||||||
<label class="form-check-label">درآمدها</label>
|
:disabled="loadingSwitches.archiveUpload"
|
||||||
</div>
|
></v-switch>
|
||||||
</div>
|
</v-list-item>
|
||||||
<div class="space-y-2">
|
<v-list-item>
|
||||||
<div class="form-check form-switch">
|
<v-switch
|
||||||
<input v-model="info.report" @change="savePerms()" class="form-check-input" type="checkbox">
|
v-model="info.archiveView"
|
||||||
<label class="form-check-label">گزارشات</label>
|
label="مشاهده فایل های آرشیو"
|
||||||
</div>
|
@change="savePerms('archiveView')"
|
||||||
</div>
|
hide-details
|
||||||
<div class="space-y-2">
|
color="success"
|
||||||
<div class="form-check form-switch">
|
density="comfortable"
|
||||||
<input v-model="info.cashdesk" @change="savePerms()" class="form-check-input" type="checkbox">
|
:loading="loadingSwitches.archiveView"
|
||||||
<label class="form-check-label">صندوقها</label>
|
:disabled="loadingSwitches.archiveView"
|
||||||
</div>
|
></v-switch>
|
||||||
</div>
|
</v-list-item>
|
||||||
<div class="space-y-2">
|
</v-list>
|
||||||
<div class="form-check form-switch">
|
</v-card-text>
|
||||||
<input v-model="info.shareholder" @change="savePerms()" class="form-check-input" type="checkbox">
|
</v-card>
|
||||||
<label class="form-check-label">سهامداران</label>
|
</v-col>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.archiveMod" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">ویرایش فایل های آرشیو</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.cheque" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">مدیریت چکها</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.accounting" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">اسناد حسابداری</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.settings" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">تنظیمات کسب و کار</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.log" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">تاریخچه کسبوکار</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.permission" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">تغییر سطوح دسترسی</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.store" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">انبارداری</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.wallet" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">کیف پول</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.archiveDelete" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">حذف فایل های آرشیو</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="this.isPluginActive('accpro')" class="row mt-2">
|
|
||||||
<b>بسته حسابداری پیشرفته</b>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugAccproRfbuy" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">فاکتورهای برگشت از خرید</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugAccproCloseYear" @change="savePerms()" class="form-check-input"
|
|
||||||
type="checkbox">
|
|
||||||
<label class="form-check-label">بستن سال مالی</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugAccproRfsell" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">فاکتورهای برگشت از فروش</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugAccproAccounting" @change="savePerms()" class="form-check-input"
|
|
||||||
type="checkbox">
|
|
||||||
<label class="form-check-label">صدور و ویرایش اسناد حسابداری</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="this.isPluginActive('repservice')" class="row mt-2">
|
|
||||||
<b>افزونه تعمیرکاران</b>
|
|
||||||
<div class="col-sm-12 col-md-4">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugRepservice" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">ثبت و ویرایش</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-2">
|
|
||||||
<div v-show="this.isPluginActive('noghre')" class="col-sm-12 col-md-4">
|
|
||||||
<b>افزونه کارگاه نقره سازی</b>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugNoghreAdmin" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">مدیر</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugNoghreSell" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">صندوق / فروش</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="this.isPluginActive('cc')" class="col-sm-12 col-md-4">
|
|
||||||
<b>افزونه باشگاه مشتریان</b>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input v-model="info.plugCCAdmin" @change="savePerms()" class="form-check-input" type="checkbox">
|
|
||||||
<label class="form-check-label">مدیر</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.buy"
|
||||||
|
label="فاکتورهای خرید"
|
||||||
|
@change="savePerms('buy')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.buy"
|
||||||
|
:disabled="loadingSwitches.buy"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.sell"
|
||||||
|
label="فاکتورهای فروش"
|
||||||
|
@change="savePerms('sell')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.sell"
|
||||||
|
:disabled="loadingSwitches.sell"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.cost"
|
||||||
|
label="هزینهها"
|
||||||
|
@change="savePerms('cost')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.cost"
|
||||||
|
:disabled="loadingSwitches.cost"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.income"
|
||||||
|
label="درآمدها"
|
||||||
|
@change="savePerms('income')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.income"
|
||||||
|
:disabled="loadingSwitches.income"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.report"
|
||||||
|
label="گزارشات"
|
||||||
|
@change="savePerms('report')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.report"
|
||||||
|
:disabled="loadingSwitches.report"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.cashdesk"
|
||||||
|
label="صندوقها"
|
||||||
|
@change="savePerms('cashdesk')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.cashdesk"
|
||||||
|
:disabled="loadingSwitches.cashdesk"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.shareholder"
|
||||||
|
label="سهامداران"
|
||||||
|
@change="savePerms('shareholder')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.shareholder"
|
||||||
|
:disabled="loadingSwitches.shareholder"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.archiveMod"
|
||||||
|
label="ویرایش فایل های آرشیو"
|
||||||
|
@change="savePerms('archiveMod')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.archiveMod"
|
||||||
|
:disabled="loadingSwitches.archiveMod"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.cheque"
|
||||||
|
label="مدیریت چکها"
|
||||||
|
@change="savePerms('cheque')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.cheque"
|
||||||
|
:disabled="loadingSwitches.cheque"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.accounting"
|
||||||
|
label="اسناد حسابداری"
|
||||||
|
@change="savePerms('accounting')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.accounting"
|
||||||
|
:disabled="loadingSwitches.accounting"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.settings"
|
||||||
|
label="تنظیمات کسب و کار"
|
||||||
|
@change="savePerms('settings')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.settings"
|
||||||
|
:disabled="loadingSwitches.settings"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.log"
|
||||||
|
label="تاریخچه کسبوکار"
|
||||||
|
@change="savePerms('log')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.log"
|
||||||
|
:disabled="loadingSwitches.log"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.permission"
|
||||||
|
label="تغییر سطوح دسترسی"
|
||||||
|
@change="savePerms('permission')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.permission"
|
||||||
|
:disabled="loadingSwitches.permission"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.store"
|
||||||
|
label="انبارداری"
|
||||||
|
@change="savePerms('store')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.store"
|
||||||
|
:disabled="loadingSwitches.store"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.wallet"
|
||||||
|
label="کیف پول"
|
||||||
|
@change="savePerms('wallet')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.wallet"
|
||||||
|
:disabled="loadingSwitches.wallet"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.archiveDelete"
|
||||||
|
label="حذف فایل های آرشیو"
|
||||||
|
@change="savePerms('archiveDelete')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.archiveDelete"
|
||||||
|
:disabled="loadingSwitches.archiveDelete"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row v-if="isPluginActive('accpro')" class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card-title class="text-h6 font-weight-bold mb-4">بسته حسابداری پیشرفته</v-card-title>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugAccproRfbuy"
|
||||||
|
label="فاکتورهای برگشت از خرید"
|
||||||
|
@change="savePerms('plugAccproRfbuy')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugAccproRfbuy"
|
||||||
|
:disabled="loadingSwitches.plugAccproRfbuy"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugAccproCloseYear"
|
||||||
|
label="بستن سال مالی"
|
||||||
|
@change="savePerms('plugAccproCloseYear')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugAccproCloseYear"
|
||||||
|
:disabled="loadingSwitches.plugAccproCloseYear"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugAccproRfsell"
|
||||||
|
label="فاکتورهای برگشت از فروش"
|
||||||
|
@change="savePerms('plugAccproRfsell')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugAccproRfsell"
|
||||||
|
:disabled="loadingSwitches.plugAccproRfsell"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugAccproPresell"
|
||||||
|
label="پیش فاکتور فروش"
|
||||||
|
@change="savePerms('plugAccproPresell')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugAccproPresell"
|
||||||
|
:disabled="loadingSwitches.plugAccproPresell"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugAccproAccounting"
|
||||||
|
label="صدور و ویرایش اسناد حسابداری"
|
||||||
|
@change="savePerms('plugAccproAccounting')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugAccproAccounting"
|
||||||
|
:disabled="loadingSwitches.plugAccproAccounting"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row v-if="isPluginActive('repservice')" class="mt-4">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه تعمیرکاران</v-card-title>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugRepservice"
|
||||||
|
label="ثبت و ویرایش"
|
||||||
|
@change="savePerms('plugRepservice')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugRepservice"
|
||||||
|
:disabled="loadingSwitches.plugRepservice"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row class="mt-4">
|
||||||
|
<v-col v-if="isPluginActive('noghre')" cols="12" md="4">
|
||||||
|
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه کارگاه نقره سازی</v-card-title>
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugNoghreAdmin"
|
||||||
|
label="مدیر"
|
||||||
|
@change="savePerms('plugNoghreAdmin')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugNoghreAdmin"
|
||||||
|
:disabled="loadingSwitches.plugNoghreAdmin"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugNoghreSell"
|
||||||
|
label="صندوق / فروش"
|
||||||
|
@change="savePerms('plugNoghreSell')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugNoghreSell"
|
||||||
|
:disabled="loadingSwitches.plugNoghreSell"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col v-if="isPluginActive('cc')" cols="12" md="4">
|
||||||
|
<v-card-title class="text-h6 font-weight-bold mb-4">افزونه باشگاه مشتریان</v-card-title>
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-text>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch
|
||||||
|
v-model="info.plugCCAdmin"
|
||||||
|
label="مدیر"
|
||||||
|
@change="savePerms('plugCCAdmin')"
|
||||||
|
hide-details
|
||||||
|
color="success"
|
||||||
|
density="comfortable"
|
||||||
|
:loading="loadingSwitches.plugCCAdmin"
|
||||||
|
:disabled="loadingSwitches.plugCCAdmin"
|
||||||
|
></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
<v-snackbar
|
||||||
|
v-model="snackbar.show"
|
||||||
|
:color="snackbar.color"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn
|
||||||
|
color="white"
|
||||||
|
variant="text"
|
||||||
|
@click="snackbar.show = false"
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -258,33 +576,92 @@ import axios from "axios";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "user_perm_edit",
|
name: "user_perm_edit",
|
||||||
data: () => {
|
data: () => ({
|
||||||
return {
|
|
||||||
info: {},
|
info: {},
|
||||||
plugins: {}
|
plugins: {},
|
||||||
}
|
snackbar: {
|
||||||
|
show: false,
|
||||||
|
text: '',
|
||||||
|
color: 'success'
|
||||||
},
|
},
|
||||||
|
loadingSwitches: {}
|
||||||
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
isPluginActive(plugName) {
|
isPluginActive(plugName) {
|
||||||
return this.plugins[plugName] !== undefined;
|
return this.plugins[plugName] !== undefined;
|
||||||
},
|
},
|
||||||
getData(id) {
|
getData(id) {
|
||||||
|
// مقداردهی اولیه همه فیلدها با false
|
||||||
|
const defaultPermissions = {
|
||||||
|
persons: false,
|
||||||
|
getpay: false,
|
||||||
|
commodity: false,
|
||||||
|
bank: false,
|
||||||
|
salary: false,
|
||||||
|
bankTransfer: false,
|
||||||
|
archiveUpload: false,
|
||||||
|
archiveView: false,
|
||||||
|
buy: false,
|
||||||
|
sell: false,
|
||||||
|
cost: false,
|
||||||
|
income: false,
|
||||||
|
report: false,
|
||||||
|
cashdesk: false,
|
||||||
|
shareholder: false,
|
||||||
|
archiveMod: false,
|
||||||
|
cheque: false,
|
||||||
|
accounting: false,
|
||||||
|
settings: false,
|
||||||
|
log: false,
|
||||||
|
permission: false,
|
||||||
|
store: false,
|
||||||
|
wallet: false,
|
||||||
|
archiveDelete: false,
|
||||||
|
plugAccproRfbuy: false,
|
||||||
|
plugAccproCloseYear: false,
|
||||||
|
plugAccproRfsell: false,
|
||||||
|
plugAccproPresell: false,
|
||||||
|
plugAccproAccounting: false,
|
||||||
|
plugRepservice: false,
|
||||||
|
plugNoghreAdmin: false,
|
||||||
|
plugNoghreSell: false,
|
||||||
|
plugCCAdmin: false
|
||||||
|
};
|
||||||
|
|
||||||
axios.post('/api/business/get/user/permissions',
|
axios.post('/api/business/get/user/permissions',
|
||||||
{
|
{
|
||||||
'bid': localStorage.getItem('activeBid'),
|
'bid': localStorage.getItem('activeBid'),
|
||||||
'email': id
|
'email': id
|
||||||
}
|
}
|
||||||
).then((response) => {
|
).then((response) => {
|
||||||
this.info = response.data;
|
// ترکیب مقادیر پیشفرض با مقادیر دریافتی از سرور
|
||||||
this.info.bid = localStorage.getItem('activeBid');
|
this.info = {
|
||||||
|
...defaultPermissions,
|
||||||
|
...response.data,
|
||||||
|
bid: localStorage.getItem('activeBid')
|
||||||
|
};
|
||||||
});
|
});
|
||||||
//get active plugins
|
//get active plugins
|
||||||
axios.post('/api/plugin/get/actives',).then((response) => {
|
axios.post('/api/plugin/get/actives',).then((response) => {
|
||||||
this.plugins = response.data;
|
this.plugins = response.data;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
savePerms() {
|
async savePerms() {
|
||||||
axios.post('/api/business/save/user/permissions', this.info)
|
try {
|
||||||
|
await axios.post('/api/business/save/user/permissions', this.info);
|
||||||
|
this.snackbar = {
|
||||||
|
show: true,
|
||||||
|
text: 'دسترسیها با موفقیت ذخیره شد',
|
||||||
|
color: 'success'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving permissions:', error);
|
||||||
|
this.snackbar = {
|
||||||
|
show: true,
|
||||||
|
text: 'خطا در ذخیره دسترسیها',
|
||||||
|
color: 'error'
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
|
@ -295,4 +672,34 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.v-list-item {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
.v-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.v-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.v-switch {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
.custom-alert {
|
||||||
|
background: linear-gradient(to right, #f8f9fa, #e9ecef);
|
||||||
|
}
|
||||||
|
.custom-card {
|
||||||
|
background: linear-gradient(to right, #ffffff, #f8f9fa);
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
.custom-warning {
|
||||||
|
background: linear-gradient(to right, #e8eaf6, #c5cae9);
|
||||||
|
}
|
||||||
|
.text-indigo-darken-2 {
|
||||||
|
color: #3949ab !important;
|
||||||
|
}
|
||||||
|
.v-switch.v-input--disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,148 +1,226 @@
|
||||||
<template>
|
<template> <v-toolbar color="toolbar" title="کاربران و دسترسیها">
|
||||||
<div class="block block-content-full">
|
<template v-slot:prepend>
|
||||||
<div id="fixed-header" class="block-header block-header-default bg-gray-light" >
|
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
||||||
<h3 class="block-title text-primary-dark">
|
<template v-slot:activator="{ props }">
|
||||||
<i class="fa fa-users-gear"></i>
|
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
||||||
کاربران و دسترسیها </h3>
|
icon="mdi-arrow-right" />
|
||||||
<div class="block-options">
|
</template>
|
||||||
</div>
|
</v-tooltip>
|
||||||
</div>
|
</template>
|
||||||
<div class="block-content p-0">
|
</v-toolbar>
|
||||||
<div class="mb-4">
|
<v-container>
|
||||||
<div class="input-group p-3">
|
<v-row>
|
||||||
<div class="form-floating">
|
<v-col cols="12">
|
||||||
<input v-model="newEmail" class="form-control" type="email">
|
<v-form @submit.prevent="submitData">
|
||||||
<label for="example-group3-floating2">برای افزودن کاربر جدید پست الکترونیکی را وارد کنید.</label>
|
<v-row>
|
||||||
</div>
|
<v-col cols="10">
|
||||||
<button @click="submitData" class="btn btn-primary" type="button"> افزودن </button>
|
<v-text-field v-model="newEmail" label="برای افزودن کاربر جدید پست الکترونیکی را وارد کنید" type="email"
|
||||||
</div>
|
variant="outlined" density="comfortable" class="email-field"></v-text-field>
|
||||||
</div>
|
</v-col>
|
||||||
<table class="table table-hover table-vcenter table-sm">
|
<v-col cols="2">
|
||||||
<thead>
|
<v-btn color="primary" type="submit" block class="submit-btn">
|
||||||
<tr>
|
افزودن
|
||||||
<th class="text-center" style="width: 50px;">#</th>
|
</v-btn>
|
||||||
<th>نام / نام خانوادگی</th>
|
</v-col>
|
||||||
<th>پست الکترونیکی</th>
|
</v-row>
|
||||||
<th class="text-center" style="width: 100px;">عملیات</th>
|
</v-form>
|
||||||
</tr>
|
</v-col>
|
||||||
</thead>
|
</v-row>
|
||||||
<tbody>
|
|
||||||
<tr v-for="(item,index) in users">
|
<v-data-table :headers="headers" :items="users" :items-per-page="10" class="elevation-1">
|
||||||
<th class="text-center" scope="row">{{ index + 1 }}</th>
|
<template v-slot:item.index="{ index }">
|
||||||
<td class="fw-semibold text-primary-dark">{{ item.name}}</td>
|
{{ index + 1 }}
|
||||||
<td class="d-none d-sm-table-cell">
|
</template>
|
||||||
<span class="badge bg-primary">{{item.email}}</span>
|
|
||||||
</td>
|
<template v-slot:item.name="{ item }">
|
||||||
<td class="text-center">
|
<span class="font-weight-bold text-primary">{{ item.name }}</span>
|
||||||
<div class="btn-group btn-group-sm" v-if="item.owner != 1">
|
</template>
|
||||||
<router-link :to="{'name':'business_user_roll_edit','params':{'email':item.email}}" class="btn btn-alt-primary" type="button" aria-label=" ویرایش ">
|
|
||||||
<i class="fa fa-pencil-alt"></i>
|
<template v-slot:item.email="{ item }">
|
||||||
</router-link>
|
<v-chip color="primary" size="small">{{ item.email }}</v-chip>
|
||||||
<button @click="deleteUser(item.email)" class="btn btn-alt-primary" type="button" aria-label=" حذف ">
|
</template>
|
||||||
<i class="fa fa-times"></i>
|
|
||||||
</button>
|
<template v-slot:item.mobile="{ item }">
|
||||||
</div>
|
<v-chip color="info" size="small">{{ item.mobile || '-' }}</v-chip>
|
||||||
<span class="badge bg-success" v-if="item.owner == 1">مدیر کل</span>
|
</template>
|
||||||
</td>
|
|
||||||
</tr>
|
<template v-slot:item.actions="{ item }">
|
||||||
</tbody>
|
<template v-if="item.owner != 1">
|
||||||
</table>
|
<v-btn-group>
|
||||||
|
<v-btn :to="{ name: 'business_user_roll_edit', params: { email: item.email } }" icon="mdi-pencil"
|
||||||
|
variant="text" color="primary" size="small"></v-btn>
|
||||||
|
<v-btn @click="confirmDelete(item.email)" icon="mdi-delete" variant="text" color="error"
|
||||||
|
size="small"></v-btn>
|
||||||
|
</v-btn-group>
|
||||||
|
</template>
|
||||||
|
<v-chip v-else color="success" size="small">مدیر کل</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<!-- دیالوگ تایید حذف -->
|
||||||
|
<v-dialog v-model="deleteDialog" max-width="400" transition="dialog-bottom-transition">
|
||||||
|
<v-card class="rounded-lg" title="تایید حذف کاربر">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-avatar>
|
||||||
|
<v-icon color="error">mdi-alert-circle</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</template>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-text class="pt-4">
|
||||||
|
<div class="text-body-1">
|
||||||
|
آیا از حذف کاربر <span class="font-weight-bold text-primary">{{ userToDelete?.name }}</span> با ایمیل <span class="font-weight-bold text-primary">{{ userToDelete?.email }}</span> اطمینان دارید؟
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-caption text-medium-emphasis mt-2">
|
||||||
|
این عملیات غیرقابل بازگشت است.
|
||||||
</div>
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey-darken-1" variant="text" @click="deleteDialog = false" class="px-4">
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="error" variant="flat" @click="confirmDeleteAction" class="px-4">
|
||||||
|
<v-icon start>mdi-delete</v-icon>
|
||||||
|
حذف کاربر
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- اسنکبار برای نمایش پیامها -->
|
||||||
|
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||||
|
{{ snackbar.text }}
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn variant="text" @click="snackbar.show = false">
|
||||||
|
بستن
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Swal from "sweetalert2";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "user_rolls",
|
name: "user_rolls",
|
||||||
data: ()=>{return{
|
data: () => ({
|
||||||
users : {},
|
users: [],
|
||||||
newEmail: '',
|
newEmail: '',
|
||||||
}},
|
headers: [
|
||||||
methods:{
|
{ title: '#', key: 'index', align: 'center', sortable: false },
|
||||||
deleteUser(email){
|
{ title: 'نام / نام خانوادگی', key: 'name', align: 'center' },
|
||||||
Swal.fire({
|
{ title: 'پست الکترونیکی', key: 'email', align: 'center' },
|
||||||
title: 'آیا برای حذف کاربر مطمئن هستید؟',
|
{ title: 'شماره موبایل', key: 'mobile', align: 'center' },
|
||||||
showCancelButton: true,
|
{ title: 'عملیات', key: 'actions', align: 'center', sortable: false }
|
||||||
confirmButtonText: 'بله',
|
],
|
||||||
cancelButtonText: `خیر`,
|
deleteDialog: false,
|
||||||
}).then((result) => {
|
userToDelete: null,
|
||||||
/* Read more about isConfirmed, isDenied below */
|
snackbar: {
|
||||||
if (result.isConfirmed) {
|
show: false,
|
||||||
axios.post('/api/business/delete/user',{
|
text: '',
|
||||||
'bid':localStorage.getItem('activeBid'),
|
color: 'success'
|
||||||
'email': email}
|
|
||||||
).then((response)=>{
|
|
||||||
if(response.data.result == 1){
|
|
||||||
let index = 0;
|
|
||||||
for(let z=0; z<this.users.length; z++){
|
|
||||||
index ++;
|
|
||||||
if(this.users[z]['email'] == email){
|
|
||||||
this.users.splice(index -1 ,1);
|
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
Swal.fire({
|
methods: {
|
||||||
text: 'کاربر با موفقیت حذف شد.',
|
showSnackbar(text, color = 'success') {
|
||||||
icon: 'success',
|
this.snackbar.text = text;
|
||||||
confirmButtonText: 'قبول'
|
this.snackbar.color = color;
|
||||||
});
|
this.snackbar.show = true;
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
submitData(){
|
confirmDelete(email) {
|
||||||
if(this.newEmail == ''){
|
const user = this.users.find(u => u.email === email);
|
||||||
Swal.fire({
|
this.userToDelete = {
|
||||||
text: 'پست الکترونیکی را وارد کنید.',
|
email: email,
|
||||||
icon: 'error',
|
name: user ? user.name : email
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
axios.post("/api/business/add/user",{'bid':localStorage.getItem('activeBid'),'email': this.newEmail}).then((response)=>{
|
|
||||||
if(response.data.result == 0){
|
|
||||||
Swal.fire({
|
|
||||||
text: 'کاربری با این پست الکترونیکی یافت نشد.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if(response.data.result == 1){
|
|
||||||
Swal.fire({
|
|
||||||
text: 'قبلا این کاربر به کسب و کار افزوده شده است.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'قبول'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
let temp = {
|
|
||||||
'name':response.data.data.name,
|
|
||||||
'email':response.data.data.email,
|
|
||||||
'owner':response.data.data.owner
|
|
||||||
};
|
};
|
||||||
this.users.push(temp);
|
this.deleteDialog = true;
|
||||||
Swal.fire({
|
},
|
||||||
text: 'کاربر با موفقیت عضو کسب و کار شد.',
|
confirmDeleteAction() {
|
||||||
icon: 'success',
|
if (this.userToDelete) {
|
||||||
confirmButtonText: 'قبول'
|
const emailToDelete = this.userToDelete.email;
|
||||||
|
axios.post('/api/business/delete/user', {
|
||||||
|
'bid': localStorage.getItem('activeBid'),
|
||||||
|
'email': emailToDelete
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.data.result == 1) {
|
||||||
|
this.users = this.users.filter(user => user.email !== emailToDelete);
|
||||||
|
this.showSnackbar('کاربر با موفقیت حذف شد.');
|
||||||
|
} else {
|
||||||
|
this.showSnackbar('خطا در حذف کاربر', 'error');
|
||||||
|
}
|
||||||
|
this.deleteDialog = false;
|
||||||
|
this.userToDelete = null;
|
||||||
|
}).catch((error) => {
|
||||||
|
this.showSnackbar('خطا در حذف کاربر', 'error');
|
||||||
|
this.deleteDialog = false;
|
||||||
|
this.userToDelete = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
submitData() {
|
||||||
|
if (this.newEmail == '') {
|
||||||
|
this.showSnackbar('پست الکترونیکی را وارد کنید.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post("/api/business/add/user", {
|
||||||
|
'bid': localStorage.getItem('activeBid'),
|
||||||
|
'email': this.newEmail
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.data.result == 0) {
|
||||||
|
this.showSnackbar('کاربری با این پست الکترونیکی یافت نشد.', 'error');
|
||||||
|
} else if (response.data.result == 1) {
|
||||||
|
this.showSnackbar('قبلا این کاربر به کسب و کار افزوده شده است.', 'error');
|
||||||
|
} else {
|
||||||
|
this.users.push({
|
||||||
|
'name': response.data.data.name,
|
||||||
|
'email': response.data.data.email,
|
||||||
|
'owner': response.data.data.owner,
|
||||||
|
'mobile': response.data.data.mobile || null
|
||||||
|
});
|
||||||
|
this.showSnackbar('کاربر با موفقیت عضو کسب و کار شد.');
|
||||||
|
}
|
||||||
this.newEmail = '';
|
this.newEmail = '';
|
||||||
|
}).catch(() => {
|
||||||
|
this.showSnackbar('خطا در افزودن کاربر', 'error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
axios.post('/api/user/get/users/of/business/' + localStorage.getItem('activeBid')).then((response)=>{
|
axios.post('/api/user/get/users/of/business/' + localStorage.getItem('activeBid')).then((response) => {
|
||||||
this.users = response.data;
|
this.users = Array.isArray(response.data) ? response.data : [];
|
||||||
})
|
}).catch(() => {
|
||||||
|
this.showSnackbar('خطا در دریافت لیست کاربران', 'error');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.v-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-field :deep(.v-field) {
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-data-table :deep(th),
|
||||||
|
.v-data-table :deep(td) {
|
||||||
|
text-align: center !important;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-data-table :deep(.v-chip) {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -36,12 +36,9 @@
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
<v-text-field
|
<Hdatepicker
|
||||||
v-model="ticket.date"
|
v-model="ticket.date"
|
||||||
label="تاریخ"
|
label="تاریخ"
|
||||||
variant="outlined"
|
|
||||||
density="compact"
|
|
||||||
readonly
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
|
@ -176,6 +173,7 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue'
|
||||||
|
|
||||||
interface TransferType {
|
interface TransferType {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -36,12 +36,9 @@
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
<v-text-field
|
<Hdatepicker
|
||||||
v-model="ticket.date"
|
v-model="ticket.date"
|
||||||
label="تاریخ"
|
label="تاریخ"
|
||||||
variant="outlined"
|
|
||||||
density="compact"
|
|
||||||
readonly
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
|
@ -176,6 +173,7 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue'
|
||||||
|
|
||||||
interface TransferType {
|
interface TransferType {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -302,12 +300,51 @@ const submit = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.post('/api/storeroom/ticket/insert', {
|
const response = await axios.post('/api/storeroom/ticket/insert', {
|
||||||
doc: doc.value,
|
doc: {
|
||||||
ticket: {
|
id: doc.value.id,
|
||||||
...ticket.value,
|
dateSubmit: doc.value.dateSubmit,
|
||||||
senderTel: ticket.value.person.mobile || ''
|
date: doc.value.date,
|
||||||
|
type: doc.value.type,
|
||||||
|
code: doc.value.code,
|
||||||
|
des: doc.value.des
|
||||||
},
|
},
|
||||||
items: items.value
|
ticket: {
|
||||||
|
type: ticket.value.type,
|
||||||
|
typeString: ticket.value.typeString,
|
||||||
|
date: ticket.value.date,
|
||||||
|
des: ticket.value.des,
|
||||||
|
transfer: ticket.value.transfer,
|
||||||
|
receiver: ticket.value.receiver,
|
||||||
|
code: ticket.value.code,
|
||||||
|
store: {
|
||||||
|
id: ticket.value.store.id,
|
||||||
|
name: ticket.value.store.name,
|
||||||
|
manager: ticket.value.store.manager,
|
||||||
|
des: ticket.value.store.des
|
||||||
|
},
|
||||||
|
person: {
|
||||||
|
id: ticket.value.person.id,
|
||||||
|
code: ticket.value.person.code,
|
||||||
|
nikename: ticket.value.person.nikename,
|
||||||
|
name: ticket.value.person.name,
|
||||||
|
tel: ticket.value.person.tel,
|
||||||
|
mobile: ticket.value.person.mobile,
|
||||||
|
address: ticket.value.person.address
|
||||||
|
},
|
||||||
|
transferType: ticket.value.transferType,
|
||||||
|
referral: ticket.value.referral,
|
||||||
|
senderTel: ticket.value.person.mobile || '',
|
||||||
|
sms: false
|
||||||
|
},
|
||||||
|
items: items.value.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
bs: item.bs,
|
||||||
|
bd: item.bd,
|
||||||
|
ticketCount: item.ticketCount,
|
||||||
|
des: item.des,
|
||||||
|
referral: item.referral,
|
||||||
|
type: item.type
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.result === 0) {
|
if (response.data.result === 0) {
|
||||||
|
|
|
@ -36,12 +36,9 @@
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
<v-text-field
|
<Hdatepicker
|
||||||
v-model="ticket.date"
|
v-model="ticket.date"
|
||||||
label="تاریخ"
|
label="تاریخ"
|
||||||
variant="outlined"
|
|
||||||
density="compact"
|
|
||||||
readonly
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
|
@ -176,6 +173,7 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue'
|
||||||
|
|
||||||
interface TransferType {
|
interface TransferType {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -49,12 +49,9 @@
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
<v-text-field
|
<Hdatepicker
|
||||||
v-model="ticket.date"
|
v-model="ticket.date"
|
||||||
label="تاریخ"
|
label="تاریخ"
|
||||||
variant="outlined"
|
|
||||||
density="compact"
|
|
||||||
readonly
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4">
|
<v-col cols="12" md="4">
|
||||||
|
@ -198,6 +195,7 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import Hdatepicker from '@/components/forms/Hdatepicker.vue'
|
||||||
|
|
||||||
interface TransferType {
|
interface TransferType {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -1,66 +1,114 @@
|
||||||
<template>
|
<template>
|
||||||
<v-toolbar title="جادوگر">
|
<v-toolbar color="toolbar" title="هوش مصنوعی حسابیکس">
|
||||||
<template v-slot:prepend>
|
|
||||||
<v-tooltip :text="$t('dialog.back')" location="bottom">
|
</v-toolbar>
|
||||||
<template v-slot:activator="{ props }">
|
<div class="page-container">
|
||||||
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
|
<div class="content-container">
|
||||||
icon="mdi-arrow-right" />
|
<v-card class="chat-container" elevation="0">
|
||||||
|
<div class="chat-box">
|
||||||
|
<div class="messages-container" ref="messagesContainer">
|
||||||
|
<!-- پیام هوش مصنوعی -->
|
||||||
|
<div class="message ai-message" v-if="displayWelcome">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text typing-text">{{ displayWelcome }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message ai-message" v-if="displayThanks">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text typing-text">{{ displayThanks }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message ai-message" v-if="displayCapabilities">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text typing-text">{{ displayCapabilities }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message ai-message" v-for="(capability, index) in displayCapabilitiesList" :key="index">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text typing-text">
|
||||||
|
<v-icon color="#1a237e" size="16" class="mr-2">mdi-check-circle</v-icon>
|
||||||
|
{{ capability }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message ai-message" v-if="displayPrompt">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text typing-text">{{ displayPrompt }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- پیامهای کاربر و پاسخهای هوش مصنوعی -->
|
||||||
|
<template v-for="(message, index) in userMessages" :key="index">
|
||||||
|
<!-- پیام کاربر -->
|
||||||
|
<div class="message user-message" v-if="typeof message === 'string'">
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text">{{ message }}</div>
|
||||||
|
</div>
|
||||||
|
<v-avatar color="grey lighten-2" size="36" class="ml-2">
|
||||||
|
<v-icon color="grey darken-1" size="20">mdi-account</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- پیام هوش مصنوعی -->
|
||||||
|
<div class="message ai-message" v-else-if="message && message.isAI">
|
||||||
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="message-content" :class="{ 'details-message': message.isDetails }">
|
||||||
|
<div class="message-text" v-html="message.text.replace(/\n/g, '<br>')"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</v-tooltip>
|
|
||||||
</template>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
</v-toolbar>
|
|
||||||
|
|
||||||
<v-container class="mt-8 px-2">
|
<!-- نشانگر تایپ -->
|
||||||
<v-row justify="center" class="wizard-content">
|
<div class="message ai-message" v-if="isTyping">
|
||||||
<v-col cols="12" md="10">
|
<v-avatar color="#1a237e" size="36" class="mr-2">
|
||||||
<v-card class="elevation-0 main-card">
|
<v-icon color="white" size="20">mdi-robot</v-icon>
|
||||||
<v-card-title class="text-h4 text-center mb-4">
|
</v-avatar>
|
||||||
<v-icon color="primary" size="40" class="mr-2">mdi-wizard-hat</v-icon>
|
<div class="message-content">
|
||||||
جادوگر هوش مصنوعی
|
<div class="message-text">
|
||||||
</v-card-title>
|
<span class="typing-indicator">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<v-card-text class="text-body-1 text-center">
|
<!-- باکس ورودی پیام -->
|
||||||
<p class="mb-4">
|
<div class="input-container">
|
||||||
به زودی با جادوگر هوش مصنوعی آشنا خواهید شد! این ابزار قدرتمند به شما امکان میدهد تا با استفاده از هوش مصنوعی، ماژولهای جدید را به صورت پویا به نرمافزار اضافه کنید. دوران انحصار نرمافزارهای حسابداری به پایان رسیده است و شما میتوانید نرمافزار خود را بهروز کرده و آن را با نیازهای خاص کسبوکار خود تطبیق دهید، بدون نیاز به انتظار برای بهروزرسانیهای طولانی مدت یا پرداخت هزینههای گزاف برای سفارشیسازی.
|
<v-textarea v-model="userMessage" placeholder="پیام خود را اینجا بنویسید..." rows="1" auto-grow hide-details
|
||||||
</p>
|
variant="plain" class="message-input" @keydown.enter.prevent="sendMessage"></v-textarea>
|
||||||
</v-card-text>
|
<v-btn color="#1a237e" icon :loading="isLoading" @click="sendMessage" class="send-button"
|
||||||
|
:disabled="!userMessage.trim()">
|
||||||
<v-row class="mt-4">
|
<v-icon>mdi-send</v-icon>
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card class="elevation-0 pa-4 text-center feature-card">
|
|
||||||
<v-icon color="success" size="40" class="mb-2">mdi-robot</v-icon>
|
|
||||||
<h3 class="text-h6 mb-2">هوش مصنوعی پیشرفته</h3>
|
|
||||||
<p class="text-body-2">استفاده از آخرین تکنولوژیهای هوش مصنوعی برای ایجاد ماژولهای سفارشی</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card class="elevation-0 pa-4 text-center feature-card">
|
|
||||||
<v-icon color="info" size="40" class="mb-2">mdi-puzzle</v-icon>
|
|
||||||
<h3 class="text-h6 mb-2">ماژولهای پویا</h3>
|
|
||||||
<p class="text-body-2">ایجاد و اضافه کردن ماژولهای جدید بدون نیاز به کدنویسی</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col cols="12" md="4">
|
|
||||||
<v-card class="elevation-0 pa-4 text-center feature-card">
|
|
||||||
<v-icon color="warning" size="40" class="mb-2">mdi-lightning-bolt</v-icon>
|
|
||||||
<h3 class="text-h6 mb-2">سرعت بالا</h3>
|
|
||||||
<p class="text-body-2">توسعه سریع و کارآمد با استفاده از ابزارهای هوشمند</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-card-actions class="justify-center mt-6">
|
|
||||||
<v-btn color="primary" size="large" prepend-icon="mdi-clock-outline" disabled>
|
|
||||||
به زودی...
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</div>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</div>
|
||||||
</v-row>
|
</div>
|
||||||
</v-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -68,66 +116,363 @@ export default {
|
||||||
name: 'WizardHome',
|
name: 'WizardHome',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
userMessage: '',
|
||||||
|
isLoading: false,
|
||||||
|
isTyping: true,
|
||||||
|
userMessages: [],
|
||||||
|
aiResponses: [
|
||||||
|
{
|
||||||
|
message: 'با عرض پوزش، در حال حاضر سختافزار پردازش داده متصل نشده است. لطفاً با پشتیبانی فنی تماس بگیرید تا در اسرع وقت مشکل را برطرف کنیم.',
|
||||||
|
details: 'برای اتصال سختافزار پردازش داده، نیاز به تنظیمات خاصی است که باید توسط تیم فنی انجام شود. این تنظیمات شامل:\n- اتصال به سرور پردازش\n- تنظیم پارامترهای امنیتی\n- راهاندازی ماژولهای پردازشی\nمیباشد.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'متأسفانه در حال حاضر سیستم پردازش داده در دسترس نیست. این مشکل موقت است و به زودی برطرف خواهد شد.',
|
||||||
|
details: 'برای فعالسازی کامل سیستم، نیاز به انجام مراحل زیر است:\n- تأیید اتصال به سرور مرکزی\n- راهاندازی ماژولهای پردازشی\n- تنظیم پارامترهای امنیتی\nلطفاً با پشتیبانی فنی تماس بگیرید.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'با کمال تأسف، سختافزار پردازش داده هنوز آماده بهرهبرداری نیست. این مشکل به زودی برطرف خواهد شد.',
|
||||||
|
details: 'برای راهاندازی کامل سیستم، تیم فنی در حال انجام مراحل زیر است:\n- نصب و پیکربندی سرور پردازش\n- تنظیم پارامترهای امنیتی\n- راهاندازی ماژولهای پردازشی\nلطفاً با پشتیبانی فنی تماس بگیرید.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'در حال حاضر سیستم پردازش داده در حالت تعمیر و نگهداری است. به زودی سرویسدهی از سر گرفته خواهد شد.',
|
||||||
|
details: 'برای فعالسازی مجدد سیستم، نیاز به انجام مراحل زیر است:\n- بررسی اتصال به سرور مرکزی\n- بهروزرسانی ماژولهای پردازشی\n- تنظیم مجدد پارامترهای امنیتی\nلطفاً با پشتیبانی فنی تماس بگیرید.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'با عرض پوزش، سختافزار پردازش داده در حال حاضر غیرفعال است. تیم فنی در حال بررسی و رفع مشکل است.',
|
||||||
|
details: 'برای فعالسازی سیستم، نیاز به انجام مراحل زیر است:\n- تأیید اتصال به سرور پردازش\n- راهاندازی ماژولهای پردازشی\n- تنظیم پارامترهای امنیتی\nلطفاً با پشتیبانی فنی تماس بگیرید.'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
welcomePatterns: [
|
||||||
|
{
|
||||||
|
welcome: 'سلام! 👋',
|
||||||
|
thanks: 'از اینکه از هوش مصنوعی حسابیکس استفاده میکنید، بسیار خوشحالم! من یک هوش مصنوعی مستقل هستم که به صورت کامل در سرورهای داخلی حسابیکس میزبانی میشوم و نیازی به سرویسهای خارجی ندارم.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
welcome: 'درود! 🌟',
|
||||||
|
thanks: 'به هوش مصنوعی حسابیکس خوش آمدید! من یک دستیار هوشمند مستقل هستم که به صورت کامل در سرورهای داخلی حسابیکس میزبانی میشوم و آماده خدمترسانی به شما هستم.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
welcome: 'سلام و وقت بخیر! ✨',
|
||||||
|
thanks: 'خوشحالم که از هوش مصنوعی حسابیکس استفاده میکنید. من یک دستیار هوشمند مستقل هستم که به صورت کامل در سرورهای داخلی حسابیکس میزبانی میشوم و میتوانم در زمینههای مختلف به شما کمک کنم.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
welcome: 'به حسابیکس خوش آمدید! 🚀',
|
||||||
|
thanks: 'من هوش مصنوعی مستقل حسابیکس هستم که به صورت کامل در سرورهای داخلی میزبانی میشوم. خوشحالم که میتوانم به شما در استفاده از این نرمافزار کمک کنم.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
welcome: 'سلام! من دستیار هوشمند شما هستم 🤖',
|
||||||
|
thanks: 'به عنوان یک هوش مصنوعی مستقل که به صورت کامل در سرورهای داخلی حسابیکس میزبانی میشوم، آمادهام تا در هر زمینهای که نیاز دارید به شما کمک کنم.'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectedPattern: null,
|
||||||
|
capabilities: 'من میتوانم به شما در موارد زیر کمک کنم:',
|
||||||
|
capabilitiesList: [
|
||||||
|
'ساخت گزارشهای سفارشی با استفاده از هوش مصنوعی داخلی',
|
||||||
|
'ایجاد ماژولهای جدید بدون نیاز به کدنویسی',
|
||||||
|
'پاسخ به سؤالات شما درباره نرمافزار با استفاده از دانش داخلی',
|
||||||
|
'راهنمایی در استفاده از امکانات مختلف با هوش مصنوعی مستقل',
|
||||||
|
'تجزیه و تحلیل دادههای مالی با استفاده از الگوریتمهای داخلی',
|
||||||
|
'پیشبینی روندهای مالی با استفاده از هوش مصنوعی اختصاصی'
|
||||||
|
],
|
||||||
|
prompt: 'لطفاً سؤال یا درخواست خود را در باکس زیر بنویسید. من با استفاده از هوش مصنوعی مستقل خود، به شما کمک خواهم کرد.',
|
||||||
|
displayWelcome: '',
|
||||||
|
displayThanks: '',
|
||||||
|
displayCapabilities: '',
|
||||||
|
displayCapabilitiesList: [],
|
||||||
|
displayPrompt: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.selectRandomPattern()
|
||||||
|
await this.startTypingAnimation()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
userMessages: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
displayWelcome() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
displayThanks() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
displayCapabilities() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
displayCapabilitiesList: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
displayPrompt() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
selectRandomPattern() {
|
||||||
|
const randomIndex = Math.floor(Math.random() * this.welcomePatterns.length)
|
||||||
|
this.selectedPattern = this.welcomePatterns[randomIndex]
|
||||||
|
this.welcome = this.selectedPattern.welcome
|
||||||
|
this.thanks = this.selectedPattern.thanks
|
||||||
|
},
|
||||||
|
async startTypingAnimation() {
|
||||||
|
// تایپ پیام خوشآمدگویی
|
||||||
|
await this.typeText(this.welcome, (text) => {
|
||||||
|
this.displayWelcome = text
|
||||||
|
}, 15)
|
||||||
|
await this.delay(100)
|
||||||
|
|
||||||
|
// تایپ پیام تشکر
|
||||||
|
await this.typeText(this.thanks, (text) => {
|
||||||
|
this.displayThanks = text
|
||||||
|
}, 15)
|
||||||
|
await this.delay(100)
|
||||||
|
|
||||||
|
// تایپ معرفی قابلیتها
|
||||||
|
await this.typeText(this.capabilities, (text) => {
|
||||||
|
this.displayCapabilities = text
|
||||||
|
}, 15)
|
||||||
|
await this.delay(100)
|
||||||
|
|
||||||
|
// تایپ لیست قابلیتها
|
||||||
|
for (const capability of this.capabilitiesList) {
|
||||||
|
this.displayCapabilitiesList.push('')
|
||||||
|
await this.typeText(capability, (text) => {
|
||||||
|
this.displayCapabilitiesList[this.displayCapabilitiesList.length - 1] = text
|
||||||
|
}, 15)
|
||||||
|
await this.delay(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
// تایپ پیام نهایی
|
||||||
|
await this.typeText(this.prompt, (text) => {
|
||||||
|
this.displayPrompt = text
|
||||||
|
}, 15)
|
||||||
|
|
||||||
|
this.isTyping = false
|
||||||
|
},
|
||||||
|
async typeText(text, callback, speed = 50) {
|
||||||
|
let currentText = ''
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
currentText += text[i]
|
||||||
|
callback(currentText)
|
||||||
|
await this.delay(speed)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delay(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
},
|
||||||
|
async sendMessage() {
|
||||||
|
if (!this.userMessage.trim()) return
|
||||||
|
|
||||||
|
const message = this.userMessage.trim()
|
||||||
|
this.userMessages.push(message)
|
||||||
|
this.userMessage = ''
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
// انتخاب پاسخ رندوم
|
||||||
|
const randomResponse = this.aiResponses[Math.floor(Math.random() * this.aiResponses.length)]
|
||||||
|
|
||||||
|
// نمایش پاسخ اصلی
|
||||||
|
await this.delay(1000)
|
||||||
|
this.userMessages.push({
|
||||||
|
text: randomResponse.message,
|
||||||
|
isAI: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// نمایش جزئیات
|
||||||
|
await this.delay(500)
|
||||||
|
this.userMessages.push({
|
||||||
|
text: randomResponse.details,
|
||||||
|
isAI: true,
|
||||||
|
isDetails: true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.isLoading = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
const container = this.$refs.messagesContainer
|
||||||
|
if (container) {
|
||||||
|
container.scrollTo({
|
||||||
|
top: container.scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fixed-toolbar {
|
.page-container {
|
||||||
position: fixed;
|
height: 100vh;
|
||||||
top: 0;
|
display: flex;
|
||||||
left: 0;
|
flex-direction: column;
|
||||||
right: 0;
|
|
||||||
z-index: 2000;
|
|
||||||
background-color: #ffffff !important;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-content {
|
.content-container {
|
||||||
position: relative;
|
flex: 1;
|
||||||
z-index: 1;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-card {
|
.chat-container {
|
||||||
background-color: #ffffff !important;
|
height: 100%;
|
||||||
border-radius: 16px;
|
background-color: #f5f5f5;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card {
|
.chat-box {
|
||||||
background-color: #f8f9fa !important;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-message {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
align-self: flex-end;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-message .message-content {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message .message-content {
|
||||||
|
background-color: #1a237e;
|
||||||
|
color: white;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: white;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input :deep(.v-field__input) {
|
||||||
|
padding: 8px !important;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card:hover {
|
.send-button:hover {
|
||||||
background-color: #ffffff !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
|
||||||
transform: translateY(-4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-icon {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-card:hover .v-icon {
|
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-card-title {
|
.typing-indicator {
|
||||||
color: #2c3e50 !important;
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-card-text {
|
.typing-indicator span {
|
||||||
color: #34495e !important;
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #1a237e;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: typing 1s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-body-2 {
|
.typing-indicator span:nth-child(2) {
|
||||||
color: #7f8c8d !important;
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-4px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-message {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-message .message-text {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue