improve business info from admin panel
This commit is contained in:
parent
ca043a913f
commit
a97a29d50e
|
@ -19,6 +19,7 @@ use App\Entity\WalletTransaction;
|
||||||
use App\Service\Extractor;
|
use App\Service\Extractor;
|
||||||
use App\Service\Jdate;
|
use App\Service\Jdate;
|
||||||
use App\Service\JsonResp;
|
use App\Service\JsonResp;
|
||||||
|
use App\Service\Log;
|
||||||
use App\Service\Notification;
|
use App\Service\Notification;
|
||||||
use App\Service\Provider;
|
use App\Service\Provider;
|
||||||
use App\Service\registryMGR;
|
use App\Service\registryMGR;
|
||||||
|
@ -603,12 +604,16 @@ class AdminController extends AbstractController
|
||||||
}
|
}
|
||||||
$temp['totalPays'] = $totalPays;
|
$temp['totalPays'] = $totalPays;
|
||||||
|
|
||||||
$walletIncomes = $entityManager->getRepository(WalletTransaction::class)->findAllIncome($bid);
|
// محاسبه درآمد از تراکنشهای sell
|
||||||
$totalIcome = 0;
|
$walletSells = $entityManager->getRepository(WalletTransaction::class)->findBy(['bid' => $bid, 'type' => 'sell']);
|
||||||
foreach ($walletIncomes as $walletIncome) {
|
$totalIncome = 0;
|
||||||
$totalIcome += $walletIncome->getAmount();
|
foreach ($walletSells as $walletSell) {
|
||||||
|
$totalIncome += (float) $walletSell->getAmount();
|
||||||
}
|
}
|
||||||
$temp['totalIncome'] = $totalIcome;
|
$temp['totalIncome'] = $totalIncome;
|
||||||
|
|
||||||
|
// محاسبه موجودی (درآمد - هزینه)
|
||||||
|
$temp['walletBalance'] = $totalIncome - $totalPays;
|
||||||
|
|
||||||
$temp['id'] = $bid->getId();
|
$temp['id'] = $bid->getId();
|
||||||
$temp['bidName'] = $bid->getName();
|
$temp['bidName'] = $bid->getName();
|
||||||
|
@ -724,4 +729,457 @@ class AdminController extends AbstractController
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/charge/add', name: 'admin_business_charge_add', methods: ['POST'])]
|
||||||
|
public function admin_business_charge_add(
|
||||||
|
Request $request,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Log $logService,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$params = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
if (!isset($params['businessId']) || !isset($params['amount']) || !isset($params['description'])) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'تمام فیلدهای ضروری را وارد کنید']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($params['businessId']);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentCharge = (float) ($business->getSmsCharge() ?? 0);
|
||||||
|
$newAmount = (float) $params['amount'];
|
||||||
|
$newCharge = $currentCharge + $newAmount;
|
||||||
|
|
||||||
|
$business->setSmsCharge((string) $newCharge);
|
||||||
|
$entityManager->persist($business);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
// ثبت لاگ
|
||||||
|
$logService->insert(
|
||||||
|
'مدیریت اعتبار',
|
||||||
|
"افزایش اعتبار پیامک به مبلغ {$newAmount} ریال. اعتبار قبلی: {$currentCharge} ریال، اعتبار جدید: {$newCharge} ریال. توضیحات: {$params['description']}",
|
||||||
|
$this->getUser(),
|
||||||
|
$business
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'اعتبار با موفقیت افزایش یافت',
|
||||||
|
'data' => [
|
||||||
|
'previousCharge' => $currentCharge,
|
||||||
|
'newCharge' => $newCharge,
|
||||||
|
'addedAmount' => $newAmount
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/plugin/activate', name: 'admin_business_plugin_activate', methods: ['POST'])]
|
||||||
|
public function admin_business_plugin_activate(
|
||||||
|
Request $request,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Log $logService
|
||||||
|
): JsonResponse {
|
||||||
|
$params = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
if (!isset($params['businessId']) || !isset($params['pluginCode']) || !isset($params['duration'])) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'تمام فیلدهای ضروری را وارد کنید']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($params['businessId']);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginProduct = $entityManager->getRepository(\App\Entity\PluginProdect::class)->findOneBy(['code' => $params['pluginCode']]);
|
||||||
|
if (!$pluginProduct) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'افزونه یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه آیا افزونه قبلاً فعال شده یا خیر
|
||||||
|
$existingPlugin = $entityManager->getRepository(\App\Entity\Plugin::class)->findOneBy([
|
||||||
|
'bid' => $business,
|
||||||
|
'name' => $params['pluginCode']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$currentTime = time();
|
||||||
|
$expireTime = $currentTime + ($params['duration'] * 86400); // تبدیل روز به ثانیه
|
||||||
|
|
||||||
|
if ($existingPlugin) {
|
||||||
|
// اگر افزونه قبلاً فعال بوده، تاریخ انقضا را تمدید کن
|
||||||
|
$oldExpire = $existingPlugin->getDateExpire();
|
||||||
|
$existingPlugin->setDateExpire((string) $expireTime);
|
||||||
|
$existingPlugin->setStatus('100');
|
||||||
|
$entityManager->persist($existingPlugin);
|
||||||
|
} else {
|
||||||
|
// ایجاد افزونه جدید
|
||||||
|
$plugin = new \App\Entity\Plugin();
|
||||||
|
$plugin->setBid($business);
|
||||||
|
$plugin->setName($params['pluginCode']);
|
||||||
|
$plugin->setDateSubmit((string) $currentTime);
|
||||||
|
$plugin->setDateExpire((string) $expireTime);
|
||||||
|
$plugin->setStatus('100');
|
||||||
|
$plugin->setSubmitter($this->getUser());
|
||||||
|
$plugin->setPrice('0'); // رایگان برای ادمین
|
||||||
|
$plugin->setDes($params['description'] ?? 'فعالسازی توسط ادمین');
|
||||||
|
$entityManager->persist($plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
// ثبت لاگ
|
||||||
|
$durationText = $params['duration'] . ' روز';
|
||||||
|
$logService->insert(
|
||||||
|
'مدیریت افزونه',
|
||||||
|
"فعالسازی افزونه {$pluginProduct->getName()} برای مدت {$durationText}. توضیحات: " . (isset($params['description']) ? $params['description'] : 'فعالسازی توسط ادمین'),
|
||||||
|
$this->getUser(),
|
||||||
|
$business
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'افزونه با موفقیت فعال شد',
|
||||||
|
'data' => [
|
||||||
|
'pluginName' => $pluginProduct->getName(),
|
||||||
|
'expireDate' => date('Y-m-d H:i:s', $expireTime),
|
||||||
|
'duration' => $params['duration']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/report/{id}', name: 'admin_business_report', methods: ['GET'])]
|
||||||
|
public function admin_business_report(
|
||||||
|
string $id,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($id);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// آمار اشخاص
|
||||||
|
$personsCount = count($entityManager->getRepository(\App\Entity\Person::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار کالا و خدمات
|
||||||
|
$commodityCount = count($entityManager->getRepository(\App\Entity\Commodity::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار اسناد حسابداری
|
||||||
|
$hesabdariDocsCount = count($entityManager->getRepository(\App\Entity\HesabdariDoc::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار اسناد انبار
|
||||||
|
$storeroomDocsCount = count($entityManager->getRepository(\App\Entity\StoreroomTicket::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار بانکها
|
||||||
|
$bankAccountsCount = count($entityManager->getRepository(\App\Entity\BankAccount::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار سالهای مالی
|
||||||
|
$yearsCount = count($entityManager->getRepository(\App\Entity\Year::class)->findBy(['bid' => $business]));
|
||||||
|
|
||||||
|
// آمار افزونههای فعال
|
||||||
|
$activePlugins = $entityManager->getRepository(\App\Entity\Plugin::class)->findBy([
|
||||||
|
'bid' => $business,
|
||||||
|
'status' => '100'
|
||||||
|
]);
|
||||||
|
$activePluginsCount = count($activePlugins);
|
||||||
|
|
||||||
|
// لیست افزونههای فعال
|
||||||
|
$activePluginsList = [];
|
||||||
|
foreach ($activePlugins as $plugin) {
|
||||||
|
$pluginProduct = $entityManager->getRepository(\App\Entity\PluginProdect::class)->findOneBy(['code' => $plugin->getName()]);
|
||||||
|
$activePluginsList[] = [
|
||||||
|
'name' => $pluginProduct ? $pluginProduct->getName() : $plugin->getName(),
|
||||||
|
'expireDate' => $jdate->jdate('Y/n/d H:i', $plugin->getDateExpire()),
|
||||||
|
'isExpired' => $plugin->getDateExpire() < time()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// محاسبه فضای آرشیو
|
||||||
|
$archiveFiles = $entityManager->getRepository(\App\Entity\ArchiveFile::class)->findBy(['bid' => $business]);
|
||||||
|
$totalArchiveSize = 0;
|
||||||
|
foreach ($archiveFiles as $file) {
|
||||||
|
$totalArchiveSize += (int) ($file->getFileSize() ? $file->getFileSize() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// آمار کیف پول
|
||||||
|
$walletTransactions = $entityManager->getRepository(\App\Entity\WalletTransaction::class)->findBy(['bid' => $business]);
|
||||||
|
$walletIncome = 0;
|
||||||
|
$walletExpense = 0;
|
||||||
|
foreach ($walletTransactions as $transaction) {
|
||||||
|
if ($transaction->getType() === 'sell') {
|
||||||
|
$walletIncome += (float) $transaction->getAmount();
|
||||||
|
} elseif ($transaction->getType() === 'pay') {
|
||||||
|
$walletExpense += (float) $transaction->getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = [
|
||||||
|
'businessInfo' => [
|
||||||
|
'id' => $business->getId(),
|
||||||
|
'name' => $business->getName(),
|
||||||
|
'legalName' => $business->getLegalName(),
|
||||||
|
'owner' => $business->getOwner()->getFullName(),
|
||||||
|
'ownerMobile' => $business->getOwner()->getMobile(),
|
||||||
|
'ownerEmail' => $business->getOwner()->getEmail(),
|
||||||
|
'dateRegister' => $jdate->jdate('Y/n/d H:i', $business->getDateSubmit()),
|
||||||
|
'field' => $business->getField(),
|
||||||
|
'type' => $business->getType(),
|
||||||
|
'address' => $business->getAddress(),
|
||||||
|
'tel' => $business->getTel(),
|
||||||
|
'mobile' => $business->getMobile(),
|
||||||
|
'email' => $business->getEmail(),
|
||||||
|
'website' => $business->getWesite(),
|
||||||
|
'shenasemeli' => $business->getShenasemeli(),
|
||||||
|
'codeeghtesadi' => $business->getCodeeghtesadi(),
|
||||||
|
'shomaresabt' => $business->getShomaresabt(),
|
||||||
|
'country' => $business->getCountry(),
|
||||||
|
'ostan' => $business->getOstan(),
|
||||||
|
'shahrestan' => $business->getShahrestan(),
|
||||||
|
'postalcode' => $business->getPostalcode(),
|
||||||
|
'maliyatafzode' => $business->getMaliyatafzode(),
|
||||||
|
'avatar' => $business->getAvatar(),
|
||||||
|
'sealFile' => $business->getSealFile(),
|
||||||
|
],
|
||||||
|
'statistics' => [
|
||||||
|
'personsCount' => $personsCount,
|
||||||
|
'commodityCount' => $commodityCount,
|
||||||
|
'hesabdariDocsCount' => $hesabdariDocsCount,
|
||||||
|
'storeroomDocsCount' => $storeroomDocsCount,
|
||||||
|
'bankAccountsCount' => $bankAccountsCount,
|
||||||
|
'yearsCount' => $yearsCount,
|
||||||
|
'activePluginsCount' => $activePluginsCount,
|
||||||
|
],
|
||||||
|
'financial' => [
|
||||||
|
'smsCharge' => (float) ($business->getSmsCharge() ?? 0),
|
||||||
|
'walletEnabled' => $business->isWalletEnable(),
|
||||||
|
'walletIncome' => $walletIncome,
|
||||||
|
'walletExpense' => $walletExpense,
|
||||||
|
'walletBalance' => $walletIncome - $walletExpense,
|
||||||
|
],
|
||||||
|
'storage' => [
|
||||||
|
'archiveSize' => $business->getArchiveSize(),
|
||||||
|
'totalArchiveSize' => $totalArchiveSize,
|
||||||
|
'archiveFilesCount' => count($archiveFiles),
|
||||||
|
],
|
||||||
|
'plugins' => [
|
||||||
|
'activeCount' => $activePluginsCount,
|
||||||
|
'activeList' => $activePluginsList,
|
||||||
|
],
|
||||||
|
'features' => [
|
||||||
|
'storeOnline' => $business->isStoreOnline(),
|
||||||
|
'shortlinks' => $business->isShortlinks(),
|
||||||
|
'walletEnable' => $business->isWalletEnable(),
|
||||||
|
'commodityUpdateSellPriceAuto' => $business->isCommodityUpdateSellPriceAuto(),
|
||||||
|
'commodityUpdateBuyPriceAuto' => $business->isCommodityUpdateBuyPriceAuto(),
|
||||||
|
'profitCalcType' => $business->getProfitCalcType(),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $report
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/wallet/balance/{id}', name: 'admin_business_wallet_balance', methods: ['GET'])]
|
||||||
|
public function admin_business_wallet_balance(
|
||||||
|
string $id,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($id);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$business->isWalletEnable()) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کیف پول برای این کسب و کار فعال نیست']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// محاسبه موجودی با استفاده از repository
|
||||||
|
$walletBalance = $entityManager->getRepository(\App\Entity\WalletTransaction::class)->calculateWalletBalance($business);
|
||||||
|
|
||||||
|
// محاسبه درآمد و هزینه جداگانه
|
||||||
|
$walletSells = $entityManager->getRepository(\App\Entity\WalletTransaction::class)->findBy(['bid' => $business, 'type' => 'sell']);
|
||||||
|
$walletPays = $entityManager->getRepository(\App\Entity\WalletTransaction::class)->findBy(['bid' => $business, 'type' => 'pay']);
|
||||||
|
|
||||||
|
$totalIncome = 0;
|
||||||
|
foreach ($walletSells as $sell) {
|
||||||
|
$totalIncome += (float) $sell->getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalExpense = 0;
|
||||||
|
foreach ($walletPays as $pay) {
|
||||||
|
$totalExpense += (float) $pay->getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'businessId' => $business->getId(),
|
||||||
|
'businessName' => $business->getName(),
|
||||||
|
'walletBalance' => $walletBalance,
|
||||||
|
'totalIncome' => $totalIncome,
|
||||||
|
'totalExpense' => $totalExpense,
|
||||||
|
'transactionsCount' => [
|
||||||
|
'sell' => count($walletSells),
|
||||||
|
'pay' => count($walletPays)
|
||||||
|
],
|
||||||
|
'lastTransactions' => [
|
||||||
|
'sells' => array_slice(array_map(function($sell) use ($jdate) {
|
||||||
|
return [
|
||||||
|
'id' => $sell->getId(),
|
||||||
|
'amount' => (float) $sell->getAmount(),
|
||||||
|
'date' => $jdate->jdate('Y/n/d H:i', $sell->getDateSubmit()),
|
||||||
|
'description' => $sell->getDes()
|
||||||
|
];
|
||||||
|
}, $walletSells), 0, 5),
|
||||||
|
'pays' => array_slice(array_map(function($pay) use ($jdate) {
|
||||||
|
return [
|
||||||
|
'id' => $pay->getId(),
|
||||||
|
'amount' => (float) $pay->getAmount(),
|
||||||
|
'date' => $jdate->jdate('Y/n/d H:i', $pay->getDateSubmit()),
|
||||||
|
'description' => $pay->getDes(),
|
||||||
|
'refID' => $pay->getRefID()
|
||||||
|
];
|
||||||
|
}, $walletPays), 0, 5)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/wallet/transactions/{id}', name: 'admin_business_wallet_transactions', methods: ['GET'])]
|
||||||
|
public function admin_business_wallet_transactions(
|
||||||
|
string $id,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate,
|
||||||
|
Request $request
|
||||||
|
): JsonResponse {
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($id);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$business->isWalletEnable()) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کیف پول برای این کسب و کار فعال نیست']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// پارامترهای صفحهبندی
|
||||||
|
$page = max(1, (int) ($request->query->get('page', 1)));
|
||||||
|
$limit = max(1, min(100, (int) ($request->query->get('limit', 20))));
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
|
// فیلتر نوع تراکنش
|
||||||
|
$type = $request->query->get('type'); // 'sell' یا 'pay' یا null برای همه
|
||||||
|
|
||||||
|
$qb = $entityManager->createQueryBuilder();
|
||||||
|
$qb->select('w')
|
||||||
|
->from(\App\Entity\WalletTransaction::class, 'w')
|
||||||
|
->where('w.bid = :business')
|
||||||
|
->setParameter('business', $business)
|
||||||
|
->orderBy('w.dateSubmit', 'DESC');
|
||||||
|
|
||||||
|
if ($type && in_array($type, ['sell', 'pay'])) {
|
||||||
|
$qb->andWhere('w.type = :type')
|
||||||
|
->setParameter('type', $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// محاسبه تعداد کل
|
||||||
|
$countQb = clone $qb;
|
||||||
|
$totalCount = $countQb->select('COUNT(w.id)')->getQuery()->getSingleScalarResult();
|
||||||
|
|
||||||
|
// اعمال صفحهبندی
|
||||||
|
$qb->setFirstResult($offset)
|
||||||
|
->setMaxResults($limit);
|
||||||
|
|
||||||
|
$transactions = $qb->getQuery()->getResult();
|
||||||
|
|
||||||
|
$transactionsData = [];
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$transactionsData[] = [
|
||||||
|
'id' => $transaction->getId(),
|
||||||
|
'type' => $transaction->getType(),
|
||||||
|
'amount' => (float) $transaction->getAmount(),
|
||||||
|
'date' => $jdate->jdate('Y/n/d H:i', $transaction->getDateSubmit()),
|
||||||
|
'description' => $transaction->getDes(),
|
||||||
|
'refID' => $transaction->getRefID(),
|
||||||
|
'shaba' => $transaction->getShaba(),
|
||||||
|
'cardPan' => $transaction->getCardPan(),
|
||||||
|
'gatePay' => $transaction->getGatePay(),
|
||||||
|
'bank' => $transaction->getBank(),
|
||||||
|
'submitter' => $transaction->getSubmitter() ? $transaction->getSubmitter()->getFullName() : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'businessId' => $business->getId(),
|
||||||
|
'businessName' => $business->getName(),
|
||||||
|
'transactions' => $transactionsData,
|
||||||
|
'pagination' => [
|
||||||
|
'page' => $page,
|
||||||
|
'limit' => $limit,
|
||||||
|
'total' => (int) $totalCount,
|
||||||
|
'totalPages' => ceil($totalCount / $limit)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/business/plugins/list/{id}', name: 'admin_business_plugins_list', methods: ['GET'])]
|
||||||
|
public function admin_business_plugins_list(
|
||||||
|
string $id,
|
||||||
|
EntityManagerInterface $entityManager,
|
||||||
|
Jdate $jdate
|
||||||
|
): JsonResponse {
|
||||||
|
$business = $entityManager->getRepository(Business::class)->find($id);
|
||||||
|
if (!$business) {
|
||||||
|
return $this->json(['success' => false, 'message' => 'کسب و کار یافت نشد']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت همه افزونههای موجود
|
||||||
|
$allPlugins = $entityManager->getRepository(\App\Entity\PluginProdect::class)->findAll();
|
||||||
|
|
||||||
|
// دریافت افزونههای فعال این کسب و کار
|
||||||
|
$businessPlugins = $entityManager->getRepository(\App\Entity\Plugin::class)->findBy([
|
||||||
|
'bid' => $business,
|
||||||
|
'status' => '100'
|
||||||
|
]);
|
||||||
|
$businessPluginCodes = array_map(fn($p) => $p->getName(), $businessPlugins);
|
||||||
|
|
||||||
|
$pluginsList = [];
|
||||||
|
foreach ($allPlugins as $plugin) {
|
||||||
|
$isActive = in_array($plugin->getCode(), $businessPluginCodes);
|
||||||
|
$businessPlugin = null;
|
||||||
|
|
||||||
|
if ($isActive) {
|
||||||
|
$businessPlugin = $entityManager->getRepository(\App\Entity\Plugin::class)->findOneBy([
|
||||||
|
'bid' => $business,
|
||||||
|
'name' => $plugin->getCode(),
|
||||||
|
'status' => '100'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pluginsList[] = [
|
||||||
|
'id' => $plugin->getId(),
|
||||||
|
'name' => $plugin->getName(),
|
||||||
|
'code' => $plugin->getCode(),
|
||||||
|
'price' => $plugin->getPrice(),
|
||||||
|
'timeLabel' => $plugin->getTimelabel(),
|
||||||
|
'icon' => $plugin->getIcon(),
|
||||||
|
'defaultOn' => $plugin->isDefaultOn(),
|
||||||
|
'isActive' => $isActive,
|
||||||
|
'expireDate' => $businessPlugin ? $jdate->jdate('Y/n/d H:i', $businessPlugin->getDateExpire()) : null,
|
||||||
|
'isExpired' => $businessPlugin ? $businessPlugin->getDateExpire() < time() : false,
|
||||||
|
'status' => $businessPlugin ? $businessPlugin->getStatus() : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => $pluginsList
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class WalletTransactionRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
return $this->createQueryBuilder('w')
|
return $this->createQueryBuilder('w')
|
||||||
->andWhere('w.bid = :val')
|
->andWhere('w.bid = :val')
|
||||||
->andWhere("w.type != 'pay'")
|
->andWhere("w.type = 'sell'")
|
||||||
->setParameter('val', $business)
|
->setParameter('val', $business)
|
||||||
->orderBy('w.id', 'DESC')
|
->orderBy('w.id', 'DESC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
|
@ -37,6 +37,31 @@ class WalletTransactionRepository extends ServiceEntityRepository
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function calculateWalletBalance(Business $business): float
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('w');
|
||||||
|
|
||||||
|
// محاسبه مجموع تراکنشهای sell (درآمد)
|
||||||
|
$incomeQuery = $qb->select('SUM(CAST(w.amount AS DECIMAL(10,2)))')
|
||||||
|
->where('w.bid = :business')
|
||||||
|
->andWhere("w.type = 'sell'")
|
||||||
|
->setParameter('business', $business)
|
||||||
|
->getQuery();
|
||||||
|
|
||||||
|
$income = $incomeQuery->getSingleScalarResult() ?? 0;
|
||||||
|
|
||||||
|
// محاسبه مجموع تراکنشهای pay (هزینه)
|
||||||
|
$expenseQuery = $qb->select('SUM(CAST(w.amount AS DECIMAL(10,2)))')
|
||||||
|
->where('w.bid = :business')
|
||||||
|
->andWhere("w.type = 'pay'")
|
||||||
|
->setParameter('business', $business)
|
||||||
|
->getQuery();
|
||||||
|
|
||||||
|
$expense = $expenseQuery->getSingleScalarResult() ?? 0;
|
||||||
|
|
||||||
|
return (float) $income - (float) $expense;
|
||||||
|
}
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?WalletTransaction
|
// public function findOneBySomeField($value): ?WalletTransaction
|
||||||
// {
|
// {
|
||||||
// return $this->createQueryBuilder('w')
|
// return $this->createQueryBuilder('w')
|
||||||
|
|
126
webUI/src/components/admin/BusinessChargeDialog.vue
Normal file
126
webUI/src/components/admin/BusinessChargeDialog.vue
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="500px" persistent>
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="primary" dark>
|
||||||
|
<v-icon class="me-3">mdi-credit-card</v-icon>
|
||||||
|
<v-toolbar-title>افزایش اعتبار پیامک - {{ business?.name || 'انتخاب نشده' }}</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon @click="closeDialog">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-card-text class="pa-6">
|
||||||
|
<v-form @submit.prevent="addCharge">
|
||||||
|
<v-text-field
|
||||||
|
v-model="amount"
|
||||||
|
label="مبلغ (ریال)"
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
class="mb-4"
|
||||||
|
:rules="[v => !!v || 'مبلغ الزامی است', v => v > 0 || 'مبلغ باید بزرگتر از صفر باشد']"
|
||||||
|
prepend-inner-icon="mdi-currency-usd"
|
||||||
|
></v-text-field>
|
||||||
|
<v-textarea
|
||||||
|
v-model="description"
|
||||||
|
label="توضیحات"
|
||||||
|
required
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
rows="4"
|
||||||
|
class="mb-4"
|
||||||
|
:rules="[v => !!v || 'توضیحات الزامی است']"
|
||||||
|
prepend-inner-icon="mdi-text"
|
||||||
|
></v-textarea>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-6 pt-0">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="error" variant="outlined" @click="closeDialog" class="me-3">
|
||||||
|
<v-icon class="me-2">mdi-close</v-icon>
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" @click="addCharge" :loading="loading">
|
||||||
|
<v-icon class="me-2">mdi-check</v-icon>
|
||||||
|
افزایش اعتبار
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
|
||||||
|
interface Business {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
business: Business | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const dialog = ref(false)
|
||||||
|
const amount = ref('')
|
||||||
|
const description = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
dialog.value = newVal
|
||||||
|
if (newVal && props.business) {
|
||||||
|
amount.value = ''
|
||||||
|
description.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialog, (newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
dialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCharge = async () => {
|
||||||
|
if (!amount.value || !description.value) {
|
||||||
|
Swal.fire('خطا', 'لطفاً تمام فیلدها را پر کنید', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/admin/business/charge/add', {
|
||||||
|
businessId: props.business?.id,
|
||||||
|
amount: parseFloat(amount.value),
|
||||||
|
description: description.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
Swal.fire('موفق', response.data.message, 'success')
|
||||||
|
closeDialog()
|
||||||
|
emit('success')
|
||||||
|
} else {
|
||||||
|
Swal.fire('خطا', response.data.message, 'error')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire('خطا', 'خطا در ارتباط با سرور', 'error')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
179
webUI/src/components/admin/BusinessPluginDialog.vue
Normal file
179
webUI/src/components/admin/BusinessPluginDialog.vue
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="600px" persistent>
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar color="success" dark>
|
||||||
|
<v-icon class="me-3">mdi-puzzle</v-icon>
|
||||||
|
<v-toolbar-title>فعالسازی افزونه - {{ business?.name || 'انتخاب نشده' }}</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon @click="closeDialog">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-card-text class="pa-6">
|
||||||
|
<v-form @submit.prevent="activatePlugin">
|
||||||
|
<v-select
|
||||||
|
v-model="selectedPlugin"
|
||||||
|
:items="pluginsList"
|
||||||
|
item-title="name"
|
||||||
|
item-value="code"
|
||||||
|
label="انتخاب افزونه"
|
||||||
|
required
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
class="mb-4"
|
||||||
|
:rules="[v => !!v || 'انتخاب افزونه الزامی است']"
|
||||||
|
prepend-inner-icon="mdi-apps"
|
||||||
|
>
|
||||||
|
<template v-slot:item="{ props, item }">
|
||||||
|
<v-list-item v-bind="props" class="py-2">
|
||||||
|
<v-list-item-title class="text-body-1">{{ item.raw.name }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-caption">
|
||||||
|
{{ item.raw.timeLabel }} - {{ item.raw.price }} ریال
|
||||||
|
<v-chip v-if="item.raw.isActive" color="success" size="small" class="ms-2">فعال</v-chip>
|
||||||
|
<v-chip v-else-if="item.raw.isExpired" color="error" size="small" class="ms-2">منقضی شده</v-chip>
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
<v-text-field
|
||||||
|
v-model="duration"
|
||||||
|
label="مدت زمان (روز)"
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
class="mb-4"
|
||||||
|
:rules="[v => !!v || 'مدت زمان الزامی است', v => v > 0 || 'مدت زمان باید بزرگتر از صفر باشد']"
|
||||||
|
prepend-inner-icon="mdi-calendar-clock"
|
||||||
|
></v-text-field>
|
||||||
|
<v-textarea
|
||||||
|
v-model="description"
|
||||||
|
label="توضیحات (اختیاری)"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
rows="3"
|
||||||
|
class="mb-4"
|
||||||
|
prepend-inner-icon="mdi-text"
|
||||||
|
></v-textarea>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-6 pt-0">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="error" variant="outlined" @click="closeDialog" class="me-3">
|
||||||
|
<v-icon class="me-2">mdi-close</v-icon>
|
||||||
|
انصراف
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="success" @click="activatePlugin" :loading="loading">
|
||||||
|
<v-icon class="me-2">mdi-check</v-icon>
|
||||||
|
فعالسازی
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
|
||||||
|
interface Business {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Plugin {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
price: string
|
||||||
|
timeLabel: string
|
||||||
|
icon: string
|
||||||
|
defaultOn: boolean
|
||||||
|
isActive: boolean
|
||||||
|
expireDate: string | null
|
||||||
|
isExpired: boolean
|
||||||
|
status: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
business: Business | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const dialog = ref(false)
|
||||||
|
const selectedPlugin = ref('')
|
||||||
|
const duration = ref(30)
|
||||||
|
const description = ref('')
|
||||||
|
const pluginsList = ref<Plugin[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, async (newVal) => {
|
||||||
|
dialog.value = newVal
|
||||||
|
if (newVal && props.business) {
|
||||||
|
selectedPlugin.value = ''
|
||||||
|
duration.value = 30
|
||||||
|
description.value = ''
|
||||||
|
await loadPlugins()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialog, (newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
dialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPlugins = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/admin/business/plugins/list/${props.business?.id}`)
|
||||||
|
if (response.data.success) {
|
||||||
|
pluginsList.value = response.data.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری لیست افزونهها:', error)
|
||||||
|
Swal.fire('خطا', 'خطا در بارگذاری لیست افزونهها', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activatePlugin = async () => {
|
||||||
|
if (!selectedPlugin.value || !duration.value) {
|
||||||
|
Swal.fire('خطا', 'لطفاً افزونه و مدت زمان را انتخاب کنید', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/admin/business/plugin/activate', {
|
||||||
|
businessId: props.business?.id,
|
||||||
|
pluginCode: selectedPlugin.value,
|
||||||
|
duration: duration.value,
|
||||||
|
description: description.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
Swal.fire('موفق', response.data.message, 'success')
|
||||||
|
closeDialog()
|
||||||
|
emit('success')
|
||||||
|
} else {
|
||||||
|
Swal.fire('خطا', response.data.message, 'error')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire('خطا', 'خطا در ارتباط با سرور', 'error')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
701
webUI/src/components/admin/BusinessReportDialog.vue
Normal file
701
webUI/src/components/admin/BusinessReportDialog.vue
Normal file
|
@ -0,0 +1,701 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="1200px" persistent scrollable>
|
||||||
|
<v-card v-if="businessReport" class="business-report-dialog">
|
||||||
|
<!-- Header -->
|
||||||
|
<v-toolbar color="primary" dark elevation="0">
|
||||||
|
<v-avatar color="white" size="40" class="me-3">
|
||||||
|
<v-icon color="primary" size="24">mdi-domain</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div>
|
||||||
|
<v-toolbar-title class="text-h6 font-weight-bold">
|
||||||
|
گزارش کامل کسب و کار
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div class="text-caption">
|
||||||
|
{{ businessReport?.businessInfo?.name || 'انتخاب نشده' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon @click="closeDialog" variant="text">
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<v-card-text v-if="loading" class="d-flex justify-center align-center" style="min-height: 400px;">
|
||||||
|
<v-progress-circular indeterminate color="primary" size="64"></v-progress-circular>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<v-card-text v-else class="pa-0">
|
||||||
|
<!-- Quick Stats Bar -->
|
||||||
|
<v-sheet color="grey-lighten-4" class="pa-4 mb-4">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h4 font-weight-bold text-primary">{{ businessReport.statistics.personsCount }}</div>
|
||||||
|
<div class="text-caption text-grey-600">اشخاص</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h4 font-weight-bold text-success">{{ businessReport.statistics.commodityCount }}</div>
|
||||||
|
<div class="text-caption text-grey-600">کالا و خدمات</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h4 font-weight-bold text-info">{{ businessReport.statistics.hesabdariDocsCount }}</div>
|
||||||
|
<div class="text-caption text-grey-600">اسناد حسابداری</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h4 font-weight-bold text-warning">{{ businessReport.statistics.storeroomDocsCount }}</div>
|
||||||
|
<div class="text-caption text-grey-600">اسناد انبار</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-sheet>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<v-tabs v-model="activeTab" color="primary" class="px-4" show-arrows>
|
||||||
|
<v-tab value="overview" prepend-icon="mdi-view-dashboard">
|
||||||
|
<span class="d-none d-sm-inline">نمای کلی</span>
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="info" prepend-icon="mdi-information">
|
||||||
|
<span class="d-none d-sm-inline">اطلاعات پایه</span>
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="financial" prepend-icon="mdi-currency-usd">
|
||||||
|
<span class="d-none d-sm-inline">مالی</span>
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="plugins" prepend-icon="mdi-puzzle">
|
||||||
|
<span class="d-none d-sm-inline">افزونهها</span>
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="storage" prepend-icon="mdi-cloud">
|
||||||
|
<span class="d-none d-sm-inline">فضای ذخیره</span>
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-window v-model="activeTab" class="pa-6">
|
||||||
|
<!-- نمای کلی -->
|
||||||
|
<v-window-item value="overview">
|
||||||
|
<v-row>
|
||||||
|
<!-- Business Info Card -->
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card class="h-100" variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="primary" class="me-2">mdi-domain</v-icon>
|
||||||
|
اطلاعات کسب و کار
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-list density="compact" class="pa-0">
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-account</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">مالک</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.owner }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-phone</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">موبایل</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.ownerMobile }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-email</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">ایمیل</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.ownerEmail }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-calendar</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">تاریخ ثبت</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.dateRegister }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- Financial Summary Card -->
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card class="h-100" variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="success" class="me-2">mdi-wallet</v-icon>
|
||||||
|
خلاصه مالی
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="d-flex justify-space-between align-center mb-2">
|
||||||
|
<span class="text-body-2">اعتبار پیامک</span>
|
||||||
|
<span class="text-h6 font-weight-bold text-primary">{{ Math.floor(businessReport.financial.smsCharge).toLocaleString() }} ریال</span>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="Math.min((businessReport.financial.smsCharge / 100000) * 100, 100)"
|
||||||
|
color="primary"
|
||||||
|
height="8"
|
||||||
|
rounded
|
||||||
|
></v-progress-linear>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="businessReport.financial.walletEnabled" class="mb-4">
|
||||||
|
<div class="d-flex justify-space-between align-center mb-2">
|
||||||
|
<span class="text-body-2">موجودی کیف پول</span>
|
||||||
|
<span class="text-h6 font-weight-bold" :class="businessReport.financial.walletBalance >= 0 ? 'text-success' : 'text-error'">
|
||||||
|
{{ Math.floor(businessReport.financial.walletBalance).toLocaleString() }} ریال
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="Math.min(Math.abs(businessReport.financial.walletBalance / 1000000) * 100, 100)"
|
||||||
|
:color="businessReport.financial.walletBalance >= 0 ? 'success' : 'error'"
|
||||||
|
height="8"
|
||||||
|
rounded
|
||||||
|
></v-progress-linear>
|
||||||
|
<div class="text-caption text-grey-600 mt-1">
|
||||||
|
موجودی = تراکنشهای فروش - تراکنشهای پرداخت
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-space-between">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-caption text-grey-600">سالهای مالی</div>
|
||||||
|
<div class="text-h6 font-weight-bold text-info">{{ businessReport.statistics.yearsCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-caption text-grey-600">حسابهای بانکی</div>
|
||||||
|
<div class="text-h6 font-weight-bold text-warning">{{ businessReport.statistics.bankAccountsCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-caption text-grey-600">افزونههای فعال</div>
|
||||||
|
<div class="text-h6 font-weight-bold text-secondary">{{ businessReport.plugins.activeCount }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- Storage Usage Card -->
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="info" class="me-2">mdi-cloud</v-icon>
|
||||||
|
استفاده از فضای ذخیره
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex align-center justify-space-between mb-4">
|
||||||
|
<div>
|
||||||
|
<div class="text-h5 font-weight-bold">{{ (businessReport.storage.totalArchiveSize / 1024 / 1024).toFixed(2) }} مگابایت</div>
|
||||||
|
<div class="text-subtitle-1 text-grey-600">{{ businessReport.storage.archiveFilesCount }} فایل</div>
|
||||||
|
</div>
|
||||||
|
<v-avatar size="80" color="info" variant="tonal">
|
||||||
|
<v-icon size="40" color="info">mdi-cloud</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="Math.min((businessReport.storage.totalArchiveSize / 1024 / 1024 / 100) * 100, 100)"
|
||||||
|
color="info"
|
||||||
|
height="12"
|
||||||
|
rounded
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<!-- اطلاعات پایه -->
|
||||||
|
<v-window-item value="info">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="primary" class="me-2">mdi-information</v-icon>
|
||||||
|
اطلاعات اصلی
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-list density="compact" class="pa-0">
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-domain</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">نام کسب و کار</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.name }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-file-document</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">نام قانونی</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.legalName }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-briefcase</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">رشته فعالیت</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.field || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-tag</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">نوع</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.type || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="primary" class="me-2">mdi-map-marker</v-icon>
|
||||||
|
اطلاعات تماس
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-list density="compact" class="pa-0">
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-map-marker</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">آدرس</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.address || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-phone</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">تلفن</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.tel || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-cellphone</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">موبایل</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.mobile || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="grey">mdi-email</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">ایمیل</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-body-1 font-weight-medium">{{ businessReport.businessInfo.email || 'تعریف نشده' }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<!-- مالی -->
|
||||||
|
<v-window-item value="financial">
|
||||||
|
<v-row>
|
||||||
|
<!-- SMS Credit -->
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="primary" class="me-2">mdi-message-text</v-icon>
|
||||||
|
اعتبار پیامک
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="text-center">
|
||||||
|
<v-avatar size="120" color="primary" variant="tonal" class="mb-4">
|
||||||
|
<v-icon size="60" color="primary">mdi-message-text</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h3 font-weight-bold text-primary mb-2">{{ Math.floor(businessReport.financial.smsCharge).toLocaleString() }}</div>
|
||||||
|
<div class="text-subtitle-1 text-grey-600">ریال</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- Wallet -->
|
||||||
|
<v-col cols="12" md="6" v-if="businessReport.financial.walletEnabled">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="success" class="me-2">mdi-wallet</v-icon>
|
||||||
|
کیف پول
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<v-tooltip location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<div v-bind="props">
|
||||||
|
<v-avatar size="60" color="success" variant="tonal" class="mb-2">
|
||||||
|
<v-icon size="30" color="success">mdi-trending-up</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h6 font-weight-bold text-success">{{ Math.floor(businessReport.financial.walletIncome).toLocaleString() }}</div>
|
||||||
|
<div class="text-caption text-grey-600">درآمد (فروش)</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>مجموع تراکنشهای فروش (sell)</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<v-tooltip location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<div v-bind="props">
|
||||||
|
<v-avatar size="60" color="error" variant="tonal" class="mb-2">
|
||||||
|
<v-icon size="30" color="error">mdi-trending-down</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h6 font-weight-bold text-error">{{ Math.floor(businessReport.financial.walletExpense).toLocaleString() }}</div>
|
||||||
|
<div class="text-caption text-grey-600">هزینه (پرداخت)</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>مجموع تراکنشهای پرداخت (pay)</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4" class="text-center">
|
||||||
|
<v-tooltip location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<div v-bind="props">
|
||||||
|
<v-avatar size="60" color="primary" variant="tonal" class="mb-2">
|
||||||
|
<v-icon size="30" color="primary">mdi-wallet</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h6 font-weight-bold text-primary">{{ Math.floor(businessReport.financial.walletBalance).toLocaleString() }}</div>
|
||||||
|
<div class="text-caption text-grey-600">موجودی</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>موجودی = درآمد (فروش) - هزینه (پرداخت)</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<!-- Financial Statistics -->
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="info" class="me-2">mdi-chart-line</v-icon>
|
||||||
|
آمار مالی
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center pa-4">
|
||||||
|
<v-avatar size="80" color="primary" variant="tonal" class="mb-3">
|
||||||
|
<v-icon size="40" color="primary">mdi-calendar</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h4 font-weight-bold text-primary">{{ businessReport.statistics.yearsCount }}</div>
|
||||||
|
<div class="text-subtitle-1">سالهای مالی</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center pa-4">
|
||||||
|
<v-avatar size="80" color="success" variant="tonal" class="mb-3">
|
||||||
|
<v-icon size="40" color="success">mdi-bank</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h4 font-weight-bold text-success">{{ businessReport.statistics.bankAccountsCount }}</div>
|
||||||
|
<div class="text-subtitle-1">حسابهای بانکی</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center pa-4">
|
||||||
|
<v-avatar size="80" color="info" variant="tonal" class="mb-3">
|
||||||
|
<v-icon size="40" color="info">mdi-file-document</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h4 font-weight-bold text-info">{{ businessReport.statistics.hesabdariDocsCount }}</div>
|
||||||
|
<div class="text-subtitle-1">اسناد حسابداری</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="3">
|
||||||
|
<div class="text-center pa-4">
|
||||||
|
<v-avatar size="80" color="warning" variant="tonal" class="mb-3">
|
||||||
|
<v-icon size="40" color="warning">mdi-warehouse</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div class="text-h4 font-weight-bold text-warning">{{ businessReport.statistics.storeroomDocsCount }}</div>
|
||||||
|
<div class="text-subtitle-1">اسناد انبار</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<!-- افزونهها -->
|
||||||
|
<v-window-item value="plugins">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center justify-space-between">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-icon color="secondary" class="me-2">mdi-puzzle</v-icon>
|
||||||
|
افزونههای فعال
|
||||||
|
</div>
|
||||||
|
<v-chip color="secondary" variant="tonal">
|
||||||
|
{{ businessReport.plugins.activeCount }} افزونه
|
||||||
|
</v-chip>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div v-if="businessReport.plugins.activeList.length > 0">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6" v-for="plugin in businessReport.plugins.activeList" :key="plugin.name">
|
||||||
|
<v-card variant="outlined" class="plugin-card">
|
||||||
|
<v-card-text class="pa-4">
|
||||||
|
<div class="d-flex align-center justify-space-between mb-3">
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<v-avatar size="40" :color="plugin.isExpired ? 'error' : 'success'" variant="tonal" class="me-3">
|
||||||
|
<v-icon :color="plugin.isExpired ? 'error' : 'success'">
|
||||||
|
{{ plugin.isExpired ? 'mdi-alert' : 'mdi-check' }}
|
||||||
|
</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<div>
|
||||||
|
<div class="text-h6 font-weight-medium">{{ plugin.name }}</div>
|
||||||
|
<div class="text-caption text-grey-600">انقضا: {{ plugin.expireDate }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<v-chip
|
||||||
|
:color="plugin.isExpired ? 'error' : 'success'"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
{{ plugin.isExpired ? 'منقضی شده' : 'فعال' }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="plugin.isExpired ? 100 : 75"
|
||||||
|
:color="plugin.isExpired ? 'error' : 'success'"
|
||||||
|
height="6"
|
||||||
|
rounded
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
|
<v-alert v-else type="info" variant="tonal" class="mt-4">
|
||||||
|
<v-icon class="me-2">mdi-information</v-icon>
|
||||||
|
هیچ افزونه فعالی یافت نشد
|
||||||
|
</v-alert>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
|
||||||
|
<!-- فضای ذخیره -->
|
||||||
|
<v-window-item value="storage">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="8">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="info" class="me-2">mdi-cloud</v-icon>
|
||||||
|
فضای آرشیو
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex align-center justify-space-between mb-6">
|
||||||
|
<div>
|
||||||
|
<div class="text-h3 font-weight-bold text-info mb-2">{{ (businessReport.storage.totalArchiveSize / 1024 / 1024).toFixed(2) }}</div>
|
||||||
|
<div class="text-subtitle-1 text-grey-600">مگابایت استفاده شده</div>
|
||||||
|
</div>
|
||||||
|
<v-avatar size="120" color="info" variant="tonal">
|
||||||
|
<v-icon size="60" color="info">mdi-cloud</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="d-flex justify-space-between align-center mb-2">
|
||||||
|
<span class="text-body-1">فضای استفاده شده</span>
|
||||||
|
<span class="text-body-1 font-weight-medium">{{ (businessReport.storage.totalArchiveSize / 1024 / 1024).toFixed(2) }} MB</span>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="Math.min((businessReport.storage.totalArchiveSize / 1024 / 1024 / 100) * 100, 100)"
|
||||||
|
color="info"
|
||||||
|
height="16"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
<template v-slot:default="{ value }">
|
||||||
|
<strong>{{ Math.ceil(value) }}%</strong>
|
||||||
|
</template>
|
||||||
|
</v-progress-linear>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-h5 font-weight-bold text-grey-600 mb-2">{{ businessReport.storage.archiveFilesCount }}</div>
|
||||||
|
<div class="text-subtitle-1">تعداد فایلهای آرشیو شده</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-card variant="outlined" class="h-100">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon color="warning" class="me-2">mdi-information</v-icon>
|
||||||
|
نکات مهم
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-list density="compact" class="pa-0">
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="info">mdi-check-circle</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">فضای آرشیو برای نگهداری فایلهای قدیمی</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="warning">mdi-alert</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">پیشنهاد میشود فضای خالی کافی داشته باشید</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item class="px-0">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon size="20" color="success">mdi-download</v-icon>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title class="text-body-2">امکان دانلود فایلهای آرشیو</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
|
||||||
|
interface Business {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BusinessReport {
|
||||||
|
businessInfo: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
legalName: string
|
||||||
|
owner: string
|
||||||
|
ownerMobile: string
|
||||||
|
ownerEmail: string
|
||||||
|
dateRegister: string
|
||||||
|
field: string
|
||||||
|
type: string
|
||||||
|
address: string
|
||||||
|
tel: string
|
||||||
|
mobile: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
statistics: {
|
||||||
|
personsCount: number
|
||||||
|
commodityCount: number
|
||||||
|
hesabdariDocsCount: number
|
||||||
|
storeroomDocsCount: number
|
||||||
|
bankAccountsCount: number
|
||||||
|
yearsCount: number
|
||||||
|
activePluginsCount: number
|
||||||
|
}
|
||||||
|
financial: {
|
||||||
|
smsCharge: number
|
||||||
|
walletEnabled: boolean
|
||||||
|
walletIncome: number
|
||||||
|
walletExpense: number
|
||||||
|
walletBalance: number
|
||||||
|
}
|
||||||
|
storage: {
|
||||||
|
archiveSize: string
|
||||||
|
totalArchiveSize: number
|
||||||
|
archiveFilesCount: number
|
||||||
|
}
|
||||||
|
plugins: {
|
||||||
|
activeCount: number
|
||||||
|
activeList: Array<{
|
||||||
|
name: string
|
||||||
|
expireDate: string
|
||||||
|
isExpired: boolean
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
business: Business | null | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const dialog = ref(false)
|
||||||
|
const activeTab = ref('overview')
|
||||||
|
const businessReport = ref<BusinessReport | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, async (newVal) => {
|
||||||
|
dialog.value = newVal
|
||||||
|
if (newVal && props.business) {
|
||||||
|
await loadReport()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialog, (newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
dialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadReport = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/api/admin/business/report/${props.business?.id}`)
|
||||||
|
if (response.data.success) {
|
||||||
|
businessReport.value = response.data.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire('خطا', 'خطا در بارگذاری گزارش', 'error')
|
||||||
|
closeDialog()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.business-report-dialog {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-tab {
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-window-item {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
export const formatNumber = (number) => {
|
export const formatNumber = (number) => {
|
||||||
if (!number) return '0'
|
if (!number) return '0'
|
||||||
return new Intl.NumberFormat('fa-IR').format(number)
|
return new Intl.NumberFormat('fa-IR').format(Math.floor(number))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -582,7 +582,7 @@ export default {
|
||||||
|
|
||||||
// فرمت کردن عدد به ریال
|
// فرمت کردن عدد به ریال
|
||||||
formatCurrency(number) {
|
formatCurrency(number) {
|
||||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
return Math.floor(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
},
|
},
|
||||||
|
|
||||||
// تعیین رنگ کارت اعتبار بر اساس میزان اعتبار
|
// تعیین رنگ کارت اعتبار بر اساس میزان اعتبار
|
||||||
|
|
|
@ -2,67 +2,147 @@
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { defineComponent, ref, watch } from 'vue'
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import type { Header, Item, ServerOptions } from "vue3-easy-data-table";
|
import BusinessChargeDialog from '@/components/admin/BusinessChargeDialog.vue'
|
||||||
|
import BusinessPluginDialog from '@/components/admin/BusinessPluginDialog.vue'
|
||||||
|
import BusinessReportDialog from '@/components/admin/BusinessReportDialog.vue'
|
||||||
|
|
||||||
|
const headers = [
|
||||||
const headers: Header[] = [
|
{ title: "نام", key: "name", sortable: true },
|
||||||
{ text: "نام", value: "name" },
|
{ title: "مالک", key: "owner", sortable: true },
|
||||||
{ text: "مالک", value: "owner" },
|
{ title: "موبایل", key: "ownerMobile", sortable: false },
|
||||||
{ text: "موبایل", value: "ownerMobile" },
|
{ title: "تاریخ ایجاد", key: "dateRegister", sortable: true },
|
||||||
{ text: "تاریخ ایجاد", value: "dateRegister" },
|
{ title: "کالا و خدمات", key: "commodityCount", sortable: true },
|
||||||
{ text: "کالا و خدمات", value: "commodityCount" },
|
{ title: "اشخاص", key: "personsCount", sortable: true },
|
||||||
{ text: "اشخاص", value: "personsCount" },
|
{ title: "اسناد حسابداری", key: "hesabdariDocsCount", sortable: true },
|
||||||
{ text: "اسناد حسابداری", value: "hesabdariDocsCount" },
|
{ title: "اسناد انبار", key: "StoreroomDocsCount", sortable: true },
|
||||||
{ text: "اسناد انبار", value: "StoreroomDocsCount" },
|
{ title: "عملیات", key: "actions", sortable: false, width: '200px' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const items = ref<Item[]>([]);
|
const items = ref<any[]>([]);
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const serverItemsLength = ref(0);
|
const totalItems = ref(0);
|
||||||
const searchValue = ref('');
|
const searchValue = ref('');
|
||||||
const serverOptions = ref<ServerOptions>({
|
const options = ref({
|
||||||
page: 1,
|
page: 1,
|
||||||
rowsPerPage: 25,
|
itemsPerPage: 25,
|
||||||
sortBy: 'id',
|
sortBy: ['id'],
|
||||||
sortType: 'desc',
|
sortDesc: [true],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// متغیرهای مربوط به دیالوگها
|
||||||
|
const showChargeDialog = ref(false);
|
||||||
|
const showPluginDialog = ref(false);
|
||||||
|
const showReportDialog = ref(false);
|
||||||
|
const selectedBusiness = ref<any>(null);
|
||||||
|
|
||||||
const loadFromServer = async () => {
|
const loadFromServer = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
axios.post('/api/admin/business/count')
|
try {
|
||||||
.then((response) => {
|
// دریافت تعداد کل
|
||||||
serverItemsLength.value = response.data;
|
const countResponse = await axios.post('/api/admin/business/count');
|
||||||
});
|
totalItems.value = countResponse.data;
|
||||||
//load items
|
|
||||||
await axios.post('/api/admin/business/search', {
|
// تنظیم مقادیر پیشفرض برای sort
|
||||||
options: serverOptions.value,
|
const sortBy = options.value.sortBy?.[0] || 'id';
|
||||||
search: searchValue.value
|
const sortDesc = options.value.sortDesc?.[0] || true;
|
||||||
})
|
|
||||||
.then((response) => {
|
// دریافت آیتمها
|
||||||
items.value = response.data.data;
|
const response = await axios.post('/api/admin/business/search', {
|
||||||
loading.value = false;
|
options: {
|
||||||
|
page: options.value.page,
|
||||||
|
rowsPerPage: options.value.itemsPerPage,
|
||||||
|
sortBy: sortBy,
|
||||||
|
sortType: sortDesc ? 'desc' : 'asc'
|
||||||
|
},
|
||||||
|
search: searchValue.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
items.value = response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('خطا در بارگذاری دادهها:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع باز کردن دیالوگ افزایش اعتبار
|
||||||
|
const openChargeDialog = (business: any) => {
|
||||||
|
console.log('openChargeDialog called with:', business);
|
||||||
|
if (!business) {
|
||||||
|
console.log('business is null/undefined, returning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedBusiness.value = business;
|
||||||
|
showChargeDialog.value = true;
|
||||||
|
console.log('Charge dialog should be open, showChargeDialog:', showChargeDialog.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع باز کردن دیالوگ فعالسازی افزونه
|
||||||
|
const openPluginDialog = (business: any) => {
|
||||||
|
console.log('openPluginDialog called with:', business);
|
||||||
|
if (!business) {
|
||||||
|
console.log('business is null/undefined, returning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedBusiness.value = business;
|
||||||
|
showPluginDialog.value = true;
|
||||||
|
console.log('Plugin dialog should be open, showPluginDialog:', showPluginDialog.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع نمایش گزارش کسب و کار
|
||||||
|
const showBusinessReport = (business: any) => {
|
||||||
|
console.log('showBusinessReport called with:', business);
|
||||||
|
if (!business) {
|
||||||
|
console.log('business is null/undefined, returning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedBusiness.value = business;
|
||||||
|
showReportDialog.value = true;
|
||||||
|
console.log('Report dialog should be open, showReportDialog:', showReportDialog.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// تابع موفقیت عملیات
|
||||||
|
const onSuccess = () => {
|
||||||
|
loadFromServer(); // بارگذاری مجدد لیست
|
||||||
};
|
};
|
||||||
|
|
||||||
// initial load
|
// initial load
|
||||||
loadFromServer();
|
loadFromServer();
|
||||||
|
|
||||||
watch(serverOptions, (value) => { loadFromServer(); }, { deep: true });
|
// watch برای تغییرات options
|
||||||
watch(searchValue, (value) => { loadFromServer(); }, { deep: true });
|
watch(options, () => {
|
||||||
|
loadFromServer();
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// watch برای تغییرات search
|
||||||
|
watch(searchValue, () => {
|
||||||
|
options.value.page = 1; // بازگشت به صفحه اول
|
||||||
|
loadFromServer();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-toolbar color="toolbar" :title="$t('user.businesses') + ' : (' + serverItemsLength + ')'">
|
<v-toolbar color="toolbar" :title="$t('user.businesses') + ' : (' + totalItems + ')'">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-container class="pa-0 ma-0">
|
<v-container class="pa-0 ma-0">
|
||||||
<v-card :loading="loading ? 'red' : null" :disabled="loading">
|
<v-card :loading="loading" :disabled="loading">
|
||||||
<v-card-text class="pa-0">
|
<v-card-text class="pa-0">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-text-field color="info" hide-details="auto" rounded="0" variant="outlined"
|
<v-text-field
|
||||||
density="compact" :placeholder="$t('dialog.search_txt')" v-model="searchValue" type="text" clearable>
|
color="info"
|
||||||
|
hide-details="auto"
|
||||||
|
rounded="0"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
:placeholder="$t('dialog.search_txt')"
|
||||||
|
v-model="searchValue"
|
||||||
|
type="text"
|
||||||
|
clearable
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
<template v-slot:prepend-inner>
|
<template v-slot:prepend-inner>
|
||||||
<v-tooltip location="bottom" :text="$t('dialog.search')">
|
<v-tooltip location="bottom" :text="$t('dialog.search')">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
|
@ -71,25 +151,92 @@ watch(searchValue, (value) => { loadFromServer(); }, { deep: true });
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<EasyDataTable table-class-name="customize-table" show-index alternating :headers="headers" :items="items"
|
|
||||||
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
|
<v-data-table-server
|
||||||
rowsOfPageSeparatorMessage="از" theme-color="#1d90ff" header-text-direction="center"
|
v-model:options="options"
|
||||||
body-text-direction="center" :loading="loading" v-model:server-options="serverOptions"
|
:headers="headers"
|
||||||
:server-items-length="serverItemsLength">
|
:items="items"
|
||||||
<template #pagination="{ prevPage, nextPage, isFirstPage, isLastPage }">
|
:items-length="totalItems"
|
||||||
<v-btn size="small" color="success" class="me-1" :disabled="isFirstPage" @click="prevPage" prepend-icon="mdi-skip-next">
|
:loading="loading"
|
||||||
{{ $t('dialog.prev_page') }}
|
class="elevation-1"
|
||||||
</v-btn>
|
>
|
||||||
<v-btn size="small" color="success" class="me-1" :disabled="isLastPage" @click="nextPage" append-icon="mdi-skip-previous">
|
<template #item.actions="{ item }">
|
||||||
{{ $t('dialog.next_page') }}
|
<v-menu location="bottom end" :close-on-content-click="true" offset="5">
|
||||||
</v-btn>
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-tooltip location="top" text="عملیات">
|
||||||
|
<template v-slot:activator="{ props: tooltipProps }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="{ ...props, ...tooltipProps }"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
icon="mdi-dots-horizontal"
|
||||||
|
class="ma-1"
|
||||||
|
>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<v-card min-width="240" class="pa-2" elevation="8">
|
||||||
|
<v-list density="compact" class="py-0">
|
||||||
|
<v-list-item
|
||||||
|
@click="openChargeDialog(item)"
|
||||||
|
prepend-icon="mdi-credit-card"
|
||||||
|
class="text-primary rounded mb-1"
|
||||||
|
hover
|
||||||
|
active-class="bg-primary-lighten-5"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-body-2 font-weight-medium">افزایش اعتبار</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-caption">افزایش اعتبار پیامک</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
@click="openPluginDialog(item)"
|
||||||
|
prepend-icon="mdi-puzzle"
|
||||||
|
class="text-success rounded mb-1"
|
||||||
|
hover
|
||||||
|
active-class="bg-success-lighten-5"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-body-2 font-weight-medium">فعالسازی افزونه</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-caption">فعالسازی یا تمدید افزونه</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
@click="showBusinessReport(item)"
|
||||||
|
prepend-icon="mdi-chart-line"
|
||||||
|
class="text-info rounded"
|
||||||
|
hover
|
||||||
|
active-class="bg-info-lighten-5"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-body-2 font-weight-medium">گزارش کامل</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="text-caption">مشاهده گزارش جامع</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
</template>
|
</template>
|
||||||
</EasyDataTable>
|
</v-data-table-server>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
|
<!-- کامپوننتهای دیالوگ -->
|
||||||
|
<BusinessChargeDialog
|
||||||
|
v-model="showChargeDialog"
|
||||||
|
:business="selectedBusiness"
|
||||||
|
@success="onSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BusinessPluginDialog
|
||||||
|
v-model="showPluginDialog"
|
||||||
|
:business="selectedBusiness"
|
||||||
|
@success="onSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BusinessReportDialog
|
||||||
|
v-model="showReportDialog"
|
||||||
|
:business="selectedBusiness"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
Loading…
Reference in a new issue