more progress in openbalance and some new options
This commit is contained in:
parent
6b656ae0ac
commit
e1e5c19112
|
@ -630,35 +630,6 @@ class AdminController extends AbstractController
|
|||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route('/api/admin/database/backup/create', name: 'app_admin_database_backup_create')]
|
||||
public function app_admin_database_backup_create(KernelInterface $kernel): JsonResponse
|
||||
{
|
||||
$application = new Application($kernel);
|
||||
$application->setAutoExit(false);
|
||||
|
||||
$input = new ArrayInput([
|
||||
'command' => 'doctrine:schema:create',
|
||||
// (optional) define the value of command arguments
|
||||
'--dump-sql' => true,
|
||||
]);
|
||||
|
||||
// You can use NullOutput() if you don't need the output
|
||||
$output = new BufferedOutput();
|
||||
$application->run($input, $output);
|
||||
// return the output, don't use if you used NullOutput()
|
||||
$content = $output->fetch();
|
||||
$time = time();
|
||||
$file = fopen(dirname(__DIR__, 3) . '/hesabixBackup/versions/Hesabix-' . $time . '.sql', 'w');
|
||||
fwrite($file, $content);
|
||||
fclose($file);
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'filename' => 'Hesabix-' . $time . '.sql',
|
||||
]);
|
||||
}
|
||||
#[Route('/api/admin/logs/last', name: 'api_admin_logs_last')]
|
||||
public function api_admin_logs_last(Extractor $extractor, Jdate $jdate, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
|
|
|
@ -145,10 +145,16 @@ class BuyController extends AbstractController
|
|||
$hesabdariRow->setBd(0);
|
||||
$hesabdariRow->setBs($params['discountAll']);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
|
||||
'code' => '51' // تخفیفات نقدی خرید
|
||||
'code' => '51'
|
||||
]);
|
||||
$hesabdariRow->setRef($ref);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
// ذخیره نوع تخفیف و درصد آن
|
||||
$doc->setDiscountType($params['discountType'] ?? 'fixed');
|
||||
if (isset($params['discountPercent'])) {
|
||||
$doc->setDiscountPercent((float)$params['discountPercent']);
|
||||
}
|
||||
}
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDate($params['date']);
|
||||
|
|
|
@ -139,8 +139,11 @@ class CommodityController extends AbstractController
|
|||
]);
|
||||
$count = 0;
|
||||
foreach ($rows as $row) {
|
||||
$count += $row->getDoc()->getType() === 'buy' ? $row->getCommdityCount() : -$row->getCommdityCount();
|
||||
}
|
||||
if ($row->getDoc()->getType() === 'buy' || $row->getDoc()->getType() === 'open_balance') {
|
||||
$count += $row->getCommdityCount();
|
||||
} else {
|
||||
$count -= $row->getCommdityCount();
|
||||
} }
|
||||
$temp['count'] = $count;
|
||||
}
|
||||
return $temp;
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Entity\HesabdariDoc;
|
|||
use App\Entity\HesabdariTable;
|
||||
use App\Entity\Person;
|
||||
use App\Entity\Salary;
|
||||
use App\Entity\Commodity;
|
||||
use App\Entity\Shareholder;
|
||||
use App\Service\Access;
|
||||
use App\Service\Explore;
|
||||
|
@ -113,6 +114,18 @@ class OpenbalanceController extends AbstractController
|
|||
}
|
||||
$res['shareholders'] = $shareholderDet;
|
||||
|
||||
//load commodities
|
||||
foreach ($doc->getHesabdariRows() as $row) {
|
||||
if ($row->getCommodity()) {
|
||||
$temp = [];
|
||||
$temp['info'] = Explore::ExploreCommodity($row->getCommodity());
|
||||
$temp['count'] = $row->getCommdityCount();
|
||||
$temp['price'] = $row->getBs()/$row->getCommdityCount();
|
||||
$temp['totalPrice'] = $row->getBs();
|
||||
$res['commodities'][] = $temp;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json($extractor->operationSuccess($res));
|
||||
}
|
||||
|
||||
|
@ -425,4 +438,99 @@ class OpenbalanceController extends AbstractController
|
|||
$entityManagerInterface->flush();
|
||||
return $this->json($extractor->operationSuccess());
|
||||
}
|
||||
|
||||
|
||||
#[Route('/api/openbalance/save/commodities', name: 'app_openbalance_save_commodity')]
|
||||
public function app_openbalance_save_commodity(Provider $provider,Jdate $jdate, Request $request, Access $access, EntityManagerInterface $entityManagerInterface, Extractor $extractor): Response
|
||||
{
|
||||
$acc = $access->hasRole('accounting');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
|
||||
//get open balance doc
|
||||
$doc = $entityManagerInterface->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'year' => $acc['year'],
|
||||
'bid' => $acc['bid'],
|
||||
'type' => 'open_balance',
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if (!$doc) {
|
||||
$doc = new HesabdariDoc();
|
||||
$doc->setBid($acc['bid']);
|
||||
$doc->setAmount(0);
|
||||
$doc->setDateSubmit(time());
|
||||
$doc->setMoney($acc['money']);
|
||||
$doc->setSubmitter($this->getUser());
|
||||
$doc->setYear($acc['year']);
|
||||
$doc->setDes('سند افتتاحیه');
|
||||
$doc->setDate($jdate->jdate('Y/n/d', time()));
|
||||
$doc->setType('open_balance');
|
||||
$doc->setCode($provider->getAccountingCode($acc['bid'],'accounting'));
|
||||
$entityManagerInterface->persist($doc);
|
||||
}
|
||||
|
||||
// ایجاد آرایه از کدهای کالاهای ارسالی
|
||||
$submittedCommodityCodes = array_map(function($param) {
|
||||
return $param['info']['code'];
|
||||
}, $params);
|
||||
|
||||
// حذف سطرهای مربوط به کالاهایی که در لیست ارسالی نیستند
|
||||
foreach ($doc->getHesabdariRows() as $row) {
|
||||
if ($row->getCommodity() && $row->getRefData() == 'commodity') {
|
||||
$commodityCode = $row->getCommodity()->getCode();
|
||||
if (!in_array($commodityCode, $submittedCommodityCodes)) {
|
||||
$doc->removeHesabdariRow($row);
|
||||
$entityManagerInterface->remove($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($params as $param) {
|
||||
$commodity = $entityManagerInterface->getRepository(Commodity::class)->findOneBy([
|
||||
'code' => $param['info']['code'],
|
||||
'bid' => $acc['bid'],
|
||||
]);
|
||||
if(!$commodity) return $this->json($extractor->operationFail());
|
||||
|
||||
$ExistBefore = false;
|
||||
foreach ($doc->getHesabdariRows() as $row) {
|
||||
if ($row->getCommodity() == $commodity) {
|
||||
if ($param['count'] != 0) {
|
||||
$ExistBefore = true;
|
||||
$row->setCommdityCount($param['count']);
|
||||
$row->setBs($param['price'] * $param['count']);
|
||||
$entityManagerInterface->persist($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((!$ExistBefore) && $param['count'] != 0) {
|
||||
$row = new HesabdariRow();
|
||||
$row->setDoc($doc);
|
||||
$row->setCommodity($commodity);
|
||||
$row->setCommdityCount($param['count']);
|
||||
$row->setBs(0);
|
||||
$row->setBd($param['price'] * $param['count']);
|
||||
$row->setRefData('commodity');
|
||||
$row->setBid($acc['bid']);
|
||||
$row->setYear($acc['year']);
|
||||
$row->setDes('موجودی اول دوره');
|
||||
$row->setRef($entityManagerInterface->getRepository(HesabdariTable::class)->findOneBy(['code' => 120]));
|
||||
$entityManagerInterface->persist($row);
|
||||
}
|
||||
}
|
||||
|
||||
//calculate amount of document
|
||||
foreach ($doc->getHesabdariRows() as $row) {
|
||||
$doc->setAmount($doc->getAmount() + $row->getBd());
|
||||
}
|
||||
$entityManagerInterface->persist($doc);
|
||||
|
||||
$entityManagerInterface->flush();
|
||||
return $this->json($extractor->operationSuccess());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -419,4 +419,137 @@ class ReportController extends AbstractController
|
|||
return $this->json(['error' => 'An error occurred: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/report/top-selling-commodities-by-price', name: 'app_report_top_selling_commodities_by_price', methods: ['POST'])]
|
||||
public function app_report_top_selling_commodities_by_price(Access $access, Explore $explore, Jdate $jdate, Request $request, EntityManagerInterface $entityManager, LoggerInterface $logger): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('report');
|
||||
if (!$acc) {
|
||||
$acc = $access->hasRole('sell');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException('شما دسترسی لازم برای مشاهده این اطلاعات را ندارید.');
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Business $business */
|
||||
$business = $acc['bid'];
|
||||
/** @var Year $year */
|
||||
$year = $acc['year'];
|
||||
|
||||
$payload = $request->getPayload();
|
||||
$period = $payload->get('period', 'year');
|
||||
$limit = (int) $payload->get('limit', 10);
|
||||
if ($limit < 3) {
|
||||
$limit = 3;
|
||||
}
|
||||
|
||||
$today = $jdate->GetTodayDate();
|
||||
list($currentYear, $currentMonth, $currentDay) = explode('/', $today);
|
||||
|
||||
switch ($period) {
|
||||
case 'today':
|
||||
$dateStart = $today;
|
||||
$dateEnd = $today;
|
||||
break;
|
||||
case 'week':
|
||||
$weekDay = (int) $jdate->jdate('w', time());
|
||||
$daysToSubtract = $weekDay;
|
||||
$dateStart = $jdate->shamsiDate(0, 0, -$daysToSubtract);
|
||||
$dateEnd = $jdate->shamsiDate(0, 0, 6 - $weekDay);
|
||||
break;
|
||||
case 'month':
|
||||
$dateStart = "$currentYear/$currentMonth/01";
|
||||
$dateEnd = "$currentYear/$currentMonth/" . $jdate->jdate('t', $jdate->jallaliToUnixTime("$currentYear/$currentMonth/01"));
|
||||
break;
|
||||
case 'year':
|
||||
default:
|
||||
$dateStart = $jdate->jdate('Y/m/d', $year->getStart());
|
||||
$dateEnd = $jdate->jdate('Y/m/d', $year->getEnd());
|
||||
break;
|
||||
}
|
||||
|
||||
$queryBuilder = $entityManager->createQueryBuilder();
|
||||
$queryBuilder
|
||||
->select('c.id AS id')
|
||||
->addSelect('c.code AS code')
|
||||
->addSelect('c.name AS name')
|
||||
->addSelect('c.des AS des')
|
||||
->addSelect('c.priceBuy AS priceBuy')
|
||||
->addSelect('c.priceSell AS priceSell')
|
||||
->addSelect('c.khadamat AS khadamat')
|
||||
->addSelect('c.orderPoint AS orderPoint')
|
||||
->addSelect('c.commodityCountCheck AS commodityCountCheck')
|
||||
->addSelect('c.minOrderCount AS minOrderCount')
|
||||
->addSelect('c.dayLoading AS dayLoading')
|
||||
->addSelect('c.speedAccess AS speedAccess')
|
||||
->addSelect('c.withoutTax AS withoutTax')
|
||||
->addSelect('c.barcodes AS barcodes')
|
||||
->addSelect('IDENTITY(c.unit) AS unitId')
|
||||
->addSelect('u.name AS unit')
|
||||
->addSelect('SUM(CAST(hr.commdityCount AS INTEGER)) AS totalCount')
|
||||
->addSelect('SUM(hr.bs) AS totalPrice') // محاسبه مجموع قیمت فروش با استفاده از فیلد bs
|
||||
->from(HesabdariRow::class, 'hr')
|
||||
->innerJoin('hr.doc', 'hd')
|
||||
->innerJoin('hr.commodity', 'c')
|
||||
->leftJoin('c.unit', 'u')
|
||||
->where('hd.bid = :business')
|
||||
->andWhere('hd.type = :type')
|
||||
->andWhere('hr.year = :year')
|
||||
->andWhere('hd.date BETWEEN :dateStart AND :dateEnd')
|
||||
->setParameter('business', $business)
|
||||
->setParameter('type', 'sell')
|
||||
->setParameter('year', $year)
|
||||
->setParameter('dateStart', $dateStart)
|
||||
->setParameter('dateEnd', $dateEnd)
|
||||
->groupBy('c.id')
|
||||
->addGroupBy('u.name')
|
||||
->orderBy('totalPrice', 'DESC') // مرتبسازی بر اساس مجموع قیمت فروش
|
||||
->setMaxResults($limit);
|
||||
|
||||
try {
|
||||
$results = $queryBuilder->getQuery()->getArrayResult();
|
||||
$logger->info('Query executed successfully', [
|
||||
'sql' => $queryBuilder->getQuery()->getSQL(),
|
||||
'params' => $queryBuilder->getQuery()->getParameters()->toArray(),
|
||||
'results' => $results
|
||||
]);
|
||||
|
||||
if (empty($results)) {
|
||||
$logger->info('No results returned from query');
|
||||
return $this->json(['message' => 'No data found'], 200);
|
||||
}
|
||||
|
||||
$topCommodities = array_map(function ($result) {
|
||||
return [
|
||||
'id' => $result['id'],
|
||||
'code' => $result['code'],
|
||||
'name' => $result['name'],
|
||||
'des' => $result['des'],
|
||||
'priceBuy' => $result['priceBuy'],
|
||||
'priceSell' => $result['priceSell'],
|
||||
'khadamat' => $result['khadamat'],
|
||||
'orderPoint' => $result['orderPoint'],
|
||||
'commodityCountCheck' => $result['commodityCountCheck'],
|
||||
'minOrderCount' => $result['minOrderCount'],
|
||||
'dayLoading' => $result['dayLoading'],
|
||||
'speedAccess' => $result['speedAccess'],
|
||||
'withoutTax' => $result['withoutTax'],
|
||||
'barcodes' => $result['barcodes'],
|
||||
'unit' => $result['unit'] ?? '',
|
||||
'count' => (int) $result['totalCount'],
|
||||
'totalPrice' => (float) $result['totalPrice'] // مجموع قیمت فروش
|
||||
];
|
||||
}, $results);
|
||||
|
||||
return $this->json($topCommodities);
|
||||
} catch (\Exception $e) {
|
||||
$logger->error('Error in top-selling commodities by price query', [
|
||||
'message' => $e->getMessage(),
|
||||
'sql' => $queryBuilder->getQuery()->getSQL(),
|
||||
'params' => $queryBuilder->getQuery()->getParameters()->toArray(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return $this->json(['error' => 'An error occurred: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -146,10 +146,16 @@ class RfsellController extends AbstractController
|
|||
$hesabdariRow->setBd(0);
|
||||
$hesabdariRow->setBs($params['discountAll']);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy([
|
||||
'code' => '104' // سایر هزینه های پخش و خرید
|
||||
'code' => '104'
|
||||
]);
|
||||
$hesabdariRow->setRef($ref);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
// ذخیره نوع تخفیف و درصد آن
|
||||
$doc->setDiscountType($params['discountType'] ?? 'fixed');
|
||||
if (isset($params['discountPercent'])) {
|
||||
$doc->setDiscountPercent((float)$params['discountPercent']);
|
||||
}
|
||||
}
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDate($params['date']);
|
||||
|
|
|
@ -27,6 +27,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use App\Entity\BankAccount;
|
||||
use App\Entity\Cashdesk;
|
||||
use App\Entity\Salary;
|
||||
|
||||
class SellController extends AbstractController
|
||||
{
|
||||
|
@ -72,7 +75,7 @@ class SellController extends AbstractController
|
|||
foreach ($doc->getHesabdariRows() as $item) {
|
||||
if ($item->getCommodity() && $item->getCommdityCount()) {
|
||||
if ($acc['bid']->getProfitCalctype() == 'simple') {
|
||||
$profit = $profit + (($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceSell()) * $item->getCommdityCount());
|
||||
$profit = $profit + (($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceBuy()) * $item->getCommdityCount());
|
||||
} elseif ($acc['bid']->getProfitCalctype() == 'lis') {
|
||||
$last = $entityManager->getRepository(HesabdariRow::class)->findOneBy([
|
||||
'commodity' => $item->getCommodity(),
|
||||
|
@ -82,7 +85,7 @@ class SellController extends AbstractController
|
|||
]);
|
||||
if ($last) {
|
||||
$price = $last->getBd() / $last->getCommdityCount();
|
||||
$profit = $profit + ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount());
|
||||
$profit = $profit + (($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount());
|
||||
} else {
|
||||
$profit = $profit + $item->getBs();
|
||||
}
|
||||
|
@ -101,7 +104,7 @@ class SellController extends AbstractController
|
|||
}
|
||||
if ($count != 0) {
|
||||
$price = $avg / $count;
|
||||
$profit = $profit + ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount());
|
||||
$profit = $profit + (($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount());
|
||||
} else {
|
||||
$profit = $profit + $item->getBs();
|
||||
}
|
||||
|
@ -195,6 +198,12 @@ class SellController extends AbstractController
|
|||
]);
|
||||
$hesabdariRow->setRef($ref);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
// ذخیره نوع تخفیف و درصد آن
|
||||
$doc->setDiscountType($params['discountType'] ?? 'fixed');
|
||||
if (isset($params['discountPercent'])) {
|
||||
$doc->setDiscountPercent((float)$params['discountPercent']);
|
||||
}
|
||||
}
|
||||
$doc->setDes($params['des']);
|
||||
$doc->setDate($params['date']);
|
||||
|
@ -264,10 +273,9 @@ class SellController extends AbstractController
|
|||
$pair = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $pairCode,
|
||||
'type' => 'buy'
|
||||
]);
|
||||
if ($pair) {
|
||||
$doc->addPairDoc($pair);
|
||||
$pair->addRelatedDoc($doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -577,7 +585,6 @@ class SellController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
// متد calculateProfit بدون تغییر
|
||||
private function calculateProfit(int $docId, array $acc, EntityManagerInterface $entityManager): int
|
||||
{
|
||||
$profit = 0;
|
||||
|
@ -591,7 +598,7 @@ class SellController extends AbstractController
|
|||
->findOneBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']);
|
||||
if ($last) {
|
||||
$price = $last->getBd() / $last->getCommdityCount();
|
||||
$profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount());
|
||||
$profit += ($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount();
|
||||
} else {
|
||||
$profit += $item->getBs();
|
||||
}
|
||||
|
@ -600,7 +607,7 @@ class SellController extends AbstractController
|
|||
}
|
||||
} elseif ($acc['bid']->getProfitCalctype() === 'simple') {
|
||||
if ($item->getCommodity() && $item->getCommodity()->getPriceSell() !== null && $item->getCommodity()->getPriceBuy() !== null) {
|
||||
$profit += (($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceBuy()) * $item->getCommdityCount());
|
||||
$profit += ($item->getCommodity()->getPriceSell() - $item->getCommodity()->getPriceBuy()) * $item->getCommdityCount();
|
||||
} else {
|
||||
$profit += $item->getBs();
|
||||
}
|
||||
|
@ -608,11 +615,15 @@ class SellController extends AbstractController
|
|||
if ($commodityId) {
|
||||
$lasts = $entityManager->getRepository(HesabdariRow::class)
|
||||
->findBy(['commodity' => $commodityId, 'bs' => 0], ['id' => 'DESC']);
|
||||
$avg = array_sum(array_map(fn($last) => $last->getBd(), $lasts));
|
||||
$count = array_sum(array_map(fn($last) => $last->getCommdityCount(), $lasts));
|
||||
$avg = 0;
|
||||
$count = 0;
|
||||
foreach ($lasts as $last) {
|
||||
$avg += $last->getBd();
|
||||
$count += $last->getCommdityCount();
|
||||
}
|
||||
if ($count != 0) {
|
||||
$price = $avg / $count;
|
||||
$profit += ((($item->getBs() / $item->getCommdityCount()) - $price) * $item->getCommdityCount());
|
||||
$profit += ($item->getBs() / $item->getCommdityCount() - $price) * $item->getCommdityCount();
|
||||
} else {
|
||||
$profit += $item->getBs();
|
||||
}
|
||||
|
@ -683,19 +694,27 @@ class SellController extends AbstractController
|
|||
throw $this->createAccessDeniedException();
|
||||
|
||||
$params = json_decode($request->getContent(), true);
|
||||
$printOptions = $params['printOptions'] ?? [];
|
||||
$params['printers'] = $params['printers'] ?? false;
|
||||
$params['pdf'] = $params['pdf'] ?? true;
|
||||
$params['posPrint'] = $params['posPrint'] ?? false;
|
||||
|
||||
// اضافه کردن کلیدهای پیشفرض
|
||||
$printOptions = array_merge([
|
||||
'note' => true,
|
||||
'bidInfo' => true,
|
||||
'taxInfo' => true,
|
||||
'discountInfo' => true,
|
||||
'pays' => false,
|
||||
'paper' => 'A4-L',
|
||||
'invoiceIndex' => false,
|
||||
'businessStamp' => false
|
||||
], $printOptions);
|
||||
// دریافت تنظیمات پیشفرض از PrintOptions
|
||||
$printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]);
|
||||
|
||||
// تنظیم مقادیر پیشفرض از تنظیمات ذخیره شده
|
||||
$defaultOptions = [
|
||||
'note' => $printSettings ? $printSettings->isSellNote() : true,
|
||||
'bidInfo' => $printSettings ? $printSettings->isSellBidInfo() : true,
|
||||
'taxInfo' => $printSettings ? $printSettings->isSellTaxInfo() : true,
|
||||
'discountInfo' => $printSettings ? $printSettings->isSellDiscountInfo() : true,
|
||||
'pays' => $printSettings ? $printSettings->isSellPays() : true,
|
||||
'paper' => $printSettings ? $printSettings->getSellPaper() : 'A4-L',
|
||||
'invoiceIndex' => $printSettings ? $printSettings->isSellInvoiceIndex() : true,
|
||||
'businessStamp' => $printSettings ? $printSettings->isSellBusinessStamp() : true
|
||||
];
|
||||
|
||||
// اولویت با پارامترهای ارسالی است
|
||||
$printOptions = array_merge($defaultOptions, $params['printOptions'] ?? []);
|
||||
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
|
@ -717,43 +736,8 @@ class SellController extends AbstractController
|
|||
}
|
||||
}
|
||||
$pdfPid = 0;
|
||||
if ($params['pdf']) {
|
||||
$printOptions = [
|
||||
'bidInfo' => true,
|
||||
'pays' => true,
|
||||
'taxInfo' => true,
|
||||
'discountInfo' => true,
|
||||
'note' => true,
|
||||
'paper' => 'A4-L'
|
||||
];
|
||||
if (array_key_exists('printOptions', $params)) {
|
||||
if (array_key_exists('bidInfo', $params['printOptions'])) {
|
||||
$printOptions['bidInfo'] = $params['printOptions']['bidInfo'];
|
||||
}
|
||||
if (array_key_exists('pays', $params['printOptions'])) {
|
||||
$printOptions['pays'] = $params['printOptions']['pays'];
|
||||
}
|
||||
if (array_key_exists('taxInfo', $params['printOptions'])) {
|
||||
$printOptions['taxInfo'] = $params['printOptions']['taxInfo'];
|
||||
}
|
||||
if (array_key_exists('discountInfo', $params['printOptions'])) {
|
||||
$printOptions['discountInfo'] = $params['printOptions']['discountInfo'];
|
||||
}
|
||||
if (array_key_exists('note', $params['printOptions'])) {
|
||||
$printOptions['note'] = $params['printOptions']['note'];
|
||||
}
|
||||
if (array_key_exists('paper', $params['printOptions'])) {
|
||||
$printOptions['paper'] = $params['printOptions']['paper'];
|
||||
}
|
||||
if (array_key_exists('invoiceIndex', $params['printOptions'])) {
|
||||
$printOptions['invoiceIndex'] = $params['printOptions']['invoiceIndex'];
|
||||
}
|
||||
if (array_key_exists('businessStamp', $params['printOptions'])) {
|
||||
$printOptions['businessStamp'] = $params['printOptions']['businessStamp'];
|
||||
}
|
||||
}
|
||||
if ($params['pdf'] == true || $params['printers'] == true) {
|
||||
$note = '';
|
||||
$printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]);
|
||||
if ($printSettings) {
|
||||
$note = $printSettings->getSellNoteString();
|
||||
}
|
||||
|
@ -775,7 +759,8 @@ class SellController extends AbstractController
|
|||
$printOptions['paper']
|
||||
);
|
||||
}
|
||||
if ($params['printers'] == true) {
|
||||
if ($params['posPrint'] == true) {
|
||||
|
||||
$pid = $provider->createPrint(
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
|
@ -831,4 +816,473 @@ class SellController extends AbstractController
|
|||
'daySells' => $daySells
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/sell/v2/mod', name: 'app_sell_v2_mod', methods: ['POST'])]
|
||||
public function app_sell_v2_mod(
|
||||
AccountingPermissionService $accountingPermissionService,
|
||||
PluginService $pluginService,
|
||||
SMS $SMS,
|
||||
Provider $provider,
|
||||
Extractor $extractor,
|
||||
Request $request,
|
||||
Access $access,
|
||||
Log $log,
|
||||
EntityManagerInterface $entityManager,
|
||||
registryMGR $registryMGR
|
||||
): JsonResponse {
|
||||
$params = [];
|
||||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
|
||||
$acc = $access->hasRole('sell');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$pkgcntr = $accountingPermissionService->canRegisterAccountingDoc($acc['bid']);
|
||||
if ($pkgcntr['code'] == 4) {
|
||||
return $this->json([
|
||||
'result' => 4,
|
||||
'message' => $pkgcntr['message']
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// بررسی وجود فاکتور برای ویرایش
|
||||
if (!empty($params['id'])) {
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $params['id'],
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
if (!$doc) {
|
||||
return $this->json($extractor->notFound());
|
||||
}
|
||||
|
||||
// حذف سطرهای قبلی
|
||||
$rows = $doc->getHesabdariRows();
|
||||
foreach ($rows as $row) {
|
||||
$entityManager->remove($row);
|
||||
}
|
||||
} else {
|
||||
// ایجاد فاکتور جدید
|
||||
$doc = new HesabdariDoc();
|
||||
$doc->setBid($acc['bid']);
|
||||
$doc->setYear($acc['year']);
|
||||
$doc->setDateSubmit(time());
|
||||
$doc->setType('sell');
|
||||
$doc->setSubmitter($this->getUser());
|
||||
$doc->setMoney($acc['money']);
|
||||
$doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||
}
|
||||
|
||||
// تنظیم اطلاعات اصلی فاکتور
|
||||
$doc->setDes($params['invoiceDescription']);
|
||||
$doc->setDate($params['invoiceDate']);
|
||||
$doc->setTaxPercent($params['taxPercent'] ?? 0);
|
||||
|
||||
// افزودن هزینه حمل
|
||||
if ($params['shippingCost'] > 0) {
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setDes('حمل و نقل کالا');
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs($params['shippingCost']);
|
||||
$hesabdariRow->setBd(0);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '61']);
|
||||
$hesabdariRow->setRef($ref);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
}
|
||||
|
||||
// افزودن تخفیف کلی
|
||||
$totalDiscount = 0;
|
||||
if ($params['discountType'] === 'percent') {
|
||||
$totalDiscount = round(($params['totalInvoice'] * $params['discountPercent']) / 100);
|
||||
$doc->setDiscountType('percent');
|
||||
$doc->setDiscountPercent((float)$params['discountPercent']);
|
||||
} else {
|
||||
$totalDiscount = $params['totalDiscount'];
|
||||
$doc->setDiscountType('fixed');
|
||||
$doc->setDiscountPercent(null);
|
||||
}
|
||||
|
||||
if ($totalDiscount > 0) {
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setDes('تخفیف فاکتور');
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs(0);
|
||||
$hesabdariRow->setBd($totalDiscount);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '104']);
|
||||
$hesabdariRow->setRef($ref);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
}
|
||||
|
||||
// افزودن اقلام فاکتور
|
||||
$sumTax = 0;
|
||||
$sumTotal = 0;
|
||||
foreach ($params['items'] as $item) {
|
||||
$sumTax += $item['tax'] ?? 0;
|
||||
$sumTotal += $item['total'] ?? 0;
|
||||
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setDes($item['description'] ?? '');
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs($item['total'] + ($item['tax'] ?? 0));
|
||||
$hesabdariRow->setBd(0);
|
||||
$hesabdariRow->setDiscount($item['discountAmount'] ?? 0);
|
||||
$hesabdariRow->setTax($item['tax'] ?? 0);
|
||||
$hesabdariRow->setDiscountType($item['showPercentDiscount'] ? 'percent' : 'fixed');
|
||||
$hesabdariRow->setDiscountPercent($item['discountPercent'] ?? 0);
|
||||
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '53']);
|
||||
$hesabdariRow->setRef($ref);
|
||||
|
||||
$commodity = $entityManager->getRepository(Commodity::class)->findOneBy([
|
||||
'id' => $item['name']['id'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
if (!$commodity) {
|
||||
throw new \Exception('کالا یافت نشد');
|
||||
}
|
||||
$hesabdariRow->setCommodity($commodity);
|
||||
$hesabdariRow->setCommdityCount($item['count']);
|
||||
|
||||
// بهروزرسانی قیمت فروش کالا اگر تنظیم شده باشد
|
||||
if ($acc['bid']->isCommodityUpdateSellPriceAuto() && $commodity->getPriceSell() != $item['price']) {
|
||||
$commodity->setPriceSell($item['price']);
|
||||
$entityManager->persist($commodity);
|
||||
}
|
||||
|
||||
$entityManager->persist($hesabdariRow);
|
||||
}
|
||||
|
||||
// افزودن ردیف مالیات
|
||||
if ($sumTax > 0) {
|
||||
$taxRow = new HesabdariRow();
|
||||
$taxRow->setDes('مالیات بر ارزش افزوده');
|
||||
$taxRow->setBid($acc['bid']);
|
||||
$taxRow->setYear($acc['year']);
|
||||
$taxRow->setDoc($doc);
|
||||
$taxRow->setBs($sumTax);
|
||||
$taxRow->setBd(0);
|
||||
$taxRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '33']);
|
||||
$taxRow->setRef($taxRef);
|
||||
$entityManager->persist($taxRow);
|
||||
}
|
||||
|
||||
// تنظیم مبلغ کل فاکتور
|
||||
$doc->setAmount($sumTotal + $sumTax - $totalDiscount + $params['shippingCost']);
|
||||
|
||||
// افزودن سطر اصلی فاکتور
|
||||
$hesabdariRow = new HesabdariRow();
|
||||
$hesabdariRow->setDes('فاکتور فروش');
|
||||
$hesabdariRow->setBid($acc['bid']);
|
||||
$hesabdariRow->setYear($acc['year']);
|
||||
$hesabdariRow->setDoc($doc);
|
||||
$hesabdariRow->setBs(0);
|
||||
$hesabdariRow->setBd($sumTotal + $sumTax + $params['shippingCost'] - $totalDiscount);
|
||||
$ref = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']);
|
||||
$hesabdariRow->setRef($ref);
|
||||
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'id' => $params['customer']
|
||||
]);
|
||||
if (!$person) {
|
||||
throw new \Exception('خریدار یافت نشد');
|
||||
}
|
||||
$hesabdariRow->setPerson($person);
|
||||
$entityManager->persist($hesabdariRow);
|
||||
|
||||
// ذخیره فاکتور
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
|
||||
// ایجاد لینک کوتاه اگر وجود نداشته باشد
|
||||
if (!$doc->getShortlink()) {
|
||||
$doc->setShortlink($provider->RandomString(8));
|
||||
$entityManager->persist($doc);
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
// ثبت اسناد پرداخت
|
||||
if (!empty($params['payments'])) {
|
||||
foreach ($params['payments'] as $payment) {
|
||||
// ایجاد سند حسابداری جدید برای پرداخت
|
||||
$paymentDoc = new HesabdariDoc();
|
||||
$paymentDoc->setBid($acc['bid']);
|
||||
$paymentDoc->setYear($acc['year']);
|
||||
$paymentDoc->setDateSubmit(time());
|
||||
$paymentDoc->setType('sell_receive');
|
||||
$paymentDoc->setSubmitter($this->getUser());
|
||||
$paymentDoc->setMoney($acc['money']);
|
||||
$paymentDoc->setCode($provider->getAccountingCode($acc['bid'], 'accounting'));
|
||||
$paymentDoc->setDate($params['invoiceDate']);
|
||||
$paymentDoc->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
|
||||
$paymentDoc->setAmount($payment['amount']);
|
||||
|
||||
// ایجاد ارتباط با فاکتور اصلی
|
||||
$doc->addRelatedDoc($paymentDoc);
|
||||
|
||||
// ایجاد سطرهای حسابداری بر اساس نوع پرداخت
|
||||
if ($payment['type'] === 'bank') {
|
||||
// دریافت از طریق حساب بانکی
|
||||
$bankRow = new HesabdariRow();
|
||||
$bankRow->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
|
||||
$bankRow->setBid($acc['bid']);
|
||||
$bankRow->setYear($acc['year']);
|
||||
$bankRow->setDoc($paymentDoc);
|
||||
$bankRow->setBs(0);
|
||||
$bankRow->setBd($payment['amount']);
|
||||
$bankRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '5']);
|
||||
$bankRow->setRef($bankRef);
|
||||
$bankRow->setBank($entityManager->getRepository(BankAccount::class)->find($payment['bank']));
|
||||
$entityManager->persist($bankRow);
|
||||
} elseif ($payment['type'] === 'cashdesk') {
|
||||
// دریافت از طریق صندوق
|
||||
$cashdeskRow = new HesabdariRow();
|
||||
$cashdeskRow->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
|
||||
$cashdeskRow->setBid($acc['bid']);
|
||||
$cashdeskRow->setYear($acc['year']);
|
||||
$cashdeskRow->setDoc($paymentDoc);
|
||||
$cashdeskRow->setBs(0);
|
||||
$cashdeskRow->setBd($payment['amount']);
|
||||
$cashdeskRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '121']);
|
||||
$cashdeskRow->setRef($cashdeskRef);
|
||||
$cashdeskRow->setCashdesk($entityManager->getRepository(Cashdesk::class)->find($payment['cashdesk']));
|
||||
$entityManager->persist($cashdeskRow);
|
||||
} elseif ($payment['type'] === 'salary') {
|
||||
// دریافت از طریق تنخواه گردان
|
||||
$salaryRow = new HesabdariRow();
|
||||
$salaryRow->setDes($payment['description'] ?? 'دریافت وجه فاکتور فروش شماره ' . $doc->getCode());
|
||||
$salaryRow->setBid($acc['bid']);
|
||||
$salaryRow->setYear($acc['year']);
|
||||
$salaryRow->setDoc($paymentDoc);
|
||||
$salaryRow->setBs(0);
|
||||
$salaryRow->setBd($payment['amount']);
|
||||
$salaryRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '122']);
|
||||
$salaryRow->setRef($salaryRef);
|
||||
$salaryRow->setSalary($entityManager->getRepository(Salary::class)->find($payment['salary']));
|
||||
$entityManager->persist($salaryRow);
|
||||
}
|
||||
|
||||
// ایجاد سطر دریافت از مشتری
|
||||
$receiveRow = new HesabdariRow();
|
||||
$receiveRow->setDes($payment['description'] ?? 'پرداخت وجه فاکتور فروش شماره ' . $doc->getCode());
|
||||
$receiveRow->setBid($acc['bid']);
|
||||
$receiveRow->setYear($acc['year']);
|
||||
$receiveRow->setDoc($paymentDoc);
|
||||
$receiveRow->setBs($payment['amount']);
|
||||
$receiveRow->setBd(0);
|
||||
$receiveRef = $entityManager->getRepository(HesabdariTable::class)->findOneBy(['code' => '3']);
|
||||
$receiveRow->setRef($receiveRef);
|
||||
$receiveRow->setPerson($person);
|
||||
$entityManager->persist($receiveRow);
|
||||
|
||||
$entityManager->persist($paymentDoc);
|
||||
}
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
// ثبت لاگ
|
||||
$log->insert(
|
||||
'حسابداری',
|
||||
'سند حسابداری شماره ' . $doc->getCode() . ' ثبت / ویرایش شد.',
|
||||
$this->getUser(),
|
||||
$request->headers->get('activeBid'),
|
||||
$doc
|
||||
);
|
||||
|
||||
// ارسال پیامک اگر درخواست شده باشد
|
||||
if (!empty($params['sendSmsToCustomer']) && $params['sendSmsToCustomer']) {
|
||||
if ($pluginService->isActive('accpro', $acc['bid']) && $person->getMobile() != '' && $acc['bid']->getTel()) {
|
||||
$SMS->sendByBalance(
|
||||
[$person->getnikename(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink(), $acc['bid']->getName(), $acc['bid']->getTel()],
|
||||
$registryMGR->get('sms', 'plugAccproSharefaktor'),
|
||||
$person->getMobile(),
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
3
|
||||
);
|
||||
} else {
|
||||
$SMS->sendByBalance(
|
||||
[$acc['bid']->getName(), 'sell/' . $acc['bid']->getId() . '/' . $doc->getShortlink()],
|
||||
$registryMGR->get('sms', 'sharefaktor'),
|
||||
$person->getMobile(),
|
||||
$acc['bid'],
|
||||
$this->getUser(),
|
||||
3
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'message' => 'فاکتور با موفقیت ثبت شد',
|
||||
'data' => [
|
||||
'id' => $doc->getCode(),
|
||||
'code' => $doc->getCode(),
|
||||
'shortlink' => $doc->getShortlink()
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/sell/v2/get/{id}', name: 'app_sell_v2_get', methods: ['GET'])]
|
||||
public function app_sell_v2_get(
|
||||
Request $request,
|
||||
Access $access,
|
||||
EntityManagerInterface $entityManager,
|
||||
string $id
|
||||
): JsonResponse {
|
||||
try {
|
||||
$acc = $access->hasRole('sell');
|
||||
if (!$acc) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'year' => $acc['year'],
|
||||
'code' => $id,
|
||||
'money' => $acc['money']
|
||||
]);
|
||||
|
||||
if (!$doc) {
|
||||
throw $this->createNotFoundException('فاکتور یافت نشد');
|
||||
}
|
||||
|
||||
$person = null;
|
||||
$discountAll = 0;
|
||||
$transferCost = 0;
|
||||
$items = [];
|
||||
$totalInvoice = 0;
|
||||
$taxPercent = $doc->getTaxPercent();
|
||||
$discountType = $doc->getDiscountType() ?? 'fixed';
|
||||
$discountPercent = $doc->getDiscountPercent() ?? 0;
|
||||
$payments = [];
|
||||
|
||||
// دریافت اسناد پرداخت مرتبط
|
||||
$relatedDocs = $doc->getRelatedDocs();
|
||||
|
||||
foreach ($relatedDocs as $relatedDoc) {
|
||||
if ($relatedDoc->getType() === 'sell_receive') {
|
||||
$payment = [
|
||||
'type' => null,
|
||||
'amount' => $relatedDoc->getAmount(),
|
||||
'reference' => '',
|
||||
'description' => $relatedDoc->getDes(),
|
||||
'bank' => null,
|
||||
'cashdesk' => null,
|
||||
'salary' => null
|
||||
];
|
||||
|
||||
foreach ($relatedDoc->getHesabdariRows() as $row) {
|
||||
if ($row->getBank()) {
|
||||
$payment['type'] = 'bank';
|
||||
$payment['bank'] = $row->getBank()->getId();
|
||||
} elseif ($row->getCashdesk()) {
|
||||
$payment['type'] = 'cashdesk';
|
||||
$payment['cashdesk'] = $row->getCashdesk()->getId();
|
||||
} elseif ($row->getSalary()) {
|
||||
$payment['type'] = 'salary';
|
||||
$payment['salary'] = $row->getSalary()->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$payments[] = $payment;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($doc->getHesabdariRows() as $row) {
|
||||
if ($row->getPerson()) {
|
||||
$person = $row->getPerson();
|
||||
} elseif ($row->getRef() && $row->getRef()->getCode() == '104') {
|
||||
$discountAll = $row->getBd();
|
||||
} elseif ($row->getRef() && $row->getRef()->getCode() == '61') {
|
||||
$transferCost = $row->getBs();
|
||||
} elseif ($row->getCommodity()) {
|
||||
$basePrice = $row->getBs();
|
||||
$itemDiscount = $row->getDiscount() ?? 0;
|
||||
$itemDiscountType = $row->getDiscountType() ?? 'fixed';
|
||||
$itemDiscountPercent = $row->getDiscountPercent() ?? 0;
|
||||
|
||||
// محاسبه تخفیف سطری
|
||||
if ($itemDiscountType === 'percent') {
|
||||
$itemDiscount = round(($basePrice * $itemDiscountPercent) / 100);
|
||||
}
|
||||
|
||||
$itemTotal = $basePrice - $itemDiscount;
|
||||
$totalInvoice += $itemTotal;
|
||||
|
||||
$items[] = [
|
||||
'name' => [
|
||||
'id' => $row->getCommodity()->getId(),
|
||||
'name' => $row->getCommodity()->getName(),
|
||||
'code' => $row->getCommodity()->getCode()
|
||||
],
|
||||
'count' => $row->getCommdityCount(),
|
||||
'price' => $row->getCommdityCount() > 0 ? $basePrice / $row->getCommdityCount() : 0,
|
||||
'discountPercent' => $itemDiscountPercent,
|
||||
'discountAmount' => $itemDiscount,
|
||||
'total' => $itemTotal,
|
||||
'description' => $row->getDes(),
|
||||
'showPercentDiscount' => $itemDiscountType === 'percent',
|
||||
'tax' => $row->getTax() ?? 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// محاسبه تخفیف کلی از HesabdariDoc
|
||||
$totalDiscount = 0;
|
||||
if ($discountType === 'percent') {
|
||||
$totalDiscount = round(($totalInvoice * $discountPercent) / 100);
|
||||
} else {
|
||||
$totalDiscount = $discountAll;
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'data' => [
|
||||
'id' => $doc->getCode(),
|
||||
'date' => $doc->getDate(),
|
||||
'person' => $person ? [
|
||||
'id' => $person->getId(),
|
||||
'name' => $person->getNikename(),
|
||||
'code' => $person->getCode()
|
||||
] : null,
|
||||
'des' => $doc->getDes(),
|
||||
'totalInvoice' => $totalInvoice,
|
||||
'taxPercent' => $taxPercent,
|
||||
'discountType' => $discountType,
|
||||
'discountPercent' => $discountPercent,
|
||||
'totalDiscount' => $totalDiscount,
|
||||
'shippingCost' => $transferCost,
|
||||
'showTotalPercentDiscount' => $discountType === 'percent',
|
||||
'items' => $items,
|
||||
'finalTotal' => $doc->getAmount(),
|
||||
'payments' => $payments
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
255
hesabixCore/src/Controller/System/DatabaseController.php
Normal file
255
hesabixCore/src/Controller/System/DatabaseController.php
Normal file
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\System;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\registryMGR;
|
||||
|
||||
final class DatabaseController extends AbstractController
|
||||
{
|
||||
private string $backupPath;
|
||||
private registryMGR $registryMGR;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(registryMGR $registryMGR, EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->registryMGR = $registryMGR;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->backupPath = dirname(__DIR__, 2) . '/hesabixBackup/versions';
|
||||
}
|
||||
|
||||
#[Route('/api/admin/database/backup/info', name: 'app_admin_database_backup_info', methods: ['GET'])]
|
||||
public function getBackupInfo(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$lastBackup = $this->getLastBackupInfo('local');
|
||||
$lastFtpBackup = $this->getLastBackupInfo('ftp');
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'lastBackup' => $lastBackup,
|
||||
'lastFtpBackup' => $lastFtpBackup
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => 'خطا در دریافت اطلاعات پشتیبان: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/admin/database/backup/create', name: 'app_admin_database_backup_create', methods: ['POST'])]
|
||||
public function app_admin_database_backup_create(): JsonResponse
|
||||
{
|
||||
try {
|
||||
// ایجاد پوشههای مورد نیاز
|
||||
$this->ensureBackupDirectoriesExist();
|
||||
|
||||
// ایجاد نام فایل با timestamp
|
||||
$filename = 'Hesabix-' . time() . '.sql';
|
||||
$filepath = $this->backupPath . '/' . $filename;
|
||||
|
||||
// دریافت تنظیمات دیتابیس از EntityManager
|
||||
$connection = $this->entityManager->getConnection();
|
||||
$params = $connection->getParams();
|
||||
|
||||
$dbName = $params['dbname'];
|
||||
$dbUser = $params['user'];
|
||||
$dbPass = $params['password'];
|
||||
$dbHost = $params['host'];
|
||||
$dbPort = $params['port'] ?? '3306';
|
||||
|
||||
// دستور mysqldump
|
||||
$command = sprintf(
|
||||
'mysqldump -h %s -P %s -u %s -p%s %s > %s',
|
||||
escapeshellarg($dbHost),
|
||||
escapeshellarg($dbPort),
|
||||
escapeshellarg($dbUser),
|
||||
escapeshellarg($dbPass),
|
||||
escapeshellarg($dbName),
|
||||
escapeshellarg($filepath)
|
||||
);
|
||||
|
||||
// اجرای دستور
|
||||
exec($command, $output, $returnVar);
|
||||
|
||||
if ($returnVar !== 0) {
|
||||
throw new \Exception('خطا در اجرای دستور mysqldump: ' . implode("\n", $output));
|
||||
}
|
||||
|
||||
// ذخیره اطلاعات آخرین پشتیبان
|
||||
$this->updateLastBackupInfo('local', $filename);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'filename' => $filename,
|
||||
'message' => 'پشتیبان با موفقیت ایجاد شد'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => 'خطا در ایجاد پشتیبان: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/api/admin/database/backup/create-and-upload', name: 'app_admin_database_backup_create_and_upload', methods: ['POST'])]
|
||||
public function createAndUploadToFtp(): JsonResponse
|
||||
{
|
||||
try {
|
||||
// ایجاد پشتیبان محلی
|
||||
$backupResponse = $this->app_admin_database_backup_create();
|
||||
$backupData = json_decode($backupResponse->getContent(), true);
|
||||
|
||||
if ($backupData['result'] !== 1) {
|
||||
throw new \Exception($backupData['message']);
|
||||
}
|
||||
|
||||
$filename = $backupData['filename'];
|
||||
$filepath = $this->backupPath . '/' . $filename;
|
||||
|
||||
// بررسی وجود فایل و دسترسیهای آن
|
||||
if (!file_exists($filepath)) {
|
||||
throw new \Exception("فایل پشتیبان در مسیر {$filepath} یافت نشد");
|
||||
}
|
||||
|
||||
if (!is_readable($filepath)) {
|
||||
throw new \Exception("عدم دسترسی به فایل پشتیبان در مسیر {$filepath}");
|
||||
}
|
||||
|
||||
// دریافت تنظیمات FTP
|
||||
$ftpEnabled = filter_var($this->registryMGR->get('system_settings', 'ftp_enabled'), FILTER_VALIDATE_BOOLEAN);
|
||||
if (!$ftpEnabled) {
|
||||
throw new \Exception('اتصال FTP غیرفعال است');
|
||||
}
|
||||
|
||||
$ftpHost = $this->registryMGR->get('system_settings', 'ftp_host');
|
||||
$ftpPort = $this->registryMGR->get('system_settings', 'ftp_port');
|
||||
$ftpUsername = $this->registryMGR->get('system_settings', 'ftp_username');
|
||||
$ftpPassword = $this->registryMGR->get('system_settings', 'ftp_password');
|
||||
$ftpPath = $this->registryMGR->get('system_settings', 'ftp_path');
|
||||
|
||||
// اتصال به FTP
|
||||
$ftp = ftp_connect($ftpHost, (int)$ftpPort, 30);
|
||||
if (!$ftp) {
|
||||
throw new \Exception('خطا در اتصال به سرور FTP');
|
||||
}
|
||||
|
||||
// ورود به FTP
|
||||
if (!ftp_login($ftp, $ftpUsername, $ftpPassword)) {
|
||||
ftp_close($ftp);
|
||||
throw new \Exception('خطا در ورود به سرور FTP');
|
||||
}
|
||||
|
||||
// فعال کردن حالت غیرفعال
|
||||
ftp_pasv($ftp, true);
|
||||
|
||||
// دریافت مسیر home کاربر
|
||||
$homeDir = ftp_pwd($ftp);
|
||||
if ($homeDir === false) {
|
||||
ftp_close($ftp);
|
||||
throw new \Exception('خطا در دریافت مسیر home کاربر FTP');
|
||||
}
|
||||
|
||||
// تنظیم مسیر نهایی نسبت به home
|
||||
$remotePath = rtrim($homeDir, '/') . '/' . ltrim($ftpPath, '/') . '/' . $filename;
|
||||
$remoteDir = dirname($remotePath);
|
||||
|
||||
// بررسی دسترسی نوشتن در مسیر
|
||||
$testFile = 'test_' . time() . '.txt';
|
||||
if (!@ftp_put($ftp, $testFile, 'test', FTP_ASCII)) {
|
||||
ftp_close($ftp);
|
||||
throw new \Exception('کاربر FTP دسترسی نوشتن ندارد');
|
||||
}
|
||||
ftp_delete($ftp, $testFile);
|
||||
|
||||
// ایجاد مسیر در صورت عدم وجود
|
||||
$this->createFtpDirectory($ftp, $remoteDir);
|
||||
|
||||
// تغییر به مسیر مورد نظر
|
||||
if (!@ftp_chdir($ftp, $remoteDir)) {
|
||||
ftp_close($ftp);
|
||||
throw new \Exception("خطا در تغییر به مسیر {$remoteDir} در سرور FTP");
|
||||
}
|
||||
|
||||
// آپلود فایل
|
||||
if (!ftp_put($ftp, basename($remotePath), $filepath, FTP_BINARY)) {
|
||||
$error = error_get_last();
|
||||
ftp_close($ftp);
|
||||
throw new \Exception('خطا در آپلود فایل به سرور FTP: ' . ($error['message'] ?? 'خطای نامشخص'));
|
||||
}
|
||||
|
||||
ftp_close($ftp);
|
||||
|
||||
// ذخیره اطلاعات آخرین پشتیبان FTP
|
||||
$this->updateLastBackupInfo('ftp', $filename);
|
||||
|
||||
return $this->json([
|
||||
'result' => 1,
|
||||
'filename' => $filename,
|
||||
'message' => 'پشتیبان با موفقیت ایجاد و به سرور FTP ارسال شد'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'result' => 0,
|
||||
'message' => 'خطا در ایجاد و ارسال پشتیبان: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureBackupDirectoriesExist(): void
|
||||
{
|
||||
$directories = [
|
||||
dirname($this->backupPath),
|
||||
$this->backupPath
|
||||
];
|
||||
|
||||
foreach ($directories as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new \Exception("خطا در ایجاد پوشه {$dir}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getLastBackupInfo(string $type): ?string
|
||||
{
|
||||
$key = $type === 'ftp' ? 'last_ftp_backup' : 'last_backup';
|
||||
return $this->registryMGR->get('system_settings', $key);
|
||||
}
|
||||
|
||||
private function updateLastBackupInfo(string $type, string $filename): void
|
||||
{
|
||||
$key = $type === 'ftp' ? 'last_ftp_backup' : 'last_backup';
|
||||
$this->registryMGR->update('system_settings', $key, $filename);
|
||||
}
|
||||
|
||||
private function createFtpDirectory($ftp, $dir): void
|
||||
{
|
||||
// اگر مسیر ریشه است، نیازی به ایجاد نیست
|
||||
if ($dir === '/' || $dir === '.') {
|
||||
return;
|
||||
}
|
||||
|
||||
// بررسی وجود مسیر
|
||||
if (@ftp_chdir($ftp, $dir)) {
|
||||
ftp_chdir($ftp, '/');
|
||||
return;
|
||||
}
|
||||
|
||||
// ایجاد مسیر والد
|
||||
$parent = dirname($dir);
|
||||
$this->createFtpDirectory($ftp, $parent);
|
||||
|
||||
// ایجاد مسیر فعلی
|
||||
$folder = basename($dir);
|
||||
if (!@ftp_mkdir($ftp, $folder)) {
|
||||
throw new \Exception("خطا در ایجاد پوشه {$folder} در سرور FTP. لطفاً دسترسیهای کاربر FTP را بررسی کنید.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,6 +57,13 @@ final class RegistrySettingsController extends AbstractController
|
|||
'appUrl' => $registryMGR->get('system', 'appUrl'),
|
||||
'appSlogan' => $registryMGR->get('system', 'appSlogan'),
|
||||
'verifyMobileViaSms' => filter_var($registryMGR->get('system', 'verifyMobileViaSms'), FILTER_VALIDATE_BOOLEAN),
|
||||
// تنظیمات FTP
|
||||
'ftpEnabled' => filter_var($registryMGR->get($rootSystem, 'ftp_enabled'), FILTER_VALIDATE_BOOLEAN),
|
||||
'ftpHost' => $registryMGR->get($rootSystem, 'ftp_host') ?: '',
|
||||
'ftpPort' => $registryMGR->get($rootSystem, 'ftp_port') ?: '21',
|
||||
'ftpUsername' => $registryMGR->get($rootSystem, 'ftp_username') ?: '',
|
||||
'ftpPassword' => $registryMGR->get($rootSystem, 'ftp_password') ?: '',
|
||||
'ftpPath' => $registryMGR->get($rootSystem, 'ftp_path') ?: '',
|
||||
];
|
||||
|
||||
return new JsonResponse([
|
||||
|
@ -89,10 +96,86 @@ final class RegistrySettingsController extends AbstractController
|
|||
$registryMGR->update('system', 'appUrl', $data['appUrl'] ?? '');
|
||||
$registryMGR->update('system', 'appSlogan', $data['appSlogan'] ?? '');
|
||||
$registryMGR->update('system', 'verifyMobileViaSms', $data['verifyMobileViaSms'] ? '1' : '0');
|
||||
// ذخیره تنظیمات FTP
|
||||
$registryMGR->update($rootSystem, 'ftp_enabled', $data['ftpEnabled'] ? '1' : '0');
|
||||
$registryMGR->update($rootSystem, 'ftp_host', $data['ftpHost'] ?? '');
|
||||
$registryMGR->update($rootSystem, 'ftp_port', $data['ftpPort'] ?? '21');
|
||||
$registryMGR->update($rootSystem, 'ftp_username', $data['ftpUsername'] ?? '');
|
||||
$registryMGR->update($rootSystem, 'ftp_password', $data['ftpPassword'] ?? '');
|
||||
$registryMGR->update($rootSystem, 'ftp_path', $data['ftpPath'] ?? '');
|
||||
|
||||
return new JsonResponse([
|
||||
'result' => 1,
|
||||
'message' => 'Settings saved successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/admin/registry/settings/test-ftp', name: 'app_registry_settings_test_ftp', methods: ['POST'])]
|
||||
public function testFtpConnection(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
// اعتبارسنجی دادههای ورودی
|
||||
$requiredFields = ['host', 'port', 'username', 'password', 'path'];
|
||||
foreach ($requiredFields as $field) {
|
||||
if (empty($data[$field])) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => "فیلد {$field} الزامی است"
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
// اعتبارسنجی پورت
|
||||
$port = (int) $data['port'];
|
||||
if ($port < 1 || $port > 65535) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'پورت باید عددی بین 1 تا 65535 باشد'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// ایجاد اتصال FTP
|
||||
$ftp = ftp_connect($data['host'], $port, 30);
|
||||
if (!$ftp) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در اتصال به سرور FTP'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// تلاش برای ورود
|
||||
if (!ftp_login($ftp, $data['username'], $data['password'])) {
|
||||
ftp_close($ftp);
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'نام کاربری یا رمز عبور اشتباه است'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// تست دسترسی به مسیر
|
||||
if (!ftp_chdir($ftp, $data['path'])) {
|
||||
ftp_close($ftp);
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'مسیر مورد نظر قابل دسترسی نیست'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// بستن اتصال
|
||||
ftp_close($ftp);
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'message' => 'اتصال به سرور FTP با موفقیت برقرار شد'
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'خطا در تست اتصال: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,6 +53,9 @@ class HesabdariDoc
|
|||
#[ORM\Column(type: Types::DECIMAL, precision: 30, scale: 0, nullable: true)]
|
||||
private ?string $amount = '0';
|
||||
|
||||
#[ORM\Column(type: Types::FLOAT, nullable: true)]
|
||||
private ?float $taxPercent = 0;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Ignore]
|
||||
|
@ -119,6 +122,12 @@ class HesabdariDoc
|
|||
#[ORM\JoinTable(name: 'pairDoc')]
|
||||
private Collection $pairDoc;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $discountType = null;
|
||||
|
||||
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2, nullable: true)]
|
||||
private ?float $discountPercent = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->hesabdariRows = new ArrayCollection();
|
||||
|
@ -273,6 +282,17 @@ class HesabdariDoc
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getTaxPercent(): ?float
|
||||
{
|
||||
return $this->taxPercent;
|
||||
}
|
||||
|
||||
public function setTaxPercent(?float $taxPercent): self
|
||||
{
|
||||
$this->taxPercent = $taxPercent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMoney(): ?Money
|
||||
{
|
||||
return $this->money;
|
||||
|
@ -572,4 +592,26 @@ class HesabdariDoc
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDiscountType(): ?string
|
||||
{
|
||||
return $this->discountType;
|
||||
}
|
||||
|
||||
public function setDiscountType(?string $discountType): static
|
||||
{
|
||||
$this->discountType = $discountType;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDiscountPercent(): ?float
|
||||
{
|
||||
return $this->discountPercent;
|
||||
}
|
||||
|
||||
public function setDiscountPercent(?float $discountPercent): static
|
||||
{
|
||||
$this->discountPercent = $discountPercent;
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -91,6 +91,12 @@ class HesabdariRow
|
|||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $tax = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true)]
|
||||
private ?string $discountType = null;
|
||||
|
||||
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 2, nullable: true)]
|
||||
private ?float $discountPercent = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
@ -338,4 +344,28 @@ class HesabdariRow
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDiscountType(): ?string
|
||||
{
|
||||
return $this->discountType;
|
||||
}
|
||||
|
||||
public function setDiscountType(?string $discountType): self
|
||||
{
|
||||
$this->discountType = $discountType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDiscountPercent(): ?float
|
||||
{
|
||||
return $this->discountPercent;
|
||||
}
|
||||
|
||||
public function setDiscountPercent(?float $discountPercent): self
|
||||
{
|
||||
$this->discountPercent = $discountPercent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -64,6 +64,8 @@ class Explore
|
|||
$person = self::ExplorePerson($item->getPerson());
|
||||
} elseif ($item->getRef()->getCode() == '104') {
|
||||
$result['discountAll'] = $item->getBd();
|
||||
$result['discountType'] = $hesabdariDoc->getDiscountType();
|
||||
$result['discountPercent'] = $hesabdariDoc->getDiscountPercent();
|
||||
} elseif ($item->getRef()->getCode() == '61') {
|
||||
$result['transferCost'] = $item->getBs();
|
||||
}
|
||||
|
@ -72,6 +74,10 @@ class Explore
|
|||
$result['discountAll'] = 0;
|
||||
if (!array_key_exists('transferCost', $result))
|
||||
$result['transferCost'] = 0;
|
||||
if (!array_key_exists('discountType', $result))
|
||||
$result['discountType'] = 'fixed';
|
||||
if (!array_key_exists('discountPercent', $result))
|
||||
$result['discountPercent'] = null;
|
||||
$result['person'] = $person;
|
||||
$result['pair_docs'] = [];
|
||||
foreach ($hesabdariDoc->getPairDoc() as $pair) {
|
||||
|
@ -105,6 +111,8 @@ class Explore
|
|||
$person = self::ExplorePerson($item->getPerson());
|
||||
} elseif ($item->getRef()->getCode() == '51') {
|
||||
$result['discountAll'] = $item->getBs();
|
||||
$result['discountType'] = $hesabdariDoc->getDiscountType();
|
||||
$result['discountPercent'] = $hesabdariDoc->getDiscountPercent();
|
||||
} elseif ($item->getRef()->getCode() == '90') {
|
||||
$result['transferCost'] = $item->getBd();
|
||||
}
|
||||
|
@ -113,6 +121,10 @@ class Explore
|
|||
$result['discountAll'] = 0;
|
||||
if (!array_key_exists('transferCost', $result))
|
||||
$result['transferCost'] = 0;
|
||||
if (!array_key_exists('discountType', $result))
|
||||
$result['discountType'] = 'fixed';
|
||||
if (!array_key_exists('discountPercent', $result))
|
||||
$result['discountPercent'] = null;
|
||||
$result['person'] = $person;
|
||||
return $result;
|
||||
}
|
||||
|
@ -126,9 +138,20 @@ class Explore
|
|||
$person = self::ExplorePerson($item->getPerson());
|
||||
} elseif ($item->getCommodity()) {
|
||||
$commodities[] = Explore::ExploreCommodity($item->getCommodity(), $item->getCommdityCount(), $item->getDes());
|
||||
} elseif ($item->getRef()->getCode() == '104') {
|
||||
$result['discountAll'] = $item->getBs();
|
||||
$result['discountType'] = $hesabdariDoc->getDiscountType();
|
||||
$result['discountPercent'] = $hesabdariDoc->getDiscountPercent();
|
||||
}
|
||||
}
|
||||
if (!array_key_exists('discountAll', $result))
|
||||
$result['discountAll'] = 0;
|
||||
if (!array_key_exists('discountType', $result))
|
||||
$result['discountType'] = 'fixed';
|
||||
if (!array_key_exists('discountPercent', $result))
|
||||
$result['discountPercent'] = null;
|
||||
$result['person'] = $person;
|
||||
$result['commodities'] = $commodities;
|
||||
return $result;
|
||||
}
|
||||
public static function ExploreHesabdariDoc(HesabdariDoc $doc)
|
||||
|
|
93
install.sh
93
install.sh
|
@ -35,26 +35,26 @@ print_header() {
|
|||
echo -e "${BOLD}${BLUE} Hesabix Installation Script ${NC}"
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}"
|
||||
echo -e "${YELLOW}Hesabix is a powerful open-source accounting software${NC}"
|
||||
echo -e "${YELLOW}developed with ❤ by Babak Alizadeh (alizadeh.babak)${NC}"
|
||||
echo -e "${YELLOW}developed with ❤ by Babak Alizadeh (alizadeh.babak)${NC}"
|
||||
echo -e "${YELLOW}License: GNU GPL v3${NC}"
|
||||
echo -e "${YELLOW}Website: ${UNDERLINE}https://hesabix.ir${NC}"
|
||||
echo -e "${YELLOW}Support us: ${UNDERLINE}https://hesabix.ir/page/sponsors${NC} ❤"
|
||||
echo -e "${YELLOW}Support us: ${UNDERLINE}https://hesabix.ir/page/sponsors${NC} ❤"
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}\n"
|
||||
|
||||
# Show prerequisites
|
||||
echo -e "${BOLD}${YELLOW}Prerequisites:${NC}"
|
||||
echo -e "1. A domain name pointing to this server"
|
||||
echo -e "2. DNS records properly configured:"
|
||||
echo -e " • A record pointing to server IP"
|
||||
echo -e " • www subdomain pointing to server IP"
|
||||
echo -e " • A record pointing to server IP"
|
||||
echo -e " • www subdomain pointing to server IP"
|
||||
echo -e "3. Port 80 and 443 open and accessible"
|
||||
echo -e "4. At least 2GB of free disk space"
|
||||
echo -e "5. At least 1GB of RAM"
|
||||
echo -e "\n${BOLD}${YELLOW}Important Notes:${NC}"
|
||||
echo -e "• SSL certificate installation requires proper DNS configuration"
|
||||
echo -e "• Domain must be accessible from the internet"
|
||||
echo -e "• Installation may take 10-15 minutes"
|
||||
echo -e "• System will be automatically rolled back if installation fails"
|
||||
echo -e "• SSL certificate installation requires proper DNS configuration"
|
||||
echo -e "• Domain must be accessible from the internet"
|
||||
echo -e "• Installation may take 10-15 minutes"
|
||||
echo -e "• System will be automatically rolled back if installation fails"
|
||||
echo -e "\n${BOLD}${YELLOW}Do you want to continue?${NC}"
|
||||
read -p "Press Enter to continue or Ctrl+C to abort..."
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}\n"
|
||||
|
@ -487,7 +487,7 @@ setup_ssl() {
|
|||
echo -e "2. Domain is pointing to this server's IP address"
|
||||
echo -e "3. Port 80 is accessible from the internet"
|
||||
echo -e "\n${YELLOW}You can run SSL setup later using:${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain -d www.$domain${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
@ -504,7 +504,7 @@ setup_ssl() {
|
|||
if ! systemctl is-active --quiet apache2; then
|
||||
log_message "ERROR" "Apache is not running. Please start Apache first."
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Try to setup SSL with multiple attempts
|
||||
while [[ $attempt -le $max_attempts ]] && [[ $success == false ]]; do
|
||||
|
@ -515,8 +515,8 @@ setup_ssl() {
|
|||
log_message "WARNING" "Failed to stop Apache, continuing anyway..."
|
||||
}
|
||||
|
||||
# Run certbot
|
||||
if certbot --apache -d "$domain" -d "www.$domain" --non-interactive --agree-tos --email "admin@$domain" --force-renewal; then
|
||||
# Run certbot only for main domain
|
||||
if certbot --apache -d "$domain" --non-interactive --agree-tos --email "admin@$domain" --force-renewal; then
|
||||
success=true
|
||||
log_message "INFO" "SSL setup completed successfully"
|
||||
else
|
||||
|
@ -550,10 +550,10 @@ setup_ssl() {
|
|||
echo -e "2. Port 80 is blocked by firewall"
|
||||
echo -e "3. Let's Encrypt rate limit exceeded"
|
||||
echo -e "\n${YELLOW}You can try setting up SSL manually using:${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain -d www.$domain${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain${NC}"
|
||||
echo -e "\n${YELLOW}Or check the logs:${NC}"
|
||||
echo -e "${GREEN}sudo certbot certificates${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain -d www.$domain --dry-run${NC}"
|
||||
echo -e "${GREEN}sudo certbot --apache -d $domain --dry-run${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
@ -656,7 +656,8 @@ setup_database() {
|
|||
local db_name="$base_db_name"
|
||||
local db_user="hesabix_user"
|
||||
local db_password
|
||||
db_password=$(openssl rand -base64 12)
|
||||
# Generate password with only alphanumeric characters
|
||||
db_password=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 12 | head -n 1)
|
||||
local domain_path="/var/www/html/$domain"
|
||||
local counter=1
|
||||
|
||||
|
@ -868,30 +869,32 @@ setup_web_ui() {
|
|||
|
||||
cd "$webui_path" || handle_error "Failed to change to webUI directory"
|
||||
|
||||
# Set initial permissions for npm operations
|
||||
chown -R "$SUDO_USER:$SUDO_USER" "$webui_path"
|
||||
chmod -R 777 "$webui_path"
|
||||
|
||||
# Install dependencies
|
||||
log_message "INFO" "Installing web UI dependencies..."
|
||||
timeout "$NPM_TIMEOUT" npm install || handle_error "Failed to install web UI dependencies"
|
||||
|
||||
# Set proper permissions for webUI directory
|
||||
log_message "INFO" "Setting proper permissions for webUI directory..."
|
||||
# Build web UI
|
||||
log_message "INFO" "Building web UI..."
|
||||
timeout "$NPM_TIMEOUT" npm run build-only || handle_error "Failed to build web UI"
|
||||
|
||||
# After build, set final ownership to apache
|
||||
chown -R "$apache_user:$apache_user" "$webui_path"
|
||||
chmod -R 755 "$webui_path"
|
||||
|
||||
# Set execute permissions for node_modules/.bin
|
||||
if [[ -d "$webui_path/node_modules/.bin" ]]; then
|
||||
chmod -R +x "$webui_path/node_modules/.bin"
|
||||
fi
|
||||
# Set final permissions
|
||||
find "$webui_path" -type d -exec chmod 755 {} \;
|
||||
find "$webui_path" -type f -exec chmod 644 {} \;
|
||||
|
||||
# Set proper permissions for node_modules
|
||||
log_message "INFO" "Setting proper permissions for node_modules directory..."
|
||||
# Set special permissions for node_modules
|
||||
if [[ -d "$webui_path/node_modules" ]]; then
|
||||
chmod -R 755 "$webui_path/node_modules"
|
||||
find "$webui_path/node_modules" -type f -exec chmod 644 {} \;
|
||||
find "$webui_path/node_modules" -type d -exec chmod 755 {} \;
|
||||
find "$webui_path/node_modules/.bin" -type f -exec chmod 755 {} \;
|
||||
|
||||
# Build web UI
|
||||
log_message "INFO" "Building web UI..."
|
||||
timeout "$NPM_TIMEOUT" npm run build-only || handle_error "Failed to build web UI"
|
||||
fi
|
||||
|
||||
log_message "INFO" "Web UI setup completed"
|
||||
}
|
||||
|
@ -950,14 +953,14 @@ confirm_installation() {
|
|||
echo -e "${BOLD}${BLUE} Installation Confirmation ${NC}"
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}"
|
||||
echo -e "${YELLOW}This script will install:${NC}"
|
||||
echo -e "• PHP and required extensions"
|
||||
echo -e "• MySQL/MariaDB"
|
||||
echo -e "• Apache"
|
||||
echo -e "• Node.js"
|
||||
echo -e "• Composer"
|
||||
echo -e "• phpMyAdmin"
|
||||
echo -e "• Hesabix Core"
|
||||
echo -e "• Hesabix Web UI"
|
||||
echo -e "• PHP and required extensions"
|
||||
echo -e "• MySQL/MariaDB"
|
||||
echo -e "• Apache"
|
||||
echo -e "• Node.js"
|
||||
echo -e "• Composer"
|
||||
echo -e "• phpMyAdmin"
|
||||
echo -e "• Hesabix Core"
|
||||
echo -e "• Hesabix Web UI"
|
||||
echo -e "\n${YELLOW}The installation will require approximately 2GB of disk space.${NC}"
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}\n"
|
||||
|
||||
|
@ -1012,7 +1015,7 @@ show_installation_summary() {
|
|||
|
||||
# Get database password from env file
|
||||
if [[ -f "$env_file" ]]; then
|
||||
db_password=$(php -r "include '$env_file'; echo \$env['DATABASE_URL']; echo PHP_EOL;" | grep -oP '(?<=://[^:]+:)[^@]+(?=@)')
|
||||
db_password=$(php -r "include '$env_file'; echo \$env['DATABASE_URL']; echo PHP_EOL;" | sed -n 's/.*:\/\/[^:]*:\([^@]*\)@.*/\1/p')
|
||||
fi
|
||||
|
||||
log_message "INFO" "Showing installation summary..."
|
||||
|
@ -1047,7 +1050,7 @@ show_installation_summary() {
|
|||
echo -e "\n${YELLOW}To get database information, you can:${NC}"
|
||||
echo -e "1. Check the file: $env_file"
|
||||
echo -e "2. Run this command to extract password:"
|
||||
echo -e " ${GREEN}php -r \"include '$env_file'; echo \$env['DATABASE_URL']; echo PHP_EOL;\" | grep -oP '(?<=://[^:]+:)[^@]+(?=@)'${NC}"
|
||||
echo -e " ${GREEN}php -r \"include '$env_file'; echo \$env['DATABASE_URL']; echo PHP_EOL;\" | sed -n 's/.*:\/\/[^:]*:\\([^@]*\\)@.*/\\1/p'${NC}"
|
||||
echo -e "3. Or check MySQL directly:"
|
||||
echo -e " ${GREEN}mysql -u root -e \"SELECT User, Host FROM mysql.user WHERE User='$db_user';\"${NC}"
|
||||
fi
|
||||
|
@ -1068,10 +1071,10 @@ show_installation_summary() {
|
|||
echo -e "4. Register the first user (system administrator)"
|
||||
|
||||
echo -e "\n${YELLOW}Support:${NC}"
|
||||
echo -e "• Developer: Babak Alizadeh (alizadeh.babak)"
|
||||
echo -e "• License: GNU GPL v3"
|
||||
echo -e "• Website: ${UNDERLINE}https://hesabix.ir${NC}"
|
||||
echo -e "• Support us: ${UNDERLINE}https://hesabix.ir/page/sponsors${NC} ❤"
|
||||
echo -e "• Developer: Babak Alizadeh (alizadeh.babak)"
|
||||
echo -e "• License: GNU GPL v3"
|
||||
echo -e "• Website: ${UNDERLINE}https://hesabix.ir${NC}"
|
||||
echo -e "• Support us: ${UNDERLINE}https://hesabix.ir/page/sponsors${NC} ❤"
|
||||
|
||||
echo -e "\n${GREEN}Installation completed successfully!${NC}"
|
||||
echo -e "${BOLD}${BLUE}=================================================${NC}"
|
||||
|
@ -1087,9 +1090,9 @@ display_telemetry_consent() {
|
|||
echo -e "${RED} Anonymous Data Collection "
|
||||
echo -e "${RED}================================================="
|
||||
echo -e "${BLUE}To improve Hesabix, we would like to collect anonymous data:"
|
||||
echo -e "${BLUE}• System information (OS, PHP, MySQL versions)"
|
||||
echo -e "${BLUE}• Installation path and domain"
|
||||
echo -e "${BLUE}• Installation date"
|
||||
echo -e "${BLUE}• System information (OS, PHP, MySQL versions)"
|
||||
echo -e "${BLUE}• Installation path and domain"
|
||||
echo -e "${BLUE}• Installation date"
|
||||
|
||||
read -p "Do you agree? (y/n) [n]: " response
|
||||
[[ "$response" =~ ^[Yy]$ ]] && SEND_TELEMETRY=true
|
||||
|
|
0
webUI/.gitignore
vendored
Executable file → Normal file
0
webUI/.gitignore
vendored
Executable file → Normal file
0
webUI/LICENSE
Executable file → Normal file
0
webUI/LICENSE
Executable file → Normal file
0
webUI/env.d.ts
vendored
Executable file → Normal file
0
webUI/env.d.ts
vendored
Executable file → Normal file
0
webUI/index.html
Executable file → Normal file
0
webUI/index.html
Executable file → Normal file
0
webUI/package.json
Executable file → Normal file
0
webUI/package.json
Executable file → Normal file
0
webUI/public/.htaccess
Executable file → Normal file
0
webUI/public/.htaccess
Executable file → Normal file
0
webUI/public/dashmix/dashmix.app.min.js
vendored
Executable file → Normal file
0
webUI/public/dashmix/dashmix.app.min.js
vendored
Executable file → Normal file
0
webUI/public/dashmix/dashmix.min.css
vendored
Executable file → Normal file
0
webUI/public/dashmix/dashmix.min.css
vendored
Executable file → Normal file
0
webUI/public/favicon.ico
Executable file → Normal file
0
webUI/public/favicon.ico
Executable file → Normal file
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
0
webUI/public/fonts/fontawesome/fa-brands-400.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-brands-400.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-brands-400.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-brands-400.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-regular-400.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-regular-400.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-regular-400.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-regular-400.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-solid-900.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-solid-900.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-solid-900.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-solid-900.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-v4compatibility.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-v4compatibility.ttf
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-v4compatibility.woff2
Executable file → Normal file
0
webUI/public/fonts/fontawesome/fa-v4compatibility.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-300.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-300.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-500.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-500.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-600.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-600.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-700.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-700.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-800.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-800.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-900.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-900.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-regular.woff2
Executable file → Normal file
0
webUI/public/fonts/inter/inter-v11-latin-regular.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Black-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Bold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-Light-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/Sahel-SemiBold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/sahel/sahel.css
Executable file → Normal file
0
webUI/public/fonts/sahel/sahel.css
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Bold-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Light-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Medium-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.eot
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.ttf
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.woff
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/Shabnam-Thin-FD.woff2
Executable file → Normal file
0
webUI/public/fonts/shabnam/shabnam.css
Executable file → Normal file
0
webUI/public/fonts/shabnam/shabnam.css
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.eot
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.eot
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.svg
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.svg
Executable file → Normal file
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 235 KiB |
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.ttf
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.ttf
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.woff
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.woff
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.woff2
Executable file → Normal file
0
webUI/public/fonts/simple-line-icons/Simple-Line-Icons.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Black.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Black.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Bold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Bold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-ExtraBold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-ExtraBold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-ExtraLight.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-ExtraLight.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Light.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Light.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Medium.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Medium.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Regular.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Regular.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-SemiBold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-SemiBold.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Thin.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/ttf/Vazirmatn-Thin.ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/variable/Vazirmatn[wght].ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/variable/Vazirmatn[wght].ttf
Executable file → Normal file
0
webUI/public/fonts/vazir/vazir.css
Executable file → Normal file
0
webUI/public/fonts/vazir/vazir.css
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-Black.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-Black.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-Bold.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-Bold.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-ExtraBold.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-ExtraBold.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-ExtraLight.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-ExtraLight.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-FD-Black.woff2
Executable file → Normal file
0
webUI/public/fonts/vazir/webfonts/Vazirmatn-FD-Black.woff2
Executable file → Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue