bug fix in ghesta,persons,etc

This commit is contained in:
Hesabix 2025-06-01 08:18:12 +00:00
parent b9c3201b9f
commit 1783aacfd4
25 changed files with 1591 additions and 195 deletions

View file

@ -448,6 +448,7 @@ class AdminController extends AbstractController
$resp['footer'] = $item->getFooter();
$resp['activeGateway'] = $registryMGR->get('system', key: 'activeGateway');
$resp['parsianGatewayAPI'] = $registryMGR->get('system', key: 'parsianGatewayAPI');
$resp['paypingKey'] = $registryMGR->get('system', key: 'paypingKey');
return $this->json($resp);
}
@ -470,6 +471,7 @@ class AdminController extends AbstractController
$item->setFooter($params['footer']);
$registryMGR->update('system', 'activeGateway', $params['activeGateway']);
$registryMGR->update('system', 'parsianGatewayAPI', $params['parsianGatewayAPI']);
$registryMGR->update('system', 'paypingKey', $params['paypingKey']);
$entityManager->persist($item);
$entityManager->flush();
return $this->json(['result' => 1]);

View file

@ -124,16 +124,23 @@ class PersonsController extends AbstractController
//check exist before
if (!$person) {
$person = new Person();
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
while ($exist) {
$maxAttempts = 10; // حداکثر تعداد تلاش برای تولید کد جدید
$code = null;
for ($i = 0; $i < $maxAttempts; $i++) {
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
if (!$exist) {
break;
}
}
if ($code === null) {
throw new \Exception('نمی‌توان کد جدیدی برای شخص تولید کرد');
}
$person->setCode($code);
}
@ -270,16 +277,23 @@ class PersonsController extends AbstractController
//check exist before
if (!$person) {
$person = new Person();
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
while ($exist) {
$maxAttempts = 10; // حداکثر تعداد تلاش برای تولید کد جدید
$code = null;
for ($i = 0; $i < $maxAttempts; $i++) {
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
if (!$exist) {
break;
}
}
if ($code === null) {
throw new \Exception('نمی‌توان کد جدیدی برای شخص تولید کرد');
}
$person->setCode($code);
}
@ -520,7 +534,7 @@ class PersonsController extends AbstractController
// جست‌وجو (بهبود داده‌شده)
if (!empty($search) || $search === '0') { // برای اطمینان از کار با "0" یا خالی
$search = trim($search); // حذف فضای خالی اضافی
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search')
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search')
->setParameter('search', "%$search%");
}
@ -1567,16 +1581,23 @@ class PersonsController extends AbstractController
//check exist before
if (!$person) {
$person = new Person();
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
while ($exist) {
$maxAttempts = 10; // حداکثر تعداد تلاش برای تولید کد جدید
$code = null;
for ($i = 0; $i < $maxAttempts; $i++) {
$code = $provider->getAccountingCode($acc['bid'], 'person');
$exist = $entityManager->getRepository(Person::class)->findOneBy([
'code' => $code
]);
if (!$exist) {
break;
}
}
if ($code === null) {
throw new \Exception('نمی‌توان کد جدیدی برای شخص تولید کرد');
}
$person->setCode($code);
$person->setNikename($item[0]);
$person->setBid($acc['bid']);

View file

@ -13,6 +13,10 @@ use App\Entity\HesabdariDoc;
use App\Entity\Person;
use App\Service\Access;
use App\Service\Provider;
use App\Service\Printers;
use App\Entity\PrintOptions;
use App\Service\Log;
use App\Entity\Business;
class PlugGhestaController extends AbstractController
{
@ -416,4 +420,72 @@ class PlugGhestaController extends AbstractController
], 500);
}
}
#[Route('/api/plugins/ghesta/print', name: 'plugin_ghesta_print', methods: ['POST'])]
public function plugin_ghesta_print(Printers $printers, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
{
$acc = $access->hasRole('plugGhestaManager');
if (!$acc)
throw $this->createAccessDeniedException();
$params = json_decode($request->getContent(), true);
$params['pdf'] = $params['pdf'] ?? true;
// دریافت تنظیمات پیش‌فرض از PrintOptions
$printSettings = $entityManager->getRepository(PrintOptions::class)->findOneBy(['bid' => $acc['bid']]);
// تنظیم مقادیر پیش‌فرض از تنظیمات ذخیره شده
$defaultOptions = [
'note' => $printSettings ? $printSettings->isSellNote() : true,
'bidInfo' => $printSettings ? $printSettings->isSellBidInfo() : true,
'paper' => $printSettings ? $printSettings->getSellPaper() : 'A4-L',
'businessStamp' => $printSettings ? $printSettings->isSellBusinessStamp() : true
];
// اولویت با پارامترهای ارسالی است
$printOptions = array_merge($defaultOptions, $params['printOptions'] ?? []);
$doc = $entityManager->getRepository(PlugGhestaDoc::class)->findOneBy([
'id' => $params['id'],
'bid' => $acc['bid']
]);
if (!$doc)
throw $this->createNotFoundException();
$pdfPid = 0;
if ($params['pdf'] == true) {
$note = '';
if ($printSettings) {
$note = $printSettings->getSellNoteString();
}
// دریافت اطلاعات کسب و کار
$business = $entityManager->getRepository(Business::class)->find($acc['bid']);
$pdfPid = $provider->createPrint(
$acc['bid'],
$this->getUser(),
$this->renderView('pdf/plugins/ghesta/report.html.twig', [
'bid' => $business,
'doc' => $doc,
'items' => array_map(function($item) {
return [
'date' => $item->getDate(),
'amount' => $item->getAmount(),
'num' => $item->getNum(),
'hesabdariDoc' => $item->getHesabdariDoc()
];
}, $doc->getPlugGhestaItems()->toArray()),
'person' => $doc->getPerson(),
'printOptions' => $printOptions,
'note' => $note
]),
false,
$printOptions['paper']
);
}
return $this->json(['id' => $pdfPid]);
}
}

View file

@ -24,6 +24,7 @@ use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\PrintOptions;
class ReportController extends AbstractController
{
@ -581,4 +582,116 @@ class ReportController extends AbstractController
return $this->json(['error' => 'An error occurred: ' . $e->getMessage()], 500);
}
}
#[Route('/api/report/person/buysell/export/pdf', name: 'app_report_person_buysell_export_pdf')]
public function app_report_person_buysell_export_pdf(Provider $provider, Access $access, Request $request, EntityManagerInterface $entityManagerInterface): BinaryFileResponse|JsonResponse|StreamedResponse
{
$acc = $access->hasRole('report');
if (!$acc)
throw $this->createAccessDeniedException();
$params = [];
if ($content = $request->getContent()) {
$params = json_decode($content, true);
}
$items = [];
foreach ($params['items'] as $param) {
$prs = $entityManagerInterface->getRepository(HesabdariRow::class)->findOneBy([
'id' => $param['rowId'],
'bid' => $acc['bid']
]);
if ($prs)
$items[] = $prs;
}
// دریافت اطلاعات شخص از اولین آیتم
$person = null;
if (count($items) > 0) {
foreach ($items[0]->getDoc()->getHesabdariRows() as $row) {
if ($row->getPerson()) {
$person = $row->getPerson();
break;
}
}
}
$response = [];
foreach ($items as $item) {
$temp = [
'id' => $item->getCommodity()->getId(),
'code' => $item->getCommodity()->getCode(),
'khadamat' => $item->getCommodity()->isKhadamat(),
'name' => $item->getCommodity()->getName(),
'unit' => $item->getCommodity()->getUnit()->getName(),
'count' => $item->getCommdityCount(),
'date' => $item->getDoc()->getDate(),
'docCode' => $item->getDoc()->getCode(),
'type' => $item->getDoc()->getType()
];
if ($item->getDoc()->getType() == 'buy') {
$temp['priceAll'] = $item->getBd();
} elseif ($item->getDoc()->getType() == 'sell') {
$temp['priceAll'] = $item->getBs();
}
if ($temp['count'] != 0) {
$temp['priceOne'] = $temp['priceAll'] / $temp['count'];
$temp['priceAll'] = number_format($temp['priceAll']);
$temp['priceOne'] = number_format($temp['priceOne']);
$temp['count'] = number_format($temp['count']);
$response[] = $temp;
}
}
// اضافه کردن شماره ردیف به داده‌ها
$responseWithRow = [];
foreach ($response as $index => $item) {
$responseWithRow[] = [
'row' => $index + 1,
'code' => $item['code'],
'name' => $item['name'],
'unit' => $item['unit'],
'count' => $item['count'],
'priceOne' => $item['priceOne'],
'priceAll' => $item['priceAll'],
'date' => $item['date'],
'docCode' => $item['docCode'],
'type' => $item['type'],
'khadamat' => $item['khadamat']
];
}
// دریافت تنظیمات چاپ
$printSettings = $entityManagerInterface->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'] ?? []);
$pdfPid = $provider->createPrint(
$acc['bid'],
$this->getUser(),
$this->renderView('pdf/printers/buysell_report.html.twig', [
'bid' => $acc['bid'],
'items' => $responseWithRow,
'printOptions' => $printOptions,
'note' => $printSettings ? $printSettings->getSellNoteString() : '',
'person' => $person
]),
false,
$printOptions['paper']
);
return $this->json(['id' => $pdfPid]);
}
}

View file

@ -747,27 +747,53 @@ class SellController extends AbstractController
$this->renderView('pdf/printers/sell.html.twig', [
'bid' => $acc['bid'],
'doc' => $doc,
'rows' => $doc->getHesabdariRows(),
'rows' => array_map(function($row) {
return [
'commodity' => $row->getCommodity(),
'commodityCount' => $row->getCommdityCount(),
'des' => $row->getDes(),
'bs' => $row->getBs(),
'tax' => $row->getTax(),
'discount' => $row->getDiscount(),
'showPercentDiscount' => $row->getDiscountType() === 'percent',
'discountPercent' => $row->getDiscountPercent()
];
}, $doc->getHesabdariRows()->toArray()),
'person' => $person,
'printInvoice' => $params['printers'],
'discount' => $discount,
'transfer' => $transfer,
'printOptions' => $printOptions,
'note' => $note
'note' => $note,
'showPercentDiscount' => $doc->getDiscountType() === 'percent',
'discountPercent' => $doc->getDiscountPercent()
]),
false,
$printOptions['paper']
);
}
if ($params['posPrint'] == true) {
$pid = $provider->createPrint(
$acc['bid'],
$this->getUser(),
$this->renderView('pdf/posPrinters/justSell.html.twig', [
'bid' => $acc['bid'],
'doc' => $doc,
'rows' => $doc->getHesabdariRows(),
'rows' => array_map(function($row) {
return [
'commodity' => $row->getCommodity(),
'commodityCount' => $row->getCommdityCount(),
'des' => $row->getDes(),
'bs' => $row->getBs(),
'tax' => $row->getTax(),
'discount' => $row->getDiscount(),
'showPercentDiscount' => $row->getDiscountType() === 'percent',
'discountPercent' => $row->getDiscountPercent()
];
}, $doc->getHesabdariRows()->toArray()),
'discount' => $discount,
'showPercentDiscount' => $doc->getDiscountType() === 'percent',
'discountPercent' => $doc->getDiscountPercent()
]),
false
);

View file

@ -79,6 +79,58 @@ class PayMGR
}
}
}
} elseif ($activeGateway == 'payping') {
$data = array(
'amount' => $price,
'returnUrl' => $callback_url,
'description' => $des,
'clientRefId' => $orderID
);
$ch = curl_init('https://api.payping.ir/v2/pay');
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 45,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
"accept: application/json",
"authorization: Bearer " . $this->registry->get('system', 'paypingKey'),
"cache-control: no-cache",
"content-type: application/json",
),
));
$response = curl_exec($ch);
$err = curl_error($ch);
$header = curl_getinfo($ch);
curl_close($ch);
if ($err) {
$res['message'] = 'خطا در ارتباط با پی‌پینگ: ' . $err;
return $res;
}
if ($header['http_code'] == 200) {
$response = json_decode($response, true);
if (isset($response['code'])) {
$res['code'] = 100;
$res['Success'] = true;
$res['gate'] = 'payping';
$res['message'] = 'OK';
$res['authkey'] = $response['code'];
$res['targetURL'] = 'https://api.payping.ir/v2/pay/gotoipg/' . $response['code'];
} else {
$res['message'] = 'خطا در دریافت کد پرداخت از پی‌پینگ';
}
} elseif ($header['http_code'] == 400) {
$res['message'] = 'خطا در درخواست پرداخت: ' . $response;
} else {
$res['message'] = 'خطا در ارتباط با پی‌پینگ. کد خطا: ' . $header['http_code'];
}
} elseif ($activeGateway == 'pec') {
ini_set("soap.wsdl_cache_enabled", "0");
$url = "https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?WSDL";
@ -150,6 +202,53 @@ class PayMGR
}
}
}
} elseif ($activeGateway == 'payping') {
$refid = $request->get('refid');
if (!$refid) {
return $res;
}
$data = array(
'amount' => $price,
'refId' => $refid
);
$ch = curl_init('https://api.payping.ir/v2/pay/verify');
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 45,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => array(
"accept: application/json",
"authorization: Bearer " . $this->registry->get('system', 'paypingKey'),
"cache-control: no-cache",
"content-type: application/json",
),
));
$response = curl_exec($ch);
$err = curl_error($ch);
$header = curl_getinfo($ch);
curl_close($ch);
if ($err) {
return $res;
}
if ($header['http_code'] == 200) {
$response = json_decode($response, true);
if (isset($refid) && $refid != '') {
$res['Success'] = true;
$res['status'] = 100;
$res['refID'] = $refid;
$res['card_pan'] = ''; // PayPing این اطلاعات را برنمی‌گرداند
return $res;
}
}
} elseif ($activeGateway == 'pec') {
$confirmUrl = 'https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?WSDL';
$params = array(

View file

@ -55,6 +55,12 @@ class pdfMGR
],
'default_font' => 'vazirmatn',
'tempDir' => $tempDir,
'margin_left' => 5,
'margin_right' => 5,
'margin_top' => 5,
'margin_bottom' => 5,
'margin_header' => 2,
'margin_footer' => 2,
'autoArabic' => true,
]);

View file

@ -0,0 +1,225 @@
<!DOCTYPE html>
<html lang="fa" direction="rtl">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<style>
.center {
text-align: center;
}
.text-white {
color: white;
}
.stimol td,
.stimol th {
border: 1px solid black;
}
.item {
height: 30px;
font-size: 11px;
}
h3 {
font-size: 14px;
}
h4 {
font-size: 12px;
}
p {
font-size: 11px;
}
</style>
</head>
<body style="direction:rtl; width:100%">
<div class="block-content pt-1 pb-3 d-none d-sm-block">
<div class="c-print container-xl">
<div class="tg-wrap" style="width:100%; border:1px solid black;border-radius: 8px;">
<table class="rounded" style="width:100%;">
<thead>
<tr>
<td style="width:20%">
<img src="{{ url('front_avatar_file_get', {id: bid.id},)}}" width="65"/>
</td>
<td style="width:60%; text-align:center">
<h4>{{ bid.name }}</h4>
<h3 class="">گزارش اقساط</h3>
</td>
<td style="width:20%">
<h4>
<b>شماره فاکتور:</b>
{{ doc.mainDoc.code }}</h4>
</td>
</tr>
</thead>
</table>
</div>
<div style="width:100%; border:1px solid black;border-radius: 8px;margin-top:5px;text-align:center;">
<div class="tg-wrap" style="width:100%;border-radius: 8px 8px 0px 0px;text-align:center;background-color:gray">
<b style="color:white;">خریدار</b>
</div>
<table style="width:100%;">
<tbody>
<tr style="text-align:center;">
<td class="">
<p>
<b>نام:</b>
{% if person.prelabel is not null %}{{ person.prelabel.label }}{% endif %}
{{ person.nikename }}
</p>
</td>
<td class="center">
<p>
<b>شناسه ملی:</b>
{{ person.shenasemeli }}
</p>
</td>
<td class="center">
<p>
<b>شماره ثبت:</b>
{{ person.sabt }}
</p>
</td>
<td class="center">
<p>
<b>شماره اقتصادی:</b>
{{ person.codeeghtesadi }}
</p>
</td>
<td class="center">
<p>
<b>تلفن / نمابر:</b>
{{ person.tel }}
</p>
</td>
</tr>
<tr>
<td class="" colspan="1">
<p>
<b>کد پستی:</b>
{{ person.postalcode }}
</p>
</td>
<td class="" colspan="3">
<p>
<b>آدرس:</b>
استان {{ person.ostan }}، شهر {{ person.shahr }}، {{ person.address }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
<div style="width:100%; border:1px solid black;border-radius: 8px;margin-top:5px;text-align:center;">
<div class="tg-wrap" style="width:100%;border-radius: 8px 8px 0px 0px;text-align:center;background-color:gray">
<b style="color:white;">اطلاعات اقساط</b>
</div>
<table style="width:100%;">
<tbody>
<tr style="text-align:center;">
<td class="">
<p>
<b>تعداد اقساط:</b>
{{ doc.count }}
</p>
</td>
<td class="center">
<p>
<b>درصد سود:</b>
{{ doc.profitPercent }}%
</p>
</td>
<td class="center">
<p>
<b>مبلغ سود:</b>
{{ doc.profitAmount|number_format }} ریال
</p>
</td>
<td class="center">
<p>
<b>نوع سود:</b>
{% if doc.profitType == 'yearly' %}
سالانه
{% elseif doc.profitType == 'monthly' %}
ماهانه
{% else %}
روزانه
{% endif %}
</p>
</td>
<td class="center">
<p>
<b>جریمه روزانه:</b>
{{ doc.daysPay }}%
</p>
</td>
</tr>
</tbody>
</table>
</div>
<div style="width:100%;margin-top:5px;text-align:center;">
<table style="width:100%;">
<thead>
<tr class="stimol" style="background-color:gray;">
<th class="text-white" style="width:80px">شماره قسط</th>
<th class="text-white">تاریخ</th>
<th class="text-white">مبلغ</th>
<th class="text-white">وضعیت</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr class="stimol">
<td class="center item">{{ item.num }}</td>
<td class="center item">{{ item.date|date('Y/m/d') }}</td>
<td class="center item">{{ item.amount|number_format }} ریال</td>
<td class="center item">
{% if item.hesabdariDoc %}
<span style="color: green;">پرداخت شده ({{ item.hesabdariDoc.code }})</span>
{% else %}
<span style="color: red;">پرداخت نشده</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if printOptions.note and note %}
<div style="width:100%;margin-top:5px;text-align:center;">
<table style="width:100%;">
<tbody>
<tr class="stimol">
<td class="item" style="padding:1%">
<h4>توضیحات:</h4>
{{ note|nl2br }}
</td>
</tr>
</tbody>
</table>
</div>
{% endif %}
<div style="width:40%;margin-top:0px;text-align:center;float:left;">
<table style="width:100%;">
<tbody>
<tr>
<td class="center" style="height:90px">
<h4>مهر و امضا خریدار</h4>
</td>
<td class="center" style="height:90px">
<h4>مهر و امضا فروشنده:</h4>
<br>
{% if printOptions.businessStamp %}
<img src="{{ url('front_seal_file_get', {id: bid.id},)}}" width="160"/>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View file

@ -6,7 +6,7 @@
body {
margin: 5px;
padding: 0;
font-size: 100%;
font-size: 11px;
}
table {
@ -19,10 +19,12 @@
th,
td {
border: 1px solid black !important;
font-size: 11px;
}
h1 {
text-align: center;
vertical-align: middle;
font-size: 14px;
}
#logo {
@ -60,7 +62,7 @@
}
.items .heading {
font-size: 12.5px;
font-size: 11px;
text-transform: uppercase;
border-top: 1px solid black;
margin-bottom: 4px;
@ -75,7 +77,7 @@
}
.items td {
font-size: 12px;
font-size: 11px;
text-align: center;
vertical-align: bottom;
}
@ -89,7 +91,7 @@
text-align: right !important;
}
.total {
font-size: 13px;
font-size: 11px;
border-top: 1px dashed black !important;
border-bottom: 1px dashed black !important;
}
@ -110,7 +112,7 @@
}
section,
footer {
font-size: 12px;
font-size: 11px;
}
tbody,
thead,
@ -150,6 +152,7 @@
<th class="heading name">کالا</th>
<th class="heading qty">تعداد</th>
<th class="heading rate">فی</th>
<th class="heading amount">قبل تخفیف</th>
<th class="heading amount">جمع</th>
</tr>
</thead>
@ -159,8 +162,29 @@
{% if row.commodity != null %}
<tr>
<td>{{row.commodity.name}}</td>
<td>{{row.commdityCount}}</td>
<td class="price">{{(row.bs / row.commdityCount) | number_format}}</td>
<td>{{row.commodityCount}}</td>
<td class="price">
{% if row.commodityCount > 0 %}
{% if row.showPercentDiscount %}
{% set originalPrice = row.bs / (1 - (row.discountPercent / 100)) %}
{% set unitPrice = originalPrice / row.commodityCount %}
{% else %}
{% set originalPrice = row.bs + row.discount %}
{% set unitPrice = originalPrice / row.commodityCount %}
{% endif %}
{{ unitPrice|round|number_format }}
{% else %}
0
{% endif %}
</td>
<td class="price">
{% if row.showPercentDiscount %}
{% set originalPrice = row.bs / (1 - (row.discountPercent / 100)) %}
{{ originalPrice|round|number_format }}
{% else %}
{{ (row.bs + row.discount)|number_format }}
{% endif %}
</td>
<td class="price">{{row.bs | number_format}}</td>
</tr>
{% endif %}
@ -169,6 +193,19 @@
<th colspan="3" class="total text">جمع فاکتور</th>
<th class="total price">{{doc.amount | number_format}}</th>
</tr>
{% if discount > 0 %}
<tr>
<th colspan="3" class="total text">تخفیف</th>
<th class="total price">
{% if showPercentDiscount %}
{{ discountPercent }}%
({{ (doc.amount * discountPercent / 100)|round|number_format }})
{% else %}
{{ discount | number_format }}
{% endif %}
</th>
</tr>
{% endif %}
</tbody>
</table>
<section style="margin-bottom:10px;margin-top:10px;text-align:center;">

View file

@ -276,7 +276,7 @@
{{transfer | number_format}}
</h4>
<h4>
مبلغ کل بدون تخفیف:
جمع بدون تخفیف:
{{ (doc.amount + discount) | number_format}}
</h4>
<h4>

View file

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="fa" direction="rtl">
<head>
<meta charset="UTF-8">
<title>گزارش خرید و فروش</title>
<style>
.center {
text-align: center;
}
.text-white {
color: white;
}
.stimol td,
.stimol th {
border: 1px solid black;
}
.item {
height: 30px;
font-size: 11px;
}
h3 {
font-size: 14px;
}
h4 {
font-size: 12px;
}
p {
font-size: 11px;
}
</style>
</head>
<body style="direction:rtl; width:100%">
<div class="block-content pt-1 pb-3 d-none d-sm-block">
<div class="c-print container-xl">
<div class="tg-wrap" style="width:100%; border:1px solid black;border-radius: 8px;">
<table class="rounded" style="width:100%;">
<thead>
<tr>
<td style="width:20%">
{% if printOptions.invoiceIndex %}
<img src="{{ url('front_avatar_file_get', {id: bid.id},)}}" width="65"/>
{% endif %}
</td>
<td style="width:60%; text-align:center">
<h3 class="">{{ bid.legalName }}</h3>
<h3 class="">گزارش خرید و فروش</h3>
</td>
<td style="width:20%">
<h4>
<b>تاریخ چاپ:</b>
{{ "now"|date("Y/m/d") }}</h4>
</td>
</tr>
</thead>
</table>
</div>
{% if printOptions.bidInfo %}
<div style="width:100%; border:1px solid black;border-radius: 8px;margin-top:5px;text-align:center;">
<div class="tg-wrap" style="width:100%;border-radius: 8px 8px 0px 0px;text-align:center;background-color:gray">
<b style="color:white;">اطلاعات شخص</b>
</div>
<table style="width:100%;">
<tbody>
<tr style="text-align:center;">
<td class="">
<p>
<b>نام:</b>
{% if person.prelabel is not null %}{{ person.prelabel.label }}{% endif %}
{{ person.nikename }}
</p>
</td>
<td class="center">
<p>
<b>شناسه ملی:</b>
{{ person.shenasemeli }}
</p>
</td>
<td class="center">
<p>
<b>شماره ثبت:</b>
{{ person.sabt }}
</p>
</td>
<td class="center">
<p>
<b>شماره اقتصادی:</b>
{{ person.codeeghtesadi }}
</p>
</td>
<td class="center">
<p>
<b>تلفن / نمابر:</b>
{{ person.tel }}
</p>
</td>
</tr>
<tr>
<td class="" colspan="1">
<p>
<b>کد پستی:</b>
{{ person.postalcode }}
</p>
</td>
<td class="" colspan="3">
<p>
<b>آدرس:</b>
استان {{ person.ostan }}، شهر {{ person.shahr }}، {{ person.address }}
</p>
</td>
</tr>
</tbody>
</table>
</div>
{% endif %}
<div style="width:100%;margin-top:5px;text-align:center;">
<table style="width:100%;">
<thead>
<tr class="stimol" style="background-color:gray;">
<th class="text-white" style="width:40px">ردیف</th>
<th class="text-white">کد کالا</th>
<th class="text-white">نام کالا</th>
<th class="text-white">واحد</th>
<th class="text-white">تعداد</th>
<th class="text-white">قیمت واحد</th>
<th class="text-white">قیمت کل</th>
<th class="text-white">تاریخ</th>
<th class="text-white">شماره سند</th>
<th class="text-white">نوع سند</th>
<th class="text-white">نوع</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr class="stimol">
<td class="center item">{{ item.row }}</td>
<td class="center item">{{ item.code }}</td>
<td class="center item">{{ item.name }}</td>
<td class="center item">{{ item.unit }}</td>
<td class="center item">{{ item.count }}</td>
<td class="center item">{{ item.priceOne }}</td>
<td class="center item">{{ item.priceAll }}</td>
<td class="center item">{{ item.date }}</td>
<td class="center item">{{ item.docCode }}</td>
<td class="center item">
{% if item.type == 'buy' %}
خرید
{% elseif item.type == 'sell' %}
فروش
{% elseif item.type == 'rfbuy' %}
برگشت از خرید
{% elseif item.type == 'rfsell' %}
برگشت از فروش
{% endif %}
</td>
<td class="center item">
{% if item.khadamat %}
خدمات
{% else %}
کالا
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div style="width:40%;margin-top:5px;text-align:center;float:left;">
<table style="width:100%;">
<tbody>
<tr>
<td class="center" style="height:90px">
<h4>مهر و امضا</h4>
{% if printOptions.businessStamp %}
<img src="{{ url('front_seal_file_get', {id: bid.id},)}}" width="160"/>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View file

@ -276,7 +276,7 @@
{{transfer | number_format}}
</h4>
<h4>
مبلغ کل بدون تخفیف:
جمع بدون تخفیف:
{{ (doc.amount + discount) | number_format}}
</h4>
<h4>

View file

@ -276,7 +276,7 @@
{{transfer | number_format}}
</h4>
<h4>
مبلغ کل بدون تخفیف:
جمع بدون تخفیف:
{{ (doc.amount + discount) | number_format}}
</h4>
<h4>

View file

@ -15,6 +15,16 @@
}
.item {
height: 30px;
font-size: 11px;
}
h3 {
font-size: 14px;
}
h4 {
font-size: 12px;
}
p {
font-size: 11px;
}
</style>
</head>
@ -191,6 +201,7 @@
<th class="text-white">مبلغ واحد</th>
{% if printOptions.discountInfo %}
<th class="text-white">تخفیف</th>
<th class="text-white">قبل تخفیف</th>
{% endif %}
{% if printOptions.taxInfo %}
<th class="text-white">مالیات</th>
@ -213,18 +224,40 @@
{{ item.commodity.name }}</td>
<td class="center item">{{ item.des }}</td>
<td class="center item">
{{ item.commdityCount }}
{{ item.commodityCount }}
{{ item.commodity.unit.name }}
</td>
<td class="center item">
{% if item.commdityCount > 0 %}
{{ ((item.bs|number_format(0, '.', '') - item.tax|number_format(0, '.', '') + item.discount|number_format(0, '.', '')) / item.commdityCount) | number_format }}
{% if item.commodityCount > 0 %}
{% if item.showPercentDiscount %}
{% set originalPrice = item.bs / (1 - (item.discountPercent / 100)) %}
{% set unitPrice = originalPrice / item.commodityCount %}
{% else %}
{% set originalPrice = item.bs + item.discount %}
{% set unitPrice = originalPrice / item.commodityCount %}
{% endif %}
{{ unitPrice|round|number_format }}
{% else %}
0
{% endif %}
</td>
{% if printOptions.discountInfo %}
<td class="center item">{{ item.discount | number_format }}</td>
<td class="center item">
{% if item.showPercentDiscount %}
{{ item.discountPercent }}%
({{ (item.bs * item.commodityCount * item.discountPercent / 100)|round|number_format }})
{% else %}
{{ item.discount|number_format }}
{% endif %}
</td>
<td class="center item">
{% if item.showPercentDiscount %}
{% set originalPrice = item.bs / (1 - (item.discountPercent / 100)) %}
{{ originalPrice|round|number_format }}
{% else %}
{{ (item.bs + item.discount)|number_format }}
{% endif %}
</td>
{% endif %}
{% if printOptions.taxInfo %}
<td class="center item">{{ item.tax | number_format}}</td>
@ -285,7 +318,7 @@
</h4>
{% if doc.amount != (doc.amount + discount) %}
<h4>
مبلغ کل بدون تخفیف:
جمع بدون تخفیف:
{{ (doc.amount + discount) | number_format}}
</h4>
{% endif %}

View file

@ -286,6 +286,7 @@ const fa_lang = {
fetch_data_error: "خطا در گرفتن داده از {url}"
},
dialog: {
person_with_det_report: 'گزارش تفضیلی اشخاص',
change_password_label: 'تغییر کلمه عبور',
download: 'دانلود',
delete_group: 'حذف گروهی',
@ -793,6 +794,7 @@ const fa_lang = {
keywords: "کلیدواژه‌ها با کاما (,) از هم جدا شوند",
zarinpal_api: "کد API زرین‌پال",
parsian_api: "کد API درگاه پارسیان",
payping_api: "کد API درگاه پی‌پینگ",
scripts: "اسکریپت‌ها",
footer_scripts: "اسکریپت‌های فوتر سایت(مثلا اسکریپت شمارنده گوگل و ...)",
site_footer: "فوتر سایت با پشتیبانی از HTML",

View file

@ -298,6 +298,12 @@ const router = createRouter({
component: () =>
import('../views/acc/reports/persons/buysellByPerson.vue'),
},
{
path: 'reports/persons/withdet',
name: 'person_withdet',
component: () =>
import('../views/acc/reports/persons/withdet.vue'),
},
{
path: 'costs/list',
name: 'costs_list',

View file

@ -933,13 +933,6 @@ export default {
this.isCalculating = true;
this.loading = true;
// اگر در حالت ویرایش هستیم و اقساط قبلاً محاسبه شدهاند، از آنها استفاده کن
if (this.$route.params.id && this.installments.length > 0) {
this.isCalculating = false;
this.loading = false;
return;
}
const totalAmount = this.remainingAmount;
const prepayment = Number(this.installmentData.prepayment) || 0;
const remainingAmount = totalAmount - prepayment;
@ -1121,6 +1114,13 @@ export default {
},
immediate: true
},
'installmentData.count': {
handler(newVal) {
if (newVal && this.installmentData.calculationType === 'count') {
this.calculateInstallments();
}
}
},
'installmentData.interestRate': {
handler(newVal, oldVal) {
if (newVal && !oldVal) {
@ -1181,4 +1181,91 @@ export default {
font-weight: 600;
background-color: rgb(var(--v-theme-surface));
}
:deep(.vpd-main) {
position: fixed !important;
z-index: 999999 !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
pointer-events: auto !important;
}
:deep(.vpd-wrapper) {
position: fixed !important;
z-index: 999999 !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
pointer-events: auto !important;
}
:deep(.vpd-container) {
position: fixed !important;
z-index: 999999 !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
pointer-events: auto !important;
}
:deep(.vpd-content) {
position: fixed !important;
z-index: 999999 !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
pointer-events: auto !important;
}
:deep(.vpd-overlay) {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
background: rgba(0, 0, 0, 0.5) !important;
z-index: 999998 !important;
pointer-events: auto !important;
}
:deep(.v-application) {
position: relative !important;
}
:deep(.v-application--wrap) {
position: relative !important;
}
:deep(.v-main) {
position: relative !important;
}
:deep(.v-main__wrap) {
position: relative !important;
}
:deep(.v-container) {
position: relative !important;
}
:deep(.v-row) {
position: relative !important;
}
:deep(.v-col) {
position: relative !important;
}
:deep(.v-card) {
position: relative !important;
}
:deep(.v-field) {
position: relative !important;
}
:deep(.v-field__input) {
position: relative !important;
}
</style>

View file

@ -8,6 +8,11 @@
</v-tooltip>
</template>
<v-spacer></v-spacer>
<v-tooltip text="چاپ گزارش اقساط" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" variant="text" icon="mdi-printer" @click="printReport" :loading="loading" />
</template>
</v-tooltip>
</v-toolbar>
<div class="pa-0">
@ -640,6 +645,40 @@ export default {
} finally {
this.loading = false
}
},
async printReport() {
try {
this.loading = true;
const response = await axios.post('/api/plugins/ghesta/print', {
id: this.$route.params.id,
pdf: true
});
if (response.data && response.data.id) {
// دریافت فایل PDF
const pdfResponse = await axios({
method: 'get',
url: '/front/print/' + response.data.id,
responseType: 'arraybuffer'
});
// ایجاد لینک دانلود
const fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
const fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', `گزارش اقساط ${this.invoice.code}.pdf`);
document.body.appendChild(fileLink);
fileLink.click();
} else {
throw new Error('خطا در دریافت شناسه چاپ');
}
} catch (error) {
console.error('خطا در چاپ گزارش:', error);
this.error = 'خطا در چاپ گزارش';
this.errorDialog = true;
} finally {
this.loading = false;
}
}
},
created() {

View file

@ -10,6 +10,29 @@
</template>
<v-spacer />
<v-menu>
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="" color="red">
<v-tooltip activator="parent" :text="$t('dialog.export_pdf')" location="bottom" />
<v-icon icon="mdi-file-pdf-box" />
</v-btn>
</template>
<v-list>
<v-list-subheader color="primary">{{ $t('dialog.export_pdf') }}</v-list-subheader>
<v-list-item :disabled="!itemsSelected.length" class="text-dark" :title="$t('dialog.selected')"
@click="pdfOutput(false)">
<template v-slot:prepend>
<v-icon color="red-darken-4" icon="mdi-check" />
</template>
</v-list-item>
<v-list-item class="text-dark" :title="$t('dialog.all')" @click="pdfOutput(true)">
<template v-slot:prepend>
<v-icon color="indigo-darken-4" icon="mdi-expand-all" />
</template>
</v-list-item>
</v-list>
</v-menu>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon="" color="green">
@ -276,7 +299,7 @@ export default {
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', 'buysell-report-list.xlsx');
fileLink.setAttribute('download', `${this.selectedPerson.nikename} - گزارش خرید و فروش.xlsx`);
document.body.appendChild(fileLink);
fileLink.click();
})
@ -301,18 +324,44 @@ export default {
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', 'buysell-report-list.xlsx');
fileLink.setAttribute('download', `${this.selectedPerson.nikename} - گزارش خرید و فروش.xlsx`);
document.body.appendChild(fileLink);
fileLink.click();
})
}
}
},
print(AllItems = true) {
pdfOutput(AllItems = true) {
if (AllItems) {
axios.post('/api/person/list/print').then((response) => {
this.printID = response.data.id;
window.open(this.$API_URL + '/front/print/' + this.printID, '_blank', 'noreferrer');
axios({
method: 'post',
url: '/api/report/person/buysell/export/pdf',
data: {
items: this.items,
printOptions: {
paper: 'A4-L',
bidInfo: true,
taxInfo: true,
discountInfo: true,
pays: true,
invoiceIndex: true,
businessStamp: true
}
}
}).then((response) => {
axios({
method: 'get',
url: '/front/print/' + response.data.id,
responseType: 'arraybuffer'
}).then((pdfResponse) => {
var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', `${this.selectedPerson.nikename} - گزارش خرید و فروش.pdf`);
document.body.appendChild(fileLink);
fileLink.click();
});
})
}
else {
@ -324,9 +373,35 @@ export default {
});
}
else {
axios.post('/api/person/list/print', { items: this.itemsSelected }).then((response) => {
this.printID = response.data.id;
window.open(this.$API_URL + '/front/print/' + this.printID, '_blank', 'noreferrer');
axios({
method: 'post',
url: '/api/report/person/buysell/export/pdf',
data: {
items: this.itemsSelected,
printOptions: {
paper: 'A4-L',
bidInfo: true,
taxInfo: true,
discountInfo: true,
pays: true,
invoiceIndex: true,
businessStamp: true
}
}
}).then((response) => {
axios({
method: 'get',
url: '/front/print/' + response.data.id,
responseType: 'arraybuffer'
}).then((pdfResponse) => {
var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', `${this.selectedPerson.nikename} - گزارش خرید و فروش.pdf`);
document.body.appendChild(fileLink);
fileLink.click();
});
})
}
}

View file

@ -0,0 +1,67 @@
<template>
<v-toolbar color="toolbar" :title="$t('dialog.person_with_det_report')">
<template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
</v-toolbar>
<v-container fluid>
<v-row>
<v-col cols="12" md="4">
<Hpersonsearch
v-model="selectedPerson"
label="شخص"
:rules="[v => !!v || 'انتخاب شخص الزامی است']"
/>
</v-col>
<v-col cols="12" md="4">
<Hdatepicker
v-model="startDate"
label="تاریخ شروع"
:rules="[v => !!v || 'تاریخ شروع الزامی است']"
/>
</v-col>
<v-col cols="12" md="4">
<Hdatepicker
v-model="endDate"
label="تاریخ پایان"
:rules="[v => !!v || 'تاریخ پایان الزامی است']"
/>
</v-col>
</v-row>
</v-container>
</template>
<script>
import Hpersonsearch from '@/components/forms/Hpersonsearch.vue'
import Hdatepicker from '@/components/forms/Hdatepicker.vue'
export default {
name: 'PersonWithDetReport',
components: {
Hpersonsearch,
Hdatepicker
},
data() {
return {
selectedPerson: null,
startDate: '',
endDate: ''
}
},
methods: {
// متدهای مورد نیاز گزارش
},
mounted() {
// کدهای اجرایی در زمان بارگذاری کامپوننت
}
}
</script>
<style scoped>
/* استایل‌های مورد نیاز */
</style>

View file

@ -1,79 +1,96 @@
<template>
<v-toolbar flat color="grey-lighten-3" class="">
<v-toolbar flat color="toolbar" class="">
<v-toolbar-title class="primary--text">گزارشات</v-toolbar-title>
</v-toolbar>
<v-container fluid class="">
<v-container fluid class="reports-container">
<v-row>
<!-- اشخاص -->
<v-col cols="12" md="6">
<v-card outlined color="grey-lighten-2">
<v-card-subtitle class="pb-0">
<v-icon small left>mdi-account</v-icon>
<v-col cols="12" md="6" lg="4">
<v-card outlined class="report-card">
<v-card-subtitle class="card-title">
<v-icon class="mr-2">mdi-account</v-icon>
اشخاص
</v-card-subtitle>
<v-list dense color="grey-lighten-2">
<v-list-item v-for="item in personReports" :key="item.to" :to="item.to">
<v-list-item-content>{{ item.text }}</v-list-item-content>
</v-list-item>
<v-list dense class="report-list">
<template v-for="item in personReports" :key="item.to">
<v-list-item v-if="item && (!item.showIf || isPluginActive(item.showIf))" :to="item.to" class="list-item">
<template v-slot:default>
<v-icon small class="mr-2">mdi-chevron-left</v-icon>
{{ item.text }}
</template>
</v-list-item>
</template>
</v-list>
</v-card>
</v-col>
<!-- بانکداری -->
<v-col cols="12" md="6">
<v-card outlined color="grey-lighten-2">
<v-card-subtitle class="pb-0">
<v-icon small left>mdi-bank</v-icon>
<v-col cols="12" md="6" lg="4">
<v-card outlined class="report-card">
<v-card-subtitle class="card-title">
<v-icon class="mr-2">mdi-bank</v-icon>
بانکداری
</v-card-subtitle>
<v-list dense color="grey-lighten-2">
<v-list-item v-for="item in bankReports" :key="item.to" :to="item.to">
<v-list-item-content>{{ item.text }}</v-list-item-content>
<v-list dense class="report-list">
<v-list-item v-for="item in bankReports" :key="item.to" :to="item.to" class="list-item">
<template v-slot:default>
<v-icon small class="mr-2">mdi-chevron-left</v-icon>
{{ item.text }}
</template>
</v-list-item>
</v-list>
</v-card>
</v-col>
<!-- گزارشات پایه -->
<v-col cols="12" md="6">
<v-card outlined color="grey-lighten-2">
<v-card-subtitle class="pb-0">
<v-icon small left>mdi-cog</v-icon>
<v-col cols="12" md="6" lg="4">
<v-card outlined class="report-card">
<v-card-subtitle class="card-title">
<v-icon class="mr-2">mdi-cog</v-icon>
گزارشات پایه
</v-card-subtitle>
<v-list dense color="grey-lighten-2">
<v-list-item to="/acc/business/logs">
<v-list-item-content>تاریخچه رویدادها</v-list-item-content>
<v-list dense class="report-list">
<v-list-item to="/acc/business/logs" class="list-item">
<template v-slot:default>
<v-icon small class="mr-2">mdi-chevron-left</v-icon>
تاریخچه رویدادها
</template>
</v-list-item>
</v-list>
</v-card>
</v-col>
<!-- کالا و خدمات -->
<v-col cols="12" md="6">
<v-card outlined color="grey-lighten-2">
<v-card-subtitle class="pb-0">
<v-icon small left>mdi-package-variant</v-icon>
<v-col cols="12" md="6" lg="4">
<v-card outlined class="report-card">
<v-card-subtitle class="card-title">
<v-icon class="mr-2">mdi-package-variant</v-icon>
کالا و خدمات
</v-card-subtitle>
<v-list dense color="grey-lighten-2">
<v-list-item to="/acc/reports/commodity/buysell">
<v-list-item-content>خرید و فروش به تفکیک کالا</v-list-item-content>
<v-list dense class="report-list">
<v-list-item to="/acc/reports/commodity/buysell" class="list-item">
<template v-slot:default>
<v-icon small class="mr-2">mdi-chevron-left</v-icon>
خرید و فروش به تفکیک کالا
</template>
</v-list-item>
</v-list>
</v-card>
</v-col>
<!-- حسابداری (conditional) -->
<v-col v-if="isPluginActive('accpro')" cols="12" md="6">
<v-card outlined color="grey-lighten-2">
<v-card-subtitle class="pb-0">
<v-icon small left>mdi-format-list-bulleted</v-icon>
<v-col v-if="isPluginActive('accpro')" cols="12" md="6" lg="4">
<v-card outlined class="report-card">
<v-card-subtitle class="card-title">
<v-icon class="mr-2">mdi-format-list-bulleted</v-icon>
حسابداری
</v-card-subtitle>
<v-list dense color="grey-lighten-2">
<v-list-item v-for="item in accountingReports" :key="item.to" :to="item.to">
<v-list-item-content>{{ item.text }}</v-list-item-content>
<v-list dense class="report-list">
<v-list-item v-for="item in accountingReports" :key="item.to" :to="item.to" class="list-item">
<template v-slot:default>
<v-icon small class="mr-2">mdi-chevron-left</v-icon>
{{ item.text }}
</template>
</v-list-item>
</v-list>
</v-card>
@ -91,10 +108,11 @@ export default {
return {
plugins: [],
personReports: [
{ text: 'کارت حساب', to: '/acc/persons/card/view/' },
{ text: 'بدهکاران', to: '/acc/reports/persons/debtors' },
{ text: 'بستانکاران', to: '/acc/reports/persons/depositors' },
{ text: 'خرید و فروش های اشخاص', to: '/acc/reports/persons/buysell' }
{ text: 'کارت حساب', to: '/acc/persons/card/view/', showIf: null },
{ text: 'بدهکاران', to: '/acc/reports/persons/debtors', showIf: null },
{ text: 'بستانکاران', to: '/acc/reports/persons/depositors', showIf: null },
{ text: 'خرید و فروش های اشخاص', to: '/acc/reports/persons/buysell', showIf: null },
{ text: 'گزارش تفضیلی اشخاص', to: '/acc/reports/persons/withdet', showIf: 'accpro' }
],
bankReports: [
{ text: 'گردش حساب بانک', to: '/acc/banks/card/view/' },
@ -124,10 +142,81 @@ export default {
</script>
<style scoped>
.v-card {
transition: all 0.2s;
.reports-container {
padding: 32px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
min-height: calc(100vh - 64px);
}
.v-card:hover {
filter: brightness(95%);
.report-card {
height: 100%;
border-radius: 16px;
background-color: white;
border: 1px solid #e2e8f0;
overflow: hidden;
}
.report-card:hover {
border-color: #3b82f6;
}
.card-title {
font-size: 1.1rem;
font-weight: 600;
padding: 16px 20px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
color: #334155;
border-radius: 16px 16px 0 0;
display: flex;
align-items: center;
position: relative;
border-bottom: 1px solid #e2e8f0;
}
.card-title::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 3px;
height: 100%;
background: linear-gradient(to bottom, #2563eb, #3b82f6);
border-radius: 0 16px 16px 0;
}
.card-title .v-icon {
color: #2563eb;
margin-left: 12px;
background-color: #eff6ff;
padding: 8px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(37, 99, 235, 0.1);
font-size: 1.2rem;
}
.report-card:hover .card-title .v-icon {
background-color: #dbeafe;
box-shadow: 0 4px 6px rgba(37, 99, 235, 0.15);
}
.report-list {
padding: 8px 0;
}
.list-item {
margin: 4px 8px;
border-radius: 10px;
color: #475569;
font-weight: 500;
font-size: 0.95rem;
}
.list-item:hover {
background: linear-gradient(135deg, #eff6ff, #f0f7ff);
color: #2563eb;
}
.v-icon {
color: #2563eb;
}
</style>

View file

@ -570,11 +570,43 @@ export default defineComponent({
'pdf': pdf,
'printers': cloudePrinters,
'printOptions': this.printOptions
}).then((response) => {
this.loading = false;
window.open(this.$API_URL + '/front/print/' + response.data.id, '_blank', 'noreferrer');
}).catch(() => {
}).then(async (response) => {
try {
if (response.data && response.data.id) {
// دریافت فایل PDF
const pdfResponse = await axios({
method: 'get',
url: '/front/print/' + response.data.id,
responseType: 'arraybuffer'
});
// ایجاد لینک دانلود
var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', `فاکتور فروش ${this.printOptions.selectedPrintCode}.pdf`);
document.body.appendChild(fileLink);
fileLink.click();
} else {
throw new Error('خطا در دریافت شناسه چاپ');
}
} catch (error) {
console.error('خطا در دریافت فایل PDF:', error);
Swal.fire({
text: 'خطا در دریافت فایل PDF',
icon: 'error',
confirmButtonText: 'قبول'
});
} finally {
this.loading = false;
}
}).catch((error) => {
this.loading = false;
Swal.fire({
text: 'خطا در ایجاد نسخه PDF',
icon: 'error',
confirmButtonText: 'قبول'
});
});
},
deleteItem(code) {

View file

@ -1019,8 +1019,20 @@ export default {
});
if (printResponse.data && printResponse.data.id) {
// باز کردن PDF در پنجره جدید
window.open(this.$API_URL + '/front/print/' + printResponse.data.id, '_blank', 'noreferrer');
// دریافت فایل PDF
const pdfResponse = await axios({
method: 'get',
url: '/front/print/' + printResponse.data.id,
responseType: 'arraybuffer'
});
// ایجاد لینک دانلود
var fileURL = window.URL.createObjectURL(new Blob([pdfResponse.data]));
var fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute('download', `فاکتور فروش ${response.data.data.code}.pdf`);
document.body.appendChild(fileLink);
fileLink.click();
} else {
throw new Error('خطا در دریافت شناسه چاپ');
}

View file

@ -1,22 +1,26 @@
<template>
<v-container fluid class="pa-0">
<v-card>
<v-toolbar
color="grey-lighten-4"
flat
title="سرویس پیامک و افزایش اعتبار"
class="text-primary-dark"
>
<template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
<v-toolbar color="grey-lighten-4" flat title="سرویس پیامک و افزایش اعتبار" class="text-primary-dark">
<template v-slot:prepend>
<v-tooltip :text="$t('dialog.back')" location="bottom">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" @click="$router.back()" class="d-none d-sm-flex" variant="text"
icon="mdi-arrow-right" />
</template>
</v-tooltip>
</template>
</v-tooltip>
</template>
</v-toolbar>
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
{{ snackbar.text }}
<template v-slot:actions>
<v-btn variant="text" @click="snackbar.show = false">
بستن
</v-btn>
</template>
</v-snackbar>
<v-tabs v-model="activeTab" bg-color="primary" align-tabs="center" grow>
<v-tab value="home">
<v-icon start>mdi-plus-circle</v-icon>
@ -40,27 +44,78 @@
<v-window-item value="home" class="pa-4">
<v-row>
<v-col cols="12">
<h4 class="mb-3">مبلغ اعتبار</h4>
<v-alert type="info" variant="tonal" class="mb-4">
به مبالغ زیر ۱۰ درصد مالیات بر ارزش افزوده اضافه میگردد.
<v-alert type="info" variant="tonal" class="mb-4" border="start" color="primary">
<template v-slot:prepend>
<v-icon icon="mdi-information-outline" color="primary" size="24"></v-icon>
</template>
<div class="text-body-1">
<div class="d-flex align-center mb-2">
<v-icon icon="mdi-currency-usd" size="18" color="primary" class="ml-1"></v-icon>
<span class="font-weight-medium">اطلاعات مالی:</span>
</div>
<ul class="mb-0 ps-4">
<li class="mb-2">به مبالغ انتخاب شده ۱۰ درصد مالیات بر ارزش افزوده اضافه میگردد.</li>
<li class="mb-2">اعتبار خریداری شده بلافاصله به حساب شما اضافه خواهد شد.</li>
<li>این اعتبار صرفاً برای استفاده از سرویس پیامک کوتاه قابل استفاده است و برای سایر خدمات قابل استفاده نمیباشد.</li>
</ul>
</div>
</v-alert>
<v-radio-group v-model="smsCharge" inline>
<v-radio label="100,000 ریال" value="100000"></v-radio>
<v-radio label="500,000 ریال" value="500000"></v-radio>
<v-radio label="1,000,000 ریال" value="1000000"></v-radio>
<v-radio label="2,000,000 ریال" value="2000000"></v-radio>
</v-radio-group>
<v-row>
<v-col cols="12" sm="6" md="3" v-for="(amount, index) in chargeAmounts" :key="index">
<v-card :class="['charge-card', { 'selected': smsCharge === amount.value }]"
@click="smsCharge = amount.value" :elevation="smsCharge === amount.value ? 4 : 1" class="h-100"
:color="smsCharge === amount.value ? 'primary' : 'surface'" variant="elevated">
<v-card-text class="text-center">
<div class="text-h6 mb-2" :class="{ 'text-white': smsCharge === amount.value }">{{ amount.label }}
</div>
<div class="text-subtitle-1"
:class="{ 'text-white': smsCharge === amount.value, 'text-medium-emphasis': smsCharge !== amount.value }">
{{ formatPrice(amount.value) }} تومان
</div>
<div class="text-caption mt-2"
:class="{ 'text-white': smsCharge === amount.value, 'text-medium-emphasis': smsCharge !== amount.value }">
با احتساب مالیات: {{ formatPrice(amount.value * 1.1) }} تومان
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" sm="6" md="3">
<v-card :class="['charge-card', { 'selected': isCustomAmount }]"
:elevation="isCustomAmount ? 4 : 1" class="h-100"
:color="isCustomAmount ? 'primary' : 'surface'" variant="elevated">
<v-card-text class="text-center">
<div class="text-h6 mb-2" :class="{ 'text-white': isCustomAmount }">مبلغ دلخواه</div>
<v-text-field
v-model="customAmount"
type="number"
density="compact"
variant="outlined"
hide-details
class="mt-2"
:class="{ 'custom-amount-input': isCustomAmount }"
placeholder="مبلغ را وارد کنید"
@click="selectCustomAmount"
@input="handleCustomAmountInput"
></v-text-field>
<div v-if="customAmount" class="text-caption mt-2"
:class="{ 'text-white': isCustomAmount, 'text-medium-emphasis': !isCustomAmount }">
با احتساب مالیات: {{ formatPrice(Number(customAmount) * 1.1) }} تومان
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
<v-row class="mt-6">
<v-col cols="12" class="text-center">
<v-btn color="primary" :loading="loading" size="large" :disabled="!smsCharge" @click="pay" class="px-8">
<v-icon start>mdi-credit-card-outline</v-icon>
پرداخت آنلاین
</v-btn>
</v-col>
</v-row>
<v-btn
color="primary"
:loading="loading"
class="mt-4"
@click="pay"
>
<v-icon start>mdi-credit-card-outline</v-icon>
پرداخت آنلاین
</v-btn>
</v-window-item>
<v-window-item value="profile" class="pa-4">
@ -69,55 +124,29 @@
در نظر داشته باشید در صورت اتمام اعتبار سرویس پیامک کسب و کار شما، این تنظیمات نادیده گرفته میشود.
<ul>
<li>پیامکهای ارسالی به شماره ثبت شده در بخش اشخاص (تلفن همراه) ارسال میشود.</li>
<li>در صورت ثبت نکردن شماره تلفن در بخش اشخاص پیامک ارسال نمی شود و هزینه ای نیز از حساب شما کسر نخواهد شد.</li>
<li>در صورت ثبت نکردن شماره تلفن در بخش اشخاص پیامک ارسال نمی شود و هزینه ای نیز از حساب شما کسر نخواهد
شد.</li>
</ul>
</v-alert>
<v-col cols="12" md="6">
<v-checkbox
v-model="settings.sendAfterSell"
@change="saveSettings(settings)"
label="ارسال پیامک به مشتری بعد از صدور فاکتور فروش"
></v-checkbox>
<v-checkbox
v-model="settings.sendAfterSellPayOnline"
@change="saveSettings(settings)"
label="ارسال پیامک به مشتری جهت پرداخت آنلاین فاکتور فروش"
disabled
></v-checkbox>
<v-checkbox v-model="settings.sendAfterSell" @change="saveSettings(settings)"
label="ارسال پیامک به مشتری بعد از صدور فاکتور فروش"></v-checkbox>
<v-checkbox v-model="settings.sendAfterSellPayOnline" @change="saveSettings(settings)"
label="ارسال پیامک به مشتری جهت پرداخت آنلاین فاکتور فروش" disabled></v-checkbox>
<v-divider class="my-2"></v-divider>
<v-checkbox
v-model="settings.sendAfterBuy"
@change="saveSettings(settings)"
label="ارسال پیامک به تامین کننده بعد از صدور فاکتور خرید"
disabled
></v-checkbox>
<v-checkbox
v-model="settings.sendAfterBuyToUser"
@change="saveSettings(settings)"
label="ارسال پیامک به تامین کننده بعد از ثبت پرداخت فاکتور خرید"
disabled
></v-checkbox>
<v-checkbox v-model="settings.sendAfterBuy" @change="saveSettings(settings)"
label="ارسال پیامک به تامین کننده بعد از صدور فاکتور خرید" disabled></v-checkbox>
<v-checkbox v-model="settings.sendAfterBuyToUser" @change="saveSettings(settings)"
label="ارسال پیامک به تامین کننده بعد از ثبت پرداخت فاکتور خرید" disabled></v-checkbox>
</v-col>
</v-window-item>
<v-window-item value="pays" class="pa-4">
<v-text-field
v-model="payssearchValue"
prepend-inner-icon="mdi-magnify"
placeholder="جست و جو ..."
variant="outlined"
class="mb-4"
density="compact"
></v-text-field>
<v-data-table
:headers="paysheaders"
:items="paysitems"
:search="payssearchValue"
:loading="loading"
:header-props="{ class: 'custom-header' }"
loading-text="در حال بارگذاری..."
no-data-text="اطلاعاتی برای نمایش وجود ندارد"
>
<v-text-field v-model="payssearchValue" prepend-inner-icon="mdi-magnify" placeholder="جست و جو ..."
variant="outlined" class="mb-4" density="compact"></v-text-field>
<v-data-table :headers="paysheaders" :items="paysitems" :search="payssearchValue" :loading="loading"
:header-props="{ class: 'custom-header' }" loading-text="در حال بارگذاری..."
no-data-text="اطلاعاتی برای نمایش وجود ندارد">
<template v-slot:item.status="{ item }">
<span :class="item.status === 0 ? 'text-danger' : 'text-success'">
{{ item.status === 0 ? 'پرداخت نشده' : 'پرداخت شده' }}
@ -127,23 +156,11 @@
</v-window-item>
<v-window-item value="contact" class="pa-4">
<v-text-field
v-model="searchValue"
prepend-inner-icon="mdi-magnify"
placeholder="جست و جو ..."
variant="outlined"
class="mb-4"
density="compact"
></v-text-field>
<v-data-table
:headers="headers"
:items="items"
:header-props="{ class: 'custom-header' }"
:search="searchValue"
:loading="loading"
loading-text="در حال بارگذاری..."
no-data-text="اطلاعاتی برای نمایش وجود ندارد"
></v-data-table>
<v-text-field v-model="searchValue" prepend-inner-icon="mdi-magnify" placeholder="جست و جو ..."
variant="outlined" class="mb-4" density="compact"></v-text-field>
<v-data-table :headers="headers" :items="items" :header-props="{ class: 'custom-header' }"
:search="searchValue" :loading="loading" loading-text="در حال بارگذاری..."
no-data-text="اطلاعاتی برای نمایش وجود ندارد"></v-data-table>
</v-window-item>
</v-window>
</v-card>
@ -164,7 +181,20 @@ export default defineComponent({
sendAfterBuy: false,
sendAfterBuyToUser: false,
},
snackbar: {
show: false,
text: '',
color: 'error'
},
smsCharge: 100000,
customAmount: '',
isCustomAmount: false,
chargeAmounts: [
{ label: '۱۰ هزار تومان', value: 10000 },
{ label: '۵۰ هزار تومان', value: 50000 },
{ label: '۱۰۰ هزار تومان', value: 100000 },
{ label: '۲۰۰ هزار تومان', value: 200000 }
],
searchValue: '',
loading: true,
items: [],
@ -177,7 +207,7 @@ export default defineComponent({
paysitems: [] as Array<{ dateSubmit: string; price: number; des: string; status: number }>,
paysheaders: [
{ title: "تاریخ", key: "dateSubmit" },
{ title: "مبلغ (ریال)", key: "price" },
{ title: "مبلغ (تومان)", key: "price" },
{ title: "توضیحات", key: "des" },
{ title: "وضعیت", key: "status" },
]
@ -205,8 +235,21 @@ export default defineComponent({
.then((response) => {
if (response.data.Success === true) {
window.location.href = response.data.targetURL;
} else {
this.snackbar.text = response.data.message || 'خطا در ایجاد درخواست پرداخت';
this.snackbar.color = 'error';
this.snackbar.show = true;
}
})
.catch((error) => {
this.snackbar.text = 'خطا در ارتباط با سرور';
this.snackbar.color = 'error';
this.snackbar.show = true;
console.error('Error:', error);
})
.finally(() => {
this.loading = false;
});
},
saveSettings(settings: { sendAfterSell: boolean; sendAfterSellPayOnline: boolean; sendAfterBuy: boolean; sendAfterBuyToUser: boolean; }) {
this.loading = true;
@ -214,7 +257,19 @@ export default defineComponent({
.then(() => {
this.loading = false;
})
}
},
formatPrice(price: number): string {
return new Intl.NumberFormat('fa-IR').format(price);
},
selectCustomAmount() {
this.isCustomAmount = true;
this.smsCharge = Number(this.customAmount);
},
handleCustomAmountInput() {
if (this.isCustomAmount) {
this.smsCharge = Number(this.customAmount);
}
},
},
beforeMount() {
this.loadData();
@ -223,5 +278,106 @@ export default defineComponent({
</script>
<style scoped>
/* استایل‌های دلخواهت رو اینجا بذار */
.charge-card {
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
}
.charge-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px 0 rgba(0, 0, 0, 0.1);
}
.charge-card.selected {
border-color: transparent;
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
color: white;
box-shadow: 0 8px 25px 0 rgba(33, 150, 243, 0.3);
}
.charge-card .v-card-text {
padding: 1.5rem;
}
.charge-card:not(.selected):hover {
border-color: #2196F3;
background: linear-gradient(135deg, #ffffff 0%, #E3F2FD 100%);
}
/* اضافه کردن انیمیشن برای تغییر رنگ */
.charge-card {
position: relative;
overflow: hidden;
}
.charge-card::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(33, 150, 243, 0.1) 0%, rgba(25, 118, 210, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.charge-card:hover::after {
opacity: 1;
}
.charge-card.selected::after {
opacity: 0;
}
.info-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.info-card .v-card-text {
background: #ffffff;
border-radius: 8px;
}
.v-alert {
background-color: rgba(var(--v-theme-primary), 0.05) !important;
border-left: 4px solid rgb(var(--v-theme-primary)) !important;
}
.v-alert ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.v-alert ul li {
position: relative;
padding-right: 1.5rem;
line-height: 1.6;
}
.v-alert ul li::before {
content: "•";
color: rgb(var(--v-theme-primary));
position: absolute;
right: 0;
font-size: 1.2em;
}
.custom-amount-input :deep(.v-field__input) {
color: white !important;
caret-color: white !important;
}
.custom-amount-input :deep(.v-field__input::placeholder) {
color: rgba(255, 255, 255, 0.7) !important;
}
.custom-amount-input :deep(.v-field__outline) {
border-color: rgba(255, 255, 255, 0.7) !important;
}
</style>

View file

@ -18,6 +18,11 @@ export default defineComponent({
value: 'pec',
props: { subtitle: 'pec.ir' },
},
{
title: 'پی‌پینگ',
value: 'payping',
props: { subtitle: 'payping.ir' },
},
],
systemInfo: {
keywords: '',
@ -26,6 +31,7 @@ export default defineComponent({
appSite: '',
activeGateway:'zarinpal',
parsianGatewayAPI: '',
paypingKey: '',
},
loading: true,
}
@ -86,6 +92,10 @@ export default defineComponent({
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.parsian_api')"
v-model="systemInfo.parsianGatewayAPI" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="4">
<v-text-field class="" hide-details="auto" :label="$t('pages.manager.payping_api')"
v-model="systemInfo.paypingKey" type="text" prepend-inner-icon="mdi-text"></v-text-field>
</v-col>
<v-col cols="12" sm="12" md="12">
<v-btn type="submit" @click="submit()" color="primary" prepend-icon="mdi-content-save" :loading="loading">
{{ $t('dialog.save') }}