improve business info from admin panel

This commit is contained in:
Hesabix 2025-08-17 21:21:38 +00:00
parent ca043a913f
commit a97a29d50e
8 changed files with 1692 additions and 56 deletions

View file

@ -19,6 +19,7 @@ use App\Entity\WalletTransaction;
use App\Service\Extractor;
use App\Service\Jdate;
use App\Service\JsonResp;
use App\Service\Log;
use App\Service\Notification;
use App\Service\Provider;
use App\Service\registryMGR;
@ -603,12 +604,16 @@ class AdminController extends AbstractController
}
$temp['totalPays'] = $totalPays;
$walletIncomes = $entityManager->getRepository(WalletTransaction::class)->findAllIncome($bid);
$totalIcome = 0;
foreach ($walletIncomes as $walletIncome) {
$totalIcome += $walletIncome->getAmount();
// محاسبه درآمد از تراکنش‌های sell
$walletSells = $entityManager->getRepository(WalletTransaction::class)->findBy(['bid' => $bid, 'type' => 'sell']);
$totalIncome = 0;
foreach ($walletSells as $walletSell) {
$totalIncome += (float) $walletSell->getAmount();
}
$temp['totalIncome'] = $totalIcome;
$temp['totalIncome'] = $totalIncome;
// محاسبه موجودی (درآمد - هزینه)
$temp['walletBalance'] = $totalIncome - $totalPays;
$temp['id'] = $bid->getId();
$temp['bidName'] = $bid->getName();
@ -724,4 +729,457 @@ class AdminController extends AbstractController
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
]);
}
}

View file

@ -29,7 +29,7 @@ class WalletTransactionRepository extends ServiceEntityRepository
{
return $this->createQueryBuilder('w')
->andWhere('w.bid = :val')
->andWhere("w.type != 'pay'")
->andWhere("w.type = 'sell'")
->setParameter('val', $business)
->orderBy('w.id', 'DESC')
->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
// {
// return $this->createQueryBuilder('w')

View 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>

View 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>

View 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>

View file

@ -5,7 +5,7 @@
*/
export const formatNumber = (number) => {
if (!number) return '0'
return new Intl.NumberFormat('fa-IR').format(number)
return new Intl.NumberFormat('fa-IR').format(Math.floor(number))
}
/**

View file

@ -582,7 +582,7 @@ export default {
// فرمت کردن عدد به ریال
formatCurrency(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return Math.floor(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
// تعیین رنگ کارت اعتبار بر اساس میزان اعتبار

View file

@ -2,67 +2,147 @@
import { defineComponent, ref, watch } from 'vue'
import axios from "axios";
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: Header[] = [
{ text: "نام", value: "name" },
{ text: "مالک", value: "owner" },
{ text: "موبایل", value: "ownerMobile" },
{ text: "تاریخ ایجاد", value: "dateRegister" },
{ text: "کالا و خدمات", value: "commodityCount" },
{ text: "اشخاص", value: "personsCount" },
{ text: "اسناد حسابداری", value: "hesabdariDocsCount" },
{ text: "اسناد انبار", value: "StoreroomDocsCount" },
const headers = [
{ title: "نام", key: "name", sortable: true },
{ title: "مالک", key: "owner", sortable: true },
{ title: "موبایل", key: "ownerMobile", sortable: false },
{ title: "تاریخ ایجاد", key: "dateRegister", sortable: true },
{ title: "کالا و خدمات", key: "commodityCount", sortable: true },
{ title: "اشخاص", key: "personsCount", sortable: true },
{ title: "اسناد حسابداری", key: "hesabdariDocsCount", sortable: true },
{ title: "اسناد انبار", key: "StoreroomDocsCount", sortable: true },
{ title: "عملیات", key: "actions", sortable: false, width: '200px' },
];
const items = ref<Item[]>([]);
const items = ref<any[]>([]);
const loading = ref(false);
const serverItemsLength = ref(0);
const totalItems = ref(0);
const searchValue = ref('');
const serverOptions = ref<ServerOptions>({
const options = ref({
page: 1,
rowsPerPage: 25,
sortBy: 'id',
sortType: 'desc',
itemsPerPage: 25,
sortBy: ['id'],
sortDesc: [true],
});
// متغیرهای مربوط به دیالوگها
const showChargeDialog = ref(false);
const showPluginDialog = ref(false);
const showReportDialog = ref(false);
const selectedBusiness = ref<any>(null);
const loadFromServer = async () => {
loading.value = true;
axios.post('/api/admin/business/count')
.then((response) => {
serverItemsLength.value = response.data;
});
//load items
await axios.post('/api/admin/business/search', {
options: serverOptions.value,
search: searchValue.value
})
.then((response) => {
items.value = response.data.data;
loading.value = false;
try {
// دریافت تعداد کل
const countResponse = await axios.post('/api/admin/business/count');
totalItems.value = countResponse.data;
// تنظیم مقادیر پیشفرض برای sort
const sortBy = options.value.sortBy?.[0] || 'id';
const sortDesc = options.value.sortDesc?.[0] || true;
// دریافت آیتمها
const response = await axios.post('/api/admin/business/search', {
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
loadFromServer();
watch(serverOptions, (value) => { loadFromServer(); }, { deep: true });
watch(searchValue, (value) => { loadFromServer(); }, { deep: true });
// watch برای تغییرات options
watch(options, () => {
loadFromServer();
}, { deep: true });
// watch برای تغییرات search
watch(searchValue, () => {
options.value.page = 1; // بازگشت به صفحه اول
loadFromServer();
});
</script>
<template>
<v-toolbar color="toolbar" :title="$t('user.businesses') + ' : (' + serverItemsLength + ')'">
<v-toolbar color="toolbar" :title="$t('user.businesses') + ' : (' + totalItems + ')'">
<v-spacer></v-spacer>
</v-toolbar>
<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-row>
<v-col>
<v-text-field color="info" hide-details="auto" rounded="0" variant="outlined"
density="compact" :placeholder="$t('dialog.search_txt')" v-model="searchValue" type="text" clearable>
<v-text-field
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>
<v-tooltip location="bottom" :text="$t('dialog.search')">
<template v-slot:activator="{ props }">
@ -71,25 +151,92 @@ watch(searchValue, (value) => { loadFromServer(); }, { deep: true });
</v-tooltip>
</template>
</v-text-field>
<EasyDataTable table-class-name="customize-table" show-index alternating :headers="headers" :items="items"
rowsPerPageMessage="تعداد سطر" emptyMessage="اطلاعاتی برای نمایش وجود ندارد"
rowsOfPageSeparatorMessage="از" theme-color="#1d90ff" header-text-direction="center"
body-text-direction="center" :loading="loading" v-model:server-options="serverOptions"
:server-items-length="serverItemsLength">
<template #pagination="{ prevPage, nextPage, isFirstPage, isLastPage }">
<v-btn size="small" color="success" class="me-1" :disabled="isFirstPage" @click="prevPage" prepend-icon="mdi-skip-next">
{{ $t('dialog.prev_page') }}
</v-btn>
<v-btn size="small" color="success" class="me-1" :disabled="isLastPage" @click="nextPage" append-icon="mdi-skip-previous">
{{ $t('dialog.next_page') }}
</v-btn>
<v-data-table-server
v-model:options="options"
:headers="headers"
:items="items"
:items-length="totalItems"
:loading="loading"
class="elevation-1"
>
<template #item.actions="{ item }">
<v-menu location="bottom end" :close-on-content-click="true" offset="5">
<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>
</EasyDataTable>
</v-data-table-server>
</v-col>
</v-row>
</v-card-text>
</v-card>
</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>
<style scoped></style>